diff --git a/Actors/admonitor.lua b/Actors/admonitor.lua index 9c746a26..c46a17d0 100644 --- a/Actors/admonitor.lua +++ b/Actors/admonitor.lua @@ -4,51 +4,48 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Actors/Admonitor") local SOUND_PATH = path.combine(PATH, "Sounds/Actors/Admonitor") -local sprite_mask = Resources.sprite_load(NAMESPACE, "AdmonitorMask", path.combine(SPRITE_PATH, "mask.png"), 1, 11, 26) -local sprite_palette = Resources.sprite_load(NAMESPACE, "AdmonitorPalette", path.combine(SPRITE_PATH, "palette.png")) -local sprite_portrait = Resources.sprite_load(NAMESPACE, "AdmonitorPortrait", path.combine(SPRITE_PATH, "portrait.png")) -local sprite_spawn = Resources.sprite_load(NAMESPACE, "AdmonitorSpawn", path.combine(SPRITE_PATH, "spawn.png"), 15, 58, 39) -local sprite_idle = Resources.sprite_load(NAMESPACE, "AdmonitorIdle", path.combine(SPRITE_PATH, "idle.png"), 18, 30, 27) -local sprite_walk = Resources.sprite_load(NAMESPACE, "AdmonitorWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 27, 31) -local sprite_jump = Resources.sprite_load(NAMESPACE, "AdmonitorJump", path.combine(SPRITE_PATH, "jump.png"), 1, 26, 33) -local sprite_jump_peak = Resources.sprite_load(NAMESPACE, "AdmonitorJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 26, 33) -local sprite_fall = Resources.sprite_load(NAMESPACE, "AdmonitorFall", path.combine(SPRITE_PATH, "fall.png"), 1, 26, 33) -local sprite_death = Resources.sprite_load(NAMESPACE, "AdmonitorDeath", path.combine(SPRITE_PATH, "death.png"), 14, 33, 53) -local sprite_shoot1 = Resources.sprite_load(NAMESPACE, "AdmonitorShoot1", path.combine(SPRITE_PATH, "shoot1.png"), 30, 53, 86) - -gm.elite_generate_palettes(sprite_palette) - -local sound_spawn = Resources.sfx_load(NAMESPACE, "AdmonitorSpawn", path.combine(SOUND_PATH, "spawn.ogg")) -local sound_shoot1a = Resources.sfx_load(NAMESPACE, "AdmonitorShoot1A", path.combine(SOUND_PATH, "shoot1_1.ogg")) -local sound_shoot1b = Resources.sfx_load(NAMESPACE, "AdmonitorShoot1B", path.combine(SOUND_PATH, "shoot1_2.ogg")) -local sound_death = Resources.sfx_load(NAMESPACE, "AdmonitorDeath", path.combine(SOUND_PATH, "death.ogg")) - -local push = Buff.new(NAMESPACE, "AdmonitorPush") +local sprite_mask = Sprite.new("AdmonitorMask", path.combine(SPRITE_PATH, "mask.png"), 1, 11, 26) +local sprite_palette = Sprite.new("AdmonitorPalette", path.combine(SPRITE_PATH, "palette.png")) +local sprite_portrait = Sprite.new("AdmonitorPortrait", path.combine(SPRITE_PATH, "portrait.png")) +local sprite_spawn = Sprite.new("AdmonitorSpawn", path.combine(SPRITE_PATH, "spawn.png"), 15, 58, 39) +local sprite_idle = Sprite.new("AdmonitorIdle", path.combine(SPRITE_PATH, "idle.png"), 18, 30, 27, 0.8) +local sprite_walk = Sprite.new("AdmonitorWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 27, 31) +local sprite_jump = Sprite.new("AdmonitorJump", path.combine(SPRITE_PATH, "jump.png"), 1, 26, 33) +local sprite_jump_peak = Sprite.new("AdmonitorJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 26, 33) +local sprite_fall = Sprite.new("AdmonitorFall", path.combine(SPRITE_PATH, "fall.png"), 1, 26, 33) +local sprite_death = Sprite.new("AdmonitorDeath", path.combine(SPRITE_PATH, "death.png"), 14, 33, 53) +local sprite_shoot1 = Sprite.new("AdmonitorShoot1", path.combine(SPRITE_PATH, "shoot1.png"), 30, 53, 86) + +GM.elite_generate_palettes(sprite_palette) + +local sound_spawn = Sound.new("AdmonitorSpawn", path.combine(SOUND_PATH, "spawn.ogg")) +local sound_shoot1a = Sound.new("AdmonitorShoot1A", path.combine(SOUND_PATH, "shoot1_1.ogg")) +local sound_shoot1b = Sound.new("AdmonitorShoot1B", path.combine(SOUND_PATH, "shoot1_2.ogg")) +local sound_death = Sound.new("AdmonitorDeath", path.combine(SOUND_PATH, "death.ogg")) + +local push = Buff.new("AdmonitorPush") push.show_icon = false push.is_debuff = true -push:clear_callbacks() -push:onPostStep(function(actor, stack) - -- anyways we want to apply this to only classic actors (actors who interact with physics, have skills, etc) - if GM.actor_is_classic(actor) and actor:get_data().puncher_push then - actor:skill_util_nudge_forward(actor.pHmax * actor:get_data().puncher_push) -- this will move the victim by their max speed * the strength of the push - end - - -- reduce the strength of the push, approaching 0 (technical: functionaly identical to math.approach from rorml) - if actor:get_data().puncher_push < 0 then - actor:get_data().puncher_push = math.min(0, actor:get_data().puncher_push + 0.1) - elseif actor:get_data().puncher_push > 0 then - actor:get_data().puncher_push = math.max(0, actor:get_data().puncher_push - 0.1) - end - - -- if the strength of the push is 0, end the debuff early - if actor:get_data().puncher_push == 0 then - GM.remove_buff(actor, push) +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(push:get_holding_actors()) do + local data = Instance.get_data(actor) + + -- anyways we want to apply this to only classic actors (actors who interact with physics, have skills, etc) + if GM.actor_is_classic(actor) and data.puncher_push then + actor:skill_util_nudge_forward(actor.pHmax * data.puncher_push) -- this will move the victim by their max speed * the strength of the push + data.puncher_push = ssr_approach(data.puncher_push, 0, 0.1) -- reduce the strength of the push, approaching 0 + end + + -- if the strength of the push is 0, end the debuff + if not data.puncher_push or data.puncher_push == 0 then + actor:buff_remove(push) + end end end) -- create the monster log -local mlog = Monster_Log.new(NAMESPACE, "admonitor") +local mlog = ssr_create_monster_log("admonitor") mlog.sprite_id = sprite_idle mlog.portrait_id = sprite_portrait mlog.sprite_offset_x = 44 @@ -57,17 +54,14 @@ mlog.stat_hp = 350 mlog.stat_damage = 17 mlog.stat_speed = 1.6 -local puncher = Object.new(NAMESPACE, "Admonitor", Object.PARENT.enemyClassic) -local puncher_id = puncher.value - -puncher.obj_sprite = sprite_idle -puncher.obj_depth = 11 -- depth of vanilla pEnemyClassic objects +local puncher = Object.new("Admonitor", Object.Parent.ENEMY_CLASSIC) +puncher:set_sprite(sprite_idle) +puncher:set_depth(11) -- depth of vanilla pEnemyClassic objects -local puncherPrimary = Skill.new(NAMESPACE, "admonitorZ") -local statePuncherPrimary = State.new(NAMESPACE, "admonitorPrimary") +local primary = Skill.new("admonitorZ") +local statePrimary = ActorState.new("admonitorPrimary") -puncher:clear_callbacks() -puncher:onCreate(function(actor) +Callback.add(puncher.on_create, function(actor) actor.sprite_palette = sprite_palette actor.sprite_spawn = sprite_spawn actor.sprite_idle = sprite_idle @@ -77,7 +71,7 @@ puncher:onCreate(function(actor) actor.sprite_fall = sprite_fall actor.sprite_death = sprite_death - actor.can_jump = false + actor.can_jump = true actor.mask_index = sprite_mask @@ -90,23 +84,21 @@ puncher:onCreate(function(actor) actor.z_range = 150 -- range of the primary actor.monster_log_drop_id = mlog.value - actor:set_default_skill(Skill.SLOT.primary, puncherPrimary) + actor:set_default_skill(Skill.Slot.PRIMARY, primary) actor:init_actor_late() end) -puncherPrimary:clear_callbacks() -puncherPrimary:onActivate(function(actor) - actor:enter_state(statePuncherPrimary) +Callback.add(primary.on_activate, function(actor, skill, slot) + actor:set_state(statePrimary) end) -statePuncherPrimary:clear_callbacks() -statePuncherPrimary:onEnter(function(actor, data) +Callback.add(statePrimary.on_enter, function(actor, data) actor.image_index = 0 data.fired = 0 end) -statePuncherPrimary:onStep(function(actor, data) +Callback.add(statePrimary.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(sprite_shoot1, 0.23) -- 0.23 is anim speed value, its 0.23 to make the animation match the sound @@ -122,8 +114,10 @@ statePuncherPrimary:onStep(function(actor, data) if data.fired == 2 and actor.image_index >= 16 then data.fired = 3 - if gm._mod_net_isHost() then + if Net.host then local attack = actor:fire_explosion(actor.x + 75 * actor.image_xscale, actor.y - 13, 130, 40, 4.2, nil, gm.constants.wSparks4).attack_info + attack.x = actor.x + 114 * actor.image_xscale + attack.y = actor.y - 6 attack.__ssr_puncher_push = 4 * actor.image_xscale end end @@ -131,66 +125,73 @@ statePuncherPrimary:onStep(function(actor, data) actor:skill_util_exit_state_on_anim_end() end) -local puncherPushSync = Packet.new() -puncherPushSync:onReceived(function(msg) - local actor = msg:read_instance() -- send to clients who got hit - local strength = msg:read_short() -- send to clients the strength of knockback +local packet = Packet.new("SyncAdmonitorPush") - if not actor:exists() then return end - - actor:get_data().puncher_push = strength - GM.apply_buff(actor, push, 3 * 60, 1) -- apply the knockback to the person who got hit -end) +local serializer = function(buffer, actor, strength) + buffer:write_instance(actor) + buffer:write_short(strength) +end -local function sync_puncher_push(actor, strength) - if not gm._mod_net_isHost() then - log.warning("sync_puncher_push called on client!") - return - end +local deserializer = function(buffer) + local actor = buffer:read_instance() -- send to clients who got hit + local strength = buffer:read_short() -- send to clients the strength of knockback - local msg = puncherPushSync:message_begin() - msg:write_instance(actor) - msg:write_short(strength) - msg:send_to_all() + if not Instance.exists(actor) then return end + + Instance.get_data(actor).puncher_push = strength + actor:buff_apply(push, 3 * 60) -- apply the knockback to the person who got hit + actor:screen_shake(6) end --- onAttackHit callbacks arent synced and only run for the host >> -Callback.add(Callback.TYPE.onAttackHit, "SSRPuncherPush", function(hit_info) +packet:set_serializers(serializer, deserializer) + +-- custom attack_info isnt synced >> +Callback.add(Callback.ON_ATTACK_HIT, function(hit_info) if hit_info.attack_info.__ssr_puncher_push then if hit_info.target and GM.actor_is_classic(hit_info.target) then - if gm._mod_net_isOnline() then - sync_puncher_push(hit_info.target, hit_info.attack_info.__ssr_puncher_push) -- >> we use a packet to sync the knockback effect for clients in multiplayer + if Net.online then + packet:send_to_all(hit_info.target, hit_info.attack_info.__ssr_puncher_push) -- >> we check if the host has it, and if it does use a packet to sync the knockback effect for clients in multiplayer end - hit_info.target:get_data().puncher_push = hit_info.attack_info.__ssr_puncher_push - GM.apply_buff(hit_info.target, push, 3 * 60, 1) + + Instance.get_data(hit_info.target).puncher_push = hit_info.attack_info.__ssr_puncher_push + hit_info.target:buff_apply(push, 3 * 60) + hit_info.target:screen_shake(6) end end end) -local monsterCardPuncher = Monster_Card.new(NAMESPACE, "admonitor") -monsterCardPuncher.object_id = puncher_id -monsterCardPuncher.spawn_cost = 160 -monsterCardPuncher.spawn_type = Monster_Card.SPAWN_TYPE.classic -monsterCardPuncher.can_be_blighted = true +local card = MonsterCard.new("admonitor") +card.object_id = puncher.value +card.spawn_cost = 160 +card.spawn_type = 0 --MonsterCard.SpawnType.CLASSIC +card.can_be_blighted = true + +if HOTLOADING then return end local stages = { - "ror-templeOfTheElders", - "ror-riskOfRain", - "ror-boarBeach", -- ive got no idea why nk put them there in ss1 but we decided it would be funny to keep it, also moved to pre loop + "templeOfTheElders", + "riskOfRain", + "boarBeach", -- ive got no idea why nk put them there in ss1 but we decided it would be funny to keep it, also moved to pre loop } local postLoopStages = { - "ror-sunkenTombs", - "ror-ancientValley", - "ror-magmaBarracks", + "sunkenTombs", + "ancientValley", + "magmaBarracks", } for _, s in ipairs(stages) do local stage = Stage.find(s) - stage:add_monster(monsterCardPuncher) + + if stage then + stage:add_monster(card) + end end for _, s in ipairs(postLoopStages) do local stage = Stage.find(s) - stage:add_monster_loop(monsterCardPuncher) + + if stage then + stage:add_monster_loop(card) + end end \ No newline at end of file diff --git a/Actors/chirrsmasGolem.lua b/Actors/chirrsmasGolem.lua new file mode 100644 index 00000000..c64147c2 --- /dev/null +++ b/Actors/chirrsmasGolem.lua @@ -0,0 +1,37 @@ +if HOTLOADING then return end +if not ssr_chirrsmas_active then return end -- christmas lasts from december 15th to january 15th +if Settings.chirrsmas == 2 then return end -- if chirrsmas is disabled in the config then we dont do anything + +gm.sprite_replace(gm.constants.sGolemIdle, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/idle.png"), 36, false, false, 26, 35) +gm.sprite_replace(gm.constants.sGolemWalk, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/walk.png"), 8, false, false, 27, 33) +gm.sprite_replace(gm.constants.sGolemShoot1, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/attack.png"), 13, false, false, 55, 54) +gm.sprite_replace(gm.constants.sGolemSpawn, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/spawn.png"), 19, false, false, 43, 99) +gm.sprite_replace(gm.constants.sGolemDeath, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/death.png"), 16, false, false, 60, 51) +gm.sprite_replace(gm.constants.sGolemJump, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/jump.png"), 1, false, false, 32, 34) +gm.sprite_replace(gm.constants.sGolemJumpPeak, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/jump_peak.png"), 1, false, false, 32, 34) +gm.sprite_replace(gm.constants.sGolemFall, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/fall.png"), 1, false, false, 32, 34) +gm.sprite_replace(gm.constants.sGolemPal, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/palette.png"), 1, false, false, 0, 0) +gm.sprite_replace(gm.constants.sCreditsMonsterGolem, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/credits.png"), 1, false, false, 20, 68) +gm.sprite_replace(gm.constants.sPing_Golem, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/ping.png"), 1, false, false, 32, 32) + +gm.sprite_replace(gm.constants.sGolemS2Idle, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/idle.png"), 36, false, false, 26, 35) +gm.sprite_replace(gm.constants.sGolemS2Walk, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/walk.png"), 8, false, false, 27, 33) +gm.sprite_replace(gm.constants.sGolemS2Shoot1, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/attack.png"), 13, false, false, 55, 54) +gm.sprite_replace(gm.constants.sGolemS2Spawn, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/spawn.png"), 19, false, false, 43, 99) +gm.sprite_replace(gm.constants.sGolemS2Death, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/death.png"), 16, false, false, 60, 51) +gm.sprite_replace(gm.constants.sGolemS2Jump, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/jump.png"), 1, false, false, 32, 34) +gm.sprite_replace(gm.constants.sGolemS2JumpPeak, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/jump_peak.png"), 1, false, false, 32, 34) +gm.sprite_replace(gm.constants.sGolemS2Fall, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/fall.png"), 1, false, false, 32, 34) +gm.sprite_replace(gm.constants.sGolemS2Pal, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/palette.png"), 1, false, false, 0, 0) +gm.sprite_replace(gm.constants.sPing_GolemS2, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/ping.png"), 1, false, false, 32, 32) + +gm.sprite_replace(gm.constants.sGolemSIdle, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/idle.png"), 36, false, false, 26, 35) +gm.sprite_replace(gm.constants.sGolemSWalk, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/walk.png"), 8, false, false, 27, 33) +gm.sprite_replace(gm.constants.sGolemSShoot1, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/attack.png"), 13, false, false, 55, 54) +gm.sprite_replace(gm.constants.sGolemSSpawn, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/spawn.png"), 19, false, false, 43, 99) +gm.sprite_replace(gm.constants.sGolemSDeath, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/death.png"), 16, false, false, 60, 51) +gm.sprite_replace(gm.constants.sGolemSPal, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/palette.png"), 1, false, false, 0, 0) +gm.sprite_replace(gm.constants.sCreditsMonsterGolemS, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/credits.png"), 1, false, false, 20, 68) +gm.sprite_replace(gm.constants.sPing_GolemS, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/ping.png"), 1, false, false, 32, 32) + +gm.sprite_replace(gm.constants.sTitleDoodadsGolemIdle, path.combine(PATH, "Sprites/Actors/ChirrsmasGolem/doodad.png"), 1, false, false, 27, 63) \ No newline at end of file diff --git a/Actors/exploder.lua b/Actors/exploder.lua index e4a27b4f..a687b614 100644 --- a/Actors/exploder.lua +++ b/Actors/exploder.lua @@ -1,37 +1,44 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Actors/Exploder") local SOUND_PATH = path.combine(PATH, "Sounds/Actors/Exploder") -local sprite_mask = Resources.sprite_load(NAMESPACE, "ExploderMask", path.combine(SPRITE_PATH, "mask.png"), 1, 15, 13) -local sprite_palette = Resources.sprite_load(NAMESPACE, "ExploderPalette", path.combine(SPRITE_PATH, "palette.png")) -local sprite_spawn = Resources.sprite_load(NAMESPACE, "ExploderSpawn", path.combine(SPRITE_PATH, "spawn.png"), 5, 21, 31) -local sprite_idle = Resources.sprite_load(NAMESPACE, "ExploderIdle", path.combine(SPRITE_PATH, "idle.png"), 6, 15, 14) -local sprite_walk = Resources.sprite_load(NAMESPACE, "ExploderWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 15, 15) -local sprite_jump = Resources.sprite_load(NAMESPACE, "ExploderJump", path.combine(SPRITE_PATH, "jump.png"), 1, 24, 16) -local sprite_jump_peak = Resources.sprite_load(NAMESPACE, "ExploderJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 24, 16) -local sprite_fall = Resources.sprite_load(NAMESPACE, "ExploderFall", path.combine(SPRITE_PATH, "fall.png"), 1, 24, 16) -local sprite_death = Resources.sprite_load(NAMESPACE, "ExploderDeath", path.combine(SPRITE_PATH, "death.png"), 7, 24, 33) - -local sprite_shoot1 = Resources.sprite_load(NAMESPACE, "ExploderShoot1", path.combine(SPRITE_PATH, "shoot1.png"), 20, 32, 55, nil, -16, -16, 16, 10) - -gm.elite_generate_palettes(sprite_palette) - -local sound_spawn = Resources.sfx_load(NAMESPACE, "ExploderSpawn", path.combine(SOUND_PATH, "spawn.ogg")) -local sound_hit = Resources.sfx_load(NAMESPACE, "ExploderHit", path.combine(SOUND_PATH, "hit.ogg")) -local sound_death = Resources.sfx_load(NAMESPACE, "ExploderDeath", path.combine(SOUND_PATH, "death.ogg")) -local sound_shoot1a = Resources.sfx_load(NAMESPACE, "ExploderShoot1a", path.combine(SOUND_PATH, "skill1a.ogg")) -local sound_shoot1b = Resources.sfx_load(NAMESPACE, "ExploderShoot1b", path.combine(SOUND_PATH, "skill1b.ogg")) - -local exploder = Object.new(NAMESPACE, "Exploder", Object.PARENT.enemyClassic) -local exploder_id = exploder.value - -exploder.obj_sprite = sprite_idle -exploder.obj_depth = 11 -- depth of vanilla pEnemyClassic objects - -local exploderPrimary = Skill.new(NAMESPACE, "exploderZ") -local stateExploderPrimary = State.new(NAMESPACE, "exploderPrimary") - -exploder:clear_callbacks() -exploder:onCreate(function(actor) +local sprite_mask = Sprite.new("ExploderMask", path.combine(SPRITE_PATH, "mask.png"), 1, 15, 13) +local sprite_palette = Sprite.new("ExploderPalette", path.combine(SPRITE_PATH, "palette.png")) +local sprite_portrait = Sprite.new("ExploderPortrait", path.combine(SPRITE_PATH, "portrait.png")) +local sprite_spawn = Sprite.new("ExploderSpawn", path.combine(SPRITE_PATH, "spawn.png"), 5, 21, 31) +local sprite_idle = Sprite.new("ExploderIdle", path.combine(SPRITE_PATH, "idle.png"), 6, 15, 14, 0.8) +local sprite_walk = Sprite.new("ExploderWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 15, 15, 0.8) +local sprite_jump = Sprite.new("ExploderJump", path.combine(SPRITE_PATH, "jump.png"), 1, 24, 16) +local sprite_jump_peak = Sprite.new("ExploderJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 24, 16) +local sprite_fall = Sprite.new("ExploderFall", path.combine(SPRITE_PATH, "fall.png"), 1, 24, 16) +local sprite_death = Sprite.new("ExploderDeath", path.combine(SPRITE_PATH, "death.png"), 7, 24, 33) +local sprite_shoot1 = Sprite.new("ExploderShoot1", path.combine(SPRITE_PATH, "shoot1.png"), 20, 32, 55) + +GM.elite_generate_palettes(sprite_palette) + +local sound_spawn = Sound.new("ExploderSpawn", path.combine(SOUND_PATH, "spawn.ogg")) +local sound_hit = Sound.new("ExploderHit", path.combine(SOUND_PATH, "hit.ogg")) +local sound_death = Sound.new("ExploderDeath", path.combine(SOUND_PATH, "death.ogg")) +local sound_shoot1a = Sound.new("ExploderShoot1a", path.combine(SOUND_PATH, "skill1a.ogg")) +local sound_shoot1b = Sound.new("ExploderShoot1b", path.combine(SOUND_PATH, "skill1b.ogg")) + +local exploder = Object.new("Exploder", Object.Parent.ENEMY_CLASSIC) +exploder:set_sprite(sprite_idle) +exploder:set_depth(11) -- depth of vanilla pEnemyClassic objects + +-- create the monster log +local mlog = ssr_create_monster_log("exploder") +mlog.sprite_id = sprite_idle +mlog.portrait_id = sprite_portrait +mlog.sprite_offset_x = 44 +mlog.sprite_offset_y = 48 +mlog.stat_hp = 100 +mlog.stat_damage = 17 +mlog.stat_speed = 2.6 + +local primary = Skill.new("exploderZ") +local statePrimary = ActorState.new("exploderPrimary") + +Callback.add(exploder.on_create, function(actor) actor.sprite_palette = sprite_palette actor.sprite_spawn = sprite_spawn actor.sprite_idle = sprite_idle @@ -53,30 +60,33 @@ exploder:onCreate(function(actor) actor.pHmax_base = 2.6 actor.z_range = 28 - actor:set_default_skill(Skill.SLOT.primary, exploderPrimary) + actor.monster_log_drop_id = mlog.value + actor:set_default_skill(Skill.Slot.PRIMARY, primary) actor:init_actor_late() end) -exploderPrimary:clear_callbacks() -exploderPrimary:onActivate(function(actor) - actor:enter_state(stateExploderPrimary) +Callback.add(primary.on_activate, function(actor, skill, slot) + actor:set_state(statePrimary) end) -stateExploderPrimary:clear_callbacks() -stateExploderPrimary:onEnter(function(actor, data) - actor.image_index = 0 +Callback.add(statePrimary.on_enter, function(actor, _) -- not gonna use data here because get_data should be faster + local data = Instance.get_data(actor) data.exploded = 0 - + actor.image_index = 0 actor.interrupt_sound = actor:sound_play(sound_shoot1a, 1.0, (0.9 + math.random() * 0.2) * actor.attack_speed) end) -stateExploderPrimary:onStep(function(actor, data) + +Callback.add(statePrimary.on_step, function(actor, _) actor:skill_util_fix_hspeed() actor:actor_animation_set(sprite_shoot1, 0.2) + + local data = Instance.get_data(actor) if data.exploded == 0 and actor.image_index >= 14 then data.exploded = 1 actor.intangible = true -- make the exploder untouchable, so it can't be hit after it has exploded but before it's deleted + --table.insert(exploder_kill_queue, actor.id) -- add to the kill queue actor:sound_play(sound_shoot1b, 1.0, (0.9 + math.random() * 0.2) * actor.attack_speed) actor:screen_shake(2) @@ -84,48 +94,33 @@ stateExploderPrimary:onStep(function(actor, data) -- every player's game simulates the explosion locally, making it easier to dodge on high ping. actor:fire_explosion_local(actor.x, actor.y - 16, 120, 56, 2) end -end) - --- destroying an actor anywhere in its state or step code causes errors in certain cases. do this in a separate pass -Callback.add("postStep", "SSDestroyExploders", function() - local exploders = Instance.find_all(exploder) - for _, actor in ipairs(exploders) do - local state_data = actor.actor_state_current_data_table - - -- check that it's advanced to the next frame. this gives time for the attack's procs and game report ("Killed by" info) to work - if state_data.exploded == 1 and actor.image_index >= 15 then - -- manually create a corpse. plays the remainder of the explosion animation - local body = gm.instance_create(actor.x, actor.y, gm.constants.oBody) - body.sprite_index = actor.sprite_index - body.image_xscale = actor.image_xscale - body.image_index = actor.image_index - body.image_speed = actor.image_speed - body.image_blend = actor.image_blend - body.sprite_palette = actor.sprite_palette - body.elite_type = actor.elite_type - - actor:destroy() - end + + if data.exploded == 1 and actor.image_index >= 15 then -- 1 image_index gap is needed to make the results screen show who you got killed by + data.exploded = 2 + actor.exp_worth = 0 -- dont drop money on death + actor.hp = -10000 -- kill ! end end) -local monsterCardExploder = Monster_Card.new(NAMESPACE, "exploder") -monsterCardExploder.object_id = exploder_id -monsterCardExploder.spawn_cost = 18 -monsterCardExploder.spawn_type = Monster_Card.SPAWN_TYPE.classic -monsterCardExploder.can_be_blighted = false -- HELL no +local card = MonsterCard.new("exploder") +card.object_id = exploder.value +card.spawn_cost = 18 +card.spawn_type = 0 --Monster_Card.SPAWN_TYPE.classic +card.can_be_blighted = false -- least threatening blighted enemy because they just blow up lol if HOTLOADING then return end --- TODO: evaluate a better approach for populating stages.. local stages = { - "ror-dampCaverns", - "ror-skyMeadow", - "ror-hiveCluster", - "ror-riskOfRain", + "dampCaverns", + "skyMeadow", + "hiveCluster", + "riskOfRain", } for _, s in ipairs(stages) do local stage = Stage.find(s) - stage:add_monster(monsterCardExploder) + + if stage then + stage:add_monster(card) + end end diff --git a/Actors/mimic.lua b/Actors/mimic.lua index 887d3e35..31dc7583 100644 --- a/Actors/mimic.lua +++ b/Actors/mimic.lua @@ -2,46 +2,32 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Actors/Mimic") local SOUND_PATH = path.combine(PATH, "Sounds/Actors/Mimic") -- assets -local sprite_mask = Resources.sprite_load(NAMESPACE, "MimicMask", path.combine(SPRITE_PATH, "mask.png"), 1, 21, 24) -local sprite_palette = Resources.sprite_load(NAMESPACE, "MimicPalette", path.combine(SPRITE_PATH, "palette.png")) -gm.elite_generate_palettes(sprite_palette) -local sprite_idle = Resources.sprite_load(NAMESPACE, "MimicIdle", path.combine(SPRITE_PATH, "idle.png"), 2, 30, 35) -local sprite_idle2 = Resources.sprite_load(NAMESPACE, "MimicIdle2", path.combine(SPRITE_PATH, "idle2.png"), 2, 30, 45) -local sprite_walk = Resources.sprite_load(NAMESPACE, "MimicWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 30, 35) -local sprite_walk2 = Resources.sprite_load(NAMESPACE, "MimicWalk2", path.combine(SPRITE_PATH, "walk2.png"), 6, 30, 35) -local sprite_jump = Resources.sprite_load(NAMESPACE, "MimicJump", path.combine(SPRITE_PATH, "jump.png"), 1, 19, 28) -local sprite_jump_peak = Resources.sprite_load(NAMESPACE, "MimicJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 19, 28) -local sprite_fall = Resources.sprite_load(NAMESPACE, "MimicFall", path.combine(SPRITE_PATH, "fall.png"), 1, 19, 28) -local sprite_death = Resources.sprite_load(NAMESPACE, "MimicDeath", path.combine(SPRITE_PATH, "death.png"), 10, 55, 93) -local sprite_shoot1a = Resources.sprite_load(NAMESPACE, "MimicShoot1a", path.combine(SPRITE_PATH, "shoot1a.png"), 10, 30, 45) -local sprite_shoot1b = Resources.sprite_load(NAMESPACE, "MimicShoot1b", path.combine(SPRITE_PATH, "shoot1b.png"), 4, 30, 45) -local sprite_shoot1c = Resources.sprite_load(NAMESPACE, "MimicShoot1c", path.combine(SPRITE_PATH, "shoot1c.png"), 6, 30, 45) ---local sprite_portrait = Resources.sprite_load(NAMESPACE, "MimicPortrait", path.combine(SPRITE_PATH, "portrait.png")) - -local sprite_inactive_idle = Resources.sprite_load(NAMESPACE, "MimicInactiveIdle", path.combine(SPRITE_PATH, "inactiveIdle.png"), 14, 30, 38) -local sprite_activate = Resources.sprite_load(NAMESPACE, "MimicActivate", path.combine(SPRITE_PATH, "spawn.png"), 18, 30, 60) -local sprite_vacuum = Resources.sprite_load(NAMESPACE, "MimicVacuumFX", path.combine(SPRITE_PATH, "vacuumParticle.png"), 4, 4, 4) - -local sound_spawn = Resources.sfx_load(NAMESPACE, "MimicSpawn", path.combine(SOUND_PATH, "spawn.ogg")) -local sound_hit = Resources.sfx_load(NAMESPACE, "MimicHit", path.combine(SOUND_PATH, "hit.ogg")) -local sound_shoot = Resources.sfx_load(NAMESPACE, "MimicShoot", path.combine(SOUND_PATH, "shoot.ogg")) -local sound_death = Resources.sfx_load(NAMESPACE, "MimicDeath", path.combine(SOUND_PATH, "death.ogg")) - -local particleVacuum = Particle.new(NAMESPACE, "Vacuum") -particleVacuum:set_sprite(sprite_vacuum, false, false, true) -particleVacuum:set_life(15, 20) -particleVacuum:set_speed(0, 0, 0.75, 0) - --- mimic -local mimic = Object.new(NAMESPACE, "Mimic", Object.PARENT.enemyClassic) -local mimic_id = mimic.value +local sprite_mask = Sprite.new("MimicMask", path.combine(SPRITE_PATH, "mask.png"), 1, 21, 24) +local sprite_palette = Sprite.new("MimicPalette", path.combine(SPRITE_PATH, "palette.png")) + +local sprite_idle = Sprite.new("MimicIdle", path.combine(SPRITE_PATH, "idle.png"), 2, 30, 35) +local sprite_idle2 = Sprite.new("MimicIdle2", path.combine(SPRITE_PATH, "idle2.png"), 2, 30, 45) +local sprite_walk = Sprite.new("MimicWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 30, 35) +local sprite_walk2 = Sprite.new("MimicWalk2", path.combine(SPRITE_PATH, "walk2.png"), 6, 30, 35) +local sprite_jump = Sprite.new("MimicJump", path.combine(SPRITE_PATH, "jump.png"), 1, 19, 28) +local sprite_jump_peak = Sprite.new("MimicJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 19, 28) +local sprite_fall = Sprite.new("MimicFall", path.combine(SPRITE_PATH, "fall.png"), 1, 19, 28) +local sprite_death = Sprite.new("MimicDeath", path.combine(SPRITE_PATH, "death.png"), 10, 55, 93) +local sprite_shoot1a = Sprite.new("MimicShoot1a", path.combine(SPRITE_PATH, "shoot1a.png"), 10, 30, 45) +local sprite_shoot1b = Sprite.new("MimicShoot1b", path.combine(SPRITE_PATH, "shoot1b.png"), 4, 30, 45) +local sprite_shoot1c = Sprite.new("MimicShoot1c", path.combine(SPRITE_PATH, "shoot1c.png"), 6, 30, 45) +local sprite_portrait = Sprite.new("MimicPortrait", path.combine(SPRITE_PATH, "portrait.png")) + +local sprite_inactive_idle = Sprite.new("MimicInactiveIdle", path.combine(SPRITE_PATH, "inactiveIdle.png"), 14, 30, 38) +local sprite_activate = Sprite.new("MimicActivate", path.combine(SPRITE_PATH, "spawn.png"), 18, 30, 60) +local sprite_vacuum = Sprite.new("MimicVacuumFX", path.combine(SPRITE_PATH, "vacuumParticle.png"), 4, 4, 4) + +local sound_spawn = Sound.new("MimicSpawn", path.combine(SOUND_PATH, "spawn.ogg")) +local sound_hit = Sound.new("MimicHit", path.combine(SOUND_PATH, "hit.ogg")) +local sound_shoot = Sound.new("MimicShoot", path.combine(SOUND_PATH, "shoot.ogg")) +local sound_death = Sound.new("MimicDeath", path.combine(SOUND_PATH, "death.ogg")) -mimic.obj_sprite = sprite_idle -mimic.obj_depth = 10 - -local efGoldSteal = Object.new(NAMESPACE, "EfGoldSteal") -efGoldSteal.obj_sprite = gm.constants.sEfGold1 -efGoldSteal.obj_depth = -279 +local SUCK_ANIMATION_SPEED = 0.3 local gold_sprites = { gm.constants.sEfGold1, @@ -53,18 +39,133 @@ local gold_sprites = { gm.constants.sEfGold7, } -local mimicPrimary = Skill.new(NAMESPACE, "mimicZ") -local stateMimicSuckStart = State.new(NAMESPACE, "mimicSuckStart") -local stateMimicSuck = State.new(NAMESPACE, "mimicSuck") -local stateMimicSuckEnd = State.new(NAMESPACE, "mimicSuckEnd") +local efGoldSteal = Object.new("EfGoldSteal") +efGoldSteal:set_sprite(gm.constants.sEfGold1) +efGoldSteal:set_depth(-279) -local packetMimicSteal = Packet.new() -local packetMimicGoldSteal = Packet.new() +local function select_item_to_steal(victim) + local inventory = victim.inventory_item_order + + if #inventory > 0 then + local chosen_id = inventory[math.random(#inventory)] + local chosen_item = Item.wrap(chosen_id) + return chosen_item + end + + return nil +end -mimic:clear_callbacks() -mimic:onCreate(function(actor) +local function do_gold_steal(actor, victim) + victim:sound_play(gm.constants.wGoldBomb, 0.5, 2.5) + + local amount = math.ceil(victim.gold) + local dir = Math.direction(victim.x, victim.y, actor.x, actor.y) + local log = math.ceil(math.log(amount, 5)) + + -- kind of ugly but, whatever, good enough + -- do some stuff to produce gold effects that grow in quantity and scale with how much has been stolen + for i = 0, log do + local effect = efGoldSteal:create(victim.x, victim.y) + effect.target = actor + effect.direction = dir + effect.speed = math.random(7) + effect.vspeed = -math.random(7) + effect.sprite_index = gold_sprites[math.random(math.min(7, log))] + end + + victim.gold = 0 + actor.gold = amount + if victim.is_local then + gm._mod_game_getHUD().gold = 0 + end + + if Net.online and Net.host then + packetGoldSteal:send_to_all(actor, victim) + end +end + +local particleVacuum = Particle.new("Vacuum") +particleVacuum:set_sprite(sprite_vacuum, false, false, true) +particleVacuum:set_life(15, 20) +particleVacuum:set_speed(0, 0, 0.75, 0) + +-- mimic +local mimic = Object.new("Mimic", Object.Parent.ENEMY_CLASSIC) +mimic:set_sprite(sprite_idle) +mimic:set_depth(10) + +-- these are just used to sync the mimic's gold when a player manages to activate a dormant one via purchase +-- i prefer this to adding yet another user packet lol +local serializer = function(actor, buffer) + actor.gold = buffer:read_uint() +end + +local deserializer = function(actor, buffer) + actor.gold = buffer:read_uint() +end + +Object.add_serializers(mimic, serializer, deserializer) + +-- create the monster log +local mlog = ssr_create_monster_log("mimic") +mlog.sprite_id = sprite_walk +mlog.portrait_id = sprite_portrait +mlog.sprite_offset_x = 42 +mlog.sprite_offset_y = 45 +mlog.stat_hp = 200 +mlog.stat_damage = 10 +mlog.stat_speed = 2.6 + +local primary = Skill.new("mimicZ") +local stateSuckStart = ActorState.new("mimicSuckStart") +local stateSuck = ActorState.new("mimicSuck") +local stateSuckEnd = ActorState.new("mimicSuckEnd") + +local packetSteal = Packet.new("SyncMimicSteal") + +local item_serializer = function(buffer, actor, x, y, tier, item_count) + buffer:write_instance(actor) + buffer:write_short(x) + buffer:write_short(y) + buffer:write_byte(tier) + buffer:write_ushort(item_count) +end + +local item_deserializer = function(buffer) + local actor = buffer:read_instance() + local x, y = buffer:read_short(), buffer:read_short() + local tier = buffer:read_byte() + local item_count = buffer:read_ushort() + + local steal = Object.find("EfRoboBuddySteal"):create(x, y) + steal.parent = actor + steal.count = item_count + steal.tier = tier +end + +packetSteal:set_serializers(item_serializer, item_deserializer) + +local packetGoldSteal = Packet.new("SyncMimicGoldSteal") + +local gold_serializer = function(buffer, actor, victim) + buffer:write_instance(actor) + buffer:write_instance(victim) +end + +local gold_deserializer = function(buffer) + local actor = buffer:read_instance() + local victim = buffer:read_instance() + + if not actor:exists() then return end + if not victim:exists() then return end + + do_gold_steal(actor, victim) +end + +packetGoldSteal:set_serializers(gold_serializer, gold_deserializer) + +Callback.add(mimic.on_create, function(actor) actor.sprite_palette = sprite_palette - --actor.sprite_spawn = sprite_spawn -- handled by MimicInactive object actor.sprite_idle = sprite_idle actor.sprite_walk = sprite_walk actor.sprite_jump = sprite_jump @@ -77,7 +178,6 @@ mimic:onCreate(function(actor) actor.mask_index = sprite_mask - --actor.sound_spawn = sound_spawn actor.sound_hit = sound_hit actor.sound_death = sound_death @@ -86,14 +186,14 @@ mimic:onCreate(function(actor) actor.pHmax_base = 2.4 + math.min(0.2 * GM._mod_game_getDirector().enemy_buff, 3) actor.z_range = 200 - actor:set_default_skill(Skill.SLOT.primary, mimicPrimary) + actor:set_default_skill(Skill.Slot.PRIMARY, primary) - -- monster logs are really scuffed in RMT right now.. - --actor.monster_log_drop_id = log.value + actor.monster_log_drop_id = mlog.value actor:init_actor_late() end) -mimic:onStep(function(actor) + +Callback.add(mimic.on_step, function(actor) if actor.stolen_item or actor.gold > 0 and not actor.fleeing then actor.fleeing = true actor:alarm_set(0, -1) -- disable the classic enemy ai -- not perfect but it does the job @@ -104,7 +204,7 @@ mimic:onStep(function(actor) end -- has to be here instead of at the start so the clients still get the unique getaway sprites - if gm._mod_net_isClient() then return end + if Net.client then return end if actor.fleeing and actor.actor_state_current_id == -1 then -- attempt to flee at all times @@ -112,11 +212,11 @@ mimic:onStep(function(actor) local sync = false if actor.target.x > actor.x then - if not gm.bool(actor.moveLeft) then sync = true end + if not Util.bool(actor.moveLeft) then sync = true end actor.moveLeft = true actor.moveRight = false else - if not gm.bool(actor.moveRight) then sync = true end + if not Util.bool(actor.moveRight) then sync = true end actor.moveLeft = false actor.moveRight = true end @@ -135,108 +235,26 @@ mimic:onStep(function(actor) end end end) -mimic:onDestroy(function(actor) - Particle.find("ror", "Spark"):create(actor.x, actor.y, 7) + +Callback.add(mimic.on_destroy, function(actor) + Particle.find("Spark"):create(actor.x, actor.y, 7) actor:screen_shake(4) if actor.gold > 0 then gm.drop_gold_and_exp(actor.x, actor.y, actor.gold, nil, true, false) -- last two args are 'drop gold', 'drop exp' end end) --- these are just used to sync the mimic's gold when a player manages to activate a dormant one via purchase --- i prefer this to adding yet another user packet lol -mimic:onSerialize(function(actor, buffer) - buffer:write_uint(actor.gold) -end) -mimic:onDeserialize(function(actor, buffer) - actor.gold = buffer:read_uint() -end) - -mimicPrimary:clear_callbacks() -mimicPrimary:onActivate(function(actor) - actor:enter_state(stateMimicSuckStart) -end) - -local SUCK_ANIMATION_SPEED = 0.3 - -local function select_item_to_steal(victim) - local inventory = victim.inventory_item_order - if #inventory > 0 then - local chosen_id = inventory[math.random(#inventory)] - local chosen_item = Item.wrap(chosen_id) - return chosen_item - end - return nil -end -local function sync_steal_effects(actor, x, y, tier, item_count) - if gm._mod_net_isClient() then error("sync_steal_effects called on client!") end - local msg = packetMimicSteal:message_begin() - msg:write_instance(actor) - msg:write_short(x) - msg:write_short(y) - msg:write_byte(tier) - msg:write_ushort(item_count) - msg:send_to_all() -end -packetMimicSteal:onReceived(function(msg) - local actor = msg:read_instance() - local x, y = msg:read_short(), msg:read_short() - local tier = msg:read_byte() - local item_count = msg:read_ushort() - - local steal = GM.instance_create(x, y, gm.constants.oEfRoboBuddySteal) - steal.parent = actor - steal.count = item_count - steal.tier = tier -end) -local function do_gold_steal(actor, victim) - victim:sound_play(gm.constants.wGoldBomb, 0.5, 2.5) - - local amount = math.ceil(victim.gold) - local dir = gm.point_direction(victim.x, victim.y, actor.x, actor.y) - local log = math.ceil(math.log(amount, 5)) - - -- kind of ugly but, whatever, good enough - -- do some stuff to produce gold effects that grow in quantity and scale with how much has been stolen - for i=0, log do - local g = efGoldSteal:create(victim.x, victim.y) - g.target = actor - g.direction = dir - g.speed = math.random(7) - g.vspeed = -math.random(7) - g.sprite_index = gold_sprites[math.random(math.min(7, log))] - end - - victim.gold = 0 - actor.gold = amount - if victim.is_local then - gm._mod_game_getHUD().gold = 0 - end - if gm._mod_net_isOnline() and gm._mod_net_isHost() then - local msg = packetMimicGoldSteal:message_begin() - msg:write_instance(actor) - msg:write_instance(victim) - msg:send_to_all() - end -end -packetMimicGoldSteal:onReceived(function(msg) - local actor = msg:read_instance() - local victim = msg:read_instance() - if not actor:exists() then return end - if not victim:exists() then return end - do_gold_steal(actor, victim) +Callback.add(primary.on_activate, function(actor, skill, slot) + actor:set_state(stateSuckStart) end) -stateMimicSuckStart:clear_callbacks() -stateMimicSuck:clear_callbacks() -stateMimicSuckEnd:clear_callbacks() - -stateMimicSuckStart:onEnter(function(actor, data) +Callback.add(stateSuckStart.on_enter, function(actor, data) actor.image_index = 0 data.noised = 0 end) -stateMimicSuckStart:onStep(function(actor, data) + +Callback.add(stateSuckStart.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(sprite_shoot1a, SUCK_ANIMATION_SPEED) @@ -246,11 +264,11 @@ stateMimicSuckStart:onStep(function(actor, data) end if actor.image_index + actor.image_speed >= 10 then - actor:enter_state(stateMimicSuck) + actor:set_state(stateSuck) end end) -stateMimicSuck:onEnter(function(actor, data) +Callback.add(stateSuck.on_enter, function(actor, data) actor.image_index = 0 data.fired = 0 data.loops = 0 @@ -259,7 +277,8 @@ stateMimicSuck:onEnter(function(actor, data) actor:screen_shake(3) end) -stateMimicSuck:onStep(function(actor, data) + +Callback.add(stateSuck.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(sprite_shoot1b, SUCK_ANIMATION_SPEED) @@ -273,43 +292,41 @@ stateMimicSuck:onStep(function(actor, data) end -- attempt to steal stuff until something gives - if data.stealed == 0 and gm._mod_net_isHost() then - local x1, y1 = actor.x, actor.y - 72 - local x2, y2 = actor.x + 248 * actor.image_xscale, actor.y + 72 - - local victims = List.wrap(actor:find_characters_rectangle(x1, y1, x2, y2, actor.team, false)) - - for _, victim in ipairs(victims) do - local item = select_item_to_steal(victim) - if item then - data.stealed = 1 - - local item_id = item.value - local item_count = victim:item_stack_count(item, Item.STACK_KIND.any) - - -- robomando enemy itemsteal effect -- gives the item to the mimic once it reaches it - local steal = GM.instance_create(victim.x, victim.y, gm.constants.oEfRoboBuddySteal) - steal.parent = actor - steal.item_id = item_id - steal.count = item_count - steal.tier = item.tier - - victim:item_remove(item, item_count, Item.STACK_KIND.any) - - -- store the item in variables for easy access when it dies - -- i'd use :get_data for this but that shit is hideously unreliable lol - actor.stolen_item = item_id - actor.stolen_item_count = item_count - - if gm._mod_net_isOnline() then - sync_steal_effects(actor, steal.x, steal.y, item.tier, item_count) + if data.stealed == 0 and Net.host then + for _, victim in ipairs(actor:get_collisions_rectangle(gm.constants.pActor, actor.x, actor.y - 72, actor.x + 248 * actor.image_xscale, actor.y + 72)) do + if victim.team ~= actor.team then + local item = select_item_to_steal(victim) + if item then + data.stealed = 1 + + local item_id = item.value + local item_count = victim:item_count(item, Item.StackKind.ANY) + + -- robomando enemy itemsteal effect -- gives the item to the mimic once it reaches it + local steal = Object.find("EfRoboBuddySteal"):create(victim.x, victim.y) + steal.parent = actor + steal.item_id = item_id + steal.count = item_count + steal.tier = item.tier + + victim:item_take(item, item_count, Item.StackKind.ANY) + + -- store the item in variables for easy access when it dies + -- i'd use :get_data for this but that shit is hideously unreliable lol + actor.stolen_item = item_id + actor.stolen_item_count = item_count + + if Net.online then + packetSteal:send_to_all(actor, steal.x, steal.y, item.tier, item_count) + end + + break + elseif victim.gold and victim.gold > 0 then + data.stealed = 1 + + do_gold_steal(actor, victim) + break end - break - elseif victim.gold and victim.gold > 0 then - data.stealed = 1 - - do_gold_steal(actor, victim) - break end end end @@ -318,8 +335,8 @@ stateMimicSuck:onStep(function(actor, data) if math.random() < 0.33 then local px = actor.x + (100 + math.random() * 120) * actor.image_xscale local py = actor.y - 60 + math.random() * 80 - local pdir = gm.point_direction(px, py, actor.x, actor.y) - particleVacuum:set_direction(pdir-3, pdir+3, 0, 0) + local pdir = Math.direction(px, py, actor.x, actor.y) + particleVacuum:set_direction(pdir - 3, pdir + 3, 0, 0) particleVacuum:create(px, py, 1) end @@ -330,18 +347,20 @@ stateMimicSuck:onStep(function(actor, data) data.fired = 0 end + if data.loops > 3 then - actor:enter_state(stateMimicSuckEnd) + actor:set_state(stateSuckEnd) end end) -stateMimicSuckEnd:onEnter(function(actor, data) +Callback.add(stateSuckEnd.on_enter, function(actor, data) actor.image_index = 0 actor:screen_shake(5) data.fired = 0 end) -stateMimicSuckEnd:onStep(function(actor, data) + +Callback.add(stateSuckEnd.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(sprite_shoot1c, SUCK_ANIMATION_SPEED) @@ -356,29 +375,29 @@ stateMimicSuckEnd:onStep(function(actor, data) actor:skill_util_exit_state_on_anim_end() end) -Callback.add(Callback.TYPE.onDeath, "SSMimicDropItem", function(actor, out_of_bounds) - if GM.get_object_index(actor) == mimic_id and not out_of_bounds then - if actor.stolen_item then - local item = Item.wrap(actor.stolen_item) - -- some items don't have corresponding objects, such as used dios or time keeper's secret - if item.object_id ~= -1 then - for i=1, actor.stolen_item_count do - item:create(actor.x, actor.y) - end +Callback.add(Callback.ON_DEATH, function(actor, out_of_bounds) + if not actor:get_object_index() == mimic.value or out_of_bounds then return end + + if actor.stolen_item then + local item = Item.wrap(actor.stolen_item) + -- some items don't have corresponding objects, such as used dios or time keeper's secret + if item.object_id ~= -1 then + for i = 1, actor.stolen_item_count do + item:create(actor.x, actor.y) end end end end) -- visual effect used for gold stealing. doesn't affect anything, just provides visual of coins similar to oEfGold -efGoldSteal:clear_callbacks() -efGoldSteal:onCreate(function(self) +Callback.add(efGoldSteal.on_create, function(self) self.image_speed = 0.25 self.target = -4 self.gravity = 0.4 self.go = 0 end) -efGoldSteal:onStep(function(self) + +Callback.add(efGoldSteal.on_step, function(self) if not Instance.exists(self.target) then self:destroy() return end local target = self.target @@ -391,33 +410,31 @@ efGoldSteal:onStep(function(self) self.gravity = 0 end else - local dir = gm.point_direction(self.x, self.y, tx, ty) + local dir = Math.direction(self.x, self.y, tx, ty) self.direction = dir self.speed = math.min(self.speed + 0.5, 5) end - if gm.point_distance(self.x, self.y, tx, ty) <= 20 + self.speed then + if Math.distance(self.x, self.y, tx, ty) <= 20 + self.speed then self:sound_play(gm.constants.wCoin, 1, 1) - Particle.find("ror", "GoldSparkleBig"):create(self.x, self.y, 1) + Particle.find("GoldSparkleBig"):create(self.x, self.y, 1) self:destroy() end end) -- object for when mimic is idle in the world, waiting for a victim -- it's actually interactable, and can be purchased, which spawns the mimic with gold :3 -local mimicInactive = Object.new(NAMESPACE, "MimicInactive", Object.PARENT.interactable) -local mimicInactive_id = mimicInactive.value - -mimicInactive.obj_sprite = sprite_inactive_idle -mimicInactive.obj_depth = 90 +local mimicInactive = Object.new("MimicInactive", Object.Parent.INTERACTABLE) +mimicInactive:set_sprite(sprite_inactive_idle) +mimicInactive:set_depth(90) -mimicInactive:clear_callbacks() -mimicInactive:onCreate(function(self) - gm.interactable_init_cost(self.id, 0, 50) +Callback.add(mimicInactive.on_create, function(self) + GM.interactable_init_cost(self, 0, 50) + self:interactable_init_name() end) -mimicInactive:onStep(function(self) +Callback.add(mimicInactive.on_step, function(self) -- pInteractable automatically stops animation when it ends -- this code uses that to its advantage if self.active == 0 then -- occasional idle fidget @@ -427,10 +444,10 @@ mimicInactive:onStep(function(self) end -- anyone nearby? - if gm._mod_net_isHost() and math.random() < 0.03 then + if Net.host and math.random() < 0.01 then local target = self:collision_rectangle(self.x - 140, self.y - 90, self.x + 140, self.y + 32, gm.constants.oP, false, false) - if target ~= -4 and gm.bool(target.is_targettable) then + if target ~= -4 and Util.bool(target.is_targettable) then self.active = 1 -- send a built-in set_active packet @@ -453,8 +470,8 @@ mimicInactive:onStep(function(self) end if self.image_speed == 0 then - if gm._mod_net_isHost() then - local actor = mimic:create(self.x, self.y-25) + if Net.host then + local actor = mimic:create(self.x, self.y - 25) -- if there's a valid activator, that means a player managed to activate it by purchase if self.activator ~= -4 then @@ -470,15 +487,15 @@ mimicInactive:onStep(function(self) end end) -Callback.add(Callback.TYPE.onStageStart, "SSMimicSpawn", function() - if gm._mod_net_isClient() then return end -- host handles spawning +Callback.add(Callback.ON_STAGE_START, function() + if Net.client then return end -- host handles spawning if Global.__gamemode_current >= 2 then return end -- don't spawn mimics in trials or tutorial.. -- try spawning up to 3 mimics, though more than 1 is extremely unlikely - for i=1, 3 do - if math.random() <= 0.05 then + for i = 0, 3 do + if math.random() <= 0.1 then -- function used by the game's director when spawninginteractables. - gm._mod_game_getDirector():mapobject_spawn(mimicInactive_id, 1) -- second arg is required tile space + gm._mod_game_getDirector():mapobject_spawn(mimicInactive.value, 1) -- second arg is required tile space else break end diff --git a/Artifacts/displacement.lua b/Artifacts/displacement.lua index a4a3dcb9..31fe3df8 100644 --- a/Artifacts/displacement.lua +++ b/Artifacts/displacement.lua @@ -2,23 +2,24 @@ -- this one is kinda hard so ill try to explain literally everything lmao -- ill be refering to the stage number as its "tier" here. for example, without the artifact of displacement active dried lake is tier 1, magma barracks is tier 4, temple of the elders is 5, risk of rain is 6, etc -- ill be refering to the stage itself as "environment" or "env" -local loadout = Resources.sprite_load(NAMESPACE, "ArtifactOfDisplacementLoadout", path.combine(PATH, "Sprites/Artifacts/Displacement/loadout.png"), 3, 19, 19) -local pickup = Resources.sprite_load(NAMESPACE, "ArtifactOfDisplacementPickup", path.combine(PATH, "Sprites/Artifacts/Displacement/pickup.png"), 1, 20, 20) +local loadout = Sprite.new("ArtifactOfDisplacementLoadout", path.combine(PATH, "Sprites/Artifacts/Displacement/loadout.png"), 3, 19, 19) +local pickup = Sprite.new("ArtifactOfDisplacementPickup", path.combine(PATH, "Sprites/Artifacts/Displacement/pickup.png"), 1, 20, 20) -local displacement = Artifact.new(NAMESPACE, "displacement") -displacement:set_sprites(loadout, pickup) +local displacement = Artifact.new("displacement") +displacement.sprite_loadout_id = loadout +displacement.sprite_pickup_id = pickup local potentialEnvs = List.new() -- this is where we will put environments that are available for random selection local validEnvs = List.new() -- this is where we will put all valid environments (no boar beach cuz its a secret and no risk of rain cuz its a final environment for example) -gm.post_script_hook(gm.constants.stage_roll_next, function(self, other, result, args) +local hook = Hook.add_post(gm.constants.stage_roll_next, function(self, other, result, args) if not displacement.active then return end validEnvs:clear() + for i = 1, 4 do -- go through 4 tiers of stages, we exclude tier 6 because we dont want to randomly go to risk of rain, and we exclude tier 5 stages because artifact of tempus can easily be cheesed if temple of the elders is chosen as one of the first stages (it also breaks the onstep code of the artifact's object for some reason? no clue why) local envs = List.wrap(gm._mod_stage_get_pool_list(i)) -- get all environments in a tier for _, env in ipairs(envs) do - print(Stage.wrap(env).identifier) validEnvs:add(env) -- add each environment to validEnvs end end @@ -50,3 +51,10 @@ gm.post_script_hook(gm.constants.stage_roll_next, function(self, other, result, end end) +-- disable the artifact initially +hook:toggle(false) + +Callback.add(displacement.on_set_active, function(active) + hook:toggle(active) +end) + diff --git a/Artifacts/gathering.lua b/Artifacts/gathering.lua index 39b167f1..8deb5a21 100644 --- a/Artifacts/gathering.lua +++ b/Artifacts/gathering.lua @@ -3,40 +3,57 @@ -- btw this should prolly be redone a bit to account for nucleus gems when that gets added -- also also also thanks to onyx (0n_x) for optimizing this mess of an artifact! ! ! ! should be way less laggier now -local loadout = Resources.sprite_load(NAMESPACE, "ArtifactOfGatheringLoadout", path.combine(PATH, "Sprites/Artifacts/Gathering/loadout.png"), 3, 19, 19) -local pickup = Resources.sprite_load(NAMESPACE, "ArtifactOfGatheringPickup", path.combine(PATH, "Sprites/Artifacts/Gathering/pickup.png"), 1, 20, 20) +local loadout = Sprite.new("ArtifactOfGatheringLoadout", path.combine(PATH, "Sprites/Artifacts/Gathering/loadout.png"), 3, 19, 19) +local pickup = Sprite.new("ArtifactOfGatheringPickup", path.combine(PATH, "Sprites/Artifacts/Gathering/pickup.png"), 1, 20, 20) -local gathering = Artifact.new(NAMESPACE, "gathering") -gathering:set_sprites(loadout, pickup) +local gathering = Artifact.new("gathering") +gathering.sprite_loadout_id = loadout +gathering.sprite_pickup_id = pickup -- we are using post_code_execute and pre_code_execute for less lag -- wrapping "self" will also cause a lot of lag so we will have to use game maker functions instead of rmt methods (self:place_meeting() instead of self:is_colliding() for example) -gm.post_code_execute("gml_Object_oEfGold_Create_0", function(self, other) +local create_hook = Hook.add_post("gml_Object_oEfGold_Create_0", function(self, other) if not gathering.active then return end + -- value gets set after creation, so waiting a frame to change it - Alarm.create(function() - self.value = self.value * 2 - end, 1) - self.lifetime = 1000 + Alarm.add(1, function() + if Instance.exists(self) then + self.value.value = self.value.value * 2 + end + end) + + local data = Instance.get_data(self) + data.lifetime = 1000 end) -- It's preferred to always let basegame functions run, this mostly just undoes what alarm[1] is doing -- also doesn't need the worksaround with alarm[2] -gm.post_code_execute("gml_Object_oEfGold_Alarm_1", function(self, other) +local alarm_hook = Hook.add_post("gml_Object_oEfGold_Alarm_1", function(self, other) if not gathering.active then return end - self.hspeed = 0 - self.vspeed = 0 - self.speed = 5 -- the pickup radius is loosely related to speed, if speed is 0 then you wouldnt be able to pick up the coin - - self.lifetime = self.lifetime - 5 - if self.lifetime < 100 then - if self.lifetime % 15 < 10 then + + self.value.speed = 5 -- the pickup radius is loosely related to speed, if speed is 0 then you wouldnt be able to pick up the coin + self.value.hspeed = 0 + self.value.vspeed = 0 + + local data = Instance.get_data(self) + data.lifetime = data.lifetime - 5 + if data.lifetime < 100 then + if data.lifetime % 15 < 10 then self.visible = true else self.visible = false end end - if self.lifetime <= 0 then - gm.instance_destroy(self) + if data.lifetime <= 0 then + GM.instance_destroy(self) end +end) + +-- disable the artifact initially +create_hook:toggle(false) +alarm_hook:toggle(false) + +Callback.add(gathering.on_set_active, function(active) + create_hook:toggle(active) + alarm_hook:toggle(active) end) \ No newline at end of file diff --git a/Artifacts/multitude.lua b/Artifacts/multitude.lua index 302a8e39..21325e6b 100644 --- a/Artifacts/multitude.lua +++ b/Artifacts/multitude.lua @@ -2,40 +2,37 @@ -- artifact!!!! makes many enemy but weak enemy!!! -- i tried commenting about everything you could get confused about -local loadout = Resources.sprite_load(NAMESPACE, "ArtifactOfMultitudeLoadout", path.combine(PATH, "Sprites/Artifacts/Multitude/loadout.png"), 3, 19, 19) -local pickup = Resources.sprite_load(NAMESPACE, "ArtifactOfMultitudePickup", path.combine(PATH, "Sprites/Artifacts/Multitude/pickup.png"), 1, 20, 20) +local loadout = Sprite.new("ArtifactOfMultitudeLoadout", path.combine(PATH, "Sprites/Artifacts/Multitude/loadout.png"), 3, 19, 19) +local pickup = Sprite.new("ArtifactOfMultitudePickup", path.combine(PATH, "Sprites/Artifacts/Multitude/pickup.png"), 1, 20, 20) -local multitude = Artifact.new(NAMESPACE, "multitude") -multitude:set_sprites(loadout, pickup) +local multitude = Artifact.new("multitude") +multitude.sprite_loadout_id = loadout +multitude.sprite_pickup_id = pickup local ARRIVING_TIME = 500 -- time before the horde arrives, used to display "prepare yourself...", time measured in game ticks (1 / 60 of a second) local APPROACHING_TIME = 1000 -- time before the horde arrives, used to display "a horde of enemies is approaching", time measured in game ticks (1 / 60 of a second) -Callback.add(Callback.TYPE.onStep, "MultitudeWave", function() +local step_callback = Callback.add(Callback.ON_STEP, function() if not multitude.active then return end local spawn = true - -- if the teleporter is charged already, disable spawns - for _, teleporter in ipairs(Instance.find_all(gm.constants.oTeleporter)) do - if teleporter.time >= teleporter.maxtime then + -- tbh not sure why in ss1 it uses a different method here for determining whether it should display or not, but who cares, it works + for _, teleporter in ipairs(Instance.find_all(gm.constants.pTeleporter)) do + if teleporter.active >= 2 then spawn = false break end end - -- apply the same thing to divine teleporters - for _, teleporter in ipairs(Instance.find_all(gm.constants.oTeleporterEpic)) do - if teleporter.time >= teleporter.maxtime then + + -- apply the same thing to the control panel on contact light + for _, teleporter in ipairs(Instance.find_all(gm.constants.oCommand)) do + if teleporter.active >= 2 then spawn = false break end end - -- If Providence or his Wurms are present, disable spawns - if Instance.find(gm.constants.oBoss1):exists() or Instance.find(gm.constants.oWurmHead):exists() then - spawn = false - end - local players = 0 -- counting how many players are currently playing for multiplayer scaling reasons for _, player in ipairs(Instance.find_all(gm.constants.oP)) do @@ -43,16 +40,17 @@ Callback.add(Callback.TYPE.onStep, "MultitudeWave", function() end local director = GM._mod_game_getDirector() + local data = Instance.get_data(director) local timeRequired = (6500 + director.time_start) / ((players * 0.3) + 0.7) -- time required for a horde of enemies to start spawning if spawn and gm._mod_game_get_timestop() <= 0 then -- if spawns are enabled and the time isnt stopped, procceed - if not director:get_data().multitudeTime then - director:get_data().multitudeTime = 100 -- set the initial multitude timer + if not data.multitudeTime then + data.multitudeTime = 100 -- set the initial multitude timer end - if director:get_data().multitudeTime < timeRequired then -- increment the timer by one until it reaches timeRequired - director:get_data().multitudeTime = director:get_data().multitudeTime + 1 + if data.multitudeTime < timeRequired then -- increment the timer by one until it reaches timeRequired + data.multitudeTime = data.multitudeTime + 1 - if director:get_data().multitudeTime > timeRequired - 100 then + if data.multitudeTime > timeRequired - 100 then director.points = director.points + director.enemy_buff -- increase credit gain by a huge amount for _, player in ipairs(Instance.find_all(gm.constants.oP)) do player:screen_shake(40) -- gimme the screen shaker @@ -69,37 +67,33 @@ Callback.add(Callback.TYPE.onStep, "MultitudeWave", function() end end else - director:get_data().multitudeTime = 0 -- reset the timer to 0 once were done + data.multitudeTime = 0 -- reset the timer to 0 once were done end end end) -Callback.add(Callback.TYPE.onDraw, "MultitudeWarningMessage", function() - if not multitude.active then return end - +local draw_callback = Callback.add(Callback.ON_HUD_DRAW, function() local director = GM._mod_game_getDirector() + local data = Instance.get_data(director) local spawn = true -- tbh not sure why in ss1 it uses a different method here for determining whether it should display or not, but who cares, it works - for _, teleporter in ipairs(Instance.find_all(gm.constants.oTeleporter)) do + for _, teleporter in ipairs(Instance.find_all(gm.constants.pTeleporter)) do if teleporter.active >= 2 then spawn = false break end end - -- apply the same thing to divine teleporters - for _, teleporter in ipairs(Instance.find_all(gm.constants.oTeleporterEpic)) do + + -- apply the same thing to the control panel on contact light + for _, teleporter in ipairs(Instance.find_all(gm.constants.oCommand)) do if teleporter.active >= 2 then spawn = false break end end - if Instance.find(gm.constants.oBoss1):exists() or Instance.find(gm.constants.oWurmHead):exists() then - spawn = false - end - local players = 0 for _, player in ipairs(Instance.find_all(gm.constants.oP)) do players = players + 1 @@ -107,35 +101,26 @@ Callback.add(Callback.TYPE.onDraw, "MultitudeWarningMessage", function() -- display text warning about an incoming wave local timeRequired = (6500 + director.time_start) / ((players * 0.3) + 0.7) - local actor = Player.get_client() - if spawn and director:get_data().multitudeTime then - if director:get_data().multitudeTime < timeRequired - 200 then - if director:get_data().multitudeTime > timeRequired - ARRIVING_TIME then + if spawn and data.multitudeTime then + if data.multitudeTime < timeRequired - 200 then + local x = Global.___view_l_x + Global.___view_l_w * (1 / 2) + local y = Global.___view_l_y + Global.___view_l_h * (3 / 4) + if data.multitudeTime > timeRequired - ARRIVING_TIME then gm.scribble_set_starting_format("fntNormal", Color.WHITE, 1) -- makes the text use normal white font - gm.scribble_draw(actor.ghost_x, actor.ghost_y - 60, Language.translate_token("artifact.multitude.arriving")) -- the line itself is in the language file - elseif director:get_data().multitudeTime > timeRequired - APPROACHING_TIME then - gm.scribble_set_starting_format("fntNormal", Color.WHITE, 1) - gm.scribble_draw(actor.ghost_x, actor.ghost_y - 60, Language.translate_token("artifact.multitude.approaching")) + gm.scribble_draw(x, y, gm.translate("artifact.multitude.arriving")) -- the line itself is in the language file + elseif data.multitudeTime > timeRequired - APPROACHING_TIME then + gm.scribble_set_starting_format("fntNormal", Color.WHITE, 1) -- makes the text use normal white font + gm.scribble_draw(x, y, gm.translate("artifact.multitude.approaching")) -- the line itself is in the language file end end end end) -Callback.add(Callback.TYPE.onEnemyInit, "MultitudeEnemyStatReduction", function(actor) - if not multitude.active then return end - - -- reduce enemy stats when the artifact is active - if actor:exists() then - if actor.team == 2 and not GM.actor_is_boss(actor) then - if actor.exp_worth then - actor.exp_worth = actor.exp_worth * 0.4 - end - actor.maxhp = actor.maxhp * 0.7 - actor.maxhp_base = actor.maxhp - actor.hp = actor.maxhp +-- disable the artifact initially +step_callback:toggle(false) +draw_callback:toggle(false) - actor.armor = actor.armor * 0.8 - actor.damage = gm.round(actor.damage * 0.7) - end - end +Callback.add(multitude.on_set_active, function(active) + step_callback:toggle(active) + draw_callback:toggle(active) end) \ No newline at end of file diff --git a/Assets/title.png b/Assets/title.png deleted file mode 100644 index 988fdadf..00000000 Binary files a/Assets/title.png and /dev/null differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 1122c6e5..2cd6b3d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,23 @@ # SSR Changelog *spoilers below!!* + +## 0.1.7 +- Mod ported to ReturnsAPI +- Disabled skins (ReturnsAPI does not support them as of yet) +- Added a new Nemesis: Nemesis Mercenary! +- Added a new stage: Verdant Woodland! +- Added a new spawn animation for Nemesis Commando +- New item: Eclipse Gummies +- Chirrsmas-exclusive content: Festive Golem sprite, snowy Contact Light, new present interactable. +- Added option to enable/disable Chirrsmas in the settings +- Revamped Empyrean Elite spawn beam visuals +- Nerfed Empyrean Boss chance +- Nerfed Empyrean Elite health and damage slightly +- Nerfed chance for strong Empyrean Elites slightly +- Added option to switch to vanilla game title in the settings +- Updated README/Credits with cool new promo stuff! +- Fixed many bugs (probably introduced even more) +- Updated Russian translation + ## 0.1.4 & 0.1.5 & 0.1.6 - Optimized Artifact of Gathering to no longer tank performance as much. Which it did even while disabled. Huge thanks to Onyx (0n_x) for this one! diff --git a/Elites/empyrean.lua b/Elites/empyrean.lua index dab9979a..c5454b1e 100644 --- a/Elites/empyrean.lua +++ b/Elites/empyrean.lua @@ -1,670 +1,764 @@ --- 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 and actor.team ~= 1 then - actor:get_data().empy_beam = 180 - actor:get_data().empy_beam_over = 0 - actor:sound_play(gotanythingsharp, 2, 1) - elseif gm.inside_view(actor.x, actor.y) ~= 1 or actor.team == 1 then - 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 not Wrap.wrap(self):get_data().empy_beam then return end - - 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) - if not Instance.wrap(self).fall_immune then return end - - 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 return end - - 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 return end - - 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 return end - - 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 return end - - 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 return end - - 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 +-- 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 = Sprite.new("EliteIconEmpyrean", path.combine(SPRITE_PATH, "icon.png"), 1, 25, 22) +local particle_sprite = Sprite.new("ParticleEmpyreanSpawnParticle", path.combine(SPRITE_PATH, "particle.png"), 6) +local particle2_sprite = Sprite.new("ParticleEmpyreanSpawnParticle2", path.combine(SPRITE_PATH, "particle2.png"), 6) +local particle3_sprite = Sprite.new("ParticleEmpyreanSpawnParticle3", path.combine(SPRITE_PATH, "particle3.png"), 7) +local sprite_beam = Sprite.new("EmpyreanBeam", path.combine(SPRITE_PATH, "beam.png"), 4, 0, 176) +local sprite_splash = Sprite.new("EmpyreanBeamSplash", path.combine(SPRITE_PATH, "splash.png"), 4, 0, 50) +local shockwave_sprite = Sprite.new("EmpyreanBeamShockwave", path.combine(SPRITE_PATH, "shockwave.png"), 6, 32, 64) +local star_sprite = Sprite.new("EmpyreanWormStar", path.combine(SPRITE_PATH, "star.png"), 8, 16, 16) +local star_small_sprite = Sprite.new("EmpyreanWormStarSmall", path.combine(SPRITE_PATH, "star_small.png"), 1, 3, 2) + +GM.elite_generate_palettes() + +local gotanythingsharp = Sound.new("EmpyreanSpawn", path.combine(SOUND_PATH, "beam.ogg")) +local sound_spawn = Sound.new("EmpyreanSpawnShort", path.combine(SOUND_PATH, "spawn.ogg")) +local sound_spawn_alt = Sound.new("EmpyreanSpawnShortAlt", path.combine(SOUND_PATH, "spawn_alt.ogg")) +local sound_spawn_worm = Sound.new("EmpyreanSpawnWorm", path.combine(SOUND_PATH, "spawn_worm.ogg")) +local sound_star_spawn = Sound.new("EmpyreanStarSpawn", path.combine(SOUND_PATH, "star_spawn.ogg")) +local sound_star_shoot = Sound.new("EmpyreanStarShoot", path.combine(SOUND_PATH, "star_shoot.ogg")) +local sound_teleport1 = Sound.new("EmpyreanTeleport1", path.combine(SOUND_PATH, "teleport1.ogg")) +local sound_teleport2 = Sound.new("EmpyreanTeleport2", path.combine(SOUND_PATH, "teleport2.ogg")) +local sound_teleport3 = Sound.new("EmpyreanTeleport3", path.combine(SOUND_PATH, "teleport3.ogg")) + +local empy = ssr_create_elite("empyrean") +empy.healthbar_icon = sprite_icon +empy.palette = gm.constants.sElitePaletteDummy +empy.blend_col = Color.WHITE + +local evil = Particle.new("EmpyreanSpawnEvil") +evil:set_sprite(particle_sprite, true, true, false) +evil:set_life(15, 30) + +local good_small = Particle.new("EmpyreanSpawnGood") +good_small:set_sprite(particle3_sprite, true, true, false) +good_small:set_orientation(0, 0, 0, 0, true) +good_small:set_speed(4, 6, -0.2, 0) +good_small:set_life(20, 40) + +local good_big = Particle.new("EmpyreanSpawnGoodBig") +good_big:set_sprite(particle2_sprite, true, true, false) +good_big:set_orientation(0, 0, 0, 0, true) +good_big:set_speed(4, 6, -0.2, 0) +good_big:set_life(20, 40) + +local rainbowspark = Particle.new("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("EmpyreanTeleport") +teleport:set_sprite(star_small_sprite, true, true, false) +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(1, 3, -0.03, 0.05) +teleport:set_direction(0, 360, 0, 10) +teleport:set_life(20, 100) + +local empyorb = Item.new("eliteOrbEmpyrean") +empyorb.is_hidden = true + +local teleorb = Item.new("elitePassiveTeleportEmpyrean") +teleorb.is_hidden = true + +-- PUT ASPECTS HERE -- +-- format is {"identifier", "namespace", stack} +local aspects = { + -- VANILLA -- + + -- volatile + {"eliteOrbExplosiveShot", "ror", 2}, + {"elitePassiveVolatile", "ror", 1}, + + -- overloading + {"eliteOrbLightning", "ror", 1}, + {"elitePassiveOverloading", "ror", 1}, + + -- leeching + {"elitePassiveLeeching", "ror", 1}, + {"eliteOrbLifesteal", "ror", 7}, + + -- frenzied (minus their teleporting orb) + {"eliteOrbAttackSpeed", "ror", 3}, + {"eliteOrbMoveSpeed", "ror", 5}, + + -- blazing + {"eliteOrbFireTrail", "ror", 5}, + + + + -- STARSTORM -- + + -- poison + {"eliteOrbPoison", "ssr", 1} +} +function ssr_give_empyrean_aspects(actor) + for _, aspect in ipairs(aspects) do + local item = Item.find(aspect[1], aspect[2]) + + -- if the elite already has these orbs, remove them + if actor:item_count(item) > 0 then + actor:item_take(item, actor:item_count(item)) + end + + -- give the orbs + actor:item_give(item, aspect[3]) + 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 + +Callback.add(empy.on_apply, function(actor) + actor:item_give(empyorb) -- applies most empyrean effects + actor:item_give(teleorb) -- makes it teleport to the player from any location + + -- 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 + if actor.sprite_jump then + actor.can_jump = true + end + actor.leap_max_distance = math.max(actor.leap_max_distance or 0, 2) + + Instance.get_data(actor).empy_quality = Global.__pref_graphics_quality -- get quality + + -- make maxhp_base modification take effect + GM.actor_queue_dirty(actor) +end) + +Callback.add(empyorb.on_acquired, function(actor, stack) + local data = Instance.get_data(actor) + + local all_monster_cards = MonsterCard.find_all() + local normal_spawn = true + 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 card.spawn_type ~= 0 then + normal_spawn = false + break + end + end + end + + -- start the beam + if actor.team ~= 1 and normal_spawn then + actor:move_contact_solid(270, -1) -- move the actor to the ground + + data.empy_beam = 180 + data.empy_beam_over = 0 + data.empy_color = 0 + actor:sound_play(gotanythingsharp, 2, 1) + else + data.empy_beam = 0 + data.empy_beam_over = 1 + data.no_beam_loser = 5 + data.empy_color = 0 + + if math.random() <= 0.5 then + actor:sound_play(sound_spawn, 3, 1) + else + actor:sound_play(sound_spawn_alt, 3, 1) + end + + -- give them all elite aspects + ssr_give_empyrean_aspects(actor) + end + + -- apply screenshake + actor:screen_shake(10) + + -- remove the passive teleport orb since empyreans have a custom one + if actor:item_count(Item.find("elitePassiveTeleport")) > 0 then + actor:item_take(Item.find("elitePassiveTeleport"), actor:item_count(Item.find("elitePassiveTeleport"))) + end +end) + +empyorb.effect_display = EffectDisplay.func(function(actor_unwrapped) + local actor = Instance.wrap(actor_unwrapped) + local data = Instance.get_data(actor) + + if not data.empy_beam or not data.empy_beam_over then return end + if data.empy_beam <= 0 or data.empy_beam_over ~= 0 then + -- 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(data.empy_color % 360, 100, 100), actor.image_alpha) + end + + actor.hud_health_color = Color.WHITE + return + end + + local width = gm.sprite_get_width(actor.mask_index) / 2 + 32 + local part_width = math.ceil((((actor.x - actor.bbox_left) + (actor.bbox_right - actor.x)) / 2) * 1.5) + local part_height = math.ceil(((actor.y - actor.bbox_top) + (actor.bbox_bottom - actor.y)) / 2) + local silhouette_y = actor.y - 16 - (16 * Math.dsin(270 + data.empy_beam * 2)) + + if data.empy_beam <= 10 then + width = width * (1000 - (10 - data.empy_beam) ^ 3) / 1000 -- make the beam shrink when its lifetime ends + elseif data.empy_beam > 168 then + width = width * ((180 - data.empy_beam) ^ 3) / 1000 -- make the beam widen when it appears + elseif data.empy_beam > 165 then + width = width * (1.3 - ((169 - data.empy_beam) / 10)) -- make the beam shrink back to its normal size after it widens + end + + -- draw the beam + if data._imalpha then + gm.draw_set_alpha(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) + + gm.gpu_set_fog(1, Color.from_hsv((data.empy_color) % 360, 100, 100), 0, 0) + local closing_anim = math.min(1, ((180 - data.empy_beam) ^ 3) / 1000) + local frame = (4 - ((data.empy_beam % 13) / 3)) + for i = 0, 8 do + if i < 1 then + GM.draw_sprite_ext(sprite_splash, frame, actor.x + (width - 2), actor.bbox_bottom, closing_anim, 1, 0, Color.WHITE, closing_anim) + GM.draw_sprite_ext(sprite_splash, frame, actor.x - (width - 2), actor.bbox_bottom, -closing_anim, 1, 0, Color.WHITE, closing_anim) + else + GM.draw_sprite_ext(sprite_beam, frame, actor.x + (width - 2), actor.bbox_bottom - 50 - 176 * (i - 1), closing_anim, 1, 0, Color.WHITE, closing_anim) + GM.draw_sprite_ext(sprite_beam, frame, actor.x - (width - 2), actor.bbox_bottom - 50 - 176 * (i - 1), -closing_anim, 1, 0, Color.WHITE, closing_anim) + end + end + gm.gpu_set_fog(0, Color.from_hsv((data.empy_color) % 360, 100, 100), 0, 0) + + -- 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 - data.empy_beam) ^ 3) / 1000)) + gm.draw_set_alpha(1) +end, EffectDisplay.DrawPriority.BODY_POST) + +-- dont draw the healthbar while the beam is there +Hook.add_pre(gm.constants.draw_hp_bar, function(self, other, result, args) + if self.elite_type ~= empy.value then return end + if not Instance.get_data(self).empy_beam then return end + + if Instance.get_data(self).empy_beam <= 0 then + return + else + return false + end +end) + +local guarded = false + +-- disable fall damage for the elite to prevent cheese +Hook.add_pre(gm.constants.actor_phy_on_landed, function(self, other, result, args) + if self.elite_type ~= empy.value then return end + if not self.fall_immune then return end + + if not Util.bool(self.invincible) and self.fall_immune == true then + self.invincible = 1 + guarded = true + end +end) + +Hook.add_post(gm.constants.actor_phy_on_landed, function(self, other, result, args) + if guarded then + self.invincible = 0 + guarded = false + end +end) + +local objShockwave = Object.new("EmpyreanSpawnShockwave") +objShockwave:set_sprite(shockwave_sprite) +objShockwave:set_depth(-200) + +Callback.add(objShockwave.on_create, function(self) + local data = Instance.get_data(self) + data.life = 12 + + self.image_speed = 0.25 +end) + +Callback.add(objShockwave.on_step, function(self) + local data = Instance.get_data(self) + + self.speed = self.speed * 0.9 + + if data.life > 0 then + data.life = data.life - 1 + else + if self.image_alpha > 0 then + self.image_alpha = self.image_alpha - 0.25 + else + self:destroy() + end + end +end) + +-- special empyrean worm projectile +local oStarStorm = Object.new("EmpyreanWormStar") +oStarStorm:set_sprite(star_sprite) + +Callback.add(oStarStorm.on_create, function(self) + self.image_speed = 0.4 + self.image_alpha = 0.75 + self.direction = 0 + self.speed = 0 + self.image_blend = Color.WHITE + self.parent = nil + + local data = Instance.get_data(self) + data.state = 0 + data.life = 120 + data.life2 = 360 + data.targetX = 0 + data.targetY = 0 + data.team = 2 + + self:sound_play(sound_star_spawn, 0.5, 0.8 + math.random() * 0.2) + + self:projectile_sync(150) +end) + +Callback.add(oStarStorm.on_draw, 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) + +Callback.add(oStarStorm.on_step, function(self) + local data = Instance.get_data(self) + + if not Instance.exists(self.parent) then + data.life2 = 0 + end + + if data.life > 0 then + data.life = data.life - 1 + elseif data.state == 0 then + data.state = 1 + end + + if data.life2 > 0 then + data.life2 = data.life2 - 1 + + if data.state == 0 then + local no_timestop = 1 + if Global.time_stop == 1 then + no_timestop = 0 + end + + self.speed = self.speed * (0.85 * no_timestop) + + if data.life % 8 == 0 then + local dir = Math.direction(self.x, self.y, data.targetX, data.targetY) + + local flow = Particle.find("WurmOrbFlow") + flow:set_direction(dir, dir, 0, 0) + flow:create(self.x + math.random(-16, 16), self.y + math.random(-16, 16)) + end + + if Net.host and Net.online then + self:instance_resync() + end + elseif data.state == 1 then + self:sound_play(sound_star_shoot.value, 0.5, 0.8 + math.random() * 0.2) -- >> play this sound + self.direction = Math.direction(self.x, self.y, data.targetX, data.targetY) + self.speed = -4 + data.state = 2 + else + self.speed = math.min(self.speed + 0.5, 40) + + for _, actor in ipairs(self:get_collisions(gm.constants.pActor)) do + if actor.team ~= data.team then + self.parent:fire_explosion_local(actor.x, actor.y, 32, 32, 0.1) + self:sound_play(gm.constants.wExplosiveShot, 1, 0.8 + math.random() * 0.2) + self:screen_shake(1) + + if Net.host and Net.online then + self:instance_resync() + end + self:destroy() + end + end + end + else + if self.image_alpha > 0 then + self.image_alpha = self.image_alpha - 0.1 + else + self:destroy() + + if Net.host and Net.online then + self:instance_destroy_sync() + end + end + end +end) + +-- networking +local serializer = function(inst, buffer) + buffer:write_instance(inst.parent) + buffer:write_byte(Instance.get_data(inst).state) + buffer:write_short(inst.direction) + buffer:write_short(inst.speed) + buffer:write_double(Instance.get_data(inst).targetX) + buffer:write_double(Instance.get_data(inst).targetY) + buffer:write_byte(Instance.get_data(inst).team) + buffer:write_color(inst.image_blend) +end + +local deserializer = function(inst, buffer) + inst.parent = buffer:read_instance() + Instance.get_data(inst).state = buffer:read_byte() + inst.direction = buffer:read_short() + inst.speed = buffer:read_short() + Instance.get_data(inst).targetX = buffer:read_double() + Instance.get_data(inst).targetY = buffer:read_double() + Instance.get_data(inst).team = buffer:read_byte() + inst.image_blend = buffer:read_color() +end + +Object.add_serializers(oStarStorm, serializer, deserializer) + +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(empyorb:get_holding_actors()) do + if Instance.exists(actor) then + local data = Instance.get_data(actor) + + data.empy_color = data.empy_color + 1 + + if data.empy_beam > 0 and data.empy_beam_over == 0 then -- freeze the enemy + -- store the enemy's certain stats so we can restore it later + if not data._hmax then + data._hmax = actor.pHmax + data._vmax = actor.pVmax + data._imspeed = actor.image_speed + data._imalpha = actor.image_alpha + 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 data._imalpha then + actor.image_alpha = data._imalpha * (10 - data.empy_beam) / 10 + else + actor.image_alpha = (10 - data.empy_beam) / 10 + end + + -- (duke nukem voice) shake it baby + actor:screen_shake(1) + + -- 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 + + local part_width = math.ceil((((actor.x - actor.bbox_left) + (actor.bbox_right - actor.x)) / 2) * 1.5) + local part_height = math.ceil(((actor.y - actor.bbox_top) + (actor.bbox_bottom - actor.y)) / 2) + local silhouette_y = actor.y - 16 - (16 * Math.dsin(270 + data.empy_beam * 2)) + + if math.random() <= 0.2 * data.empy_quality 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.ABOVE) + end + + if data.empy_quality >= 2 and data.empy_beam % (30 / data.empy_quality) == 0 then -- shockwaves + local width = gm.sprite_get_width(actor.mask_index) / 2 + 32 + local shockwave1 = objShockwave:create(actor.x + width, actor.bbox_bottom + 1) + shockwave1.speed = 12 + + local shockwave2 = objShockwave:create(actor.x - width, actor.bbox_bottom + 1) + shockwave2.speed = -12 + shockwave2.image_xscale = -1 + end + + if data.empy_quality >= 2 and data.empy_beam % math.floor((30 / data.empy_quality) / 2) == 0 then -- particles + local width = gm.sprite_get_width(actor.mask_index) / 2 + 32 + local good_side = gm.choose(-1, 1) + local good_dir = 90 - 90 * good_side + good_big:set_direction(good_dir, good_dir, 0, 0) + good_small:set_direction(good_dir, good_dir, 0, 0) + + if Util.chance(0.5) then + good_big:create(actor.x + width * good_side, actor.y - math.random(768)) + else + good_small:create(actor.x + width * good_side, actor.y - math.random(768)) + end + end + + data.empy_beam = data.empy_beam - 1 + elseif data.empy_beam_over == 0 and data.empy_beam <= 0 then -- unfreeze the enemy + data.empy_beam_over = 1 + + -- restore their speed + actor.pHmax = data._hmax + actor.pVmax = data._vmax + actor.image_speed = data._imspeed + actor.image_alpha = data._imalpha + actor.intangible = data._intang + + --remove all the temporary variables + data._hmax = nil + data._vmax = nil + data._imspeed = nil + data._imalpha = nil + data._intang = nil + + -- give them all elite aspects + ssr_give_empyrean_aspects(actor) + + -- make the boss bar appear + if actor.team ~= 1 and Net.host 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.value) + end + + -- make them move again !! yippie!! + actor.state = 1 + actor:skill_util_reset_activity_state() + end + + if data.no_beam_loser and Net.host then + if 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) + data.no_beam_loser = data.no_beam_loser - 1 + else + 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.value) + end + end + end + + if data.empy_beam_over == 1 and data.empy_beam <= 0 and data.empy_color % 2 == 0 and data.empy_quality >= 2 then + rainbowspark:create_color(actor.x, actor.y, Color.from_hsv((data.empy_color) % 360, 100, 100), 1, Particle.System.BELOW) + end + + if actor.object_index == gm.constants.oJellyG or actor.object_index == gm.constants.oJellyG2 then + actor.image_blend = Color.from_hsv(data.empy_color % 360, 65, 100) + end + + -- empyrean worms !!! + if actor.object_index == gm.constants.oWorm and actor.body then + for i, segment_unwrapped in ipairs(actor.body) do + local segment = Instance.wrap(segment_unwrapped) + + if Instance.exists(segment) then + segment.image_blend = Color.from_hsv((data.empy_color + i * 18) % 360, 65, 100) + + if i % 3 == 0 and data.empy_color % 5 == 0 and data.empy_quality >= 2 then + rainbowspark:create_color(segment.x, segment.y, Color.from_hsv((data.empy_color + i * 18) % 360, 100, 100), 1, Particle.System.BELOW) + end + + if i % 2 == 0 and actor.target and Instance.exists(actor.target) and Net.host then + if not Instance.get_data(segment).empyrean_orb_attack then + Instance.get_data(segment).empyrean_orb_attack = 150 + i * 2 + end + + if Instance.get_data(segment).empyrean_orb_attack > 0 then + local no_timestop = 1 + if Global.time_stop == 1 then + no_timestop = 0 + end + + Instance.get_data(segment).empyrean_orb_attack = Instance.get_data(segment).empyrean_orb_attack - 1 * no_timestop + else + local star = oStarStorm:create(segment.x, segment.y) + star.parent = actor + star.direction = segment.image_angle + 90 * gm.choose(-1, 1) + math.random(-45, 45) + star.speed = math.random(4, 8) + star.image_blend = segment.image_blend + + local data = Instance.get_data(star) + data.team = actor.team + + data.targetX = actor.target.x + data.targetY = actor.target.y + + Instance.get_data(segment).empyrean_orb_attack = 500 + end + end + end + end + end + end + end +end) + +-- play the empyrean worm spawn sound +Hook.add_pre("gml_Object_oWorm_Alarm_3", function(self, other) + if self.elite_type ~= empy.value then return end + + if self.elite_type == empy.value then + GM.sound_play_global(sound_spawn_worm, 1, 1) + end +end) + +RecalculateStats.add(Callback.Priority.AFTER, function(actor, api) + if actor.elite_type ~= empy.value then return end + + -- stat changes are multiplicative with normal elite stat changes + -- so max hp for example will be base * 9 * 2.8 = 25.2x total multiplier + + api.maxhp_mult(9) + api.damage_mult(4 / 1.9) -- totals to 4x + api.cooldown_mult(0.5 / 0.3) -- totals to 50% +end) + +Callback.add(teleorb.on_acquired, function(actor, stack) + Instance.get_data(actor).empyrean_teleport = 480 + math.random(240) +end) + +local tp_sounds = { + sound_teleport1, + sound_teleport2, + sound_teleport3, +} + +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(teleorb:get_holding_actors()) do + if Instance.exists(actor) then + local data = Instance.get_data(actor) + + if GM.actor_is_classic(actor) and not actor:is_climbing() then + if data.empyrean_teleport > 0 then + data.empyrean_teleport = data.empyrean_teleport - 1 + else + local target = Instance.find(gm.constants.oP) + + -- teleport! + if target and Math.distance(actor.x, actor.y, target.x, target.y) > 250 and Instance.exists(target) then + if Net.host then + GM.teleport_nearby(actor, target.x - (150 + math.random(50)) * Math.sign(target.pHspeed), target.y) -- teleport to this fool + + if Net.online then + GM.net_send_instance_message(actor, 25) + end + + actor.ghost_x = actor.x + actor.ghost_y = actor.y + actor.pVspeed = 0 + actor.pHspeed = 0 + actor.ai_tick_rate = 1 + end + + data.empyrean_teleport = 480 + math.random(240) + + -- create some effects so you know youre about to die + local circle = Object.find("EfCircle"):create(actor.x, actor.y) + circle.parent = actor + circle.radius = math.ceil((((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 = Object.find("EfFlash"):create(actor.x, actor.y) + flash.parent = actor + flash.rate = 0.02 + flash.image_alpha = 1 + + -- disable skills for a second to prevent unfair deaths + if Net.host then + local frame = Global._current_frame + for i = 0, 3 do + local skill = actor:get_active_skill(i) + skill.use_next_frame = math.max(skill.use_next_frame, frame + 60) + end + end + + teleport:create(actor.x, actor.y, (data.empy_quality - 1) * 15, Particle.System.MIDDLE) + + -- play one of 3 sounds randomly + actor:sound_play(tp_sounds[math.random(1, 3)], 1, 0.8 + math.random() * 0.2) + else + data.empyrean_teleport = 60 + end + end + end + end + 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.ON_STAGE_START, function() + if not GM._mod_game_getDirector().__ssr_empyrean_chance then + GM._mod_game_getDirector().__ssr_empyrean_chance = 0.02 -- higher chance so you see your first empyrean sooner + end +end) + +Callback.add(Callback.ON_ELITE_INIT, function(actor) + if GM._mod_game_getDirector().stages_passed <= 8 then return end -- only spawns if its stage 9+ + if Global.__gamemode_current >= 2 then return end -- dont spawn empyreans in judgement + if not Net.host then return end + + if actor.elite_type ~= empy.value then -- if the actor is not already empyrean + local all_monster_cards = MonsterCard.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 penalty = 0 + if card.is_boss then -- decrease the chance for an empyrean by 2-3 stages worth of scaling if the target is a boss + penalty = 1 + end + + local cost = math.min(6, math.max(1, card.spawn_cost / 40 * (diff - penalty))) + + if Util.chance(math.max(0, 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/Elites/poison.lua b/Elites/poison.lua index bc57ed65..784298e6 100644 --- a/Elites/poison.lua +++ b/Elites/poison.lua @@ -1,34 +1,33 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Elites/Poison") local SOUND_PATH = path.combine(PATH, "Sounds/Elites/Poison") -local sprite_icon = Resources.sprite_load(NAMESPACE, "EliteIconPoison", path.combine(SPRITE_PATH, "icon.png"), 1, 14, 10) -local sprite_palette = Resources.sprite_load(NAMESPACE, "ElitePalettePoison", path.combine(SPRITE_PATH, "palette.png")) +local sprite_icon = Sprite.new("EliteIconPoison", path.combine(SPRITE_PATH, "icon.png"), 1, 14, 10) +local sprite_palette = Sprite.new("ElitePalettePoison", path.combine(SPRITE_PATH, "palette.png")) -local sound_poison_cloud = Resources.sfx_load(NAMESPACE, "PoisonCloud", path.combine(SOUND_PATH, "poisonCloud.ogg")) -local sound_poison_inflict = Resources.sfx_load(NAMESPACE, "PoisonInflict", path.combine(SOUND_PATH, "poisonInflict.ogg")) +local sound_poison_cloud = Sound.new("PoisonCloud", path.combine(SOUND_PATH, "poisonCloud.ogg")) +local sound_poison_inflict = Sound.new("PoisonInflict", path.combine(SOUND_PATH, "poisonInflict.ogg")) -local elitePoison = Elite.new(NAMESPACE, "poison") +local elitePoison = ssr_create_elite("poison") elitePoison.healthbar_icon = sprite_icon elitePoison.palette = sprite_palette elitePoison.blend_col = Color.PURPLE --- when called without arguments, generates palettes for every elite affix, for every registered monster palette --- once more elite types are implemented, this should probably be moved elsewhere to be called just once after all elite types are initialized GM.elite_generate_palettes() -local itemEliteOrbPoison = Item.new(NAMESPACE, "eliteOrbPoison", true) -- true for no logbook +local itemEliteOrbPoison = Item.new("eliteOrbPoison") itemEliteOrbPoison.is_hidden = true -elitePoison:clear_callbacks() -elitePoison:onApply(function(actor) +Callback.add(elitePoison.on_apply, function(actor) actor:item_give(itemEliteOrbPoison) end) -itemEliteOrbPoison:clear_callbacks() -itemEliteOrbPoison:onHitProc(function(actor, victim, stack, hit_info) +Callback.add(Callback.ON_HIT_PROC, function(actor, victim, hit_info) + local stack = actor:item_count(itemEliteOrbPoison) + if stack <= 0 then return end + GM.sound_play_networked(sound_poison_inflict, 1, 0.9 + math.random() * 0.2, victim.x, victim.y) - local dot = GM.instance_create(victim.x, victim.y, gm.constants.oDot) + local dot = Object.find("Dot"):create(victim.x, victim.y) dot.parent = actor dot.team = actor.team dot.target = victim @@ -40,26 +39,32 @@ itemEliteOrbPoison:onHitProc(function(actor, victim, stack, hit_info) dot:alarm_set(0, dot.rate) -- oDot usually does its first tick instantly -- make it delayed end) -itemEliteOrbPoison:onPostStep(function(actor, stack) - if gm._mod_net_isClient() then return end - local data = actor:get_data() - if not data.poison_timer then - data.poison_timer = 30 - end - - data.poison_timer = data.poison_timer - 1 - if data.poison_timer < 0 then - data.poison_timer = 30 + math.random(30) - - if math.random() < 0.05 then - GM.sound_play_networked(sound_poison_cloud, 1, 0.9 + math.random() * 0.2, actor.x, actor.y) - - local cloud = GM.instance_create(actor.x, actor.bbox_bottom - 24, gm.constants.oMushDust) - cloud.parent = actor - cloud.team = actor.team - cloud.damage = actor.damage * 0.3 - cloud:alarm_set(0, 60 * 5) -- set lifetime +Callback.add(Callback.ON_STEP, function() + if Net.client then return end + + for _, actor in ipairs(itemEliteOrbPoison:get_holding_actors()) do + if Instance.exists(actor) then + local data = Instance.get_data(actor) + + if not data.poison_timer then + data.poison_timer = 30 + end + + data.poison_timer = data.poison_timer - 1 + if data.poison_timer < 0 then + data.poison_timer = 30 + math.random(30) + + if math.random() < 0.05 and actor.damage then + GM.sound_play_networked(sound_poison_cloud, 1, 0.9 + math.random() * 0.2, actor.x, actor.y) + + local cloud = Object.find("MushDust"):create(actor.x, actor.bbox_bottom - 24) + cloud.parent = actor + cloud.team = actor.team + cloud.damage = actor.damage * 0.3 + cloud:alarm_set(0, 60 * 5) -- set lifetime + end + end end end end) @@ -69,7 +74,7 @@ local blacklist = { } -- dunno if this is the best way to go about this, but it works well enough for now -local all_monster_cards = Monster_Card.find_all() +local all_monster_cards = MonsterCard.find_all() for i, card in ipairs(all_monster_cards) do if not blacklist[card.identifier] then local elite_list = List.wrap(card.elite_list) diff --git a/Equipments/midas.lua b/Equipments/midas.lua index fa6419fb..baf6fdbc 100644 --- a/Equipments/midas.lua +++ b/Equipments/midas.lua @@ -1,5 +1,5 @@ -local sprite = Resources.sprite_load(NAMESPACE, "Midas", path.combine(PATH, "Sprites/Equipments/midas.png"), 2, 16, 14) -local sound = Resources.sfx_load(NAMESPACE, "MidasUse", path.combine(PATH, "Sounds/Items/midasUse.ogg")) +local sprite = Sprite.new("Midas", path.combine(PATH, "Sprites/Equipments/midas.png"), 2, 16, 14) +local sound = Sound.new("MidasUse", path.combine(PATH, "Sounds/Items/midasUse.ogg")) -- these come from this file sarn posted in the rorr modding server, listing most enums used by the game's code. -- https://discord.com/channels/1171745917272084550/1175150719700058233/1189771316522389566 @@ -10,13 +10,14 @@ local DAMAGE_INFLICT_FLAGS = { ignore_invincibility = 1 << 3, } -local midas = Equipment.new(NAMESPACE, "midas") +local midas = Equipment.new("midas") midas:set_sprite(sprite) -midas:set_cooldown(60) -midas:set_loot_tags(Item.LOOT_TAG.category_utility, Item.LOOT_TAG.equipment_blacklist_chaos) +midas.cooldown = 60 * 60 +midas.loot_tags = Item.LootTag.CATEGORY_UTILITY + Item.LootTag.EQUIPMENT_BLACKLIST_CHAOS -midas:clear_callbacks() -midas:onUse(function(actor, embryo) +ItemLog.new_from_equipment(midas) + +Callback.add(midas.on_use, function(actor, embryo) actor:sound_play(sound, 1, 1) local deduction = math.floor(actor.hp * 0.5) diff --git a/Equipments/strangeCan.lua b/Equipments/strangeCan.lua index 30a57aba..8cf2ea1e 100644 --- a/Equipments/strangeCan.lua +++ b/Equipments/strangeCan.lua @@ -1,107 +1,116 @@ -local sprite_item = Resources.sprite_load(NAMESPACE, "StrangeCan", path.combine(PATH, "Sprites/Equipments/strangeCan.png"), 2, 15, 15) -local sprite_projectile = Resources.sprite_load(NAMESPACE, "EfStrangeCan", path.combine(PATH, "Sprites/Equipments/effects/strangeCan.png"), 1, 12, 7) -local sprite_explosion = Resources.sprite_load(NAMESPACE, "EfStrangeCanExplosion", path.combine(PATH, "Sprites/Equipments/effects/strangeCanExplosion.png"), 5, 48, 38) -local sprite_buff = Resources.sprite_load(NAMESPACE, "BuffIntoxication", path.combine(PATH, "Sprites/Buffs/intoxication.png"), 1, 11, 12) +local sprite_item = Sprite.new("StrangeCan", path.combine(PATH, "Sprites/Equipments/strangeCan.png"), 2, 15, 15) +local sprite_projectile = Sprite.new("EfStrangeCan", path.combine(PATH, "Sprites/Equipments/effects/strangeCan.png"), 1, 12, 7) +local sprite_explosion = Sprite.new("EfStrangeCanExplosion", path.combine(PATH, "Sprites/Equipments/effects/strangeCanExplosion.png"), 5, 48, 38) +local sprite_buff = Sprite.new("BuffIntoxication", path.combine(PATH, "Sprites/Buffs/intoxication.png"), 1, 11, 12) local INTOXICATION_COLOR = Color.from_rgb(126, 182, 134) local INTOXICATION_RADIUS = 75 local GAS_WIDTH = 200 local GAS_HEIGHT = 50 -local sound = Resources.sfx_load(NAMESPACE, "IntoxicateApply", path.combine(PATH, "Sounds/Items/strangeCan.ogg")) +local sound = Sound.new("IntoxicateApply", path.combine(PATH, "Sounds/Items/strangeCan.ogg")) -local strangeCan = Equipment.new(NAMESPACE, "strangeCan") +local strangeCan = Equipment.new("strangeCan") strangeCan:set_sprite(sprite_item) -strangeCan:set_cooldown(30) -strangeCan:set_loot_tags(Item.LOOT_TAG.category_damage) - -local objTossedCan = Object.new(NAMESPACE, "EfCan") -objTossedCan.obj_sprite = sprite_projectile -objTossedCan.obj_depth = -10 - -local objCanGas = Object.new(NAMESPACE, "EfCanGas") - -local buffIntoxication = Buff.new(NAMESPACE, "intoxication") -buffIntoxication.show_icon = true -buffIntoxication.icon_sprite = sprite_buff -buffIntoxication.is_debuff = true -buffIntoxication.is_timed = false - -local particleRadioactive = Particle.find("ror", "Radioactive") - -local particleGas = Particle.new(NAMESPACE, "StrangeGas") -particleGas:set_sprite(gm.constants.sEfCloud, 0, false, false, false) -particleGas:set_alpha3(0, 0.3, 0) -particleGas:set_colour1(INTOXICATION_COLOR) -particleGas:set_orientation(0, 360, 0.5, 0, 0) -particleGas:set_size(0.4, 1, 0, 0.01) -particleGas:set_life(90, 120) - -strangeCan:clear_callbacks() -strangeCan:onUse(function(actor, embryo) - local c = 1 - if embryo then c = 2 end - - for i=1, c do - local obj = objTossedCan:create(actor.x + 8 * actor.image_xscale, actor.y-6) - obj.direction = 90 - actor.image_xscale * 60 - obj.speed = 6.5 - obj.team = actor.team +strangeCan.cooldown = 60 * 30 +strangeCan.loot_tags = Item.LootTag.CATEGORY_DAMAGE + +ItemLog.new_from_equipment(strangeCan) + +local efTossedCan = Object.new("EfCan") +efTossedCan:set_sprite(sprite_projectile) +efTossedCan:set_depth(-10) + +local efCanGas = Object.new("EfCanGas") + +local buff = Buff.new("intoxication") +buff.show_icon = true +buff.icon_sprite = sprite_buff +buff.is_debuff = true +buff.is_timed = false + +local radioactive = Particle.find("Radioactive") + +local particle = Particle.new("StrangeGas") +particle:set_sprite(gm.constants.sEfCloud, 0, false, false, false) +particle:set_alpha3(0, 0.3, 0) +particle:set_colour1(INTOXICATION_COLOR) +particle:set_orientation(0, 360, 0.5, 0, 0) +particle:set_size(0.4, 1, 0, 0.01) +particle:set_life(90, 120) + +Callback.add(strangeCan.on_use, function(actor, embryo) + local cans = 0 + if embryo then cans = 1 end + + for i = 0, cans do + local inst = efTossedCan:create(actor.x + 8 * actor.image_xscale, actor.y - 6) + inst.direction = 90 - actor.image_xscale * (60 - 10 * i) + inst.speed = 6.5 + inst.team = actor.team end end) -objTossedCan:clear_callbacks() -objTossedCan:onCreate(function(self) +local quality = 3 + +Callback.add(efTossedCan.on_create, function(self) self.gravity = 0.3 self.team = 1 + quality = Global.__pref_graphics_quality end) -objTossedCan:onStep(function(self) + +Callback.add(efTossedCan.on_step, function(self) self.image_angle = self.image_angle + 8 - particleRadioactive:create_color(self.x, self.y, INTOXICATION_COLOR, 1) + + if math.random() <= 0.5 * (quality - 1) then + radioactive:create_color(self.x, self.y, INTOXICATION_COLOR, 1) + end if self:is_colliding(gm.constants.pBlock) or self:is_colliding(gm.constants.pEnemy) then - local ef = GM.instance_create(self.x, self.y, gm.constants.oEfExplosion) - ef.sprite_index = sprite_explosion + local inst = Object.find("EfExplosion"):create(self.x, self.y) + inst.sprite_index = sprite_explosion self:sound_play(gm.constants.wClayDeath, 1, 1.5) self:screen_shake(7) - local victims = List.wrap(self:find_characters_circle(self.x, self.y, INTOXICATION_RADIUS, false, self.team)) local target local target_hp = -math.huge - for _, candidate in ipairs(victims) do - -- find enemy that has the highest maxhp, isn't intoxicated, and isn't immune to intoxication - if candidate.maxhp > target_hp and candidate:buff_stack_count(buffIntoxication) == 0 and not candidate.buff_immune:get(buffIntoxication) then - target = candidate - target_hp = candidate.maxhp + for _, hitbox in ipairs(self:get_collisions_circle(gm.constants.pActorCollisionBase, INTOXICATION_RADIUS, self.x, self.y)) do + local candidate = GM.attack_collision_resolve(hitbox) + if Instance.wrap(candidate).team then + if gm.team_canhit(Instance.wrap(candidate).team, self.team) then + -- find enemy that has the highest maxhp, isn't intoxicated, and isn't immune to intoxication + if candidate.maxhp > target_hp and candidate:buff_count(buff) == 0 and not candidate.buff_immune:get(buff) then + target = candidate + target_hp = candidate.maxhp + end + end end end if target then - target:buff_apply(buffIntoxication, 60) + target:buff_apply(buff, 60) end self:destroy() end end) -buffIntoxication:clear_callbacks() -buffIntoxication:onApply(function(actor, stack) +Callback.add(buff.on_apply, function(actor, stack) actor:sound_play(sound, 1, 1) actor:sound_play(gm.constants.wMushShoot1, 1, 0.7) end) -Callback.add(Callback.TYPE.onKillProc, "SSStrangeCanDetonate", function(victim, killer) - if victim:buff_stack_count(buffIntoxication) > 0 then - local gas = objCanGas:create(victim.x, victim.bbox_bottom - GAS_HEIGHT * 0.5) - gas.parent = killer - gas.team = killer.team - gas.damage = victim.maxhp * 0.025 - end +Callback.add(Callback.ON_KILL_PROC, function(victim, killer) + if victim:buff_count(buff) <= 0 then return end + + local gas = efCanGas:create(victim.x, victim.bbox_bottom - GAS_HEIGHT * 0.5) + gas.parent = killer + gas.team = killer.team + gas.damage = victim.maxhp * 0.025 end) -objCanGas:clear_callbacks() -objCanGas:onCreate(function(self) +Callback.add(efCanGas.on_create, function(self) self.parent = -4 self.team = 1 self.life = 5 * 60 @@ -111,17 +120,19 @@ objCanGas:onCreate(function(self) self:instance_sync() end) -objCanGas:onStep(function(self) + +Callback.add(efCanGas.on_step, function(self) if self.life % 2 == 0 then local px = self.x - GAS_WIDTH * 0.5 + math.random(GAS_WIDTH) local py = self.y - GAS_HEIGHT * 0.5 + math.random(GAS_HEIGHT) - particleGas:create(px, py, 1) + particle:create(px, py, 1) end - if self.life % 15 == 0 and gm._mod_net_isHost() then - local victims = List.wrap(self:find_characters_rectangle(self.x - GAS_WIDTH*0.5, self.y - GAS_HEIGHT*0.5, self.x + GAS_WIDTH*0.5, self.y + GAS_HEIGHT*0.5, self.team, false)) - for _, target in ipairs(victims) do - gm.damage_inflict(target.id, self.damage, 0, self.parent, target.x, target.y, self.damage, self.team, INTOXICATION_COLOR) + if self.life % 15 == 0 and Net.host then + for _, target in ipairs(self:get_collisions_rectangle(gm.constants.pActor, self.x - GAS_WIDTH * 0.5, self.y - GAS_HEIGHT * 0.5, self.x + GAS_WIDTH * 0.5, self.y + GAS_HEIGHT * 0.5)) do + if target.team ~= self.team then + gm.damage_inflict(target.id, self.damage, 0, self.parent, target.x, target.y, self.damage, self.team, INTOXICATION_COLOR) + end end end diff --git a/Equipments/whiteFlag.lua b/Equipments/whiteFlag.lua index 4c26980b..ed7f1034 100644 --- a/Equipments/whiteFlag.lua +++ b/Equipments/whiteFlag.lua @@ -1,39 +1,40 @@ -local sprite_item = Resources.sprite_load(NAMESPACE, "WhiteFlag", path.combine(PATH, "Sprites/Equipments/whiteFlag.png"), 2, 15, 16) -local sprite_flag_spawn = Resources.sprite_load(NAMESPACE, "EfWhiteFlagSpawn", path.combine(PATH, "Sprites/Equipments/Effects/whiteFlagSpawn.png"), 13, 32, 45) -local sprite_flag_idle = Resources.sprite_load(NAMESPACE, "EfWhiteFlagIdle", path.combine(PATH, "Sprites/Equipments/Effects/whiteFlagIdle.png"), 5, 15, 26) -local sprite_buff = Resources.sprite_load(NAMESPACE, "BuffPeace", path.combine(PATH, "Sprites/Buffs/peace.png"), 1, 10, 10) -local sprite_skill = Resources.sprite_load(NAMESPACE, "SkillPeace", path.combine(PATH, "Sprites/Buffs/peaceLock.png"), 1, 2, 1) -local sound = Resources.sfx_load(NAMESPACE, "WhiteFlag", path.combine(PATH, "Sounds/Items/whiteFlag.ogg")) - -local whiteFlag = Equipment.new(NAMESPACE, "whiteFlag") +local sprite_item = Sprite.new("WhiteFlag", path.combine(PATH, "Sprites/Equipments/whiteFlag.png"), 2, 15, 16) +local sprite_flag_spawn = Sprite.new("EfWhiteFlagSpawn", path.combine(PATH, "Sprites/Equipments/Effects/whiteFlagSpawn.png"), 13, 32, 45) +local sprite_flag_idle = Sprite.new("EfWhiteFlagIdle", path.combine(PATH, "Sprites/Equipments/Effects/whiteFlagIdle.png"), 5, 15, 26) +local sprite_buff = Sprite.new("BuffPeace", path.combine(PATH, "Sprites/Buffs/peace.png"), 1, 10, 10) +local sprite_skill = Sprite.new("SkillPeace", path.combine(PATH, "Sprites/Buffs/peaceLock.png"), 1, 2, 1) +local sound = Sound.new("WhiteFlag", path.combine(PATH, "Sounds/Items/whiteFlag.ogg")) + +local whiteFlag = Equipment.new("whiteFlag") whiteFlag:set_sprite(sprite_item) -whiteFlag:set_cooldown(45) -whiteFlag:set_loot_tags(Item.LOOT_TAG.category_utility, Item.LOOT_TAG.equipment_blacklist_chaos) +whiteFlag.cooldown = 45 * 60 +whiteFlag.loot_tags = Item.LootTag.CATEGORY_UTILITY + Item.LootTag.EQUIPMENT_BLACKLIST_CHAOS -local buffPeace = Buff.new(NAMESPACE, "peace") -buffPeace.icon_sprite = sprite_buff +ItemLog.new_from_equipment(whiteFlag) + +local buff = Buff.new("peace") +buff.icon_sprite = sprite_buff + +local skill = Skill.new("peace") +skill.sprite = sprite_skill +skill.is_primary = true -- prevent icon from graying out -local skillPeace = Skill.new(NAMESPACE, "peace") -skillPeace.sprite = sprite_skill -skillPeace.is_primary = true -- prevent icon from graying out -- ensure this "skill" cannot be activated -skillPeace.max_stock = -math.huge -- prevent backup mag and afterburner from giving it extra stocks -skillPeace.auto_restock = false -skillPeace.start_with_stock = false +skill.max_stock = -math.huge -- prevent backup mag and afterburner from giving it extra stocks +skill.auto_restock = false +skill.start_with_stock = false -local objFlag = Object.new(NAMESPACE, "EfWhiteFlag") -objFlag.obj_sprite = sprite_flag_idle +local efFlag = Object.new("EfWhiteFlag") +efFlag:set_sprite(sprite_flag_idle) -whiteFlag:clear_callbacks() -whiteFlag:onUse(function(actor, embryo) - local flag = objFlag:create(actor.x, actor.y) +Callback.add(whiteFlag.on_use, function(self, embryo) + local flag = efFlag:create(self.x, self.y) if embryo then self.life = self.life * 2 end end) -objFlag:clear_callbacks() -objFlag:onCreate(function(self) +Callback.add(efFlag.on_create, function(self) self:move_contact_solid(270, -1) self.radius = 160 self.life = 8 * 60 @@ -43,16 +44,15 @@ objFlag:onCreate(function(self) self:sound_play(sound, 1, 1) end) -objFlag:onStep(function(self) - if self.life % 5 == 0 then - local to_peace = List.wrap(self:find_characters_circle(self.x, self.y, self.radius, false, 3)) - for _, target in ipairs(to_peace) do +Callback.add(efFlag.on_step, function(self) + if self.life % 5 == 0 then + for _, target in ipairs(self:get_collisions_circle(gm.constants.pActor, self.radius, self.x, self.y)) do if gm.actor_is_classic(target.id) then - if target:buff_stack_count(buffPeace) == 0 then - target:buff_apply(buffPeace, 10) + if target:buff_count(buff) == 0 then + target:buff_apply(buff, 10) else - GM.set_buff_time_nosync(target, buffPeace, 10) + GM.set_buff_time_nosync(target, buff, 10) end end end @@ -69,7 +69,8 @@ objFlag:onStep(function(self) self:destroy() end end) -objFlag:onDraw(function(self) + +Callback.add(efFlag.on_draw, function(self) local a = 0.1 + math.sin(Global._current_frame * 0.02) * 0.05 a = a * math.min(1, self.life / 15) @@ -77,7 +78,7 @@ objFlag:onDraw(function(self) local pulse = (self.life % 60) / 60 local r2 = r1 * (1 - pulse) - gm.draw_set_alpha(a*10) + gm.draw_set_alpha(a * 10) gm.draw_set_colour(Color.WHITE) gm.draw_circle(self.x, self.y, r1, true) @@ -90,25 +91,14 @@ objFlag:onDraw(function(self) gm.draw_set_alpha(1) end) ---[[ -enum SKILL_OVERRIDE_PRIORITY { - upgrade, - boosted, - reload, - cancel, - SIZE -} ---]] -buffPeace:clear_callbacks() -buffPeace:onApply(function(actor) - for i=1, 4 do - local slot = actor.skills[i] - GM._mod_ActorSkillSlot_addOverride(slot, skillPeace, 4) -- SKILL_OVERRIDE_PRIORITY.SIZE +Callback.add(buff.on_apply, function(actor) + for i = 0, 3 do + actor:add_skill_override(i, skill, math.huge) end end) -buffPeace:onRemove(function(actor) - for i=1, 4 do - local slot = actor.skills[i] - GM._mod_ActorSkillSlot_removeOverride(slot, skillPeace, 4) -- SKILL_OVERRIDE_PRIORITY.SIZE + +Callback.add(buff.on_remove, function(actor) + for i = 0, 3 do + actor:remove_skill_override(i, skill, math.huge) end end) diff --git a/Gameplay/nemesis_spawn_portal.lua b/Gameplay/nemesis_spawn_portal.lua new file mode 100644 index 00000000..368e2211 --- /dev/null +++ b/Gameplay/nemesis_spawn_portal.lua @@ -0,0 +1,77 @@ +local sound = Sound.new("NemPortalPopOut", path.combine(PATH, "Sounds/Gameplay/portal_pop_out.ogg")) + +local portal = Object.new("StartPortal") +portal:set_depth(100) +portal:set_sprite(Sprite.find("NemCommandoPortal")) + +Callback.add(portal.on_create, function(self) + self.image_index = 0 + self.image_speed = 0 + + local data = Instance.get_data(self) + data.timer = 0 + data.state = 0 +end) + +Callback.add(portal.on_step, function(self) + if not Instance.exists(self.parent) then self:destroy() return end + local data = Instance.get_data(self) + + if data.timer >= 120 and data.state == 0 then + data.state = 1 + + self.sprite_index = self.sprite_portal + self.image_xscale = self.parent.image_xscale + self.image_index = 0 + self.image_speed = 0.2 + + gm.sound_play_global(self.sound_portal, 1.5, 1) + elseif data.timer >= 210 and data.state == 1 then + data.state = 2 + + self.parent.pVspeed = -5 + + gm.actor_activity_set(self.parent.id, 0, 0) + + gm.sound_play_global(sound.value, 1.5, 1) + end + + data.timer = data.timer + 1 + + if data.state < 2 then + self.parent:actor_teleport(self.x, self.bbox_bottom - gm.sprite_get_height(self.sprite_index) / 2) + elseif data.state >= 2 and self.image_index + self.image_speed >= self.image_number then + self:destroy() + end +end) + +Callback.add(portal.on_draw, function(self) + -- this is how umbrae do their cool parallax texture + gm.shader_set(gm.constants.shd_umbra) + local t2 = gm.sprite_get_texture(gm.constants.sUmbraStars, 0) + gm.texture_set_stage(gm.shader_get_sampler_index(gm.constants.shd_umbra, "tex_stars1"), t2) + gm.texture_set_stage(gm.shader_get_sampler_index(gm.constants.shd_umbra, "tex_stars2"), gm.sprite_get_texture(gm.constants.sUmbraStars, 1)) + local t = gm.sprite_get_texture(self.sprite_index, self.image_index) + gm.shader_set_uniform_f(gm.shader_get_uniform(gm.constants.shd_umbra, "star_sample_scale"), (1 / gm.texture_get_texel_width(t)) * gm.texture_get_texel_width(t2), (1 / gm.texture_get_texel_height(t)) * gm.texture_get_texel_height(t2)) + local px = Instance.get_data(self).timer * 1.2 + local py = 0 + gm.shader_set_uniform_f(gm.shader_get_uniform(gm.constants.shd_umbra, "star_parallax"), px * gm.texture_get_texel_width(t2), py * gm.texture_get_texel_height(t2)) + GM.draw_sprite_ext(self.sprite_portal_inside, self.image_index, self.x, self.y, self.image_xscale, self.image_yscale, self.image_angle, self.image_blend, self.image_alpha) + gm.shader_reset() +end) + +Callback.add(Callback.ON_STAGE_START, function() + if GM._mod_game_getDirector().stages_passed > 0 then return end + if Global.__gamemode_current >= 2 then return end + if Net.online then return end + + if Instance.find(gm.constants.oP).is_nemesis then + local pod = Instance.find(gm.constants.oBase) + local inst = portal:create(pod.x, pod.y) + inst.parent = Instance.find(gm.constants.oP) + inst.sprite_portal = Instance.find(gm.constants.oP).sprite_portal or Sprite.find("NemCommandoPortal") + inst.sprite_portal_inside = Instance.find(gm.constants.oP).sprite_portal_inside or Sprite.find("NemCommandoPortalInside") + inst.sound_portal = Instance.find(gm.constants.oP).sound_portal or Sound.find("NemCommandoPortal") + pod:destroy() + end +end) \ No newline at end of file diff --git a/Gameplay/typhoon.lua b/Gameplay/typhoon.lua index 68a1646d..db21dc80 100644 --- a/Gameplay/typhoon.lua +++ b/Gameplay/typhoon.lua @@ -1,18 +1,21 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Menu") local SOUND_PATH = path.combine(PATH, "Sounds/Menu") -local sprite_small = Resources.sprite_load(NAMESPACE, "DifficultyTyphoon", path.combine(SPRITE_PATH, "DifficultyTyphoon.png"), 5, 11, 9) -local sprite_large = Resources.sprite_load(NAMESPACE, "DifficultyTyphoon2X", path.combine(SPRITE_PATH, "DifficultyTyphoon_2x.png"), 4, 22, 20) - -local sound_select = Resources.sfx_load(NAMESPACE, "UI_Diff_Typhoon", path.combine(SOUND_PATH, "UI_Diff_Typhoon.ogg")) - -local typhoon = Difficulty.new(NAMESPACE, "typhoon") -typhoon:set_sprite(sprite_small, sprite_large) -typhoon:set_primary_color(Color.from_rgb(195, 28, 124)) -typhoon:set_sound(sound_select) -typhoon:set_scaling(0.2, 4.0, 1.7) -typhoon:set_monsoon_or_higher(true) -typhoon:set_allow_blight_spawns(true) +local sprite_small = Sprite.new("DifficultyTyphoon", path.combine(SPRITE_PATH, "DifficultyTyphoon.png"), 5, 11, 9) +local sprite_large = Sprite.new("DifficultyTyphoon2X", path.combine(SPRITE_PATH, "DifficultyTyphoon_2x.png"), 4, 22, 20) + +local sound_select = Sound.new("UI_Diff_Typhoon", path.combine(SOUND_PATH, "UI_Diff_Typhoon.ogg")) + +local typhoon = Difficulty.new("typhoon") +typhoon.sprite_id = sprite_small +typhoon.sprite_loadout_id = sprite_large +typhoon.primary_color = Color.from_rgb(195, 28, 124) +typhoon.sound_id = sound_select +typhoon.diff_scale = 0.2 +typhoon.general_scale = 4.0 +typhoon.point_scale = 1.7 +typhoon.is_monsoon_or_higher = true +typhoon.allow_blight_spawns = true --[[ Callback.add(Callback.TYPE.onGameStart, "SSTyphoonStart", function() diff --git a/Interactables/chirrsmas_present.lua b/Interactables/chirrsmas_present.lua new file mode 100644 index 00000000..0c25ab0c --- /dev/null +++ b/Interactables/chirrsmas_present.lua @@ -0,0 +1,107 @@ +local sprite = Sprite.new("ChirrsmasPresent", path.combine(PATH, "Sprites/Interactables/ChirrsmasPresent/present.png"), 13, 41, 112) + +local firework = Object.new("ChirrsmasPresentFirework") +firework:set_sprite(gm.constants.sEFFirework) +firework:set_depth(45) + +Callback.add(firework.on_create, function(self) + local dir = 90 + math.random(-10, 10) + self.direction = dir + self.speed = 6 + self.image_index = math.random(0, 3) + self.image_speed = 0 + self.image_angle = dir + self.image_xscale = 0.5 + self.image_yscale = 0.5 + self.y = self.y - 32 + Instance.get_data(self).quality = Global.__pref_graphics_quality + + self:sound_play(gm.constants.wMissileLaunch, 0.6, 1.1 + math.random(2) * 0.1) +end) + +Callback.add(firework.on_step, function(self) + self.speed = self.speed - 0.1 + + if Instance.get_data(self).quality >= 2 then + Particle.find("FireworkSmoke"):set_orientation(self.direction, self.direction, 0, 0, false) + Particle.find("FireworkSmoke"):create(self.x - gm.lengthdir_x(16, self.direction), self.y - gm.lengthdir_y(16, self.direction), 1, Particle.System.BELOW) + end + + if self.speed <= 0 then + local color = gm.choose(11009922, 8577791, 16646056, 15438998) + for i = 0, 6 do + local burst = Object.find("EfFireworkBurst"):create(self.x, self.y) + burst.color = color + end + self:sound_play(gm.constants.wBanditShoot4_2, 0.6, 1.1 + math.random(2) * 0.1) + self:destroy() + end +end) + +local present = Object.new("ChirrsmasPresent", Object.Parent.INTERACTABLE) +present:set_sprite(sprite) +present:set_depth(90) + +Callback.add(present.on_create, function(self) + self:interactable_init() + self.mask_index = gm.constants.sChest1 + self:interactable_init_name() +end) + +Callback.add(present.on_step, function(self) + local data = Instance.get_data(self) + + if data.open_delay then + if data.open_delay > 0 then + data.open_delay = data.open_delay - 1 + else + data.open_delay = nil + end + end + + if data.missile_timer then + if data.missile_timer > 0 then + data.missile_timer = data.missile_timer - 1 + else + data.missile_timer = nil + end + end + + if self.active == 1 then + self:sound_play(gm.constants.wChest1, 1, 1) + self:sound_play(gm.constants.wSnowglobe, 0.5, 1) + self.active = 2 + self:screen_shake(1) + self.image_speed = 0.2 + data.open_delay = 25 + data.missile_timer = 45 * 3 - 1 + elseif self.active == 2 and data.open_delay == 0 then + local pickup = gm.treasure_weights_roll_pickup(29) -- gonna use drifter's roll item function because it includes all item tiers and rolls reds pretty commonly. 29 corresponds to drifter's loot pool struct index + local item = Item.wrap(gm.object_to_item(pickup)) -- treasure_weights_roll_pickup returns an object instead of an item so we use object_to_item to get the item + + local inst = item:create(self.x, self.y - 32) + inst.item_stack_kind = Item.StackKind.TEMPORARY_BLUE + inst.spawn_x = inst.x + math.random(-16, 16) + end + + if data.missile_timer and data.missile_timer % 45 == 0 then + firework:create(self.x, self.y) + end +end) + +if HOTLOADING then return end +if not ssr_chirrsmas_active then return end -- christmas lasts from december 15th to january 15th +if Settings.chirrsmas == 2 then return end -- if chirrsmas is disabled in the config then we dont do anything + +local card = InteractableCard.new("chirrsmasPresent") +card.object_id = present +card.spawn_with_sacrifice = true +card.spawn_cost = 7 +card.spawn_weight = 8 + +for i = 1, 6 do + local tier = List.wrap(gm._mod_stage_get_pool_list(i)) + for _, stage in ipairs(tier) do + Stage.wrap(stage):add_interactable(card) + end +end \ No newline at end of file diff --git a/Items/armedBackpack.lua b/Items/armedBackpack.lua index 591abef5..8eac744f 100644 --- a/Items/armedBackpack.lua +++ b/Items/armedBackpack.lua @@ -1,15 +1,18 @@ -local sprite_item = Resources.sprite_load(NAMESPACE, "ArmedBackpack", path.combine(PATH, "Sprites/Items/armedBackpack.png"), 1, 16, 16) -local sprite_sparks1 = Resources.sprite_load(NAMESPACE, "ArmedBackpackSpark1", path.combine(PATH, "Sprites/Items/Effects/armedBackpack1.png"), 3, 0, 10) -local sprite_sparks2 = Resources.sprite_load(NAMESPACE, "ArmedBackpackSpark2", path.combine(PATH, "Sprites/Items/Effects/armedBackpack2.png"), 4, 16, 16) +local sprite_item = Sprite.new("ArmedBackpack", path.combine(PATH, "Sprites/Items/armedBackpack.png"), 1, 16, 16) +local sprite_sparks1 = Sprite.new("ArmedBackpackSpark1", path.combine(PATH, "Sprites/Items/Effects/armedBackpack1.png"), 3, 0, 10) +local sprite_sparks2 = Sprite.new("ArmedBackpackSpark2", path.combine(PATH, "Sprites/Items/Effects/armedBackpack2.png"), 4, 16, 16) -local sound = Resources.sfx_load(NAMESPACE, "ArmedBackpack", path.combine(PATH, "Sounds/Items/armedBackpack.ogg")) +local sound = Sound.new("ArmedBackpack", path.combine(PATH, "Sounds/Items/armedBackpack.ogg")) -local tracer, tracer_info = CustomTracer.new(function(x1, y1, x2, y2) +local tracer = Tracer.new("ArmedBackpackTracer") +tracer.sparks_offset_y = -5 + +tracer:set_callback(function(x1, y1, x2, y2, color) local offset = -7 + math.random() * 3 y1 = y1 + offset y2 = y2 + offset - local tr = gm.instance_create(x1, y1, gm.constants.oEfLineTracer) + local tr = GM.instance_create(x1, y1, gm.constants.oEfLineTracer) tr.xend = x2 tr.yend = y2 tr.sprite_index = gm.constants.sPilotShoo1BTracer @@ -18,23 +21,31 @@ local tracer, tracer_info = CustomTracer.new(function(x1, y1, x2, y2) tr.bm = 1 -- additive tr.image_blend = Color.from_rgb(170, 78, 66) - local ef = gm.instance_create(x1, y1, gm.constants.oEfSparks) + local ef = GM.instance_create(x1, y1, gm.constants.oEfSparks) ef.sprite_index = sprite_sparks1 ef.image_angle = gm.point_direction(x1, y1, x2, y2) ef.image_speed = 0.4 end) -tracer_info.sparks_offset_y = -5 -local armedBackpack = Item.new(NAMESPACE, "armedBackpack") +local armedBackpack = Item.new("armedBackpack") armedBackpack:set_sprite(sprite_item) -armedBackpack:set_tier(Item.TIER.common) -armedBackpack:set_loot_tags(Item.LOOT_TAG.category_damage) - -armedBackpack:clear_callbacks() -armedBackpack:onAttackCreateProc(function(actor, stack, attack_info) +armedBackpack:set_tier(ItemTier.COMMON) +armedBackpack.loot_tags = Item.LootTag.CATEGORY_DAMAGE + +ItemLog.new_from_item(armedBackpack) + +Callback.add(Callback.ON_ATTACK_CREATE, function(attack_info) + if not attack_info.proc then return end + if not Instance.exists(attack_info.parent) then return end + + local actor = attack_info.parent + local stack = actor:item_count(armedBackpack) + + if stack <= 0 then return end + if math.random() <= 0.12 + (0.065 * stack) then local dir = actor:skill_util_facing_direction() + 180 - + -- infer the direction from the attack direction if its horizontal enough -- this improves many cases like huntress autoaim, suppressive fire, engi turrets, etc. -- though it also has issues in other cases, hm. diff --git a/Items/balloon.lua b/Items/balloon.lua index 2cd43dcd..a7b30ea2 100644 --- a/Items/balloon.lua +++ b/Items/balloon.lua @@ -1,6 +1,6 @@ -local sprite_item = Resources.sprite_load(NAMESPACE, "Balloon", path.combine(PATH, "Sprites/Items/balloon.png"), 1, 17, 19) -local sprite_effect = Resources.sprite_load(NAMESPACE, "EfBalloon", path.combine(PATH, "Sprites/Items/Effects/balloon.png"), 6, 4, 6) -local sound_pop = Resources.sfx_load(NAMESPACE, "BalloonPop", path.combine(PATH, "Sounds/Items/balloonPop.ogg")) +local sprite_item = Sprite.new("Balloon", path.combine(PATH, "Sprites/Items/balloon.png"), 1, 17, 19) +local sprite_effect = Sprite.new("EfBalloon", path.combine(PATH, "Sprites/Items/Effects/balloon.png"), 6, 4, 6) +local sound_pop = Sound.new("BalloonPop", path.combine(PATH, "Sounds/Items/balloonPop.ogg")) -- used for popping particles local BALLOON_COLORS = { @@ -14,125 +14,140 @@ local BALLOON_COLORS = { local GOLDEN_RATIO = (math.pi * 360) / 137.50776405004 -- magical constant used in balloon positioning -local particlePop = Particle.new(NAMESPACE, "BalloonPop") +local particlePop = Particle.new("BalloonPop") particlePop:set_direction(0, 360, false, false, false) -particlePop:set_shape(Particle.SHAPE.disk) +particlePop:set_shape(Particle.Shape.DISK) particlePop:set_size(0.08, 0.12, -0.005, 0.02) particlePop:set_speed(1, 4, -0.08, 0) particlePop:set_gravity(0.1, 270) particlePop:set_life(20, 40) -local balloon = Item.new(NAMESPACE, "balloon") -balloon:set_sprite(sprite_item) -balloon:set_tier(Item.TIER.uncommon) -balloon:set_loot_tags(Item.LOOT_TAG.category_utility) - -local buff = Buff.new(NAMESPACE, "balloon") +local buff = Buff.new("balloon") buff.show_icon = false buff.is_timed = false -balloon:clear_callbacks() -balloon:onPreStep(function(actor, stack) - if actor.in_danger_last_frame < Global._current_frame and actor:buff_stack_count(buff) == 0 then - actor:buff_apply(buff, 1) +local balloon = Item.new("balloon") +balloon:set_sprite(sprite_item) +balloon:set_tier(ItemTier.UNCOMMON) +balloon.loot_tags = Item.LootTag.CATEGORY_UTILITY + +ItemLog.new_from_item(balloon) + +balloon.effect_display = EffectDisplay.func(function(actor_unwrapped) + local actor = Instance.wrap(actor_unwrapped) + if actor:buff_count(buff) == 0 then return end + + local stack = actor:item_count(balloon) + local data = Instance.get_data(actor) + + gm.draw_set_colour(Color.WHITE) + + local start_x = actor.ghost_x + local start_y = actor.ghost_y - gm.sprite_get_yoffset(actor.sprite_idle) + 3 + + for i, balloon in ipairs(data.balloons) do + local balloon = data.balloons[i] + GM.draw_sprite(sprite_effect, (i - 1) % 6, balloon.x, balloon.y) + gm.draw_set_alpha(0.3) + gm.draw_line(balloon.x, balloon.y, start_x, start_y) + gm.draw_set_alpha(1) end -end) -balloon:onRemove(function(actor, stack) - local data = actor:get_data() - if data.balloons then - table.remove(data.balloons, #data.balloons) +end, EffectDisplay.DrawPriority.BODY_POST) + +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(balloon:get_holding_actors()) do + if Instance.exists(actor) and actor.in_danger_last_frame then + if actor.in_danger_last_frame < Global._current_frame and actor:buff_count(buff) == 0 then + actor:buff_apply(buff, 1) + end + end end - - if stack == 1 then - actor:buff_remove(buff) + + for _, actor in ipairs(buff:get_holding_actors()) do + if Instance.exists(actor) then + local stack = actor:item_count(balloon) + local data = Instance.get_data(actor) + + if not data.balloons then + data.balloons = {} + end + + while #data.balloons < stack do + table.insert(data.balloons, { + x = actor.x, + y = actor.y, + rot = 0, + }) + end + + for i = 1, stack do + local b = data.balloons[i] + + local tx = actor.ghost_x + local ty = actor.ghost_y-50 + + local ang = math.floor(i * 0.5) * GOLDEN_RATIO + local mag = math.sqrt(ang) * 1.8 + + if i % 2 == 0 then + mag = -mag + end + + tx = tx + math.sin(ang) * mag + ty = ty + math.cos(ang) * mag + + local lerp = 0.05 * (0.95 ^ i ) + 0.05 + + b.x = Math.lerp(b.x, tx, lerp) + b.y = Math.lerp(b.y, ty, lerp) + end + + if actor.in_danger_last_frame > Global._current_frame then + actor:buff_remove(buff) + end + end end end) -buff:clear_callbacks() -buff:onPostStatRecalc(function(actor, _) - local stack = actor:item_stack_count(balloon) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buff) + if stack <= 0 then return end + local exponent = stack ^ 0.7 -- try and somewhat reduce the potency of stacking -- modifying downward/default gravity is iffy, rusty jetpack doesn't do this and doing it makes falling uncontrollably slower -- ... but also, let mimics/non-players benefit from balloon if actor.object_index ~= gm.constants.oP then - actor.pGravity1 = actor.pGravity1 * 0.65 ^ exponent + api.pGravity1_mult(0.65 ^ exponent) end - actor.pGravity2 = actor.pGravity2 * 0.65 ^ exponent + + api.pGravity2_mult(0.65 ^ exponent) end) -buff:onPostStep(function(actor, _) - local stack = actor:item_stack_count(balloon) - - if actor.in_danger_last_frame > Global._current_frame then - actor:buff_remove(buff) - return - end - - local data = actor:get_data() - if not data.balloons then - data.balloons = {} - end - - while #data.balloons < stack do - table.insert(data.balloons, { - x = actor.x, - y = actor.y, - rot = 0, - }) - end - - for i=1, stack do - local b = data.balloons[i] - - local tx = actor.ghost_x - local ty = actor.ghost_y-50 - - local ang = math.floor(i * 0.5) * GOLDEN_RATIO - local mag = math.sqrt(ang) * 1.8 - - if i % 2 == 0 then - mag = -mag - end - - tx = tx + math.sin(ang) * mag - ty = ty + math.cos(ang) * mag - - local lerp = 0.05 * (0.95 ^ i ) + 0.05 - - b.x = gm.lerp(b.x, tx, lerp) - b.y = gm.lerp(b.y, ty, lerp) +Callback.add(balloon.on_removed, function(actor, stack) + local data = Instance.get_data(actor) + + if data.balloons then + table.remove(data.balloons, #data.balloons) end -end) - -buff:onPreDraw(function(actor, _) - local stack = actor:item_stack_count(balloon) - local data = actor:get_data() - gm.draw_set_colour(Color.WHITE) - local start_x = actor.ghost_x - local start_y = actor.ghost_y - gm.sprite_get_yoffset(actor.sprite_idle) + 3 - - for i, b in ipairs(data.balloons) do - local b = data.balloons[i] - gm.draw_sprite(sprite_effect, (i-1)%6, b.x, b.y) - gm.draw_set_alpha(0.3) - gm.draw_line(b.x, b.y, start_x, start_y) - gm.draw_set_alpha(1) + if stack == 1 then + actor:buff_remove(buff) end end) -buff:onRemove(function(actor) - local data = actor:get_data() +Callback.add(buff.on_remove, function(actor) + local data = Instance.get_data(actor) if data.balloons and #data.balloons > 0 then actor:screen_shake(7) actor:sound_play(sound_pop, 1, 1) - for i, b in ipairs(data.balloons) do - local color = BALLOON_COLORS[(i-1)%6] or Color.WHITE - particlePop:create_color(b.x, b.y, color, 8) + for i, balloon in ipairs(data.balloons) do + local color = BALLOON_COLORS[(i - 1) % 6] or Color.WHITE + particlePop:create_color(balloon.x, balloon.y, color, 8) end - actor:get_data().balloons = nil + + data.balloons = nil end end) diff --git a/Items/blastKnuckles.lua b/Items/blastKnuckles.lua deleted file mode 100644 index e50b8e39..00000000 --- a/Items/blastKnuckles.lua +++ /dev/null @@ -1,115 +0,0 @@ --- this item sucks. needs to be redesigned completely at some point. --- but for now we have too many damage items in the green pool anyway, so, bleh ---[[ -local item_sprite = Resources.sprite_load(NAMESPACE, "BlastKnuckles", path.combine(PATH, "Sprites/Items/blastKnuckles.png"), 1, 16, 14) -local buff_sprite = Resources.sprite_load(NAMESPACE, "BuffBlastCharge", path.combine(PATH, "Sprites/Buffs/blastCharge.png"), 1, 5, 14) -local effect_sprite = Resources.sprite_load(NAMESPACE, "BlastKnuckleBoom", path.combine(PATH, "Sprites/Items/Effects/blastKnuckleBoom.png"), 6, 56, 16) - -local RADIUS = 96 -local RADIUS_SQUARED = RADIUS * RADIUS - -local BLAST_WIDTH = 96 -local BLAST_HEIGHT = 20 -local BLAST_OFFSET_MARGIN = 1 -local BLAST_DAMAGE_COEFFICIENT = 2.2 -- scales off damage stat, not TOTAL damage - -local CHARGE_CAPACITY = 5 -local CHARGE_INITIAL = 3 -local CHARGE_INTERVAL = 3 * 60 - -local COLOR = Color.from_rgb(76, 128, 86) - -local blastKnuckles = Item.new(NAMESPACE, "blastKnuckles") -blastKnuckles:set_sprite(item_sprite) -blastKnuckles:set_tier(Item.TIER.uncommon) -blastKnuckles:set_loot_tags(Item.LOOT_TAG.category_damage) - -local buffBlastCharge = Buff.new(NAMESPACE, "blastKnuckles") -buffBlastCharge.is_timed = false -buffBlastCharge.max_stack = CHARGE_CAPACITY -buffBlastCharge.draw_stack_number = true -buffBlastCharge.stack_number_col = Array.new({COLOR}) -buffBlastCharge.icon_sprite = buff_sprite -buffBlastCharge.icon_stack_subimage = false - -blastKnuckles:clear_callbacks() -blastKnuckles:onAcquire(function(actor, stack) - actor:buff_apply(buffBlastCharge, 60, CHARGE_INITIAL) -end) - --- doesn't seem to be built-in syncing for removing just 1 stack of a buff, so we have to user packet it, ah well -local packetConsumeCharge = Packet.new() -packetConsumeCharge:onReceived(function(buffer) - local actor = buffer:read_instance() - if actor:exists() then - actor:buff_remove(buffBlastCharge, 1) - end -end) - --- uses `__ssr_blasted` magic variable to mark the attack_info to prevent one attack from firing more than one blast -blastKnuckles:onHitProc(function(actor, victim, stack, hit_info) - if hit_info.attack_info.__ssr_blasted then return end - - local dx = hit_info.x - actor.x - local dy = hit_info.y - actor.y - if (dx * dx + dy * dy) <= RADIUS_SQUARED and actor:buff_stack_count(buffBlastCharge) > 0 then - local attack_info = hit_info.attack_info - attack_info.__ssr_blasted = true - - actor:buff_remove(buffBlastCharge, 1) - if gm._mod_net_isOnline() then - local msg = packetConsumeCharge:message_begin() - msg:write_instance(actor) - msg:send_to_all() - end - - gm.sound_play_networked(gm.constants.wExplosiveShot, 1.0, 1.2, victim.x, victim.y) - - -- calculate the blast's location, such that it's flush with the victim's bounding box on the opposite direction to the hit - local width = victim.bbox_right - victim.bbox_left - local x_origin = (victim.bbox_right + victim.bbox_left) * 0.5 -- centre of victim based on collision, accounts for offset - local attack_vector = gm.lengthdir_x(1, attack_info.direction) - attack_vector = gm.sign(attack_vector) - - local knockback_compensate = 0 - if gm.actor_is_classic(victim.id) then - -- compensate for 1-frame delay to attack processing so the blast doesn't hit the victim when they're knocked back - knockback_compensate = attack_info.knockback * 2 + attack_info.stun * 20 - - -- compensate for stagger - if hit_info.damage > victim.knockback_cap then - knockback_compensate = knockback_compensate + 6 - end - end - local offset = (0.5 * (width + BLAST_WIDTH) + BLAST_OFFSET_MARGIN + knockback_compensate) * attack_vector - - local blast = actor:fire_explosion(x_origin + offset, hit_info.y, BLAST_WIDTH, BLAST_HEIGHT, BLAST_DAMAGE_COEFFICIENT * stack, effect_sprite, nil, false).attack_info - blast.direction = hit_info.direction - blast.damage_color = COLOR - blast.knockback_direction = attack_info.knockback_direction - blast.climb = hit_info.attack_info.climb + 8 * 1.35 - end -end) - -blastKnuckles:onPostStep(function(actor, stack) - if gm._mod_net_isClient() then return end - if Global._current_frame % CHARGE_INTERVAL == 0 then - actor:buff_apply(buffBlastCharge, 60, 1) - end -end) - -blastKnuckles:onPostDraw(function(actor, stack) - local alpha = 0.65 + math.sin(Global._current_frame*0.03) * 0.1 - local x, y = math.floor(actor.ghost_x+0.5), math.floor(actor.ghost_y+0.5) - local charges = actor:buff_stack_count(buffBlastCharge) - - if charges == 0 then - alpha = alpha * 0.5 - end - - gm.draw_set_alpha(alpha) - gm.draw_set_colour(COLOR) - gm.draw_circle(x, y, RADIUS, true) - gm.draw_set_alpha(1) -end) ---]] diff --git a/Items/brassKnuckles.lua b/Items/brassKnuckles.lua index 6d39c62b..800e7e04 100644 --- a/Items/brassKnuckles.lua +++ b/Items/brassKnuckles.lua @@ -1,68 +1,51 @@ -local sprite = Resources.sprite_load(NAMESPACE, "BrassKnuckles", path.combine(PATH, "Sprites/Items/brassKnuckles.png"), 1, 15, 12) -local sprite_effect = Resources.sprite_load(NAMESPACE, "EfBrassKnuckles", path.combine(PATH, "Sprites/Items/Effects/brassKnuckles.png"), 4, 28, 24) -local sound = Resources.sfx_load(NAMESPACE, "BrassKnuckles", path.combine(PATH, "Sounds/Items/brassKnuckles.ogg")) +local sprite = Sprite.new("BrassKnuckles", path.combine(PATH, "Sprites/Items/brassKnuckles.png"), 1, 15, 12) +local sprite_effect = Sprite.new("EfBrassKnuckles", path.combine(PATH, "Sprites/Items/Effects/brassKnuckles.png"), 4, 28, 24) +local sound = Sound.new("BrassKnuckles", path.combine(PATH, "Sounds/Items/brassKnuckles.ogg")) -local brassKnuckles = Item.new(NAMESPACE, "brassKnuckles") +local brassKnuckles = Item.new("brassKnuckles") brassKnuckles:set_sprite(sprite) -brassKnuckles:set_tier(Item.TIER.common) -brassKnuckles:set_loot_tags(Item.LOOT_TAG.category_damage) +brassKnuckles:set_tier(ItemTier.COMMON) +brassKnuckles.loot_tags = Item.LootTag.CATEGORY_DAMAGE -local brassKnucklesID = brassKnuckles.value +ItemLog.new_from_item(brassKnuckles) -brassKnuckles:clear_callbacks() -brassKnuckles:onPostDraw(function(actor, stack) - local radius = 30 + stack * 30 - local x, y = math.floor(actor.ghost_x+0.5), math.floor(actor.ghost_y+0.5) +brassKnuckles.effect_display = EffectDisplay.func(function(actor_unwrapped) + local actor = Instance.wrap(actor_unwrapped) + + local radius = 30 + 30 * actor:item_count(brassKnuckles) gm.draw_set_alpha(0.2) gm.draw_set_colour(0) - gm.draw_circle(x, y, radius-1, true) - gm.draw_circle(x, y, radius+1, true) + gm.draw_circle_colour(actor.x, actor.y, radius + 1, 0, 0, true) + gm.draw_circle_colour(actor.x, actor.y, radius - 1, 0, 0, true) gm.draw_set_alpha(1) -end) - --- onAttackHit callbacks and such can't be used to increase the damage because by then the visual damage number was already decided on. - --- signature: --- damager_calculate_damage(hit_info, true_hit, hit, damage, critical, parent, proc, attack_flags, damage_col, team, climb, percent_hp, xscale, hit_x, hit_y) -gm.pre_script_hook(gm.constants.damager_calculate_damage, function(self, other, result, args) - local parent = args[6].value +end, EffectDisplay.DrawPriority.BODY_POST) - if gm.instance_exists(parent) == 0.0 then return end +DamageCalculate.add(function(api) + if not Instance.exists(api.parent) then return end - local count = gm.item_count(parent or -4, brassKnucklesID) or 0 + local count = api.parent:item_count(brassKnuckles) + if count <= 0 then return end + if count > 0 then - local hit_x = args[14].value - local hit_y = args[15].value - - local radius = 30 + count * 30 - local dx = hit_x - parent.x - local dy = hit_y - parent.y + local radius = 30 + 30 * count + local dx = api.hit_x - api.parent.x + local dy = api.hit_y - api.parent.y -- squared distance check if (dx * dx + dy * dy) <= (radius * radius) then - local _damage = args[4] - local _damage_col = args[9] - local attack_flags = args[8].value - local true_hit = args[2].value - - _damage.value = math.ceil(_damage.value * 1.35) - _damage_col.value = gm.merge_colour(_damage_col.value, 0, 0.25) -- tint damage number grey - gm.sound_play_at(sound, 0.55, 0.8 + math.random() * 0.4, hit_x, hit_y) + api.damage = math.ceil(api.damage * 1.35) + api.damage_col = gm.merge_colour(api.damage_col, 0, 0.25) -- tint damage number grey + sound:play(api.hit_x, api.hit_y, 0.55, 0.8 + math.random() * 0.4) -- don't do hitsparks if the attack is the red damage from commando's wound debuff -- cause it always has an xscale of 1 and creates an ugly doubled up effect - if attack_flags & Attack_Info.ATTACK_FLAG.commando_wound_damage == 0 then - -- "true hit" is the instance that the attack actually hit - -- helps for positioning the effect on brambles, worms, etc. - if type(true_hit) == "number" then - -- sometimes true_hit is an instance ID instead, fix it if that's the case - true_hit = gm.CInstance.instance_id_to_CInstance[true_hit] - end - if not true_hit then return end + if api.attack_flags & AttackFlag.COMMANDO_WOUND_DAMAGE == 0 then + + if not api.true_hit then return end - local ef = gm.instance_create(true_hit.x, true_hit.y, gm.constants.oEfSparks) - ef.sprite_index = sprite_effect - ef.image_xscale = args[13].value + local effect = Object.find("EfSparks"):create(api.true_hit.x, api.true_hit.y) + effect.sprite_index = sprite_effect + effect.image_xscale = api.xscale end end end diff --git a/Items/coffeeBag.lua b/Items/coffeeBag.lua index 12d5535c..8d3e9a3e 100644 --- a/Items/coffeeBag.lua +++ b/Items/coffeeBag.lua @@ -1,23 +1,30 @@ -local item_sprite = Resources.sprite_load(NAMESPACE, "CoffeeBag", path.combine(PATH, "Sprites/Items/coffeeBag.png"), 1, 16, 16) -local buff_sprite = Resources.sprite_load(NAMESPACE, "BuffCoffee", path.combine(PATH, "Sprites/Buffs/coffeeBuff.png"), 8, 7, 7) - -local coffeeBuff = Buff.new(NAMESPACE, "coffeeBuff") - -local coffeeBag = Item.new(NAMESPACE, "coffeeBag") -coffeeBag:set_sprite(item_sprite) -coffeeBag:set_tier(Item.TIER.common) -coffeeBag:set_loot_tags(Item.LOOT_TAG.category_utility) - -coffeeBag:clear_callbacks() -coffeeBag:onInteractableActivate(function(actor, stack, interactable) - actor:buff_apply(coffeeBuff, 60 * (5 + stack * 5)) -end) +local sprite = Sprite.new("CoffeeBag", path.combine(PATH, "Sprites/Items/coffeeBag.png"), 1, 16, 16) +local buff_sprite = Sprite.new("BuffCoffee", path.combine(PATH, "Sprites/Buffs/coffeeBuff.png"), 8, 7, 7) +local coffeeBuff = Buff.new("coffeeBuff") coffeeBuff.icon_sprite = buff_sprite coffeeBuff.icon_frame_speed = 0.2 -coffeeBuff:clear_callbacks() -coffeeBuff:onStatRecalc(function(actor, stack) - actor.pHmax = actor.pHmax + 0.6 - actor.attack_speed = actor.attack_speed + 0.22 +local coffeeBag = Item.new("coffeeBag") +coffeeBag:set_sprite(sprite) +coffeeBag:set_tier(ItemTier.COMMON) +coffeeBag.loot_tags = Item.LootTag.CATEGORY_UTILITY + +ItemLog.new_from_item(coffeeBag) + +Callback.add(Callback.ON_INTERACTABLE_ACTIVATE, function(interactable, actor) + if not Instance.exists(actor) then return end + + local stack = actor:item_count(coffeeBag) + if stack <= 0 then return end + + actor:buff_apply(coffeeBuff, 60 * (5 + stack * 5)) end) + +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(coffeeBuff) + if stack <= 0 then return end + + api.pHmax_add(0.6) + api.attack_speed_add(0.22) +end) \ No newline at end of file diff --git a/Items/crypticSource.lua b/Items/crypticSource.lua index 7bcbf774..1fc6986d 100644 --- a/Items/crypticSource.lua +++ b/Items/crypticSource.lua @@ -1,46 +1,55 @@ -local sprite = Resources.sprite_load(NAMESPACE, "CrypticSource", path.combine(PATH, "Sprites/Items/crypticSource.png"), 1, 18, 18) +local sprite = Sprite.new("CrypticSource", path.combine(PATH, "Sprites/Items/crypticSource.png"), 1, 18, 18) -local crypticSource = Item.new(NAMESPACE, "crypticSource") +local crypticSource = Item.new("crypticSource") crypticSource:set_sprite(sprite) -crypticSource:set_tier(Item.TIER.uncommon) -crypticSource:set_loot_tags(Item.LOOT_TAG.category_utility) - -crypticSource:clear_callbacks() -crypticSource:onPostStep(function(actor, stack) - if gm._mod_net_isClient() then return end - - local actor_data = actor:get_data() - - local true_direction = actor.image_xscale - if actor.object_index == gm.constants.oEngiTurret then - true_direction = actor.image_xscale2 - end - - if actor.pHspeed ~= 0 then - true_direction = gm.sign(actor.pHspeed) - end - - if not actor_data.last_direction then actor_data.last_direction = true_direction end - - if not actor_data.cryptic_source_cd then - if actor_data.last_direction == true_direction * -1 then - actor_data.cryptic_source_cd = 10 - actor_data.last_direction = true_direction - - local t = gm.instance_create(actor.x, actor.y, gm.constants.oChainLightning) - t.parent = actor.value - t.team = actor.team - t.damage = math.ceil(actor.damage * (0.15 + (0.55 * stack))) - t.blend = Color.from_rgb(211, 242, 87) - t.bounce = 2 - - actor:sound_play_networked(gm.constants.wLightning, 0.25, 3.4 + math.random() * 0.3, actor.x, actor.y) - end - else - if actor_data.cryptic_source_cd > 0 then - actor_data.cryptic_source_cd = actor_data.cryptic_source_cd - 1 - else - actor_data.cryptic_source_cd = nil +crypticSource:set_tier(ItemTier.UNCOMMON) +crypticSource.loot_tags = Item.LootTag.CATEGORY_UTILITY + +ItemLog.new_from_item(crypticSource) + +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(crypticSource:get_holding_actors()) do + if Net.host then + if Instance.exists(actor) then + local stack = actor:item_count(crypticSource) + local data = Instance.get_data(actor) + + local true_direction = actor.image_xscale + + if true_direction then + if actor.object_index == gm.constants.oEngiTurret then + true_direction = actor.image_xscale2 + end + + if actor.pHspeed ~= 0 then + true_direction = Math.sign(actor.pHspeed) + end + + if not data.last_direction then data.last_direction = true_direction end + + if not data.cryptic_source_cd then + if data.last_direction == true_direction * -1 then + data.cryptic_source_cd = 10 + data.last_direction = true_direction + + local lightning = Object.find("ChainLightning"):create(actor.x, actor.y) + lightning.parent = actor + lightning.team = actor.team + lightning.damage = math.ceil(actor.damage * (0.15 + (0.55 * stack))) + lightning.blend = Color.from_rgb(211, 242, 87) + lightning.bounce = 2 + + actor:sound_play_networked(gm.constants.wLightning, 0.25, 3.4 + math.random() * 0.3, actor.x, actor.y) + end + else + if data.cryptic_source_cd > 0 then + data.cryptic_source_cd = data.cryptic_source_cd - 1 + else + data.cryptic_source_cd = nil + end + end + end + end end end end) diff --git a/Items/detritiveTrematode.lua b/Items/detritiveTrematode.lua index 6f71d050..9f119220 100644 --- a/Items/detritiveTrematode.lua +++ b/Items/detritiveTrematode.lua @@ -1,70 +1,49 @@ -local item_sprite = Resources.sprite_load(NAMESPACE, "DetritiveTrematode", path.combine(PATH, "Sprites/Items/detritiveTrematode.png"), 1, 14, 20) -local buff_sprite = Resources.sprite_load(NAMESPACE, "BuffDisease", path.combine(PATH, "Sprites/Buffs/disease.png"), 1, 8, 8) +local item_sprite = Sprite.new("DetritiveTrematode", path.combine(PATH, "Sprites/Items/detritiveTrematode.png"), 1, 14, 20) +local buff_sprite = Sprite.new("BuffDisease", path.combine(PATH, "Sprites/Buffs/disease.png"), 1, 8, 8) local DAMAGE_COLOR = Color.from_rgb(187, 211, 91) -local detritiveTrematode = Item.new(NAMESPACE, "detritiveTrematode") +local detritiveTrematode = Item.new("detritiveTrematode") detritiveTrematode:set_sprite(item_sprite) -detritiveTrematode:set_tier(Item.TIER.common) -detritiveTrematode:set_loot_tags(Item.LOOT_TAG.category_damage) -local detritiveTrematodeID = detritiveTrematode.value +detritiveTrematode:set_tier(ItemTier.COMMON) +detritiveTrematode.loot_tags = Item.LootTag.CATEGORY_DAMAGE -local buffDisease = Buff.new(NAMESPACE, "disease") +ItemLog.new_from_item(detritiveTrematode) + +local buffDisease = Buff.new("disease") buffDisease.icon_sprite = buff_sprite buffDisease.is_debuff = true buffDisease.is_timed = false -buffDiseaseID = buffDisease.value - -detritiveTrematode:clear_callbacks() --- this effect honestly looks ugly ..?? idk ---[[ -local effect_blend_color = Color.from_rgb(128, 128, 0) -detritiveTrematode:onPostDraw(function(actor, stack) - local preserve_alpha, preserve_blend = actor.image_alpha, actor.image_blend - - actor.image_alpha = 0.25 - actor.image_blend = effect_blend_color - gm.actor_drawscript_call(actor.value, actor.ghost_x, actor.ghost_y, 0, 0) - - actor.image_alpha = preserve_alpha - actor.image_blend = preserve_blend -end) ---]] -buffDisease:clear_callbacks() -buffDisease:onApply(function(actor, stack) +Callback.add(buffDisease.on_apply, function(actor) actor:sound_play(gm.constants.wBrambleShoot1, 0.8, 0.4 + math.random() * 0.2) end) -buffDisease:onPostStep(function(actor, stack) - if gm._mod_net_isClient() then return end + +Callback.add(buffDisease.on_step, function(actor) + if Net.client then return end + if Global._current_frame % 30 == 0 then - local dmg = actor.__ssr_trematode_damage or 1 + local dmg = Instance.get_data(actor).__ssr_trematode_damage or 1 gm.damage_inflict(actor.id, dmg, 0, -4, actor.x, actor.y, dmg, 1, DAMAGE_COLOR) end end) --- can't use attack callbacks because they run before the actual hp is decreased, so just hook this instead -gm.post_script_hook(gm.constants.damager_hit_process, function(self, other, result, args) - local _hit_info = args[1].value - local _target = args[2].value - local _parent = _hit_info.parent +DamageCalculate.add(function(api) + if not Instance.exists(api.hit) then return end + if not Instance.exists(api.parent) then return end + if api.hit:buff_count(buffDisease) > 0 then return end - if gm.instance_exists(_target) == 0.0 then return end - if gm.instance_exists(_parent) == 0.0 then return end - if gm.get_buff_stack(_target, buffDiseaseID) > 0 then return end - - local count = gm.item_count(_parent or -4, detritiveTrematodeID) or 0 + local count = api.parent:item_count(detritiveTrematode) if count > 0 then - local target = Instance.wrap(_target) -- sometimes _target is an instance ID, so gotta wrap it for the dot syntax - if target.hp <= 0 then return end - if target.id == _parent.id then return end -- meteor counts as self-damage. prevent this from becoming an issue lmaoooo - + if api.hit.hp <= 0 then return end + if api.hit.team == api.parent.team then return end -- meteor procs trematode on your teammates and yourself. prevent this from becoming an issue lmaoooo + local threshold = 0.1 + 0.05 * count - if target.hp <= target.maxhp * threshold then - target:buff_apply(buffDisease, 60) - target.__ssr_trematode_damage = _parent.damage * 0.5 + if api.hit.hp <= api.hit.maxhp * threshold then + api.hit:buff_apply(buffDisease, 60) + Instance.get_data(api.hit).__ssr_trematode_damage = api.parent.damage * 0.5 end end end) diff --git a/Items/distinctiveStick.lua b/Items/distinctiveStick.lua index 260c163a..42abd001 100644 --- a/Items/distinctiveStick.lua +++ b/Items/distinctiveStick.lua @@ -1,90 +1,52 @@ -local sprite_item = Resources.sprite_load(NAMESPACE, "DistinctiveStick", path.combine(PATH, "Sprites/Items/distinctiveStick.png"), 1, 16, 16) -local sprite_effect = Resources.sprite_load(NAMESPACE, "EfDistinctiveTree", path.combine(PATH, "Sprites/Items/Effects/distinctiveTree.png"), 1, 9, 48) +local sprite_item = Sprite.new("DistinctiveStick", path.combine(PATH, "Sprites/Items/distinctiveStick.png"), 1, 16, 16) +local sprite_effect = Sprite.new("EfDistinctiveTree", path.combine(PATH, "Sprites/Items/Effects/distinctiveTree.png"), 1, 9, 48) local RADIUS_BASE = 220 local RADIUS_STACK = 80 -local distinctiveStick = Item.new(NAMESPACE, "distinctiveStick") +local distinctiveStick = Item.new("distinctiveStick") distinctiveStick:set_sprite(sprite_item) -distinctiveStick:set_tier(Item.TIER.common) -distinctiveStick:set_loot_tags(Item.LOOT_TAG.category_healing) +distinctiveStick:set_tier(ItemTier.COMMON) +distinctiveStick.loot_tags = Item.LootTag.CATEGORY_HEALING -local objTree = Object.new(NAMESPACE, "EfDistinctiveTree") -objTree.obj_sprite = sprite_effect -objTree.obj_depth = 50 +ItemLog.new_from_item(distinctiveStick) -objTree:clear_callbacks() -objTree:onCreate(function(self) +local efTree = Object.new("EfDistinctiveTree") +efTree:set_sprite(sprite_effect) +efTree:set_depth(50) + +Callback.add(efTree.on_create, function(self) self.radius = RADIUS_BASE self:instance_sync() end) -objTree:onStep(function(self) - if gm._mod_net_isClient() then return end - - if Global._current_frame % 120 == 0 then - local targets = List.wrap(self:find_characters_circle(self.x, self.y, self.radius, false, 1, true)) - - for _, actor in ipairs(targets) do - local amount = actor.maxhp * 0.022 - if actor.object_index == gm.constants.oP then - local heal = GM.instance_create(self.x, self.y, gm.constants.oEfHeal2) - heal.target = actor - --heal.value = amount -- this doesn't work because RMT wrappers use the `value` key lol - gm.variable_instance_set(heal.id, "value", amount) - else - -- spwaning healing orbs for non-players looks too noisy, so just heal them directly - actor:heal(amount) - end - end - end -end) -objTree:onDraw(function(self) - -- time it so that peak brightness coincides with spawning heal - local t = (Global._current_frame / 60) * math.pi - local a = 0.6 + math.cos(t) * 0.2 - local r = self.radius - - gm.draw_set_colour(Color.from_hex(0x82FF9F)) - for i=0, 5 do - local tr = r - i / 10 * RADIUS_BASE - gm.draw_set_alpha(a / (i+1)) - gm.draw_circle(self.x, self.y, tr, true) - end - gm.draw_set_alpha(1) -end) - -objTree:onSerialize(function(self, buffer) - buffer:write_uint(self.radius) -end) -objTree:onDeserialize(function(self, buffer) - self.radius = buffer:read_uint() -end) -objTree:onDestroy(function(self) - self:instance_destroy_sync() -end) local function update_trees(removal) - if gm._mod_net_isClient() then return end + if Net.client then return end local stack = 0 - for _, actor in ipairs(Instance.find_all(gm.constants.oP)) do - stack = stack + actor:item_stack_count(distinctiveStick) + + for _, actor in ipairs(distinctiveStick:get_holding_actors()) do + if Instance.exists(actor) then + stack = stack + actor:item_count(distinctiveStick) + end end + if removal then stack = stack - 1 end - if Instance.count(objTree) == 0 then + if Instance.count(efTree) == 0 then for _, inst in ipairs(Instance.find_all(gm.constants.pTeleporter)) do - objTree:create(inst.x + math.random(-80, 80), inst.y) + efTree:create(inst.x + math.random(-80, 80), inst.y) end + for _, inst in ipairs(Instance.find_all(gm.constants.oCommand)) do - objTree:create(inst.x + math.random(-80, 80), inst.y) + efTree:create(inst.x + math.random(-80, 80), inst.y) end end - for _, inst in ipairs(Instance.find_all(objTree)) do + for _, inst in ipairs(Instance.find_all(efTree)) do if stack > 0 then inst.radius = RADIUS_BASE + RADIUS_STACK * (stack - 1) inst:instance_resync() @@ -94,14 +56,69 @@ local function update_trees(removal) end end -distinctiveStick:clear_callbacks() -distinctiveStick:onAcquire(function(actor, stack) +Callback.add(distinctiveStick.on_acquired, function(actor, stack) update_trees() end) -distinctiveStick:onRemove(function(actor, stack) + +Callback.add(distinctiveStick.on_removed, function(actor, stack) update_trees(true) end) -Callback.add(Callback.TYPE.onStageStart, "SSDistinctiveTree", function() +Callback.add(Callback.ON_STAGE_START, function() update_trees() end) + +Callback.add(efTree.on_step, function(self) + if Net.client then return end + + if Global._current_frame % 120 == 0 then + local targets = self:get_collisions_circle(gm.constants.pActor, self.radius, self.x, self.y) + + for _, actor in ipairs(targets) do + if actor.team == 1 then + local amount = actor.maxhp * 0.022 + + if actor.object_index == gm.constants.oP then + local heal = Object.find("EfHeal2"):create(self.x, self.y) + heal.target = actor + heal.value.value = amount + else + -- spwaning healing orbs for non-players looks too noisy, so just heal them directly + actor:heal(amount) + end + end + end + end +end) + +Callback.add(efTree.on_draw, function(self) + -- time it so that peak brightness coincides with spawning heal + local timer = (Global._current_frame / 60) * math.pi + local alpha = 0.6 + math.cos(timer) * 0.2 + local radius = self.radius + + gm.draw_set_colour(Color.from_hex(0x82FF9F)) + + for i = 0, 5 do + local circle_radius = radius - i / 10 * RADIUS_BASE + gm.draw_set_alpha(alpha / (i + 1)) + gm.draw_circle(self.x, self.y, circle_radius, true) + end + + gm.draw_set_alpha(1) +end) + +Callback.add(efTree.on_destroy, function(self) + self:instance_destroy_sync() +end) + +-- networking +local serializer = function(self, buffer) + buffer:write_uint(self.radius) +end + +local deserializer = function(self, buffer) + self.radius = buffer:read_uint() +end + +Object.add_serializers(efTree, serializer, deserializer) \ No newline at end of file diff --git a/Items/dormantFungus.lua b/Items/dormantFungus.lua index 4290cab6..9e2d77d2 100644 --- a/Items/dormantFungus.lua +++ b/Items/dormantFungus.lua @@ -1,65 +1,42 @@ -local sprite = Resources.sprite_load(NAMESPACE, "DormantFungus", path.combine(PATH, "Sprites/Items/dormantFungus.png"), 1, 18, 18) -local sprite_footstep = Resources.sprite_load(NAMESPACE, "DormantFungusFootstep", path.combine(PATH, "Sprites/Items/Effects/dungus.png"), 24, 14, 24) -local sound = Resources.sfx_load(NAMESPACE, "DormantFungus", path.combine(PATH, "Sounds/Items/dormantFungus.ogg")) +local sprite = Sprite.new("DormantFungus", path.combine(PATH, "Sprites/Items/dormantFungus.png"), 1, 18, 18) +local sprite_footstep = Sprite.new("DormantFungusFootstep", path.combine(PATH, "Sprites/Items/Effects/dungus.png"), 8, 14, 24) +local sound = Sound.new("DormantFungus", path.combine(PATH, "Sounds/Items/dormantFungus.ogg")) -local dormantFungus = Item.new(NAMESPACE, "dormantFungus") -dormantFungus:set_sprite(sprite) -dormantFungus:set_tier(Item.TIER.common) -dormantFungus:set_loot_tags(Item.LOOT_TAG.category_healing) -dormantFungus:clear_callbacks() +local dungus = Item.new("dormantFungus") +dungus:set_sprite(sprite) +dungus:set_tier(ItemTier.COMMON) +dungus.loot_tags = Item.LootTag.CATEGORY_HEALING -local parDungus = Particle.new(NAMESPACE, "parDungus") -parDungus:set_sprite(sprite_footstep, true, true, false) -parDungus:set_life(96, 96) -parDungus:set_alpha3(1, 1, 0) +ItemLog.new_from_item(dungus) -local dungusShroomSync = Packet.new() -dungusShroomSync:onReceived(function(msg) - local actor = msg:read_instance() - - if not actor:exists() then return end - - -- copied and pasted from uranium horseshoe basically - parDungus:create(actor.x, actor.bbox_bottom + 1, 1) -end) - -local function sync_dungus(actor) - if not gm._mod_net_isHost() then - log.warning("sync_dungus called on client!") - return - end - - local msg = dungusShroomSync:message_begin() - msg:write_instance(actor) - msg:send_to_all() -end - -dormantFungus:onPostStep(function(actor, stack) - if gm._mod_net_isClient() then return end - - if (math.abs(actor.pHspeed) >= actor.pHmax * 0.98) or actor.pVspeed ~= 0 then - local actor_data = actor:get_data() - - if not actor_data.dungusTimer then - actor_data.dungusTimer = 120 - end - if actor_data.dungusTimer <= 0 then - actor_data.dungusTimer = 120 - - local regen = math.ceil(actor.maxhp * (1 - 1 / (0.02 * stack + 1))) - actor:heal(regen) - gm.sound_play_networked(sound, 1, (0.9 + math.random() * 0.2), actor.x, actor.y) +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(dungus:get_holding_actors()) do + if Instance.exists(actor) then + local stack = actor:item_count(dungus) - -- copied and pasted from uranium horseshoe basically - if Helper.is_false(actor.free) then - parDungus:create(actor.x, actor.bbox_bottom + 1, 1) - - if gm._mod_net_isOnline() then - sync_dungus(actor) -- particles arent synced by default so we have to use a packet for that + if (math.abs(actor.pHspeed) >= actor.pHmax * 0.98) or actor.pVspeed ~= 0 then + local data = Instance.get_data(actor) + + if not data.dungusTimer then + data.dungusTimer = 120 + end + if data.dungusTimer <= 0 then + data.dungusTimer = 120 + + if Net.host then + actor:heal(math.ceil(actor.maxhp * (1 - 1 / (0.02 * stack + 1)))) + end + + actor:sound_play(sound, 1, 0.9 + math.random() * 0.2) + + -- copied and pasted from uranium horseshoe basically + if actor:is_grounded() then + ssr_create_fadeout(actor.x, actor.bbox_bottom + 1, 1, sprite_footstep, 0.25, 0.05) + end + else + data.dungusTimer = data.dungusTimer - 1 end end - else - actor_data.dungusTimer = actor_data.dungusTimer - 1 end end end) diff --git a/Items/eclipseGummies.lua b/Items/eclipseGummies.lua new file mode 100644 index 00000000..f92546eb --- /dev/null +++ b/Items/eclipseGummies.lua @@ -0,0 +1,209 @@ +-- uhmmmm its me azuline .... im a gummy now ... +local sprite_item = Sprite.new("EclipseGummies", path.combine(PATH, "Sprites/Items/eclipseGummies.png"), 1, 15, 16) +local sprite_buff = Sprite.new("EclipseGummiesBuff", path.combine(PATH, "Sprites/Buffs/gummiesBuff.png"), 10, 11, 12) +local sprite_ef = Sprite.new("EclipseGummiesEffect", path.combine(PATH, "Sprites/Items/Effects/gummies.png"), 6, 7, 7) + +local sound_bounce = Sound.new("EclipseGummiesBounce", path.combine(PATH, "Sounds/Items/gummiesBounce.ogg")) +local sound_buff = Sound.new("EclipseGummiesBuff", path.combine(PATH, "Sounds/Items/gummiesBuff.ogg")) +local sound_buff_max = Sound.new("EclipseGummiesBuffMax", path.combine(PATH, "Sounds/Items/gummiesBuffMax.ogg")) + +local gummies = Item.new("eclipseGummies") +gummies:set_sprite(sprite_item) +gummies:set_tier(ItemTier.COMMON) +gummies.loot_tags = Item.LootTag.CATEGORY_DAMAGE + Item.LootTag.CATEGORY_UTILITY + +ItemLog.new_from_item(gummies) + +local buff = Buff.new("gummyBuff") +buff.icon_sprite = sprite_buff +buff.icon_stack_subimage = true +buff.max_stack = 10 +buff.is_timed = true + +Callback.add(buff.on_apply, function(actor) + if actor:buff_count(buff) < 9 then + actor:sound_play(sound_buff.value, 3, 1 + 0.05 * actor:buff_count(buff)) + else + actor:sound_play(sound_buff_max.value, 3, 1) + end +end) + +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buff) + if stack <= 0 then return end + + if Instance.get_data(actor).gummy_stack_strength then + api.pHmax_add(0.07 * stack * Instance.get_data(actor).gummy_stack_strength) + api.attack_speed_add(0.025 * stack * Instance.get_data(actor).gummy_stack_strength) + else + api.pHmax_add(0.07 * stack) + api.attack_speed_add(0.025 * stack) + end +end) + +Callback.add(buff.on_remove, function(actor) + Instance.get_data(actor).gummy_stack_strength = nil +end) + +local gummy_colors = { + Color.from_hex(0xC76F91), + Color.from_hex(0x9A6FC7), + Color.from_hex(0x6FC781), + Color.from_hex(0xE9BD93), + Color.from_hex(0x6FBEC7), + Color.from_hex(0xD4D161), +} + +local obj_gummy = Object.new("EfGummy") +obj_gummy:set_depth(-20) +obj_gummy:set_sprite(sprite_ef) + +Callback.add(obj_gummy.on_create, function(self) + self.image_index = math.random(0, 5) + self.image_speed = 0 + self.image_xscale = gm.choose(-1, 1) + + self.speed = 3 + math.random(2) + self.direction = 45 + math.random(90) + self.gravity = 0.2 + + local data = Instance.get_data(self) + data.bounces = 0 + data.spin_direction = self.image_xscale + data.team = 1 + data.life = 600 + data.stack = 1 + + self:instance_sync() +end) + +Callback.add(obj_gummy.on_step, function(self) + local data = Instance.get_data(self) + + if data.life > 0 then + data.life = data.life - 1 + elseif data.life <= 0 and self.image_alpha > 0 then + self.image_alpha = self.image_alpha - 0.1 + else + self:destroy() + end + + if data.bounces < 2 then + self.image_angle = self.image_angle + self.speed * 2 * data.spin_direction + + if self:is_colliding(gm.constants.pBlock, self.x + gm.lengthdir_x(self.speed, self.direction), self.y + gm.lengthdir_y(self.speed, self.direction)) then + if data.bounces < 1 then + self.x = self.xprevious + self.y = self.yprevious + + self:move_contact_solid(self.direction, -1) + + if self:is_colliding(gm.constants.pBlock, self.x, self.y + 1) then + self.direction = 90 + math.random(-30, 30) + elseif self:is_colliding(gm.constants.pBlock, self.x, self.y - 1) then + self.direction = 270 + math.random(-30, 30) + elseif self:is_colliding(gm.constants.pBlock, self.x + 1, self.y) then + self.direction = 0 + math.random(-30, 30) + else + self.direction = 180 + math.random(-30, 30) + end + + self.speed = self.speed * 0.5 + data.spin_direction = data.spin_direction * -1 + + self:move_bounce_solid(false) + + self:sound_play(sound_bounce.value, 3, 0.9 + math.random() * 0.2) + else + self.x = self.xprevious + self.y = self.yprevious + + self.gravity = 0 + self.speed = 0 + + self:move_contact_solid(self.direction, -1) + + if self:is_colliding(gm.constants.pBlock, self.x, self.y + 1) then + self.image_angle = 0 + elseif self:is_colliding(gm.constants.pBlock, self.x, self.y - 1) then + self.image_angle = 180 + elseif self:is_colliding(gm.constants.pBlock, self.x + 1, self.y) then + self.image_angle = 90 + else + self.image_angle = 270 + end + + self:move_contact_solid(self.image_angle - 90, -1) + + self:sound_play(sound_bounce.value, 3, 0.9 + math.random() * 0.2) + end + + data.bounces = data.bounces + 1 + self:instance_resync() + end + end + + if data.team == 1 then + for _, actor in ipairs(self:get_collisions(gm.constants.oP)) do + if Instance.exists(actor) and GM.actor_is_alive(actor) then + local flash = Object.find("EfFlash"):create(actor.x, actor.y) + flash.parent = actor + flash.rate = 0.05 + flash.image_alpha = 1 + flash.image_blend = gummy_colors[math.floor(self.image_index) + 1] + + Instance.get_data(actor).gummy_stack_strength = data.stack + actor:buff_apply(buff, 10 * 60) + self:destroy() + self:instance_destroy_sync() + end + end + else + for _, actor in ipairs(self:get_collisions(gm.constants.pActor)) do + if Instance.exists(actor) and GM.actor_is_alive(actor) and actor.team == data.team then + local flash = Object.find("EfFlash"):create(actor.x, actor.y) + flash.parent = actor + flash.rate = 0.05 + flash.image_alpha = 1 + flash.image_blend = gummy_colors[math.floor(self.image_index) + 1] + + Instance.get_data(actor).gummy_stack_strength = data.stack + actor:buff_apply(buff, 10 * 60) + self:destroy() + self:instance_destroy_sync() + end + end + end +end) + +Callback.add(Callback.ON_KILL_PROC, function(victim, killer) + if not Net.host then return end + + local stack = killer:item_count(gummies) + if stack <= 0 then return end + + local inst = Instance.get_data(obj_gummy:create(victim.x, victim.y)) + inst.team = killer.team + inst.stack = killer:item_count(gummies) +end) + +-- networking +local serializer = function(inst, buffer) + buffer:write_byte(inst.image_index) + buffer:write_short(inst.image_xscale) + buffer:write_byte(Instance.get_data(inst).team) + buffer:write_int(Instance.get_data(inst).stack) + buffer:write_short(inst.speed) + buffer:write_int(inst.direction) +end + +local deserializer = function(inst, buffer) + inst.image_index = buffer:read_byte() + inst.image_xscale = buffer:read_short() + Instance.get_data(inst).team = buffer:read_byte() + Instance.get_data(inst).stack = buffer:read_int() + inst.speed = buffer:read_short() + inst.direction = buffer:read_int() +end + +Object.add_serializers(obj_gummy, serializer, deserializer) \ No newline at end of file diff --git a/Items/fork.lua b/Items/fork.lua index c449bced..da46729d 100644 --- a/Items/fork.lua +++ b/Items/fork.lua @@ -1,11 +1,15 @@ -local sprite_fork = Resources.sprite_load(NAMESPACE, "Fork", path.combine(PATH, "Sprites/Items/fork.png"), 1, 16, 16) +local sprite_fork = Sprite.new("Fork", path.combine(PATH, "Sprites/Items/fork.png"), 1, 16, 16) -local fork = Item.new(NAMESPACE, "fork") +local fork = Item.new("fork") fork:set_sprite(sprite_fork) -fork:set_tier(Item.TIER.common) -fork:set_loot_tags(Item.LOOT_TAG.category_damage) +fork:set_tier(ItemTier.COMMON) +fork.loot_tags = Item.LootTag.CATEGORY_DAMAGE -fork:clear_callbacks() -fork:onStatRecalc(function(actor, stack) - actor.damage = actor.damage + 3 * stack +ItemLog.new_from_item(fork) + +RecalculateStats.add(function(actor, api) + local stack = actor:item_count(fork) + if stack <= 0 then return end + + api.damage_add(3 * stack) end) diff --git a/Items/guardingAmulent.lua b/Items/guardingAmulent.lua index de6f9e92..879a7538 100644 --- a/Items/guardingAmulent.lua +++ b/Items/guardingAmulent.lua @@ -1,15 +1,15 @@ -local item_sprite = Resources.sprite_load(NAMESPACE, "GuardingAmulet", path.combine(PATH, "Sprites/Items/guardingAmulet.png"), 1, 19, 16) -local effect_sprite = Resources.sprite_load(NAMESPACE, "EfGuardingAmulet", path.combine(PATH, "Sprites/Items/Effects/guardingAmulet.png"), 1, 4, 9) -local sound = Resources.sfx_load(NAMESPACE, "GuardingAmulet", path.combine(PATH, "Sounds/Items/guardingAmulet.ogg")) +local item_sprite = Sprite.new("GuardingAmulet", path.combine(PATH, "Sprites/Items/guardingAmulet.png"), 1, 19, 16) +local effect_sprite = Sprite.new("EfGuardingAmulet", path.combine(PATH, "Sprites/Items/Effects/guardingAmulet.png"), 1, 4, 9) +local sound = Sound.new("GuardingAmulet", path.combine(PATH, "Sounds/Items/guardingAmulet.ogg")) local DAMAGE_MULTIPLIER = 0.6 -local guardingAmulet = Item.new(NAMESPACE, "guardingAmulet") +local guardingAmulet = Item.new("guardingAmulet") guardingAmulet:set_sprite(item_sprite) -guardingAmulet:set_tier(Item.TIER.common) -guardingAmulet:set_loot_tags(Item.LOOT_TAG.category_utility) +guardingAmulet:set_tier(ItemTier.COMMON) +guardingAmulet.loot_tags = Item.LootTag.CATEGORY_UTILITY -local guardingAmuletID = guardingAmulet.value +ItemLog.new_from_item(guardingAmulet) local function get_true_xscale(actor) if actor.object_index == gm.constants.oEngiTurret then @@ -18,73 +18,69 @@ local function get_true_xscale(actor) return actor.image_xscale end -guardingAmulet:clear_callbacks() -guardingAmulet:onAcquire(function(actor, stack) - actor:get_data().amulet_pulse = 0 -end) -guardingAmulet:onPostStep(function(actor, stack) - local data = actor:get_data() - if data.amulet_pulse > 0 then - data.amulet_pulse = data.amulet_pulse - 1 - end -end) -guardingAmulet:onPostDraw(function(actor, stack) +guardingAmulet.effect_display = EffectDisplay.func(function(actor_unwrapped) + local actor = Instance.wrap(actor_unwrapped) + if not Instance.get_data(actor).amulet_pulse then return end + + local pulse = Instance.get_data(actor).amulet_pulse * 0.2 + local actor_xscale = get_true_xscale(actor) - - local x, y = math.floor(actor.ghost_x+0.5), math.floor(actor.ghost_y+0.5) - - local f = Global._current_frame - local x = x - 10 * actor_xscale - local y = y + math.sin(f * 0.04) - local a = 0.75 + math.sin(f * 0.02) * 0.1 - - local pulse = actor:get_data().amulet_pulse * 0.2 local xscale = (1 + pulse) * actor_xscale local yscale = 1 + pulse - + + local xx = 0 + if actor.sprite_walk_half ~= nil and actor.sprite_walk_half[4] ~= nil then + xx = actor.x - (gm.round(gm.sprite_get_width(actor.sprite_walk_half[4]) / 2 + 5)) * actor_xscale + else + xx = actor.x - 20 * actor_xscale + end + + local yy = actor.bbox_bottom - gm.round(gm.sprite_get_height(actor.sprite_idle) / 2) + math.sin(Global._current_frame * 0.04) + gm.gpu_set_blendmode(1) - gm.draw_sprite_ext( effect_sprite, 0, x, y, xscale, yscale, 0, Color.WHITE, a) + GM.draw_sprite_ext(effect_sprite, 0, xx, yy, xscale, yscale, 0, Color.WHITE, 0.75 + math.sin(Global._current_frame * 0.02) * 0.1) if pulse > 0 then - gm.draw_sprite_ext( effect_sprite, 0, x, y, xscale, yscale, 0, Color.WHITE, pulse * 3) + GM.draw_sprite_ext(effect_sprite, 0, xx, yy, xscale, yscale, 0, Color.WHITE, pulse * 3) end gm.gpu_set_blendmode(0) +end, EffectDisplay.DrawPriority.BODY_POST) + +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(guardingAmulet:get_holding_actors()) do + if Instance.exists(actor) then + local data = Instance.get_data(actor) + + if not data.amulet_pulse then + data.amulet_pulse = 0 + end + + if data.amulet_pulse > 0 then + data.amulet_pulse = data.amulet_pulse - 1 + end + end + end end) - -- onAttackHit callbacks and such can't be used to modify the damage because by then the visual damage number was already drawn. - --- signature: --- damager_calculate_damage(hit_info, true_hit, hit, damage, critical, parent, proc, attack_flags, damage_col, team, climb, percent_hp, xscale, hit_x, hit_y) -gm.pre_script_hook(gm.constants.damager_calculate_damage, function(self, other, result, args) - --local _true_hit = args[2] - local _hit = args[3] - local _damage = args[4] - --local _critical = args[5] - local _parent = args[6] - --local _proc = args[7] - --local _attack_flags = args[8] - --local _damage_col = args[9] - --local _team = args[10] - --local _climb = args[11] - --local _percent_hp = args[12] - --local _xscale = args[13] - local _hit_x = args[14] - local _hit_y = args[15] - - local count = gm.item_count(_hit.value or -4, guardingAmuletID) or 0 +DamageCalculate.add(function(api) + local count = api.hit:item_count(guardingAmulet) + if count <= 0 then return end + if count > 0 then - local attacker = Instance.wrap(_parent.value) - local target = Instance.wrap(_hit.value) - - local attack_x = attacker.x or _hit_x.value - + local attack_x = nil + if api.parent and Instance.exists(api_parent) then + attack_x = api_parent.x + else + attack_x = api.hit_x + end + -- use the x locations of the actors to determine blocking .... not ideal but it works? - if gm.sign(target.x - attack_x) == get_true_xscale(target) then - _damage.value = math.ceil(_damage.value * DAMAGE_MULTIPLIER ^ count) + if Math.sign(api.hit.x - attack_x) == get_true_xscale(api.hit) then + api.damage = math.ceil(api.damage * DAMAGE_MULTIPLIER ^ count) - target:get_data().amulet_pulse = 10 - target:sound_play(sound, 1, 0.9 + math.random() * 0.2) - target:sound_play(gm.constants.wMercenary_Parry_Deflection, 0.5, 1.5 + math.random() * 0.5) + Instance.get_data(api.hit).amulet_pulse = 10 + api.hit:sound_play(sound, 1, 0.9 + math.random() * 0.2) + api.hit:sound_play(gm.constants.wMercenary_Parry_Deflection, 0.5, 1.5 + math.random() * 0.5) end end end) diff --git a/Items/huntersSigil.lua b/Items/huntersSigil.lua index a5bbac53..bad0d154 100644 --- a/Items/huntersSigil.lua +++ b/Items/huntersSigil.lua @@ -1,91 +1,106 @@ -local item_sprite = Resources.sprite_load(NAMESPACE, "HuntersSigil", path.combine(PATH, "Sprites/Items/huntersSigil.png"), 1, 15, 15) -local buff_sprite = Resources.sprite_load(NAMESPACE, "BuffSigil", path.combine(PATH, "Sprites/Buffs/sigil.png"), 1, 6, 9) -local sound = Resources.sfx_load(NAMESPACE, "SigilBuff", path.combine(PATH, "Sounds/Items/huntersSigil.ogg")) +local item_sprite = Sprite.new("HuntersSigil", path.combine(PATH, "Sprites/Items/huntersSigil.png"), 1, 15, 15) +local buff_sprite = Sprite.new("BuffSigil", path.combine(PATH, "Sprites/Buffs/sigil.png"), 1, 6, 9) +local sound = Sound.new("SigilBuff", path.combine(PATH, "Sounds/Items/huntersSigil.ogg")) -local ZONE_RADIUS = 64 +local ZONE_RADIUS = 64 -- change to 80 later cuz this is just too small local ZONE_RADIUS_SQ = ZONE_RADIUS^2 -local huntersSigil = Item.new(NAMESPACE, "huntersSigil") +local huntersSigil = Item.new("huntersSigil") huntersSigil:set_sprite(item_sprite) -huntersSigil:set_tier(Item.TIER.uncommon) -huntersSigil:set_loot_tags(Item.LOOT_TAG.category_damage) +huntersSigil:set_tier(ItemTier.UNCOMMON) +huntersSigil.loot_tags = Item.LootTag.CATEGORY_DAMAGE -local buffSigil = Buff.new(NAMESPACE, "huntersSigil") +ItemLog.new_from_item(huntersSigil) + +local buffSigil = Buff.new("huntersSigil") buffSigil.icon_sprite = buff_sprite buffSigil.is_timed = true buffSigil.max_stack = math.huge -local objSigilZone = Object.new(NAMESPACE, "EfSigilZone") +local objSigilZone = Object.new("EfSigilZone") objSigilZone.obj_depth = -260 -huntersSigil:clear_callbacks() -huntersSigil:onAcquire(function(actor, stack) - if gm._mod_net_isClient() then return end - local data = actor:get_data() +Callback.add(huntersSigil.on_acquired, function(actor, stack) + if Net.client then return end + + local data = Instance.get_data(actor) + if not data.sigil_timer then data.sigil_timer = 0 data.sigil_zone = -4 end end) -huntersSigil:onRemove(function(actor, stack) - if gm._mod_net_isClient() then return end + +Callback.add(huntersSigil.on_removed, function(actor, stack) + if Net.client then return end + if stack == 1 then - local data = actor:get_data() - Instance.wrap(data.sigil_zone):destroy() + local data = Instance.get_data(actor) data.sigil_timer = nil data.sigil_zone = nil end end) -huntersSigil:onPostStep(function(actor, stack) - if gm._mod_net_isClient() then return end - local data = actor:get_data() - - if actor.pHspeed == 0 and actor.pVspeed == 0 and not gm.actor_state_is_climb_state(actor.actor_state_current_id) then - data.sigil_timer = data.sigil_timer + 1 - if data.sigil_timer > 60 and not Instance.exists(data.sigil_zone) then - local zone = objSigilZone:create(actor.x, actor.y) - zone.parent = actor - zone.stack = stack - data.sigil_zone = zone.id -- use the inst's ID instead of the inst wrapper directly because that caused random errors ... +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(huntersSigil:get_holding_actors()) do + if Net.host then + if Instance.exists(actor) and GM.actor_is_alive(actor) then + local stack = actor:item_count(huntersSigil) + local data = Instance.get_data(actor) + + if actor.pHspeed == 0 and actor.pVspeed == 0 and not actor:is_climbing() and (not Instance.get_data(actor).nemmerc_special_state or (Instance.get_data(actor).nemmerc_special_state and Instance.get_data(actor).nemmerc_special_state == 0)) then + data.sigil_timer = data.sigil_timer + 1 + + if data.sigil_timer > 60 and not Instance.exists(data.sigil_zone) then + local zone = objSigilZone:create(actor.x, actor.y) + zone.parent = actor + zone.stack = stack + data.sigil_zone = zone.value -- use the inst's ID instead of the inst wrapper directly because that caused random errors ... + end + else + data.sigil_timer = 0 + end + end end - else - data.sigil_timer = 0 end end) -objSigilZone:clear_callbacks() -objSigilZone:onCreate(function(self) - self.parent = -4 - self.timer = 0 - self.stack = 1 +Callback.add(objSigilZone.on_create, function(self) + local data = Instance.get_data(self) + data.timer = 0 + data.stack = 1 + + self.parent = -4 self:sound_play(sound, 1, 0.9 + math.random() * 0.2) self:instance_sync() end) -objSigilZone:onStep(function(self) - if not GM.actor_is_alive(self.parent) then + +Callback.add(objSigilZone.on_step, function(self) + if not GM.actor_is_alive(self.parent) or self.parent:item_count(huntersSigil) <= 0 then self:destroy() return end - - if self.timer % 5 == 0 then - local to_buff = List.wrap(self:find_characters_circle(self.x, self.y, ZONE_RADIUS, false, self.parent.team, true)) - - for _, target in ipairs(to_buff) do - local diff = self.stack - target:buff_stack_count(buffSigil) - if diff > 0 then - -- only has an effect on the host - target:buff_apply(buffSigil, 10, diff) - else - -- maintain the buff without clobbering the network - GM.set_buff_time_nosync(target, buffSigil, 10) + + local data = Instance.get_data(self) + + if data.timer % 5 == 0 then + for _, target in ipairs(self:get_collisions_circle(gm.constants.pActor, ZONE_RADIUS, self.x, self.y)) do + if target.team == self.parent.team then + local diff = data.stack - target:buff_count(buffSigil) + if diff > 0 then + -- only has an effect on the host + target:buff_apply(buffSigil, 15, diff) + else + -- maintain the buff without clobbering the network + GM.set_buff_time_nosync(target, buffSigil, 15) + end end end end - self.timer = self.timer + 1 + data.timer = data.timer + 1 - if gm._mod_net_isClient() then return end + if Net.client then return end -- host handles destroying the zone when its owner leaves it local dx = self.x - self.parent.x @@ -94,32 +109,37 @@ objSigilZone:onStep(function(self) self:destroy() end end) -objSigilZone:onDestroy(function(self) + +Callback.add(objSigilZone.on_destroy, function(self) self:instance_destroy_sync() end) -objSigilZone:onDraw(function(self) +Callback.add(objSigilZone.on_draw, function(self) gm.draw_set_colour(Color.RED) gm.gpu_set_blendmode(1) - gm.draw_set_alpha(0.5 + math.random()*0.2) - - gm.draw_circle(self.x, self.y, ZONE_RADIUS, true) - + gm.draw_set_alpha(0.5 + math.random() * 0.2) + gm.draw_circle(self.x, self.y, math.min(ZONE_RADIUS, math.sqrt(Instance.get_data(self).timer * 300)), true) gm.draw_set_alpha(1) gm.gpu_set_blendmode(0) end) -objSigilZone:onSerialize(function(self, buffer) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffSigil) + if stack <= 0 then return end + + api.armor_add(5 + 10 * stack) + api.critical_chance_add(5 + 20 * stack) +end) + +-- networking +local serializer = function(self, buffer) buffer:write_instance(self.parent) buffer:write_ushort(self.stack) -end) -objSigilZone:onDeserialize(function(self, buffer) +end + +local deserializer = function(self, buffer) self.parent = buffer:read_instance() self.stack = buffer:read_ushort() -end) +end -buffSigil:clear_callbacks() -buffSigil:onStatRecalc(function(actor, stack) - actor.armor = actor.armor + 5 + (10 * stack) - actor.critical_chance = actor.critical_chance + 5 + (20 * stack) -end) +Object.add_serializers(objSigilZone, serializer, deserializer) diff --git a/Items/iceTool.lua b/Items/iceTool.lua index 806cc375..9af67535 100644 --- a/Items/iceTool.lua +++ b/Items/iceTool.lua @@ -1,127 +1,147 @@ -local sprite_item = Resources.sprite_load(NAMESPACE, "IceTool", path.combine(PATH, "Sprites/Items/iceTool.png"), 1, 16, 16) -local sprite_effect = Resources.sprite_load(NAMESPACE, "EfIceTool", path.combine(PATH, "Sprites/Items/Effects/iceTool.png"), 4, 13, 8) -local sound = Resources.sfx_load(NAMESPACE, "IceTool", path.combine(PATH, "Sounds/Items/iceTool.ogg")) +local sprite_item = Sprite.new("IceTool", path.combine(PATH, "Sprites/Items/iceTool.png"), 1, 16, 16) +local sprite_effect = Sprite.new("EfIceTool", path.combine(PATH, "Sprites/Items/Effects/iceTool.png"), 4, 13, 8) +local sound = Sound.new("IceTool", path.combine(PATH, "Sounds/Items/iceTool.ogg")) -local particleSpark = Particle.find("ror", "Spark") - -local efIceTool = Object.new(NAMESPACE, "EfIceTool") -efIceTool.obj_sprite = sprite_effect +local particleSpark = Particle.find("Spark") -- this buff is used for the brief speed boost on walljump and while climbing -- all applications of it use _internal functions to bypass networking, because player movement is clientside -local buffIceTool = Buff.new(NAMESPACE, "IceTool") +local buffIceTool = Buff.new("IceTool") buffIceTool.client_handles_removal = true buffIceTool.show_icon = false buffIceTool.max_stack = math.huge -local iceTool = Item.new(NAMESPACE, "iceTool") +local iceTool = Item.new("iceTool") iceTool:set_sprite(sprite_item) -iceTool:set_tier(Item.TIER.common) -iceTool:set_loot_tags(Item.LOOT_TAG.category_utility) - -iceTool:clear_callbacks() -iceTool:onAcquire(function(actor, stack) - local data = actor:get_data() - data.iceTool_jumps = stack - - if gm.actor_state_is_climb_state(actor.actor_state_current_id) then - GM.apply_buff_internal(actor, buffIceTool, math.huge, 1) - end -end) - -iceTool:onPostStep(function(actor, stack) - local data = actor:get_data() - - local is_climbing = gm.actor_state_is_climb_state(actor.actor_state_current_id) - local is_airborne = gm.bool(actor.free) - - local collision_dir = 0 - if actor:is_colliding(gm.constants.pBlock, actor.x-1) then - collision_dir = -1 - elseif actor:is_colliding(gm.constants.pBlock, actor.x+1) then - collision_dir = 1 +iceTool:set_tier(ItemTier.COMMON) +iceTool.loot_tags = Item.LootTag.CATEGORY_UTILITY + +ItemLog.new_from_item(iceTool) + +local packet = Packet.new("SyncIceTool") + +local serializer = function(buffer, self, dir) + buffer:write_instance(self) + buffer:write_short(dir) +end + +local deserializer = function(buffer, self) + local actor = buffer:read_instance() + local collision_dir = buffer:read_short() + + local data = Instance.get_data(actor) + + actor.pVspeed = -actor.pVmax - 1.5 + actor.free_jump_timer = 0 + actor.jumping = true + actor.moveUp = false + actor.moveUp_buffered = false + + actor.pHspeed = -actor.pHmax * collision_dir + actor.image_xscale = -collision_dir + + data.iceTool_jumps = data.iceTool_jumps - 1 + + if actor:buff_count(buffIceTool) > 0 then + GM.set_buff_time_nosync(actor, buffIceTool, 30) + else + GM.apply_buff_internal(actor, buffIceTool, 30, 3) end - local can_do_it = is_airborne and collision_dir ~= 0 and data.iceTool_jumps > 0 + actor:sound_play(sound, 1, 1) + particleSpark:create(actor.x + 6 * collision_dir, actor.y, 2) + ssr_create_fadeout(actor.x, actor.y, -collision_dir, sprite_effect, 0.25, 0.2) +end - -- do some dumb jank to make ice tool take precedence over hopoo feather - if can_do_it and not data.iceTool_feather_preserve then - data.iceTool_feather_preserve = actor.jump_count - actor.jump_count = math.huge - elseif not can_do_it and data.iceTool_feather_preserve then - actor.jump_count = data.iceTool_feather_preserve - data.iceTool_feather_preserve = nil - end +packet:set_serializers(serializer, deserializer) - -- just in-case something like a geyser or whatever changes the jump count .... - -- this is all very jank and terrible but it's good enough and the QoL is worth it .... - if actor.jump_count ~= math.huge and data.iceTool_feather_preserve then - actor.jump_count = math.huge - end - - if not is_airborne or is_climbing then - data.iceTool_jumps = stack - return - end - - if gm.bool(actor.moveUp_buffered) and can_do_it then - actor.pVspeed = -actor.pVmax - 1.5 - actor.free_jump_timer = 0 - actor.jumping = true - actor.moveUp = false - actor.moveUp_buffered = false - - actor.pHspeed = -actor.pHmax * collision_dir - actor.image_xscale = -collision_dir - - data.iceTool_jumps = data.iceTool_jumps - 1 - - if actor:is_authority() then - -- send actor_position_info packet to sync the jumping velocity and stuff - actor:net_send_instance_message(0) - end - - if actor:buff_stack_count(buffIceTool) > 0 then - GM.set_buff_time_nosync(actor, buffIceTool, 30) - else - GM.apply_buff_internal(actor, buffIceTool, 30, 3) - end - - actor:sound_play(sound, 1, 1) - particleSpark:create(actor.x + 6 * collision_dir, actor.y, 2) +Callback.add(iceTool.on_acquired, function(actor, stack) + local data = Instance.get_data(actor) + data.iceTool_jumps = stack - efIceTool:create(actor.x, actor.y).image_xscale = collision_dir + if actor:is_climbing() then + GM.apply_buff_internal(actor, buffIceTool, math.huge, 1) end end) -efIceTool:clear_callbacks() -efIceTool:onCreate(function(self) - self.image_speed = 0.2 -end) -efIceTool:onStep(function(self) - if self.image_index + self.image_speed >= 4 then - self.image_speed = 0 - end - if self.image_speed == 0 then - self.image_alpha = self.image_alpha - 0.2 - if self.image_alpha <= 0 then - self:destroy() +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(iceTool:get_holding_actors()) do + if Instance.exists(actor) then + local data = Instance.get_data(actor) + local stack = actor:item_count(iceTool) + + local collision_dir = 0 + if actor:is_colliding(gm.constants.pBlock, actor.x - 1) then + collision_dir = -1 + elseif actor:is_colliding(gm.constants.pBlock, actor.x + 1) then + collision_dir = 1 + end + + local can_do_it = not actor:is_grounded() and collision_dir ~= 0 and data.iceTool_jumps > 0 and not actor:is_climbing() + + -- do some dumb jank to make ice tool take precedence over hopoo feather + if can_do_it and not data.iceTool_feather_preserve then + data.iceTool_feather_preserve = actor.jump_count + actor.jump_count = math.huge + elseif not can_do_it and data.iceTool_feather_preserve then + actor.jump_count = data.iceTool_feather_preserve + data.iceTool_feather_preserve = nil + end + + -- just in-case something like a geyser or whatever changes the jump count .... + -- this is all very jank and terrible but it's good enough and the QoL is worth it .... + if actor.jump_count ~= math.huge and data.iceTool_feather_preserve then + actor.jump_count = math.huge + end + + if actor:is_grounded() or actor:is_climbing() then + data.iceTool_jumps = stack + end + + -- moveUp_buffered isnt synced so all of this only occurs for the guys who is currently walljumping + if Util.bool(actor.moveUp_buffered) and can_do_it then + actor.pVspeed = -actor.pVmax - 1.5 + actor.free_jump_timer = 0 + actor.jumping = true + actor.moveUp = false + actor.moveUp_buffered = false + + actor.pHspeed = -actor.pHmax * collision_dir + actor.image_xscale = -collision_dir + + data.iceTool_jumps = data.iceTool_jumps - 1 + + if actor:buff_count(buffIceTool) > 0 then + GM.set_buff_time_nosync(actor, buffIceTool, 30) + else + GM.apply_buff_internal(actor, buffIceTool, 30, 3) + end + + actor:sound_play(sound, 1, 1) + particleSpark:create(actor.x + 6 * collision_dir, actor.y, 2) + ssr_create_fadeout(actor.x, actor.y, -collision_dir, sprite_effect, 0.25, 0.2) + + if Net.online then + packet:send_to_all(actor, collision_dir) + end + end end end end) -buffIceTool:clear_callbacks() -buffIceTool:onStatRecalc(function(actor, stack) - actor.pHmax = actor.pHmax + stack * 0.5 +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffIceTool) + if stack <= 0 then return end + + api.pHmax_add(0.5 * stack) end) -local stateClimb = State.find("ror", "climb") +local stateClimb = ActorState.find("climb") -stateClimb:clear_callbacks() -stateClimb:onEnter(function(actor, data) - local stack = actor:item_stack_count(iceTool) - GM.apply_buff_internal(actor, buffIceTool, math.huge, stack) +Callback.add(stateClimb.on_enter, function(actor, data) + GM.apply_buff_internal(actor, buffIceTool, math.huge, actor:item_count(iceTool)) end) -stateClimb:onExit(function(actor, data) + +Callback.add(stateClimb.on_exit, function(actor, data) GM.remove_buff_internal(actor, buffIceTool) end) diff --git a/Items/judderingEgg.lua b/Items/judderingEgg.lua index 4ab99d54..4afc2298 100644 --- a/Items/judderingEgg.lua +++ b/Items/judderingEgg.lua @@ -1,10 +1,12 @@ -local sprite_egg = Resources.sprite_load(NAMESPACE, "JudderingEgg", path.combine(PATH, "Sprites/Items/judderingEgg.png"), 1, 17, 18) -local sprite_wurm = Resources.sprite_load(NAMESPACE, "BabyWurm", path.combine(PATH, "Sprites/Items/Effects/babyWurm.png"), 5, 5, 13) +local sprite_egg = Sprite.new("JudderingEgg", path.combine(PATH, "Sprites/Items/judderingEgg.png"), 1, 17, 18) +local sprite_wurm = Sprite.new("BabyWurm", path.combine(PATH, "Sprites/Items/Effects/babyWurm.png"), 5, 5, 13) -local judderingEgg = Item.new(NAMESPACE, "judderingEgg") +local judderingEgg = Item.new("judderingEgg") judderingEgg:set_sprite(sprite_egg) -judderingEgg:set_tier(Item.TIER.rare) -judderingEgg:set_loot_tags(Item.LOOT_TAG.category_damage, Item.LOOT_TAG.item_blacklist_engi_turrets) +judderingEgg:set_tier(ItemTier.RARE) +judderingEgg.loot_tags = Item.LootTag.CATEGORY_DAMAGE + Item.LootTag.ITEM_BLACKLIST_ENGI_TURRETS + +ItemLog.new_from_item(judderingEgg) local WURM_SEGMENT_COUNT = 7 local WURM_SEGMENT_LENGTH = 25 @@ -23,7 +25,7 @@ local function create_wurm(x, y, owner) speed = 2, firing = false, shots = 0, - target = Instance.wrap_invalid(), + target = Instance.wrap(), laser_x = -1, laser_y = -1, } @@ -40,20 +42,21 @@ local function create_wurm(x, y, owner) return wurm end + local function find_target(wurm) local candidate = wurm.parent:find_target_nearest() - if Instance.exists(candidate) and gm.point_distance(wurm.parent.x, wurm.parent.y, candidate.x, candidate.y) < WURM_ATTACK_RANGE then + if Instance.exists(candidate) and Math.distance(wurm.parent.x, wurm.parent.y, candidate.x, candidate.y) < WURM_ATTACK_RANGE then return candidate.parent end - return Instance.wrap_invalid() + + return Instance.wrap() end local wurm_owners = {} -judderingEgg:clear_callbacks() -judderingEgg:onAcquire(function(actor, stack) - local data = actor:get_data() +Callback.add(judderingEgg.on_acquired, function(actor, stack) + local data = Instance.get_data(actor) if not data.wurm_pets then data.wurm_pets = {} @@ -62,8 +65,9 @@ judderingEgg:onAcquire(function(actor, stack) data.wurm_pets[stack] = create_wurm(actor.x, actor.y, actor) end) -judderingEgg:onRemove(function(actor, stack) - local data = actor:get_data() + +Callback.add(judderingEgg.on_removed, function(actor, stack) + local data = Instance.get_data(actor) if not data.wurm_pets then return end data.wurm_pets[stack] = nil @@ -73,18 +77,55 @@ judderingEgg:onRemove(function(actor, stack) wurm_owners[actor.id] = nil end end) -judderingEgg:onPreStep(function(actor, stack) - local data = actor:get_data() - if not wurm_owners[actor.id] then - wurm_owners[actor.id] = true + +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(judderingEgg:get_holding_actors()) do + if Instance.exists(actor) then + local data = Instance.get_data(actor) + if not wurm_owners[actor.id] then + wurm_owners[actor.id] = true + end + end end end) -local sparkParticle = Particle.find("ror", "Spark") -local packetSyncWurm = Packet.new() +local sparkParticle = Particle.find("Spark") + +local packet = Packet.new("SyncJudderingEgg") + +local serializer = function(buffer, actor, index, target) + buffer:write_instance(actor) + buffer:write_uint(index) + buffer:write_instance(target) +end + +local deserializer = function(buffer, self) + local actor = buffer:read_instance() + local index = buffer:read_uint() + 1 + local target = buffer:read_instance() + + if not Instance.exists(actor) then return end + + local wurm_pets = Instance.get_data(actor).wurm_pets + if not wurm_pets then return end --- use a global callback instead of an item callback, so that the wurm can still exist while the player is dead. -Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() + local wurm = wurm_pets[index] + if wurm then + local previous_firing = wurm.firing + + wurm.firing = Instance.exists(target) + wurm.target = target + + if wurm.firing and not previous_firing then + local head = wurm.segments[1] + gm.sound_play_at(gm.constants.wWurmLaser, 0.8, 1.075 + math.random() * 0.1, head.x, head.y) + end + end +end + +packet:set_serializers(serializer, deserializer) + +Callback.add(Callback.ON_STEP, Callback.Priority.AFTER, function() for id, _ in pairs(wurm_owners) do if not Instance.exists(id) then wurm_owners[id] = nil @@ -92,9 +133,9 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() end for id, _ in pairs(wurm_owners) do local actor = Instance.wrap(id) - local stack = actor:item_stack_count(judderingEgg) + local stack = actor:item_count(judderingEgg) - local data = actor:get_data() + local data = Instance.get_data(actor) local wurm_pets = data.wurm_pets if not wurm_pets then return end @@ -110,8 +151,8 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() -- prevent multiple wurms from overlapping while following their owner, by shifting their target positions slightly if not wurm.firing and stack > 1 then - target_x = target_x - math.sin((i * math.pi * 2) / stack) * WURM_FOLLOW_RADIUS*0.4 - target_y = target_y - math.cos((i * math.pi * 2) / stack) * WURM_FOLLOW_RADIUS*0.4 + target_x = target_x - math.sin((i * math.pi * 2) / stack) * WURM_FOLLOW_RADIUS * 0.4 + target_y = target_y - math.cos((i * math.pi * 2) / stack) * WURM_FOLLOW_RADIUS * 0.4 end if wurm.firing and Instance.exists(wurm.target) then @@ -121,7 +162,7 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() -- as the wurm approaches WURM_FOLLOW_RADIUS distance to the target, it will begin to turn up to 90 degrees to steer clear of the target. -- this gives it a circling motion, both when following its owner and when pursuing an enemy. - local dist = gm.point_distance(head.x, head.y, target_x, target_y) + local dist = Math.distance(head.x, head.y, target_x, target_y) local target_speed = 8 local target_direction = gm.point_direction(head.x, head.y, target_x, target_y) @@ -131,7 +172,7 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() local steer_factor = 1 - ((dist - WURM_FOLLOW_RADIUS) / WURM_FOLLOW_MARGIN) steer_factor = math.max(0, math.min(1, steer_factor)) - steer_direction = gm.sign(gm.angle_difference(head.direction, target_direction)) + steer_direction = Math.sign(gm.angle_difference(head.direction, target_direction)) target_speed = math.max(1, steer_factor * 3) @@ -147,15 +188,15 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() head.direction = head.direction - 1 + math.random() * 2 -- using lerp smoothing is kinda iffy but ehhh good enough? - wurm.speed = gm.lerp(wurm.speed, target_speed, 0.01) + wurm.speed = Math.lerp(wurm.speed, target_speed, 0.01) head.x = head.x + gm.lengthdir_x(wurm.speed, head.direction) head.y = head.y + gm.lengthdir_y(wurm.speed, head.direction) -- update body segment positions - for i=2, #wurm.segments do + for i = 2, #wurm.segments do local seg = wurm.segments[i] - local seg_prev = wurm.segments[i-1] - seg.direction = gm.point_direction(seg.x, seg.y, seg_prev.x, seg_prev.y) + local seg_prev = wurm.segments[i - 1] + seg.direction = Math.direction(seg.x, seg.y, seg_prev.x, seg_prev.y) seg.x = seg_prev.x - gm.lengthdir_x(WURM_SEGMENT_LENGTH, seg.direction) seg.y = seg_prev.y - gm.lengthdir_y(WURM_SEGMENT_LENGTH, seg.direction) end @@ -175,11 +216,11 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() if wurm.cooldown < 0 then local sync = false -- only matters on the host - if gm._mod_net_isHost() then + if Net.host then -- host handles targetting if not wurm.firing then wurm.target = find_target(wurm) - if wurm.target:exists() then + if Instance.exists(wurm.target) then wurm.firing = true wurm.shots = WURM_ATTACK_SHOTS wurm.cooldown = 0 @@ -190,14 +231,16 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() else wurm.cooldown = 15 + math.random(15) end - elseif not wurm.target:exists() then + elseif not Instance.exists(wurm.target) then wurm.target = find_target(wurm) - if not wurm.target:exists() then + + if not Instance.exists(wurm.target) then wurm.firing = false - wurm.target = Instance.wrap_invalid() + wurm.target = Instance.wrap() wurm.shots = 0 wurm.cooldown = WURM_ATTACK_COOLDOWN end + sync = true end end @@ -211,13 +254,13 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() wurm.cooldown = WURM_ATTACK_INTERVAL - if gm._mod_net_isHost() then + if Net.host then actor:fire_explosion(wurm.target.x, wurm.target.y, 32, 32, 0.8, nil, nil, false) wurm.shots = wurm.shots - 1 if wurm.shots <= 0 then wurm.firing = false - wurm.target = Instance.wrap_invalid() + wurm.target = Instance.wrap() wurm.shots = 0 wurm.cooldown = WURM_ATTACK_COOLDOWN @@ -226,47 +269,16 @@ Callback.add(Callback.TYPE.onStep, "SSJudderingEggStep", function() end end - if gm._mod_net_isHost() and gm._mod_net_isOnline() and sync then - local msg = packetSyncWurm:message_begin() - - msg:write_instance(actor) -- wurm owner - msg:write_uint(i - 1) -- wurm index - msg:write_instance(wurm.target) -- if the target is invalid, the wurm is inferred to not be firing - - msg:send_to_all() + if Net.host and Net.online and sync then + packet:send_to_all(actor, i - 1, wurm.target) end end end end end) --- this is kind of horrible but it works well enough -packetSyncWurm:onReceived(function(msg) - local actor = msg:read_instance() - local index = msg:read_uint() + 1 - local target = msg:read_instance() - - if not actor:exists() then return end - - local wurm_pets = actor:get_data().wurm_pets - if not wurm_pets then return end - - local wurm = wurm_pets[index] - if wurm then - local previous_firing = wurm.firing - - wurm.firing = Instance.exists(target) - wurm.target = target - - if wurm.firing and not previous_firing then - local head = wurm.segments[1] - gm.sound_play_at(gm.constants.wWurmLaser, 0.8, 1.075 + math.random() * 0.1, head.x, head.y) - end - end -end) - -- draw wurms with a global callback, so that they appear even if their player is invisible for any reason (teleporting, dead, etc.) -Callback.add(Callback.TYPE.onDraw, "SSJudderingEggDraw", function() +Callback.add(Callback.ON_DRAW, function() for id, _ in pairs(wurm_owners) do if not Instance.exists(id) then wurm_owners[id] = nil @@ -275,7 +287,7 @@ Callback.add(Callback.TYPE.onDraw, "SSJudderingEggDraw", function() for id, _ in pairs(wurm_owners) do local actor = Instance.wrap(id) - local wurm_pets = actor:get_data().wurm_pets + local wurm_pets = Instance.get_data(actor).wurm_pets if not wurm_pets then return end -- draw laser first @@ -314,7 +326,7 @@ Callback.add(Callback.TYPE.onDraw, "SSJudderingEggDraw", function() end -- draw body for _, wurm in ipairs(wurm_pets) do - for i=0, #wurm.segments-1 do + for i = 0, #wurm.segments - 1 do -- segments are drawn in reverse order (tail to head) local real_index = #wurm.segments - i local segment = wurm.segments[real_index] @@ -331,22 +343,28 @@ Callback.add(Callback.TYPE.onDraw, "SSJudderingEggDraw", function() elseif real_index == #wurm.segments then subimage = 4 end - gm.draw_sprite_ext(sprite_wurm, subimage, segment.x, segment.y, 1, 1, segment.direction, Color.WHITE, 1) + + GM.draw_sprite_ext(sprite_wurm, subimage, segment.x, segment.y, 1, 1, segment.direction, Color.WHITE, 1) end end end end) -judderingEgg:onStageStart(function(actor, stack) - local wurm_pets = actor:get_data().wurm_pets - if not wurm_pets then return end +Callback.add(Callback.ON_STAGE_START, function() + for _, actor in ipairs(judderingEgg:get_holding_actors()) do + if Instance.exists(actor) then + local wurm_pets = Instance.get_data(actor).wurm_pets + if not wurm_pets then return end - for _, wurm in ipairs(wurm_pets) do - wurm.firing = false + for _, wurm in ipairs(wurm_pets) do + wurm.firing = false - local head = wurm.segments[1] - local angle = math.random() * math.pi * 2 - head.x = actor.x + math.sin(angle) * WURM_FOLLOW_RADIUS - head.y = actor.y + math.cos(angle) * WURM_FOLLOW_RADIUS + local head = wurm.segments[1] + local angle = math.random() * math.pi * 2 + + head.x = actor.x + math.sin(angle) * WURM_FOLLOW_RADIUS + head.y = actor.y + math.cos(angle) * WURM_FOLLOW_RADIUS + end + end end end) diff --git a/Items/malice.lua b/Items/malice.lua index b6e0bf69..40573fa8 100644 --- a/Items/malice.lua +++ b/Items/malice.lua @@ -1,5 +1,5 @@ -local item_sprite = Resources.sprite_load(NAMESPACE, "malice", path.combine(PATH, "Sprites/Items/malice.png"), 1, 16, 18) -local spark_sprite = gm.constants.sSparks18s--Resources.sprite_load(NAMESPACE, "SparkMalice", path.combine(PATH, "Sprites/Items/Effects/malice.png"), 4, 8, 4) +local item_sprite = Sprite.new("malice", path.combine(PATH, "Sprites/Items/malice.png"), 1, 16, 18) +local spark_sprite = gm.constants.sSparks18s local RANGE_BASE = 90 local RANGE_STACK = 21 @@ -8,53 +8,52 @@ local DAMAGE_COEFFICIENT = 0.45 local TRAVEL_TIME = 1 / 10 -- 10 frames local COLOR_BRIGHT = Color.from_rgb(188, 17, 255) -local malice = Item.new(NAMESPACE, "malice") -local objEfMalice = Object.new(NAMESPACE, "EfMalice") -local partMalice = Particle.new(NAMESPACE, "Malice") +local malice = Item.new("malice") +local efMalice = Object.new("EfMalice") +local partMalice = Particle.new("Malice") malice:set_sprite(item_sprite) -malice:set_tier(Item.TIER.common) -malice:set_loot_tags(Item.LOOT_TAG.category_damage) +malice:set_tier(ItemTier.COMMON) +malice.loot_tags = Item.LootTag.CATEGORY_DAMAGE -partMalice:set_shape(Particle.SHAPE.disk) +ItemLog.new_from_item(malice) + +partMalice:set_shape(Particle.Shape.DISK) partMalice:set_life(15, 25) partMalice:set_size(0.08, 0.12, -0.005, 0) partMalice:set_colour3(COLOR_BRIGHT, Color.PURPLE, Color.PURPLE) -local buffWound = Buff.find("ror", "commandoWound") +local buffWound = Buff.find("commandoWound") -malice:clear_callbacks() -malice:onHitProc(function(attacker, victim, stack, hit_info) +Callback.add(Callback.ON_HIT_PROC, function(actor, victim, hit_info) + local stack = actor:item_count(malice) + if stack <= 0 then return end + -- prevent proccing more than once per attack, not ideal (doesn't even work for multihit projectiles) but performance is very bad otherwise if hit_info.attack_info.__ssr_maliced then return end hit_info.attack_info.__ssr_maliced = true -- wounding works by firing direct attacks on each hit. this bypasses the above thing, so work around it .... - if hit_info.attack_info:get_attack_flag(Attack_Info.ATTACK_FLAG.commando_wound_damage) then return end - if victim:buff_stack_count(buffWound) > 0 then stack = stack * 2 end - + if hit_info.attack_info:get_flag(AttackFlag.COMMANDO_WOUND_DAMAGE) then return end + if victim:buff_count(buffWound) > 0 then stack = stack * 2 end + -- actually handle malice functionality now. - -- avoids use of wrapping here as much as possible to try and claw up perf - local _malice_target_list = gm.ds_list_create() - -- looks for actor hitboxes rather than just actors directly, so that worms, brambles, etc. interact intuitively with malice - local _count = victim.value:collision_circle_list(hit_info.x, hit_info.y, RANGE_BASE + (RANGE_STACK * stack - 1), gm.constants.pActorCollisionBase, false, true, _malice_target_list, true) + local hitbox_table = victim:get_collisions_circle(gm.constants.pActorCollisionBase, RANGE_BASE + (RANGE_STACK * stack - 1), hit_info.x, hit_info.y) local maliced_actors = {} local maliced_count = 0 - for i=1, _count do - local target_hitbox = gm.ds_list_find_value(_malice_target_list, i - 1) - local target_actor = gm.attack_collision_resolve(target_hitbox) - - if target_actor ~= -4 and not maliced_actors[target_actor.id] - and target_actor.id ~= victim.id and gm.team_canhit(attacker.team, target_actor.team) then - local m = objEfMalice:create(hit_info.x, hit_info.y) - m.target = target_hitbox - m.parent = attacker - m.critical = hit_info.critical - m.damage = math.ceil(hit_info.damage * DAMAGE_COEFFICIENT) - - maliced_actors[target_actor.id] = true + for _, hitbox in ipairs(hitbox_table) do + local enemy = GM.attack_collision_resolve(hitbox) + + if Instance.exists(enemy) and not maliced_actors[enemy.id] and enemy.id ~= victim.id and gm.team_canhit(actor.team, enemy.team) then + local inst = efMalice:create(hit_info.x, hit_info.y) + inst.parent = actor + Instance.get_data(inst).target = hitbox + Instance.get_data(inst).critical = hit_info.critical + Instance.get_data(inst).damage = math.ceil(hit_info.damage * DAMAGE_COEFFICIENT) + + maliced_actors[enemy.id] = true maliced_count = maliced_count + 1 if maliced_count >= stack then @@ -62,66 +61,71 @@ malice:onHitProc(function(attacker, victim, stack, hit_info) end end end - - gm.ds_list_destroy(_malice_target_list) end) -local __quality = 3 +local quality = 3 -objEfMalice:clear_callbacks() -objEfMalice:onCreate(function(self) - self.damage = 0 - self.critical = false - self.parent = -4 - self.target = -4 - - self.fract = 0 - self.direction = math.random(360) +Callback.add(efMalice.on_create, function(inst) + local data = Instance.get_data(inst) + + data.damage = 0 + data.critical = false + data.target = -4 + data.fract = 0 -- this is a bit silly but it seemed like a bad idea to read it in the step so lol - __quality = Global.__pref_graphics_quality - - self:instance_sync() + quality = Global.__pref_graphics_quality + + inst.direction = math.random(360) + inst.parent = -4 + inst:instance_sync() end) -objEfMalice:onStep(function(self) - if not Instance.exists(self.target) then self:destroy() return end - if not Instance.exists(self.parent) then self:destroy() return end - self.fract = self.fract + TRAVEL_TIME +Callback.add(efMalice.on_step, function(inst) + local data = Instance.get_data(inst) + + if not Instance.exists(data.target) then inst:destroy() return end + if not Instance.exists(inst.parent) then inst:destroy() return end + + data.fract = data.fract + TRAVEL_TIME - local target, parent = self.target, self.parent + local target, parent = data.target, inst.parent local tx, ty = target.x, target.y - local coff = math.sin(self.fract * math.pi) - self.x = gm.lerp(self.xstart, tx, self.fract) + gm.lengthdir_x(16, self.direction) * coff - self.y = gm.lerp(self.ystart, ty, self.fract) + gm.lengthdir_y(16, self.direction) * coff + local coff = math.sin(data.fract * math.pi) + inst.x = gm.lerp(inst.xstart, tx, data.fract) + gm.lengthdir_x(16, inst.direction) * coff + inst.y = gm.lerp(inst.ystart, ty, data.fract) + gm.lengthdir_y(16, inst.direction) * coff - local dx, dy = self.x - self.xprevious, self.y - self.yprevious + local dx, dy = inst.x - inst.xprevious, inst.y - inst.yprevious -- spawn 3 particles per step at max quality, and only 1 at min - for i=0, __quality - 1 do - local f = i / __quality - partMalice:create(self.x - dx * f, self.y - dy * f, 1, Particle.SYSTEM.above) + for i = 0, quality - 1 do + local offset = i / quality + partMalice:create(inst.x - dx * offset, inst.y - dy * offset, 1) end - if self.fract + TRAVEL_TIME > 1 then - if gm._mod_net_isHost() then - local dir = gm.point_direction(self.xprevious, self.yprevious, self.x, self.y) - local atk = self.parent:fire_direct(target, 1, dir, self.x, self.y, spark_sprite, false).attack_info - atk.damage = self.damage - atk.critical = self.critical - atk.damage_color = COLOR_BRIGHT + if data.fract + TRAVEL_TIME > 1 then + if Net.host then + local dir = Math.direction(inst.xprevious, inst.yprevious, inst.x, inst.y) + local attack = inst.parent:fire_direct(target, 1, dir, inst.x, inst.y, gm.constants.sSparks18s, false).attack_info + attack.damage = data.damage + attack.critical = data.critical + attack.damage_color = COLOR_BRIGHT end - self:destroy() + inst:destroy() end end) -objEfMalice:onSerialize(function(self, buffer) - buffer:write_instance(self.parent) - buffer:write_instance(self.target) -end) -objEfMalice:onDeserialize(function(self, buffer) - self.parent = buffer:read_instance() - self.target = buffer:read_instance() -end) \ No newline at end of file +-- networking +local serializer = function(inst, buffer) + buffer:write_instance(inst.parent) + buffer:write_instance(Instance.get_data(inst).target) +end + +local deserializer = function(inst, buffer) + inst.parent = buffer:read_instance() + Instance.get_data(inst).target = buffer:read_instance() +end + +Object.add_serializers(efMalice, serializer, deserializer) \ No newline at end of file diff --git a/Items/manOWar.lua b/Items/manOWar.lua index 7c012e7a..764cdaeb 100644 --- a/Items/manOWar.lua +++ b/Items/manOWar.lua @@ -1,19 +1,20 @@ -local item_sprite = Resources.sprite_load(NAMESPACE, "ManOWar", path.combine(PATH, "Sprites/Items/manOWar.png"), 1, 16, 18) +local item_sprite = Sprite.new("ManOWar", path.combine(PATH, "Sprites/Items/manOWar.png"), 1, 16, 18) -local EFFECT_COLOR = Color.from_hex(0xC6AAFF) - -local manOWar = Item.new(NAMESPACE, "manOWar") +local manOWar = Item.new("manOWar") manOWar:set_sprite(item_sprite) -manOWar:set_tier(Item.TIER.uncommon) -manOWar:set_loot_tags(Item.LOOT_TAG.category_damage) +manOWar:set_tier(ItemTier.UNCOMMON) +manOWar.loot_tags = Item.LootTag.CATEGORY_DAMAGE + +ItemLog.new_from_item(manOWar) -manOWar:clear_callbacks() -manOWar:onKillProc(function(killer, victim, stack) - local lightning = GM.instance_create(victim.x, victim.y, gm.constants.oChainLightning) +Callback.add(Callback.ON_KILL_PROC, function(victim, killer) + local stack = killer:item_count(manOWar) + if stack <= 0 then return end + + local lightning = Object.find("ChainLightning"):create(victim.x, victim.y) lightning.team = killer.team lightning.damage = killer.damage * (0.6 + 0.4 * stack) lightning.bounce = 2 - lightning.blend = EFFECT_COLOR - - gm.sound_play_at(gm.constants.wChainLightning, 1.1 + math.random() * 0.2, 0.5, victim.x, victim.y) + lightning.blend = Color.from_hex(0xC6AAFF) + lightning:sound_play(gm.constants.wChainLightning, 0.5, 1.1 + math.random() * 0.2) end) diff --git a/Items/moltenCoin.lua b/Items/moltenCoin.lua index 92ba8f27..f30418c2 100644 --- a/Items/moltenCoin.lua +++ b/Items/moltenCoin.lua @@ -1,55 +1,54 @@ -local item_sprite = Resources.sprite_load(NAMESPACE, "MoltenCoin", path.combine(PATH, "Sprites/Items/moltenCoin.png"), 1, 12, 16) -local coin_sprite = Resources.sprite_load(NAMESPACE, "EfGoldMoltenCoin", path.combine(PATH, "Sprites/Items/Effects/moltenCoin.png"), 6, 5, 5) -local sound = Resources.sfx_load(NAMESPACE, "MoltenCoin", path.combine(PATH, "Sounds/Items/moltenCoin.ogg")) +local item_sprite = Sprite.new("MoltenCoin", path.combine(PATH, "Sprites/Items/moltenCoin.png"), 1, 12, 16) +local coin_sprite = Sprite.new("EfGoldMoltenCoin", path.combine(PATH, "Sprites/Items/Effects/moltenCoin.png"), 6, 5, 5) +local sound = Sound.new("MoltenCoin", path.combine(PATH, "Sounds/Items/moltenCoin.ogg")) + +local moltenCoin = Item.new("moltenCoin") + +local packet = Packet.new("SyncMoltenCoin") + +local serializer = function(buffer, x, y, stack) + buffer:write_int(x) + buffer:write_int(y) + buffer:write_uint(stack) +end + +local deserializer = function(buffer, self) + local x = buffer:read_int() + local y = buffer:read_int() + local stack = buffer:read_uint() + + local inst = Object.find("EfGold"):create(x, y) + inst.hspeed = -4 + math.random() * 8 + inst.vspeed = -4 + math.random() * 8 + inst.sprite_index = coin_sprite + inst.value.value = stack +end + +packet:set_serializers(serializer, deserializer) -local moltenCoin = Item.new(NAMESPACE, "moltenCoin") +moltenCoin:set_sprite(item_sprite) +moltenCoin:set_tier(ItemTier.COMMON) +moltenCoin.loot_tags = Item.LootTag.CATEGORY_DAMAGE -local packetMoltenCoinProc = Packet.new() +ItemLog.new_from_item(moltenCoin) -moltenCoin:set_sprite(item_sprite) -moltenCoin:set_tier(Item.TIER.common) -moltenCoin:set_loot_tags(Item.LOOT_TAG.category_damage) - -moltenCoin:clear_callbacks() -moltenCoin:onHitProc(function(actor, victim, stack, hit_info) - if math.random() <= 0.06 or hit_info.attack_info:get_attack_flag(Attack_Info.ATTACK_FLAG.force_proc) then - local dot = gm.instance_create(victim.x, victim.y, gm.constants.oDot) - dot.target = victim.value -- unwrap the Instance - dot.parent = actor.value - dot.damage = hit_info.damage * 0.2 - dot.ticks = 2 + stack * 4 - dot.team = actor.team - dot.textColor = 4235519 - dot.sprite_index = gm.constants.sSparks9 +Callback.add(Callback.ON_HIT_PROC, function(actor, victim, hit_info) + local stack = actor:item_count(moltenCoin) + if stack <= 0 then return end + + if math.random() <= 0.06 or hit_info.attack_info:get_flag(AttackFlag.FORCE_PROC) then + victim:apply_dot((hit_info.damage / actor.damage) * 0.2, 2 + stack * 4, 30, actor, 4235519) victim:sound_play(sound, 1.0, 0.9 + math.random() * 0.2) - local g = gm.instance_create(victim.x, victim.y, gm.constants.oEfGold) - g.hspeed = -4 + math.random() * 8 - g.vspeed = -4 + math.random() * 8 - g.sprite_index = coin_sprite - g.value = stack - - if gm._mod_net_isOnline() then - local msg = packetMoltenCoinProc:message_begin() - msg:write_int(victim.x) - msg:write_int(victim.y) - msg:write_uint(stack) - msg:send_to_all() + local inst = Object.find("EfGold"):create(victim.x, victim.y) + inst.hspeed = -4 + math.random() * 8 + inst.vspeed = -4 + math.random() * 8 + inst.sprite_index = coin_sprite + inst.value.value = stack + + if Net.online and Net.host then + packet:send_to_all(victim.x, victim.y, stack) end end end) - -packetMoltenCoinProc:onReceived(function(msg) - local x = msg:read_int() - local y = msg:read_int() - local stack = msg:read_uint() - - gm.sound_play_at(sound, 1.0, 0.9 + math.random() * 0.2, x, y) - - local g = gm.instance_create(x, y, gm.constants.oEfGold) - g.hspeed = -4 + math.random() * 8 - g.vspeed = -4 + math.random() * 8 - g.sprite_index = coin_sprite - g.value = stack -end) diff --git a/Items/needles.lua b/Items/needles.lua index 3ae64969..b1c83003 100644 --- a/Items/needles.lua +++ b/Items/needles.lua @@ -1,44 +1,39 @@ -local sprite_item = Resources.sprite_load(NAMESPACE, "Needles", path.combine(PATH, "Sprites/Items/needles.png"), 1, 16, 15) -local sprite_buff = Resources.sprite_load(NAMESPACE, "BuffNeedles", path.combine(PATH, "Sprites/Buffs/needles.png"), 1, 9, 8) -local sound = Resources.sfx_load(NAMESPACE, "Needles", path.combine(PATH, "Sounds/Items/needles.ogg")) +local sprite_item = Sprite.new("Needles", path.combine(PATH, "Sprites/Items/needles.png"), 1, 16, 15) +local sprite_buff = Sprite.new("BuffNeedles", path.combine(PATH, "Sprites/Buffs/needles.png"), 1, 9, 8) +local sound = Sound.new("Needles", path.combine(PATH, "Sounds/Items/needles.ogg")) -local needles = Item.new(NAMESPACE, "needles") +local needles = Item.new("needles") needles:set_sprite(sprite_item) -needles:set_tier(Item.TIER.common) -needles:set_loot_tags(Item.LOOT_TAG.category_damage) +needles:set_tier(ItemTier.COMMON) +needles.loot_tags = Item.LootTag.CATEGORY_DAMAGE -local buffNeedles = Buff.new(NAMESPACE, "Needles") +ItemLog.new_from_item(needles) + +local buffNeedles = Buff.new("Needles") buffNeedles.icon_sprite = sprite_buff buffNeedles.is_debuff = true -buffNeedlesID = buffNeedles.value - -needles:clear_callbacks() -needles:onHitProc(function(actor, victim, stack, hit_info) - local force_proc = hit_info.attack_info:get_attack_flag(Attack_Info.ATTACK_FLAG.force_proc) - if (math.random() <= 0.01 + stack * 0.02 or force_proc) and victim:buff_stack_count(buffNeedles) == 0 then +Callback.add(Callback.ON_HIT_PROC, function(actor, victim, hit_info) + local stack = actor:item_count(needles) + if stack <= 0 then return end + + if (math.random() <= 0.01 + stack * 0.02 or hit_info.attack_info:get_flag(AttackFlag.FORCE_PROC)) and victim:buff_count(buffNeedles) == 0 then victim:buff_apply(buffNeedles, 190) end end) -buffNeedles:clear_callbacks() -buffNeedles:onApply(function(actor) +Callback.add(buffNeedles.on_apply, function(actor) actor:sound_play(sound, 1, 1) end) -- onAttackHit callbacks and such can't be used to increase the damage because by then the visual damage number and crit sound were already decided on. - --- signature: --- damager_calculate_damage(hit_info, true_hit, hit, damage, critical, parent, proc, attack_flags, damage_col, team, climb, percent_hp, xscale, hit_x, hit_y) -gm.pre_script_hook(gm.constants.damager_calculate_damage, function(self, other, result, args) - -- RValues, access underlying value using .value - local _hit = args[3] - local _damage = args[4] - local _critical = args[5] - - local needled = gm.has_buff(_hit.value or -4, buffNeedlesID) - if needled and not gm.bool(_critical.value) then - _critical.value = true - _damage.value = _damage.value * 2 +DamageCalculate.add(function(api) + if not Instance.exists(api.parent) then return end + + local count = api.hit:buff_count(buffNeedles) + if count <= 0 then return end + + if count > 0 then + api:set_critical(true) end end) diff --git a/Items/roulette.lua b/Items/roulette.lua index 5dcd21e2..f51108d7 100644 --- a/Items/roulette.lua +++ b/Items/roulette.lua @@ -1,23 +1,23 @@ -local item_sprite = Resources.sprite_load(NAMESPACE, "Roulette", path.combine(PATH, "Sprites/Items/roulette.png"), 1, 16, 11) -local wheel_sprite = Resources.sprite_load(NAMESPACE, "RouletteWheel", path.combine(PATH, "Sprites/Items/Effects/rouletteWheel.png"), 8, 40, 40) -local buffs_sprite = Resources.sprite_load(NAMESPACE, "RouletteBuffs", path.combine(PATH, "Sprites/Buffs/roulette.png"), 7, 8, 8) +local item_sprite = Sprite.new("Roulette", path.combine(PATH, "Sprites/Items/roulette.png"), 1, 16, 11) +local wheel_sprite = Sprite.new("RouletteWheel", path.combine(PATH, "Sprites/Items/Effects/rouletteWheel.png"), 8, 40, 40) +local buffs_sprite = Sprite.new("RouletteBuffs", path.combine(PATH, "Sprites/Buffs/roulette.png"), 7, 8, 8) +local sound = Sound.new("Roulette", path.combine(PATH, "Sounds/Items/roulette.ogg")) -local sound = Resources.sfx_load(NAMESPACE, "Roulette", path.combine(PATH, "Sounds/Items/roulette.ogg")) +local roulette = Item.new("roulette") -local roulette = Item.new(NAMESPACE, "roulette") roulette:set_sprite(item_sprite) -roulette:set_tier(Item.TIER.uncommon) -roulette:set_loot_tags( Item.LOOT_TAG.category_healing, - Item.LOOT_TAG.category_damage, - Item.LOOT_TAG.category_utility) - -local buffMaxHP = Buff.new(NAMESPACE, "rouletteMaxHP") -local buffRegen = Buff.new(NAMESPACE, "rouletteRegen") -local buffDamage = Buff.new(NAMESPACE, "rouletteDamage") -local buffAttackSpeed = Buff.new(NAMESPACE, "rouletteAttackSpeed") -local buffCritChance = Buff.new(NAMESPACE, "rouletteCritChance") -local buffMoveSpeed = Buff.new(NAMESPACE, "rouletteMoveSpeed") -local buffArmor = Buff.new(NAMESPACE, "rouletteArmor") +roulette:set_tier(ItemTier.UNCOMMON) +roulette.loot_tags = Item.LootTag.CATEGORY_HEALING + Item.LootTag.CATEGORY_DAMAGE + Item.LootTag.CATEGORY_UTILITY + +ItemLog.new_from_item(roulette) + +local buffMaxHP = Buff.new("rouletteMaxHP") +local buffRegen = Buff.new("rouletteRegen") +local buffDamage = Buff.new("rouletteDamage") +local buffAttackSpeed = Buff.new("rouletteAttackSpeed") +local buffCritChance = Buff.new("rouletteCritChance") +local buffMoveSpeed = Buff.new("rouletteMoveSpeed") +local buffArmor = Buff.new("rouletteArmor") local roulette_buffs = { buffMaxHP, @@ -29,10 +29,17 @@ local roulette_buffs = { buffArmor, } -local rouletteObject = Object.new(NAMESPACE, "RouletteWheel") +for i, buff in ipairs(roulette_buffs) do + buff.icon_sprite = buffs_sprite + buff.icon_subimage = i - 1 +end + +local rouletteObject = Object.new("RouletteWheel") +rouletteObject:set_sprite(wheel_sprite) +rouletteObject:set_depth(-1) local function roulette_roll(actor) - if gm._mod_net_isClient() then return end + if Net.client then return end local buff_index = math.random(#roulette_buffs) @@ -43,44 +50,13 @@ end local function roulette_clear_buffs(actor) for _, buff in ipairs(roulette_buffs) do - if actor:buff_stack_count(buff) > 0 then + if actor:buff_count(buff) > 0 then actor:buff_remove(buff) end end end -roulette:clear_callbacks() -roulette:onAcquire(function(actor, stack) - if stack == 1 then -- first stack gained - roulette_roll(actor) - end -end) -roulette:onRemove(function(actor, stack) - if stack == 1 then -- final stack lost - roulette_clear_buffs(actor) - end -end) - -Callback.add(Callback.TYPE.onMinute, "SSRouletteReroll", function(_, _) - if gm._mod_net_isClient() then return end - - local actors = Instance.find_all(gm.constants.pActor) - - for _, actor in ipairs(actors) do - if GM.actor_is_alive(actor) then - local stack = actor:item_stack_count(roulette) - if stack > 0 then - roulette_roll(actor) - end - end - end -end) - -rouletteObject.obj_sprite = wheel_sprite -rouletteObject.obj_depth = -1 - -rouletteObject:clear_callbacks() -rouletteObject:onCreate(function(self) +Callback.add(rouletteObject.on_create, function(self) self.image_index = 0 self.image_speed = 0 self.persistent = true @@ -93,7 +69,8 @@ rouletteObject:onCreate(function(self) self:sound_play(sound, 1, 1) self:instance_sync() end) -rouletteObject:onStep(function(self) + +Callback.add(rouletteObject.on_step, function(self) if not Instance.exists(self.parent) then self:destroy() return @@ -111,7 +88,7 @@ rouletteObject:onStep(function(self) self.image_index = self.buff_index self.vspeed = -0.1 - if gm._mod_net_isHost() and self.parent:item_stack_count(roulette) > 0 then + if Net.host and self.parent:item_count(roulette) > 0 then roulette_clear_buffs(self.parent) local new_buff = roulette_buffs[self.buff_index] @@ -120,6 +97,7 @@ rouletteObject:onStep(function(self) end end end + self.image_alpha = self.image_alpha - 0.01 if self.image_alpha <= 0 then @@ -127,64 +105,94 @@ rouletteObject:onStep(function(self) end end end) -rouletteObject:onSerialize(function(self, buffer) - buffer:write_instance(self.parent) - buffer:write_byte(self.buff_index) + +Callback.add(roulette.on_acquired, function(actor, stack) + if stack == 1 then -- first stack gained + roulette_roll(actor) + end end) -rouletteObject:onDeserialize(function(self, buffer) - self.parent = buffer:read_instance() - self.buff_index = buffer:read_byte() + +Callback.add(roulette.on_removed, function(actor, stack) + if stack == 1 then -- final stack lost + roulette_clear_buffs(actor) + end end) -for i, buff in ipairs(roulette_buffs) do - buff.icon_sprite = buffs_sprite - buff.icon_subimage = i-1 - buff:clear_callbacks() -end +Callback.add(Callback.ON_MINUTE, function(minute, second) + if Net.client then return end --- adjust hp value so the player doesn't have missing hp after the buff --- TODO: this should really be the toolkit's problem, not ours. -local maxhp_old -local hp_old -gm.pre_script_hook(gm.constants.recalculate_stats, function(self, other, result, args) - maxhp_old = self.maxhp - hp_old = self.hp + for _, actor in ipairs(roulette:get_holding_actors()) do + if Instance.exists(actor) then + roulette_roll(actor) + end + end end) -buffMaxHP:onStatRecalc(function(actor) - local stack = actor:item_stack_count(roulette) - actor.maxhp = actor.maxhp + 60 * (0.6 + 0.4 * stack) - - local hp_restore = hp_old - actor.hp - actor.hp = math.min(actor.maxhp, actor.hp + math.max(0, actor.maxhp - maxhp_old + hp_restore)) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffMaxHP) + if stack <= 0 then return end + + local item = actor:item_count(roulette) + api.maxhp_add(60 * (0.6 + 0.4 * item)) end) -buffRegen:onStatRecalc(function(actor) - local stack = actor:item_stack_count(roulette) - actor.hp_regen = actor.hp_regen + 0.06 * (0.6 + 0.4 * stack) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffRegen) + if stack <= 0 then return end + + local item = actor:item_count(roulette) + api.hp_regen_add(0.06 * (0.6 + 0.4 * item)) end) -buffDamage:onStatRecalc(function(actor) - local stack = actor:item_stack_count(roulette) - actor.damage = actor.damage + 14 * (0.6 + 0.4 * stack) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffDamage) + if stack <= 0 then return end + + local item = actor:item_count(roulette) + api.damage_add(14 * (0.6 + 0.4 * item)) end) -buffAttackSpeed:onStatRecalc(function(actor) - local stack = actor:item_stack_count(roulette) - actor.attack_speed = actor.attack_speed + 0.35 * (0.6 + 0.4 * stack) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffAttackSpeed) + if stack <= 0 then return end + + local item = actor:item_count(roulette) + api.attack_speed_add(0.35 * (0.6 + 0.4 * item)) end) -buffCritChance:onStatRecalc(function(actor) - local stack = actor:item_stack_count(roulette) - actor.critical_chance = actor.critical_chance + 25 * (0.6 + 0.4 * stack) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffCritChance) + if stack <= 0 then return end + + local item = actor:item_count(roulette) + api.critical_chance_add(25 * (0.6 + 0.4 * item)) end) -buffMoveSpeed:onStatRecalc(function(actor) - local stack = actor:item_stack_count(roulette) - actor.pHmax = actor.pHmax + 0.52 * (0.6 + 0.4 * stack) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffMoveSpeed) + if stack <= 0 then return end + + local item = actor:item_count(roulette) + api.pHmax_add(0.52 * (0.6 + 0.4 * item)) end) -buffArmor:onStatRecalc(function(actor) - local stack = actor:item_stack_count(roulette) - actor.armor = actor.armor + 35 * (0.6 + 0.4 * stack) +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffArmor) + if stack <= 0 then return end + + local item = actor:item_count(roulette) + api.armor_add(35 * (0.6 + 0.4 * item)) end) + +-- networking +local serializer = function(self, buffer) + buffer:write_instance(self.parent) + buffer:write_byte(self.buff_index) +end + +local deserializer = function(self, buffer) + self.parent = buffer:read_instance() + self.buff_index = buffer:read_byte() +end + +Object.add_serializers(rouletteObject, serializer, deserializer) diff --git a/Items/uraniumHorseshoe.lua b/Items/uraniumHorseshoe.lua index 79248ced..e5a0a9ab 100644 --- a/Items/uraniumHorseshoe.lua +++ b/Items/uraniumHorseshoe.lua @@ -5,52 +5,61 @@ -- hi it's kris i'm here too, i touched up this item a bit while refining things -- load the sprites for the item and the footstep effect -local sprite = Resources.sprite_load(NAMESPACE, "UraniumHorseshoe", path.combine(PATH, "Sprites/Items/uraniumHorseshoe.png"), 1, 14, 17) -local sprite_footstep = Resources.sprite_load(NAMESPACE, "UraniumHorseshoeFootstep", path.combine(PATH, "Sprites/Items/Effects/horseshoeFootstep.png"), 7, 3, 0) +local sprite = Sprite.new("UraniumHorseshoe", path.combine(PATH, "Sprites/Items/uraniumHorseshoe.png"), 1, 14, 17) +local sprite_footstep = Sprite.new("UraniumHorseshoeFootstep", path.combine(PATH, "Sprites/Items/Effects/horseshoeFootstep.png"), 7, 3, 0) -- create and setup the footstep particle -local parHorseshoe = Particle.new(NAMESPACE, "parHorshoe") +local parHorseshoe = Particle.new("parHorshoe") parHorseshoe:set_sprite(sprite_footstep, true, true, false) parHorseshoe:set_life(56, 56) -- 56 is used here because it is a multiple of 7, and 7 is how many frames are in the footstep animation -- create and setup the item itself -local horseshoe = Item.new(NAMESPACE, "uraniumHorseshoe") +local horseshoe = Item.new("uraniumHorseshoe") horseshoe:set_sprite(sprite) -horseshoe:set_tier(Item.TIER.common) -horseshoe:set_loot_tags(Item.LOOT_TAG.category_utility) +horseshoe:set_tier(ItemTier.COMMON) +horseshoe.loot_tags = Item.LootTag.CATEGORY_UTILITY + +ItemLog.new_from_item(horseshoe) -- setup the effects of the item -horseshoe:clear_callbacks() -horseshoe:onStatRecalc(function(actor, stack) - actor.pHmax = actor.pHmax + 0.28 * stack -- default player speed is almost always 2.8, meaning that increasing the speed by +0.28 were essentially increasing it by 10% - actor.pVmax = actor.pVmax + 0.6 * stack -- same as above except the default max jump height is 6 +RecalculateStats.add(function(actor, api) + local stack = actor:item_count(horseshoe) + if stack <= 0 then return end + + api.pHmax_add(0.28 * stack) -- default player speed is almost always 2.8, meaning that increasing the speed by +0.28 were essentially increasing it by 10% + api.pVmax_add(0.6 * stack) -- same as above except the default max jump height is 6 end) -- making the footstep particle actually appear in game -horseshoe:onPostStep(function(actor, stack) - local data = actor:get_data() -- get_data() can be used for storing any information about an instance (the player here). were gonna be using it in the code below - - -- the horseshoe timer will be nil when we start the game, and since trying to do math on a nil value is impossible we just detect if a value is nil and then set it to 1 instead - if not data.horseshoeTimer then - data.horseshoeTimer = 1 - end - - if actor.pHspeed ~= 0 and Helper.is_false(actor.free) then -- if the player is moving on the ground, we increase the timer by one on every frame - data.horseshoeTimer = data.horseshoeTimer + 1 - else -- if they arent, we reset it back to 1 - data.horseshoeTimer = 1 - end - - -- if the timer is a multiple of 10, we create a footstep particle - -- the operator % in lua will return the remainder of a division - -- this is also why we have been using 1 as our starting value instead of 0, cuz 0 divided by 10 will have a remainder of 0, meaning that this code will run on every tick while youre in the air or moving (which is bad cuz we dont want to do that) - if data.horseshoeTimer % 10 == 0 then - local orientation = 90 + 90 * actor.image_xscale -- actor.image_xscale is the direction the player is looking in, with 1 being right and -1 being left - local offset = 0 - if orientation == 180 then - offset = 1 -- compensate for 1-pixel offset when the particle is flipped +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(horseshoe:get_holding_actors()) do + if Instance.exists(actor) then + local stack = actor:item_count(horseshoe) + local data = Instance.get_data(actor) -- get_data() can be used for storing any information about an instance (the player here). were gonna be using it in the code below + + -- the horseshoe timer will be nil when we start the game, and since trying to do math on a nil value is impossible we just detect if a value is nil and then set it to 1 instead + if not data.horseshoeTimer then + data.horseshoeTimer = 1 + end + + if actor.pHspeed ~= 0 and actor:is_grounded() then -- if the player is moving on the ground, we increase the timer by one on every frame + data.horseshoeTimer = data.horseshoeTimer + 1 + else -- if they arent, we reset it back to 1 + data.horseshoeTimer = 1 + end + + -- if the timer is a multiple of 10, we create a footstep particle + -- the operator % in lua will return the remainder of a division + -- this is also why we have been using 1 as our starting value instead of 0, cuz 0 divided by 10 will have a remainder of 0, meaning that this code will run on every tick while youre in the air or moving (which is bad cuz we dont want to do that) + if data.horseshoeTimer % 10 == 0 then + local orientation = 90 + 90 * actor.image_xscale -- actor.image_xscale is the direction the player is looking in, with 1 being right and -1 being left + local offset = 0 + if orientation == 180 then + offset = 1 -- compensate for 1-pixel offset when the particle is flipped + end + parHorseshoe:set_orientation(orientation, orientation, 0, 0, false) + parHorseshoe:create(actor.x, actor.bbox_bottom + offset, 1) -- the bbox_bottom variable always holds the global y coordinate of the bottom of the actor's hitbox + end end - parHorseshoe:set_orientation(orientation, orientation, 0, 0, false) - parHorseshoe:create(actor.x, actor.bbox_bottom + offset, 1) -- the bbox_bottom variable always holds the global y coordinate of the bottom of the actor's hitbox end end) diff --git a/Items/watchMetronome.lua b/Items/watchMetronome.lua index 9bfd3677..ab4da1fe 100644 --- a/Items/watchMetronome.lua +++ b/Items/watchMetronome.lua @@ -1,128 +1,37 @@ -local sprite = Resources.sprite_load(NAMESPACE, "WatchMetronome", path.combine(PATH, "Sprites/Items/watchMetronome.png"), 1, 17, 15) -local bar_sprite = Resources.sprite_load(NAMESPACE, "MetronomeBar2", path.combine(PATH, "Sprites/Items/Effects/metronomeBar.png"), 1, 26, 5) ---local bar_sprite_big = Resources.sprite_load(NAMESPACE, "MetronomeBarBig", path.combine(PATH, "Sprites/Items/Effects/metronomeBarBig.png"), 1, 2, 8) +local sprite = Sprite.new("WatchMetronome", path.combine(PATH, "Sprites/Items/watchMetronome.png"), 1, 17, 15) +local bar_sprite = Sprite.new("MetronomeBar2", path.combine(PATH, "Sprites/Items/Effects/metronomeBar.png"), 1, 26, 5) -local sound_tick1 = Resources.sfx_load(NAMESPACE, "MetronomeTick1", path.combine(PATH, "Sounds/Items/watchTick1.ogg")) -local sound_tick2 = Resources.sfx_load(NAMESPACE, "MetronomeTick2", path.combine(PATH, "Sounds/Items/watchTick2.ogg")) +local sound_tick1 = Sound.new("MetronomeTick1", path.combine(PATH, "Sounds/Items/watchTick1.ogg")) +local sound_tick2 = Sound.new("MetronomeTick2", path.combine(PATH, "Sounds/Items/watchTick2.ogg")) local CHARGE_RATE = 1 / (60 * 3) local TICK_INTERVAL = 1 / 9 -local watchMetronome = Item.new(NAMESPACE, "watchMetronome") -watchMetronome:set_sprite(sprite) -watchMetronome:set_tier(Item.TIER.uncommon) -watchMetronome:set_loot_tags(Item.LOOT_TAG.category_utility, Item.LOOT_TAG.item_blacklist_engi_turrets) - -local buffChrono = Buff.new(NAMESPACE, "chrono") -buffChrono.show_icon = false -buffChrono.is_timed = false - -local objMetronomeBar = Object.new(NAMESPACE, "MetronomeBar") -objMetronomeBar.obj_depth = -400 - -watchMetronome:clear_callbacks() -watchMetronome:onAcquire(function(actor, stack) - local data = actor:get_data() - if not data.chrono_charge then - data.chrono_charge = 0 - data.chrono_tick = 0 - end -end) -watchMetronome:onRemove(function(actor, stack) - local data = actor:get_data() - if stack == 1 then - data.chrono_charge = nil - data.chrono_tick = nil - if data.chrono_bar:exists() then - data.chrono_bar:destroy() - end - - actor:buff_remove(buffChrono) - end -end) -watchMetronome:onPostStep(function(actor, stack) - local data = actor:get_data() - - if not Instance.exists(data.chrono_bar) then - data.chrono_bar = objMetronomeBar:create() - data.chrono_bar.parent = actor - end - - local charged = actor:buff_stack_count(buffChrono) > 0 - local motion_frac = math.abs(actor.pHspeed) / actor.pHmax - - if gm.actor_state_is_climb_state(actor.actor_state_current_id) then - motion_frac = 0 - - if gm.bool(actor.ropeUp) or gm.bool(actor.ropeDown) then - motion_frac = 1 - end - end - - if not charged then - motion_frac = 1 - motion_frac - if motion_frac > 0.01 then - data.chrono_charge = math.min(1, data.chrono_charge + motion_frac * CHARGE_RATE) - - if data.chrono_charge >= 0.666 and data.chrono_tick + TICK_INTERVAL < data.chrono_charge then - actor:sound_play(sound_tick1, 0.8, 1.0) - data.chrono_tick = data.chrono_charge - end - - if data.chrono_charge >= 1 then - actor:buff_apply(buffChrono, 60) - data.chrono_tick = 1 - end - end - else - if motion_frac > 0.01 then - local decay_reduction = 1 / (1 + (stack - 1) * 0.333) - - data.chrono_charge = math.max(0, data.chrono_charge - motion_frac * CHARGE_RATE * decay_reduction) - - if data.chrono_charge <= 0.333 * decay_reduction and data.chrono_tick - TICK_INTERVAL * decay_reduction > data.chrono_charge then - actor:sound_play(sound_tick2, 0.8, 1.0) - data.chrono_tick = data.chrono_charge - end - - if data.chrono_charge <= 0 then - actor:buff_remove(buffChrono) - data.chrono_tick = 0 - end - end - end -end) - local BAR_COLOR = Color.from_rgb(130, 157, 255) local BAR_COLOR_LIT = Color.from_rgb(178, 211, 255) -- account for survivor bars and such local class_offsets = { - [Survivor.find("ror", "drifter").value] = 19, - [Survivor.find("ror", "sniper").value] = 22, + [Survivor.find("drifter").value] = 19, + [Survivor.find("sniper").value] = 22, } -objMetronomeBar:clear_callbacks() -objMetronomeBar:onCreate(function(self) - self.parent = -4 - self.persistent = true -end) -objMetronomeBar:onStep(function(self) - if not GM.actor_is_alive(self.parent) then - self:destroy() - end -end) -objMetronomeBar:onDraw(function(self) - if not Instance.exists(self.parent) then return end - if not gm.bool(self.parent.visible) then return end +local buffChrono = Buff.new("chrono") +buffChrono.show_icon = false +buffChrono.is_timed = false - local actor = self.parent - local data = actor:get_data() +local watchMetronome = Item.new("watchMetronome") +watchMetronome:set_sprite(sprite) +watchMetronome:set_tier(ItemTier.UNCOMMON) +watchMetronome.loot_tags = Item.LootTag.CATEGORY_UTILITY + Item.LootTag.ITEM_BLACKLIST_ENGI_TURRETS - local x, y = math.floor(actor.ghost_x+0.5), math.floor(actor.ghost_y+0.5) +ItemLog.new_from_item(watchMetronome) - local x = x + 1 - local y = y + 19 +watchMetronome.effect_display = EffectDisplay.func(function(actor_unwrapped) + local actor = Instance.wrap(actor_unwrapped) + + local x = actor.x + 1 + local y = actor.y + 19 local offset = class_offsets[actor.class] if offset then @@ -135,41 +44,116 @@ objMetronomeBar:onDraw(function(self) local bar_top = y - 2 local bar_bottom = y + 2 - local fraction = data.chrono_charge or 0 - local charged = actor:buff_stack_count(buffChrono) > 0 + local fraction = Instance.get_data(actor).chrono_charge or 0 - if charged then + if actor:buff_count(buffChrono) > 0 then gm.draw_set_colour(BAR_COLOR_LIT) else gm.draw_set_colour(BAR_COLOR) end + gm.draw_rectangle(bar_left, bar_top, bar_left + bar_width * fraction, bar_bottom, false) + GM.draw_sprite(bar_sprite, 0, x, y) +end, EffectDisplay.DrawPriority.ABOVE) - gm.draw_sprite(bar_sprite, 0, x, y) +Callback.add(watchMetronome.on_acquired, function(actor, stack) + local data = Instance.get_data(actor) + + if not data.chrono_charge then + data.chrono_charge = 0 + data.chrono_tick = 0 + end end) -buffChrono:clear_callbacks() -buffChrono:onApply(function(actor, stack) +Callback.add(watchMetronome.on_removed, function(actor, stack) + local data = Instance.get_data(actor) + + if stack == 1 then + data.chrono_charge = nil + data.chrono_tick = nil + + actor:buff_remove(buffChrono) + end +end) + +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(watchMetronome:get_holding_actors()) do + if Instance.exists(actor) then + local stack = actor:item_count(watchMetronome) + local data = Instance.get_data(actor) + local motion_frac = math.abs(actor.pHspeed) / actor.pHmax + + if actor:is_climbing() then + motion_frac = 0 + + if (Util.bool(actor.ropeUp) or Util.bool(actor.ropeDown)) and actor.activity_type ~= 8 then + motion_frac = 1 + end + end + + if actor:buff_count(buffChrono) <= 0 then + motion_frac = 1 - motion_frac + + if motion_frac > 0.01 and (not Instance.get_data(actor).nemmerc_special_state or (Instance.get_data(actor).nemmerc_special_state and Instance.get_data(actor).nemmerc_special_state == 0)) then + data.chrono_charge = math.min(1, data.chrono_charge + motion_frac * CHARGE_RATE) + + if data.chrono_charge >= 0.666 and data.chrono_tick + TICK_INTERVAL < data.chrono_charge then + actor:sound_play(sound_tick1, 0.8, 1.0) + data.chrono_tick = data.chrono_charge + end + + if data.chrono_charge >= 1 then + actor:buff_apply(buffChrono, 60) + data.chrono_tick = 1 + end + end + else + if motion_frac > 0.01 and (not Instance.get_data(actor).nemmerc_special_state or (Instance.get_data(actor).nemmerc_special_state and Instance.get_data(actor).nemmerc_special_state == 0)) then + local decay_reduction = 1 / (1 + (stack - 1) * 0.333) + + data.chrono_charge = math.max(0, data.chrono_charge - motion_frac * CHARGE_RATE * decay_reduction) + + if data.chrono_charge <= 0.333 * decay_reduction and data.chrono_tick - TICK_INTERVAL * decay_reduction > data.chrono_charge then + actor:sound_play(sound_tick2, 0.8, 1.0) + data.chrono_tick = data.chrono_charge + end + + if data.chrono_charge <= 0 then + actor:buff_remove(buffChrono) + data.chrono_tick = 0 + end + end + end + end + end +end) + +Callback.add(buffChrono.on_apply, function(actor, stack) actor:sound_play(gm.constants.wChefShoot2_1, 0.8, 1.5) -- the charge value isn't synced, but the buff *is* -- so just incase the clients' charge desyncs, it gets corrected by this - actor:get_data().chrono_charge = 1 + Instance.get_data(actor).chrono_charge = 1 - local ef = GM.instance_create(0, 0, gm.constants.oEfFlash) - ef.parent = actor - ef.image_blend = BAR_COLOR_LIT - ef.rate = 0.04 + local flash = Object.find("EfFlash"):create(actor.x, actor.y) + flash.parent = actor + flash.image_blend = BAR_COLOR_LIT + flash.rate = 0.04 end) -buffChrono:onRemove(function(actor, stack) + +Callback.add(buffChrono.on_remove, function(actor, stack) actor:sound_play(gm.constants.wWispSpawn, 0.8, 1.5) - actor:get_data().chrono_charge = 0 + Instance.get_data(actor).chrono_charge = 0 - local ef = GM.instance_create(0, 0, gm.constants.oEfFlash) - ef.parent = actor - ef.image_blend = Color.WHITE - ef.rate = 0.08 + local flash = Object.find("EfFlash"):create(actor.x, actor.y) + flash.parent = actor + flash.image_blend = Color.WHITE + flash.rate = 0.08 end) -buffChrono:onStatRecalc(function(actor, stack) - actor.pHmax = actor.pHmax + 1.4 + +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buffChrono) + if stack <= 0 then return end + + api.pHmax_add(1.4) end) diff --git a/Items/wonderHerbs.lua b/Items/wonderHerbs.lua index 8d2ffb19..44da3340 100644 --- a/Items/wonderHerbs.lua +++ b/Items/wonderHerbs.lua @@ -1,49 +1,33 @@ -local sprite = Resources.sprite_load(NAMESPACE, "WonderHerbs", path.combine(PATH, "Sprites/Items/wonderHerbs.png"), 1, 16, 16) +local sprite = Sprite.new("WonderHerbs", path.combine(PATH, "Sprites/Items/wonderHerbs.png"), 1, 16, 16) -local wonderHerbs = Item.new(NAMESPACE, "wonderHerbs") +local wonderHerbs = Item.new("wonderHerbs") wonderHerbs:set_sprite(sprite) -wonderHerbs:set_tier(Item.TIER.common) -wonderHerbs:set_loot_tags(Item.LOOT_TAG.category_healing) +wonderHerbs:set_tier(ItemTier.COMMON) +wonderHerbs.loot_tags = Item.LootTag.CATEGORY_HEALING -local wonderHerbsID = wonderHerbs.value +ItemLog.new_from_item(wonderHerbs) local HEAL_COLOR = Color.from_rgb(143, 255, 38) --- doesn't use onHeal due to its implementation not sufficiently covering every source of healing - -local preserve_number -gm.pre_script_hook(gm.constants.actor_heal_raw, function(self, other, result, args) - --local actor = Instance.wrap(args[1].value) - --local stack = actor:item_stack_count(wonderHerbs) - - -- it's a smidge faster to just use these directly - local actor = args[1].value or -4 - local stack = gm.item_count(actor, wonderHerbsID) or 0 - - if stack > 0 then - local in_amount = args[2].value - local is_passive = args[3].value - preserve_number = in_amount - - local mult = 1 + stack * 0.12 - - local new_amount = in_amount * mult - if not is_passive then - -- non-regen healing will always be increased by atleast 1 - new_amount = math.max(new_amount, in_amount + 1) - - local diff = new_amount - in_amount - gm.draw_damage(actor.x, actor.bbox_top+2, diff, 0, HEAL_COLOR, 4, 0) - end - - args[2].value = new_amount - end +Callback.add(Callback.ON_HEAL, function(actor_unwrapped, amount) + local actor = Instance.wrap(actor_unwrapped) + if not Instance.exists(actor) then return end + + local stack = actor:item_count(wonderHerbs) + if stack <= 0 then return end + + -- non-regen healing will always be increased by atleast 1 + new_amount = math.max(amount.value * (1 + stack * 0.12), amount.value + 1) + + local diff = new_amount - amount.value + + if Net.client then return end -- healing amount cant be changed as client + amount.value = new_amount end) --- the argument is set back to its unmodified value to avoid weird inconsistencies where some cases end up modifying the vanilla healing number and others don't --- it's weird and annoying and stupid and i hate it, but it is what it is -gm.post_script_hook(gm.constants.actor_heal_raw, function(self, other, result, args) - if preserve_number then - args[2].value = preserve_number - preserve_number = nil - end + +RecalculateStats.add(Callback.Priority.AFTER, function(actor, api) + local stack = actor:item_count(wonderHerbs) + if stack <= 0 then return end + + api.hp_regen_mult(1 + stack * 0.12) end) diff --git a/Items/x4Stimulant.lua b/Items/x4Stimulant.lua index de7c3856..4c1f862a 100644 --- a/Items/x4Stimulant.lua +++ b/Items/x4Stimulant.lua @@ -1,37 +1,53 @@ -local sprite = Resources.sprite_load(NAMESPACE, "x4Stimulant", path.combine(PATH, "Sprites/Items/x4Stimulant.png"), 1, 16, 16) +local sprite = Sprite.new("x4Stimulant", path.combine(PATH, "Sprites/Items/x4Stimulant.png"), 1, 16, 16) local REGEN = 2 / 60 local REGEN_STACK = 1 / 60 -local x4Stimulant = Item.new(NAMESPACE, "x4Stimulant") +local x4Stimulant = Item.new("x4Stimulant") x4Stimulant:set_sprite(sprite) -x4Stimulant:set_tier(Item.TIER.common) -x4Stimulant:set_loot_tags(Item.LOOT_TAG.category_utility) +x4Stimulant:set_tier(ItemTier.COMMON) +x4Stimulant.loot_tags = Item.LootTag.CATEGORY_UTILITY -local x4Buff = Buff.new(NAMESPACE, "x4Stimulant") +ItemLog.new_from_item(x4Stimulant) + +local x4Buff = Buff.new("x4Stimulant") x4Buff.show_icon = false -local particleHeal = Particle.find("ror", "Heal") +local particleHeal = Particle.find("Heal") + +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(x4Buff) + if stack <= 0 then return end + + api.hp_regen_mult(REGEN + REGEN_STACK * (actor:item_count(x4Stimulant) - 1)) +end) -x4Stimulant:clear_callbacks() -x4Stimulant:onStatRecalc(function(actor, stack) - local secondary = actor:get_active_skill(Skill.SLOT.secondary) +RecalculateStats.add(function(actor) + local stack = actor:item_count(x4Stimulant) + if stack <= 0 then return end + + local secondary = actor:get_active_skill(Skill.Slot.SECONDARY) -- counts in frames. has to be rounded otherwise the cooldown stopwatch breaks ! local mult = 0.9 ^ stack secondary.cooldown = math.ceil(secondary.cooldown * mult) end) -x4Stimulant:onSecondaryUse(function(actor, stack) - actor:buff_apply(x4Buff, 60 * 3) -end) +Callback.add(Callback.ON_SKILL_ACTIVATE, function(actor, slot) + if slot ~= Skill.Slot.SECONDARY then return end -x4Buff:clear_callbacks() -x4Buff:onStatRecalc(function(actor, stack) - actor.hp_regen = actor.hp_regen + REGEN + REGEN_STACK * (actor:item_stack_count(x4Stimulant) - 1) + local stack = actor:item_count(x4Stimulant) + if stack <= 0 then return end + + actor:buff_apply(x4Buff, 60 * 3) end) -x4Buff:onPostStep(function(actor, stack) - if math.random() < 0.1 then - particleHeal:create(actor.x - 4 + math.random() * 8, actor.y - 6 + math.random() * 12, 1) + +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(x4Buff:get_holding_actors()) do + if Instance.exists(actor) then + if math.random() < 0.15 then + particleHeal:create(actor.x - 4 + math.random() * 8, actor.y - 6 + math.random() * 12, 1) + end + end end -end) +end) \ No newline at end of file diff --git a/Language/english.json b/Language/english.json deleted file mode 100644 index 85b75144..00000000 --- a/Language/english.json +++ /dev/null @@ -1,494 +0,0 @@ -{ - "difficulty" : { - "typhoon.name" : "Typhoon", - "typhoon.description" : "The maximum challenge.\nThe planet is a nightmare, survival is merely an illusion.\nNobody has what it takes." - }, - "survivor" : { - "executioner" : { - "name" : "Executioner", - "nameUpper" : "EXECUTIONER", - "description" : "The Executioner is a mobile fighter who specializes in counting heads. Using Ion projectors, the Executioner fabricates illusions which cause foes to run away in fear, while also projecting an axe to take out the strongest enemies. Make sure to chain kills with Ion Burst and Execution to keep the damage pouring.", - "endQuote" : "..and so he left, bloodlust unfulfilled.", - "story": "
    Passenger Details:\n[Military Class]\n\n
      Luggage and Equipment:\nOfficer boarded with EXN Acting Officer carbonweave uniform, civilian-grade plated oversuit and an issued service pistol. At military checkpoint, passenger's armament was noted to have an additional under-barrel \nmodification- and was cleared after an agreement was made to store it in a secure container. The passenger also cleared one ION 204X series battery customized with propulsion orifices. As a safety measure, passenger's \ntechnological goods were temporarily fitted with electrical auto-nullifiers and returned to them.\n\n[SECURITY RECALL]\n
        [Note E1a] Several reports of Passenger making entire security checkpoint crew uneasy. Light surveillance will be required until further notice.\n
          [Note E1b] Passenger would often reply in sounds rather than speech before opting to use Universal Sign Language.\n
            [Event E1] Contracted commando performs security check in Passenger's quarters.\n
              [Event E1a] Contracted commando requests 24 hour private surveillance in hall leading to Passenger's quarters.\n
                [Event E2] Passenger asked to move tables during mealtime after another Passenger sent a formal complaint to onboard security.", - "id" : "Travel ID: 4383354378334FF3D34D", - "departure" : "Departure:\nOld Alcatraz,\nRustborough,\nMars", - "arrival" : "Destination:\nStillwater Bay,\nNewdredge,\nEuropa" - }, - "mule" : { - "name" : "MULE", - "nameUpper" : "MULE", - "description" : "EVERY MULE UNIT IS ADEQUATELY EQUIPPED AND READY FOR DUTY. BUILT WITH HIGH QUALITY MATERIALS FOR MAXIMUM WORK PERFORMANCE. THIS MODEL INCLUDES THE FORAGER SET OF TOOLS, PERFECT FOR HUNTING, EXPLORATION AND OTHER DANGEROUS TASKS FOR THE COMMON USER. IN ORDER TO INITIALIZE PLEASE STATE A DIRECTIVE ON SYSTEM BOOT.", - "endQuote" : "..and so it left, pistons creaking, directive unstated.", - "story": "
                  Warrantee Expiration: \n8/16/2041 \n\nInputted Note: \n(ID: #1244C4F4F5D494) \nNOTICE: MULE - MODEL F \nI'd rather not get into hot water for “tampering with ship property”, but I recently booted up this MULE unit and took 'em for repairs. It was just lying there, so I took it that no one would really mind if I touched it up a little. Heard talks of personnel wanting to throw 'em away before takeoff, but I just couldn't let it happen to such fine craftmanship.. it's a sight for sore eyes, sure, but you've gotta admit, it's novelty! Multi-purpose units haven't been considered in a long time since the first quadruped model was a hit. Besides, we all know what these sorts of bots can get up to! With a chassis like this, I wouldn't doubt that this big guy's roughed some pretty gnarly terrain. It's a bit of a walking time capsule, wouldn't you say? \nAll needed footage attached below yadda yadda— Look, I'll level with ya. Just.. consider keepin' it around. Even for just a bit. Whether those rumors are true or not.. I think his owner would be happy, y'know.. knowing it's up 'n runnin' still. \nJust give 'em something to do. Give it one more chance. \n\n[MAINTENANCE REPORT] \n
                    [Report M1] On boot-up, MULE unit remained completely motionless until around 20 minutes later when given a verbal command to deploy its support drone. MULE unit demonstrated idle processing and protocols only after the command. \n
                      [Report M1a] Support drone's power bank was found to be degraded. No compatible replacements could be found, leaving it to only be recharged. Battery longevity not guaranteed. \n
                        [Report M2] MULE unit was to be familiarized with Contact Light passengers and staff via facial recognition after suddenly attempting to constrain a mechanic it failed to recognize. Most UES personnel are now logged in its memory. \n
                          [Report M2a] Remotely accessing MULE unit's memory reveals previous entries in its database dating back to 2036. Initial entries in its memory were found to be corrupted or expunged." - }, - "nemesisCommando" : { - "name" : "Nemesis Commando", - "nameUpper" : "NEMESIS COMMANDO", - "description" : "The Nemesis Commando is a flexible character with the firepower to deal with any situation. Wound enemies using Blade of Cessation to setup for devastating damage from your other abilities. Use Tactical Roll to weave in and out of melee range, and turn even the most dangerous situations in your favour.", - "endQuote": "..and so he left, carrying new sense of humanity within.", - "story": "UNREGISTERED FIELD DATA LOG: DATA LAST MODIFIED ON [##090??!?] \n[ERROR 0x00000ce: Data Package Corrupted, viewing a restore point.] \n\n
                            [Entry 1] - Patrol group just picked up a wounded straggler a few miles off from camp. Word going around is he “showed up out of nowhere,” (as if I'm buying that), what with nothing in the surrounding area to trace his origin. Regardless of the validity of their claims, a survivor's a survivor. As I'm writing this, we're carrying him back. He's certainly not making it easy on us, though, considering the sheer heft of his gear- he's lugging some unorthodox modifications to a standard Commando armor rig. It looks to be- or maybe was originally- the initial issue of the Mk. 1 model (even though I'm pretty sure they stopped producing those years ago?). \n\n
                              [Entry 2] - Update- buddy's got some scarily heavy artillery on hand for a Commando unit- a rocket launcher still warm to the touch and some almost certainly alien grenades- I sure as hell haven't seen anything like them. He’s out cold right now, and from what we can tell looking through his helmet he's well-battered. His left arm seems especially stiff- his hand's clamped shut around the handle of his vibroblade. Some of us tried to get a good look at it just now, but it's almost like his fist is fused to the damn thing. Amongst other things on his person, it's thoroughly coated in this crusted black.. substance. We don't know if it was the result of a fight between a few more survivors, or evidence for some kind of self defe??!0049### \n[End of legible data.] \n\n
                                [Entry 2a] - I dont know how much time I have left as im typing this it woke up and camp is DESTROYED people wont stop bleeding we dont even know if it was human with what it can do noone knows what to do now it went straight for Adrian \nwe dont even know what set it off??? what did WE do to deserve all of this??? why did that THING attack U!??!?##$0959090302???! \n[End of legible data.] \n\n

                                Th?e ga?dflies swarm thi?s nob?le beas?t as it gra?zes.\n\nYour plag??ue upon it?s surfa?ce wi?ll be undo?ne by the?se toiling hand?s.", - }, - "nemesisMercenary" : { - "name" : "Nemesis Mercenary", - "nameUpper" : "NEMESIS MERCENARY", - "description" : "The Nemesis Mercenary was born with a special power.", - "endQuote": "..and so he left, loaded up for more.", - "story": "

                                I DON'T NEED NO FRIENDS\nI DON'T NEED NO PHONE\nJUST A BAG OF SEVERED HEADS\nAND MY NUCLEAR THRONE" - }, - "technician": { - "name": "Technician", - "nameUpper": "TECHNICIAN", - "description": "The Technician is excellent at setting up and maintaining zones of enemy denial. Forced Shutdown can forcibly lock down an area when upgraded. Upgrading gadgets improves their effectiveness, but be careful when doing so in dangerous situations!", - "endQuote": "..and so he left, beyond his own repair.", - "story": "

                                  Passenger Details:\n[Employee Class]\n\n
                                    Employee Details:\nEmployee contracted to work on-site for the full duration of the scheduled voyage of the UES Contact Light, to perform maintenance on machinery should technical issues arise aboard the ship. Employee qualified with 6 years of hands-on experience in software and hardware engineering.\n\n
                                      Luggage & Equipment:\nEmployee boarded with a Durarend welding spacesuit rated with strong vacuum resistance, and additionally leather and cloth attire made for planetary wear. Employee notified that clothes may tear in the case of a vacuum event. Security flagged the sizable wrench they held for appearing impractical for use while potentially dangerous. Upon questioning, the employee indicated that it was \"custom-built ... for the toughest jobs.\" Following procedure, the wrench was moved to an automated lockbox in loading bay 1a. Remaining inspection of carry-ons revealed several standard-issue wrenches, 3 color-coded USBs, a 2047 model GeForm Laptop, tool boxes, and a welding tool. USBs were inspected on company-owned QPuting Disposable Computer installed with ReoVirus Detector v1.6.2. No traces of malware were detected and stored files are UPG format containing blueprints of various devices. Employee permitted to pass with no further difficulties.\n\n[SECURITY RECALL]\n
                                        [Event T1] Employee forcibly removed from cabin during liftoff preparations when refusing to leave after conversating with ship pilot for multiple hours.\n
                                          [Event T2] Employee observed stealing sodas from a vending machine.\n
                                            [Event T3] Automated lockdown originating in loading bay 4a resolved by employee.\n
                                              [Event T4] Escape pod B-08 status shifted from ERROR to ALERT after the employee was requested by ship personnel to troubleshoot. Security personnel instructed to investigate.\n
                                                [Event T1a] Employee additionally reported to have tampered with cabin control panel to make it \"easier to cruise.\" Scheduled launch delayed and employee instructed to revert any modifications made.", - "id" : "Travel ID: 54E4F4C434F59474E454", - "departure" : "Departure:\nUES Shipping Dock 5,\nRedview,\nMars", - "arrival" : "Destination:\nUES Shipping Dock 0,\nRedview,\nMars" - }, - "swapNemesis": { - "name": "Nemesis Survivors", - "nameUpper": "NEMESIS SURVIVORS", - "description": "Swap to the familiar yet twisted Nemesis survivors." - }, - "swapNormal": { - "name": "Normal Survivors", - "nameUpper": "NORMAL SURVIVORS", - "description": "Swap to the regular set of survivors." - } - }, - "skill" : { - "executionerZ": { - "name": "Service Pistol", - "description": "Shoot for 100% damage." - }, - "executionerX": { - "name": "Ion Burst", - "description": "Rapidly shoot ionized bullets for 320% damage each.\nGain bullets by slaying enemies." - }, - "executionerC": { - "name": "Crowd Dispersion", - "description": "Dash forward, fearing nearby enemies.\nYou cannot be hit while dashing." - }, - "executionerV": { - "name": "Execution", - "description": "Launch into the air, and slam down with a projected axe for 1000% damage.\nEach successful execution reduces skill cooldowns by one second." - }, - "executionerVBoosted": { - "name": "Crowd Execution", - "description": "Launch into the air, and slam down with a projected axe for 1500% damage, fearing enemies.\nEach successful execution reduces skill cooldowns by one second." - }, - "executionerV2": { - "name": "Skullsplitter", - "description": "Throw a projected axe in a circular arc for 1500% damage per second. Critically strikes against feared enemies.\nEach successful execution reduces skill cooldowns by one second." - }, - "executionerV2Boosted": { - "name": "Skullbuster", - "description": "Throw two projected axes in a circular arc for 1500% damage per second. Critically strikes against feared enemies.\nEach successful execution reduces skill cooldowns by one second." - }, - "muleZ": { - "name": "INTERFERENCE REMOVAL", - "description": "PUNCH ENEMIES FOR 200% DAMAGE. HOLD TO CHARGE AND SMASH THE GROUND FOR 1400% DAMAGE." - }, - "muleX": { - "name": "IMMOBILIZE", - "description": "FIRE A TRAP FOR 125% DAMAGE. THE TRAP SNARES UP TO 5 ENEMIES IN THE TARGETS' PROXIMITY." - }, - "muleC": { - "name": "TORQUE CALIBRATION", - "description": "SPIN FORWARD FOR 4X100% DAMAGE. THE LAST HIT STUNS." - }, - "muleV": { - "name": "FAIL-SAFE ASSISTANCE", - "description": "DEPLOY A PERSONAL REPAIR DRONE FOR 8 SECONDS. THE DRONE HEALS 5X7% OF YOUR TOTAL HEALTH. AT FULL HEALTH, GRANTS BARRIER INSTEAD." - }, - "muleVBoosted": { - "name": "FAIL-SAFE ASSISTANCE 2.0", - "description": "LAUNCH A HEALING DRONE LASTING 8 SECONDS. THE DRONE HEALS 5X10% OF YOUR TOTAL HEALTH. AT FULL HEALTH, GRANTS BARRIER INSTEAD." - }, - "nemesisCommandoZ": { - "name": "Blade of Cessation", - "description": "Cut through nearby enemies for 120% damage, wounding them for 6 seconds. Cannot be stacked.\nWounded enemies take an extra 50% damage from all sources." - }, - "nemesisCommandoX": { - "name": "Single Tap", - "description": "Shoot an enemy for 200% damage.\nHold up to 4. Reload 2 charges when rolling." - }, - "nemesisCommandoX2": { - "name": "Distant Gash", - "description": "Slash in a line forward for 90% damage, wounding enemies for 6 seconds." - }, - "nemesisCommandoC": { - "name": "Tactical Roll", - "description": "Quickly roll forward a short distance.\nYou cannot be hit while rolling. Hold up to 2." - }, - "nemesisCommandoV": { - "name": "Flush Out", - "description": "Throw a grenade for 700% damage. Hold to cook the grenade.\nPress the down direction to toss at a low arc instead." - }, - "nemesisCommandoVBoosted": { - "name": "Cluster Bomb", - "description": "Throw a grenade for 700% damage, stunning enemies and spitting into 3 grenades for 3x150% damage.\nHold to cook the grenade. Press the down direction to toss at a low arc instead." - }, - "nemesisCommandoV2": { - "name": "Devastator", - "description": "Fire a rocket for 1000% damage to a single enemy. Surrounding enemies take 50% damage and are stunned.\nIf used airborne, fire downward at an angle." - }, - "nemesisCommandoV2Boosted": { - "name": "Spanker", - "description": "Fire a rocket for 1000% damage to a single enemy. Surrounding enemies take 50% damage and are scattered.\nIf used airborne, fire downward at an angle.\nHold up to 2." - }, - "nemesisMercenaryZ": { - "name": "Lascerate", - "description": "Injure enemies with your shotgun's attachment for 130% damage." - }, - "nemesisMercenaryX": { - "name": "Quick Trigger", - "description": "Fire your shotgun, stunning and hitting enemies nearby for 600% damage." - }, - "nemesisMercenaryC": { - "name": "Blinding Slide", - "description": "Quickly slide forwards. Getting hit at the start reloads your shotgun. \nYou can attack while sliding." - }, - "nemesisMercenaryV": { - "name": "Devitalize", - "description": "Target the weakest enemy in front of you, attacking them for 850% damage, dealing +50% TOTAL damage to stunned enemies. \nYou cannot be hit for the duration." - }, - "nemesisMercenaryVBoosted": { - "name": "Absolute Devitalization", - "description": "Target the weakest enemy in front of you, attacking them for 1100% damage, dealing +50% TOTAL damage to stunned enemies. \nYou cannot be hit for the duration." - }, - "technicianZ": { - "name": "Fine Tune", - "description": "Strike forward with a wrench for 180% damage.\nHitting gadgets 3 times temporarily upgrades them for 30 seconds." - }, - "technicianZ2": { - "name": "Troubleshoot", - "description": "Throw a wrench forward, piercing up to 3 enemies for 180% damage.\nHitting gadgets 3 times temporarily upgrades them for 20 seconds." - }, - "technicianX": { - "name": "Forced Shutdown", - "description": "Toss out a bomb dealing 400% damage on reactivation.\nStuns and passively pulls in enemies when upgraded." - }, - "technicianXD": { - "name": "The Red Button", - "description": "Detonate the dropped bomb for 500% damage.\nStuns enemies when upgraded." - }, - "technicianX2": { - "name": "", - "description": "" - }, - "technicianC": { - "name": "24/7 Energy", - "description": "Deploy a vending machine that gives a movement and attack speed bonus.\nAdditionally increases critical strike chance when upgraded." - }, - "technicianC2": { - "name": "Radial Amplifier", - "description": "Deploy an antenna that deals 15% additional damage to attacked enemies.\nHas 100% critical strike chance when upgraded." - }, - "technicianV": { - "name": "Backup Firewall", - "description": "Place a turret firing forward for 220% damage per second.\nFires rapidly for 350% damage per second when upgraded." - }, - "technicianVBoosted": { - "name": "Backup Firewall 2.0", - "description": "Place a turret firing rapidly for 350% damage per second.\nFires missiles for 4x100% damage when upgraded." - }, - "peace": { - "name": "<#a4eae4>Peace!", - "description": "Cannot use this skill." - } - }, - - "item" : { - "armedBackpack" : { - "name" : "Armed Backpack", - "pickup": "Chance of firing a bullet behind you.", - "description": "18.5% (+6.5% per stack) chance on attack to fire a bullet behind you for 150% damage.", - "destination": "832B,\nHautenuit,\nEarth", - "date": "11/7/2056", - "story": "Being secure is always important. I don't want you to get in trouble, so please wear this whenever you go out. 'Specially in the canyons, there's a lot of thieves there!\nI'll send you some extra ammunition later this year, alright?", - "priority": "Standard" - }, - "brassKnuckles" : { - "name": "Brass Knuckles", - "pickup" : "Deal extra damage at close range.", - "description": "Deal +35% damage to enemies within 2.8m (+1.4 per stack).", - "destination": "UESPD,\nE42,\nUES Halberd B2", - "date": "03/01/2056", - "story" : "We found this at the crime scene. We need to check for DNA against the rest of the evidence. I still cant believe they got away.\nThe back has some kind of symbol, maybe that could give us a clue. I'll be heading to the west of [REDACTED], I know someone who can help us solve the case.", - "priority": "Standard/Biological" - }, - "detritiveTrematode" : { - "name": "Detritive Trematode", - "pickup" : "Low health enemies receive damage over time.", - "description": "Enemies below 15% (+5% per stack) health become permanently infected, receiving 100% damage per second.", - "destination": "112,\nNeaus 2,\nMars", - "date": "03/11/2057", - "story" : "Eh, this is yours.\nDon't send one of those weird microscopic egg hatcheries EVER again, please.", - "priority" : "Priority/Biological" - }, - "dormantFungus" : { - "name" : "Dormant Fungus", - "pickup" : "Regenerate health while moving.", - "description": "Regenerates 2% (+2% per stack) of your health every two seconds while moving.", - "destination": "Gate 1,\nModule 13,\nGea Station", - "date": "11/01/2056", - "story" : "When you commanded me to explore the fungal caves all I expected to find was.. well, fungi, of course. Though I wasn't excited by any means, this specimen caught my eye.\nIt seems to only grow around flowing water, and ever since I collected and contained this sample, both the colouration and the smell faded out quickly.\n\nI hope you can study it further.", - "priority": "Standard" - }, - "fork" : { - "name" : "Fork", - "pickup" : "Deal more damage.", - "description": "Increases base damage by 3 (+3 per stack).", - "destination": "Antima Building - Rooftop,\nBlossom Hills,\nEarth", - "date": "09/24/2056", - "story" : "Surprise! A fork! Hahaha!!!\nYou said any gift was a good gift, HERE YOU GO! Jokes aside, this fork is magical. Trust me Duncan, I once got to eat a salad with it and later that same day I found a cent!\nAlso uh please come play with us someday soon, we miss destroying your build in SS2 (please tell me you changed your main, lol).", - "priority": "Standard" - }, - "watchMetronome" : { - "name" : "Watch Metronome", - "pickup" : "Charge the watch by standing still. Move faster at full charge.", - "description": "Standing still charges the watch over 3 seconds. At full charge, increase movement speed by 50%, consuming charge over 3 (+1 per stack) seconds.", - "destination": "Cage 1,\nG-44 (Underground),\nEarth", - "date": "09/05/2056", - "story" : "Grandpa gave me this as a relic of the past. He says it belonged to our great-great-great-great-great grandfather. I couldn't care less about antiques and family belongings so I think it's best if you have it instead.\nNot like it's of any use anyways..", - "priority": "Priority" - }, - "wonderHerbs" : { - "name" : "Wonder Herbs", - "pickup" : "Slightly increase all healing effects.", - "description": "Increases healing from all sources by 12% (+12% per stack).", - "destination": "2342,\nPlot 3,\nEarth", - "date": "11/07/2056", - "story" : "Rikka! I heard you're working with the EC on making a new recipe. Here at the farm we've been growing some special herbs which we think may come handy. They're known for being very, very useful for treating physical and emotional wounds.\nIf you need some more don't hesitate to call me! XOXO\n- Eden", - "priority": "Standard" - }, - "coffeeBag" : { - "name" : "Coffee Bag", - "pickup" : "Activating an interactable boosts movement and attack speed.", - "description": "Activating an interactable increases movement and attack speed by 22% for 10 (+5 per stack) seconds.", - "destination": "n1,\nThe Capital,\nUES PRIME", - "date": "05/22/2056", - "story" : "Sleeeeeeeeeeepyheaaaaaaaaaaaaaaad idk why u say u dont like coffee its so gooooood for staying up. ik ik theres other other ways to get hyped but oh boy i think u will dig this one, its my favorite!!! give it a try before yelling at me mmkay?", - "priority": "Standard" - }, - "x4Stimulant" : { - "name" : "X4 Stimulant", - "pickup" : "Decrease secondary skill cooldown, and heal slightly when using it.", - "description": "Decrease your secondary skill cooldown by 10% (+10% per stack). Using your secondary skill increases health regeneration by 2 (+1 per stack) hp per second for 3 seconds.", - "destination": "Romeo 33,\nBatang Ai National Park,\nEarth", - "date": "4/12/2057", - "story" : "Encased, including manual. Does the machine show symptoms of a 540? If so you may want to avoid using above 100ml of stimulant.\n\nCheers." - }, - "moltenCoin" : { - "name" : "Molten Coin", - "pickup" : "Chance to ignite enemies on hit and gain gold", - "description": "6% chance on hit to incinerate enemies for 6 seconds, dropping $1 (+$1 per stack) gold.", - "destination": "Toera 2,\nB44,\nMother Station", - "date": "05/22/2056", - "story" : "Hey! Uh, I'm sorry, I'm really sorry.. I know you really wanted me to keep this coin but I can't take the responsibility any more..\nSee, I accidentally put the coin at the edge of a plasma furnace so.. well.. it's a bit burnt on the side, please don't get mad at me..", - "priority": "Standard" - }, - "crypticSource" : { - "name": "Cryptic Source", - "pickup" : "Changing direction creates bursts of energy.", - "description": "Changing movement direction fires chain lightning for 70% (+55% per stack) damage on up to 2 targets.", - "destination" : "O32,\nLow End,\nEarth", - "date" : "03/30/2058", - "story" : "From atoms to sentient beings, everything comes from energy. However, this rather cryptic object seems to emanate energy on its own. High amounts of friction seem to trigger a chain reaction which makes it highly unstable. However, it may also be a manipulable source of (infinite?) energy. Whether this will lead us to the utopian future we crave for is completely uncertain, but this completely changes our previous thoughts about the universe.", - "priority" : "Volatile" - }, - "huntersSigil" : { - "name": "Hunter's Sigil", - "pickup" : "Standing still creates a zone that increases armor and critical strike chance.", - "description": "After standing still for 1 second, create a small zone that buffs you and allies for 15 (+10 per stack) armor and 25% (+20% per stack) critical chance.", - "destination" : "2East,\nBeckham Building,\nEarth", - "date" : "02/02/2056", - "story" : "Hey Sett, welcome to the club. We've been looking for candidates and now that you're with us, we can begin working next season. We're counting on you.\n\n-Irix out", - "priority": "Priority" - }, - "roulette" : { - "name": "Roulette", - "pickup" : "Roll a random buff every minute.", - "description": "Gain a random buff, which gets rerolled every minute. Possible buffs:\n\n: Increase max health by 60 (+24 per stack).\n: Increase health regeneration by 3.6 (+1.8 per stack) hp per second.\n: Increase base damage by 14 (+5 per stack).\n: Increase attack speed by 35% (+15.5% per stack).\n: Increase critical chance by 25% (+10% per stack).\n: Increase movement speed by 20% (+7% per stack).\n: Increase armor by 35 (+14 per stack).", - "destination" : "PRoom 3.1,\nSecva Casino,\nEarth", - "date" : "7/17/2057", - "story" : "Replacement Roulette model 144Bella-1 in follow-up to the recent events that unfolded in the casino. For further inquiries, please contact us at the E-direction given by our representatives.", - "priority": "Priority" - }, - "judderingEgg" : { - "name": "Juddering Egg", - "pickup" : "Gain a little friend!", - "description": "Hatch 1 (+1 per stack) young wurm who fights with you, periodically attacking surrounding enemies for 18x80% damage.", - "destination" : "Gate B,\nLee Compound,\nEarth", - "date" : "11/21/2056", - "story" : "Another one from the breeder, this time it's a very strange specimen.\nBe extremely cautious, I've heard juveniles can become highly aggressive (and lethal!) in a matter of seconds if you don't handle them correctly. Shouldn't be too long before it hatches, so you better have the structure ready for it.", - "priority": "Fragile" - }, - "uraniumHorseshoe" : { - "name": "Uranium Horseshoe", - "pickup" : "Increase movement speed and jump height.", - "description": "Increases movement speed and jump height by 10% (+10% per stack)", - "destination" : "6900,\nMartian's Face Horse Care and Farm Supplies,\nMars", - "date" : "04/20/2056", - "story" : "We found this weird green horse shoe on the hoof of one of our prized show horses after it mysteriously died late one night.\nOur horse doctor said you guys might know something about this strange horseshoe. Just holding it makes my hand burn. Handle it with care.", - "priority": "Standard" - }, - "blastKnuckles" : { - "name": "Blast Knuckles", - "pickup" : "Your attacks explode at close range!", - "description": "Hitting an enemy within 4.5m also blasts behind them for 220% (+220% per stack) damage. Hold up to 5 charges. Gain a charge every 3 seconds.", - "destination" : "todo", - "date" : "todo", - "story" : "todo", - "priority": "Priority" - }, - "malice" : { - "name": "Malice", - "pickup" : "Damage spreads to nearby enemies.", - "description": "Attacks

                                                spread to nearby enemies for 45% TOTAL damage on up to 1 (+1 per stack) target(s) within 4.2m (+1m per stack).", - "destination" : "P24,\nRomeo,\nPol-A Station", - "date" : "02/11/2056", - "story" : "Contain at all costs. This specimen can be a massive threat if not handled correctly. You don't know how much we lost already in order to keep this at bay.", - "priority": "

                                                Malicious" - }, - "manOWar" : { - "name": "Man-o'-war", - "pickup" : "Create an electric discharge on kill.", - "description": "Killing an enemy fires chain lightning for 100% (+40% per stack) damage on up to 2 targets.", - "destination" : "Hidden Cubby,\nMt. Creation,\nVenus", - "date" : "11/20/2056", - "story" : "Guys, remember the tiny guy I sent over some time ago? Well.. this one is a bit more aggressive.. although she is a funny little gal! She loves spinnin' around and watching me from inside the bottle. Man, if I hadn't rescued her, she would be in some alien stomach right now.\nI kinda want to name her Ann, thoughts?", - "priority": "Priority" - }, - "guardingAmulet" : { - "name": "Guarding Amulet", - "pickup" : "Reduce damage from behind you.", - "description": "Damage taken from behind you is reduced by 40% (+40% per stack).", - "destination" : "C8,\nAzure Garden,\nSanctuary", - "date" : "03/11/2056", - "story" : "I know he's somewhere out there, I know you can find him.. but you must be persistent. By the time my soul leaves this shell, the words will be spoken and you will take my place. He will be the one to show you the path to the answers you always wanted from me.\nEmbrace this amulet; you will depend on it, and it will keep you from ever looking back.", - "priority": "Standard" - }, - "distinctiveStick" : { - "name": "Distinctive Stick", - "pickup" : "Heal near the teleporter.", - "description": "Grows a tree near Teleporters, healing allies within 10m (+3.75m per stack) for 2.2% of their health every 2 seconds.", - "destination" : "Dreq Mineli,\nTesaft,\nEarth", - "date" : "01/15/2056", - "story" : "Darling, I have something special for you..\nThe other day on my mission, I came across this. It caught my eye because it's unlike any of the branches I've seen in my life! This branch excudes a peculiar energy. It made me feel connected to nature in a way I thought was only possible in stories! In honor of our anniversary, I'm giving it to you.\nI'll arrive home soon with an even greater present. Please, don't lose faith.", - "priority": "Standard" - }, - "balloon" : { - "name": "Balloon", - "pickup" : "Lower gravity outside of danger.", - "description": "Decrease gravity while holding the jump button by 35% (-15% per stack). Pops when damaged. Recovers when outside of danger for 7 seconds.", - "destination" : "55 Mo.,\nPaare Plaza,\nEarth", - "date" : "06/31/2056", - "story" : "When I was younger I used to love balloons. They're quite fun to watch in places with an atmosphere. However, this one is of special value to me, as it was of the first ones to be made out of reinforced rubber, back in the '22s. I hope you take care of it, it might cost a fortune in a few more years (if it's still floating, of course!).", - "priority": "Priority/Fragile" - }, - "iceTool" : { - "name": "Ice Tool", - "pickup" : "Gain a wall jump. Climb faster.", - "description": "Gain 1 (+1 per stack) extra jump while in contact with a wall. Increase climbing speed by 18% (+18% per stack).", - "destination" : "Mon's Tower #33,\nSolei Shore,\nEarth", - "date" : "03/12/2056", - "story" : "Good day fellow hiking friend, I found the ice tool you lost last time we went to Mt. [REDACTED]. Please keep an eye out for the snow next time!\nI wonder if the ice tool is still usable though, it's been through so much!", - "priority": "Standard" - }, - "needles" : { - "name": "Needles", - "pickup" : "Chance to mark enemies for guaranteed critical strikes.", - "description": "3% (+2% per stack) chance on hit to mark enemies for 100% critical chance against them for 3 seconds.", - "destination" : "E2,\nOren's Loop,\nVenus", - "date" : "[REDACTED]", - "story" : "Uh.. madre dice que si puedes leer esto, es porque no eres tan torpe como pensaba, y pues eso. Esperamos que te sirvan estas agujas. Me temo que no son las que pediste, pero no hace mucha diferencia, pienso.\nNo recuerdo si los enviamos bien embalados. Ten cuidado.", - "priority": "Piercing" - }, - - "midas" : { - "name" : "M.I.D.A.S", - "pickup" : "Convert half of your health into gold.", - "description" : "Lose 50% health, gaining $1 gold per health spent, plus the cost of a chest.", - "destination" : "R A 5-5,\nPrime,\nTitan", - "date" : "05/04/2056", - "story" : "\"A miracle from the gods..\"\nYou keep saying it over and over again. Are you out of your mind? You keep treating us like we're ignorant when all we tried to do was to SAVE YOU.\n\nDo us a favour and keep it. Do whatever you want with it, we do NOT want to see you around here anymore. We do NOT want to be part of whatever is going on with you and this thing.\n\nIt's making you a greedy, souless person and I hope you know it.", - "priority": "Volatile" - }, - "strangeCan" : { - "name": "Strange Can", - "pickup" : "Throw a can to mark an enemy. Killing it releases a toxic cloud.", - "description": "Throw out a peculiar can that intoxicates the enemy with the highest maximum health. Intoxicated enemies release a toxic cloud on death for 50% of the enemy's maximum health as damage over 5 seconds.", - "destination" : "1530,\n563,\nA-LC12", - "date" : "05/22/2056", - "story" : "These are as delicious as I told you. I just hope it doesn't crack open on the way there like the last one.\nI should get a job...", - "priority": "Volatile" - }, - "whiteFlag" : { - "name": "White Flag", - "pickup": "Place a white flag. Everyone around it is unable to attack.", - "description": "Place down a white flag for 8 seconds. Disables skills for everyone within its radius.\nDoesn't affect drones and flying enemies.", - "destination" : "Room 2B,\nSomnus Hotel,\nEarth", - "date" : "10/5/2056", - "story" : "Save this for me until I get back home, I didn't need it. In fact, we became friends! I can't wait to tell you all about it soon. It's been a long trip and an unexpected series of events. I've told them about you and they want me to invite you over the next time. How's that, huh!? Love you.", - "priority": "Volatile" - } - }, - - "artifact" : { - "multitude" : { - "pickupName" : "Artifact of Multitude", - "name" : "Multitude", - "description": "Enemies come in hordes.", - "approaching": "A horde of enemies is approaching.", - "arriving": "Prepare yourself...", - }, - "displacement" : { - "pickupName" : "Artifact of Displacement", - "name" : "Displacement", - "description": "Stages are shuffled.", - }, - "gathering" : { - "pickupName" : "Artifact of Gathering", - "name" : "Gathering", - "description": "Gold drops are doubled but they must be picked up.", - } - }, - - - "actor" : { - "Exploder.name" : "Exploder", - "Mimic.name" : "Security Chest", - "Gatekeeper.name" : "Gatekeeper", - "Admonitor.name" : "Clay Admonitor", - "Protector.name" : "Protector", - "Protector.text" : "Keeper of the Artifact" - }, - - "monster" : { - "admonitor.name" : "Clay Admonitor", - "admonitor.story" : "In the distance, a familiar clay 'person' trudged across the temple grounds. However, despite their similar appearances, this creature was not the same type I had seen before. For what it lacked in cunning, unlike its smaller brethren, this potted protector bore two skulking fists, clenched in a vice grip. The stride it assumed almost made it look... confident. I know I would be a fool to assume it couldn't produce intelligent emotions such as conviction. Nevertheless, out of concern for my general wellbeing, I chose not to test the mettle of this swaggering oil slick and hid inside an upturned ruin. \n\nIf I'm to make it off of this planet alive, I'd prefer my femur to be intact." - }, - - "interactable" : { - "MimicInactive.text" : "..?", - "MimicInactive.name" : "Large Chest?" - }, - - "elite" : { - "poison.name" : "Poisoning %s", - "empyrean.name" : "Empyrean %s", - "empyrean.text" : "Harbinger of Judgement", - "empyrean.worm" : "Prismatic Leviathan" - }, - "stage" : { - "whistlingBasin.name" : "Whistling Basin", - "whistlingBasin.subname" : "Dwindling Oasis", - "whistlingBasin.story" : "This planet continues to hold nothing but surprises for me. Considering the actions of its denizens prior to coming here, I would never have thought such sprawling and decadent architecture could be built by such creatures. Incredibly deep ebony pillars and arches constructed of beautiful stone that rise from the ground, all adorned with carvings placed by intentional hands. All along the walls are these amazingly intricate displays of deceptively simple shapes. The most peculiarly turquoise foliage attack the structures, climbing subtly along their bases. It seems the preconceived notion of savagery I held for this planet is waning. Even still, my guard is held high. It's too soon for me to rest, despite protests from my aching muscles.", - - "torridOutlands.name" : "Torrid Outlands", - "torridOutlands.subname" : "Silent Sunburn", - "torridOutlands.story" : "Even with my suit, I can feel the pulsing solar rays flash down and bend the atmosphere of this god-forsaken planet. It's getting harder to keep my wits about me what with this heat, and the condensation beginning to cloud my helmet's walls won't help me either. Although, without it, my lungs would be halfway full of all the dust and detritus I've kicked up in my wandering- 'find a silver lining' and whatnot. With the sun high in the sky, even just the idea of surveying my environment with a manner of ease is nothing more than a pipe dream. I'm finding it easy to stumble into cacti and boulders in my delirium, taking brief reprieve inside these sprawling tunnels and caves that litter the surface. With the unwieldly terrain slowing my progression, the sweat trickling along my brow towards my eyes is bound to be the least of my worries." - } -} diff --git a/Language/english.lua b/Language/english.lua new file mode 100644 index 00000000..960590e2 --- /dev/null +++ b/Language/english.lua @@ -0,0 +1,564 @@ +return { + difficulty = { + typhoon = { + name = "Typhoon", + description = "The maximum challenge.\nThe planet is a nightmare, survival is merely an illusion.\nNobody has what it takes." + } + }, + + survivor = { + executioner = { + name = "Executioner", + nameUpper = "EXECUTIONER", + description = "The Executioner is a mobile fighter who specializes in counting heads. Using Ion projectors, the Executioner fabricates illusions which cause foes to run away in fear, while also projecting an axe to take out the strongest enemies. Make sure to chain kills with Ion Burst and Execution to keep the damage pouring.", + endQuote = "..and so he left, bloodlust unfulfilled.", + story = "

                                                  Passenger Details:\n[Military Class]\n\n
                                                    Luggage and Equipment:\nOfficer boarded with EXN Acting Officer carbonweave uniform, civilian-grade plated oversuit and an issued service pistol. At military checkpoint, passenger's armament was noted to have an additional under-barrel \nmodification- and was cleared after an agreement was made to store it in a secure container. The passenger also cleared one ION 204X series battery customized with propulsion orifices. As a safety measure, passenger's \ntechnological goods were temporarily fitted with electrical auto-nullifiers and returned to them.\n\n[SECURITY RECALL]\n
                                                      [Note E1a] Several reports of Passenger making entire security checkpoint crew uneasy. Light surveillance will be required until further notice.\n
                                                        [Note E1b] Passenger would often reply in sounds rather than speech before opting to use Universal Sign Language.\n
                                                          [Event E1] Contracted commando performs security check in Passenger's quarters.\n
                                                            [Event E1a] Contracted commando requests 24 hour private surveillance in hall leading to Passenger's quarters.\n
                                                              [Event E2] Passenger asked to move tables during mealtime after another Passenger sent a formal complaint to onboard security.", + id = "Travel ID: 4383354378334FF3D34D", + departure = "Departure:\nOld Alcatraz,\nRustborough,\nMars", + arrival = "Destination:\nStillwater Bay,\nNewdredge,\nEuropa" + }, + mule = { + name = "MULE", + nameUpper = "MULE", + description = "EVERY MULE UNIT IS ADEQUATELY EQUIPPED AND READY FOR DUTY. BUILT WITH HIGH QUALITY MATERIALS FOR MAXIMUM WORK PERFORMANCE. THIS MODEL INCLUDES THE FORAGER SET OF TOOLS, PERFECT FOR HUNTING, EXPLORATION AND OTHER DANGEROUS TASKS FOR THE COMMON USER. IN ORDER TO INITIALIZE PLEASE STATE A DIRECTIVE ON SYSTEM BOOT.", + endQuote = "..and so it left, pistons creaking, directive unstated.", + story = "
                                                                Warrantee Expiration: \n8/16/2041 \n\nInputted Note: \n(ID: #1244C4F4F5D494) \nNOTICE: MULE - MODEL F \nI'd rather not get into hot water for “tampering with ship property”, but I recently booted up this MULE unit and took 'em for repairs. It was just lying there, so I took it that no one would really mind if I touched it up a little. Heard talks of personnel wanting to throw 'em away before takeoff, but I just couldn't let it happen to such fine craftmanship.. it's a sight for sore eyes, sure, but you've gotta admit, it's novelty! Multi-purpose units haven't been considered in a long time since the first quadruped model was a hit. Besides, we all know what these sorts of bots can get up to! With a chassis like this, I wouldn't doubt that this big guy's roughed some pretty gnarly terrain. It's a bit of a walking time capsule, wouldn't you say? \nAll needed footage attached below yadda yadda— Look, I'll level with ya. Just.. consider keepin' it around. Even for just a bit. Whether those rumors are true or not.. I think his owner would be happy, y'know.. knowing it's up 'n runnin' still. \nJust give 'em something to do. Give it one more chance. \n\n[MAINTENANCE REPORT] \n
                                                                  [Report M1] On boot-up, MULE unit remained completely motionless until around 20 minutes later when given a verbal command to deploy its support drone. MULE unit demonstrated idle processing and protocols only after the command. \n
                                                                    [Report M1a] Support drone's power bank was found to be degraded. No compatible replacements could be found, leaving it to only be recharged. Battery longevity not guaranteed. \n
                                                                      [Report M2] MULE unit was to be familiarized with Contact Light passengers and staff via facial recognition after suddenly attempting to constrain a mechanic it failed to recognize. Most UES personnel are now logged in its memory. \n
                                                                        [Report M2a] Remotely accessing MULE unit's memory reveals previous entries in its database dating back to 2036. Initial entries in its memory were found to be corrupted or expunged." + }, + nemesisCommando = { + name = "Nemesis Commando", + nameUpper = "NEMESIS COMMANDO", + description = "The Nemesis Commando is a flexible character with the firepower to deal with any situation. Wound enemies using Blade of Cessation to setup for devastating damage from your other abilities. Use Tactical Roll to weave in and out of melee range, and turn even the most dangerous situations in your favour.", + endQuote = "..and so he left, carrying new sense of humanity within.", + story = "UNREGISTERED FIELD DATA LOG: DATA LAST MODIFIED ON [##090??!?] \n[ERROR 0x00000ce: Data Package Corrupted, viewing a restore point.] \n\n
                                                                          [Entry 1] - Patrol group just picked up a wounded straggler a few miles off from camp. Word going around is he “showed up out of nowhere,” (as if I'm buying that), what with nothing in the surrounding area to trace his origin. Regardless of the validity of their claims, a survivor's a survivor. As I'm writing this, we're carrying him back. He's certainly not making it easy on us, though, considering the sheer heft of his gear- he's lugging some unorthodox modifications to a standard Commando armor rig. It looks to be- or maybe was originally- the initial issue of the Mk. 1 model (even though I'm pretty sure they stopped producing those years ago?). \n\n
                                                                            [Entry 2] - Update- buddy's got some scarily heavy artillery on hand for a Commando unit- a rocket launcher still warm to the touch and some almost certainly alien grenades- I sure as hell haven't seen anything like them. He’s out cold right now, and from what we can tell looking through his helmet he's well-battered. His left arm seems especially stiff- his hand's clamped shut around the handle of his vibroblade. Some of us tried to get a good look at it just now, but it's almost like his fist is fused to the damn thing. Amongst other things on his person, it's thoroughly coated in this crusted black.. substance. We don't know if it was the result of a fight between a few more survivors, or evidence for some kind of self defe??!0049### \n[End of legible data.] \n\n
                                                                              [Entry 2a] - I dont know how much time I have left as im typing this it woke up and camp is DESTROYED people wont stop bleeding we dont even know if it was human with what it can do noone knows what to do now it went straight for Adrian \nwe dont even know what set it off??? what did WE do to deserve all of this??? why did that THING attack U!??!?##$0959090302???! \n[End of legible data.] \n\n

                                                                              Th?e ga?dflies swarm thi?s nob?le beas?t as it gra?zes.\n\nYour plag??ue upon it?s surfa?ce wi?ll be undo?ne by the?se toiling hand?s.", + }, + nemesisMercenary = { + name = "Nemesis Mercenary", + nameUpper = "NEMESIS MERCENARY", + description = "The Nemesis Mercenary uses dirty tactics and ruthless speed to dominate oncoming foes. Weaken opponents with his Quick Trigger, recuperate ammo with Blinding Slide, and chain-kill your remaining targets effortlessly with Devitalize.", + endQuote = "..and so he left, paid handsomely in blood and desolation.", + story = "

                                                                              I DON'T NEED NO FRIENDS\nI DON'T NEED NO PHONE\nJUST A BAG OF SEVERED HEADS\nAND MY NUCLEAR THRONE" + }, + technician = { + name = "Technician", + nameUpper = "TECHNICIAN", + description = "The Technician is excellent at setting up and maintaining zones of enemy denial. Forced Shutdown can forcibly lock down an area when upgraded. Upgrading gadgets improves their effectiveness, but be careful when doing so in dangerous situations!", + endQuote = "..and so he left, beyond his own repair.", + story = "

                                                                                Passenger Details:\n[Employee Class]\n\n
                                                                                  Employee Details:\nEmployee contracted to work on-site for the full duration of the scheduled voyage of the UES Contact Light, to perform maintenance on machinery should technical issues arise aboard the ship. Employee qualified with 6 years of hands-on experience in software and hardware engineering.\n\n
                                                                                    Luggage & Equipment:\nEmployee boarded with a Durarend welding spacesuit rated with strong vacuum resistance, and additionally leather and cloth attire made for planetary wear. Employee notified that clothes may tear in the case of a vacuum event. Security flagged the sizable wrench they held for appearing impractical for use while potentially dangerous. Upon questioning, the employee indicated that it was \"custom-built ... for the toughest jobs.\" Following procedure, the wrench was moved to an automated lockbox in loading bay 1a. Remaining inspection of carry-ons revealed several standard-issue wrenches, 3 color-coded USBs, a 2047 model GeForm Laptop, tool boxes, and a welding tool. USBs were inspected on company-owned QPuting Disposable Computer installed with ReoVirus Detector v1.6.2. No traces of malware were detected and stored files are UPG format containing blueprints of various devices. Employee permitted to pass with no further difficulties.\n\n[SECURITY RECALL]\n
                                                                                      [Event T1] Employee forcibly removed from cabin during liftoff preparations when refusing to leave after conversating with ship pilot for multiple hours.\n
                                                                                        [Event T2] Employee observed stealing sodas from a vending machine.\n
                                                                                          [Event T3] Automated lockdown originating in loading bay 4a resolved by employee.\n
                                                                                            [Event T4] Escape pod B-08 status shifted from ERROR to ALERT after the employee was requested by ship personnel to troubleshoot. Security personnel instructed to investigate.\n
                                                                                              [Event T1a] Employee additionally reported to have tampered with cabin control panel to make it \"easier to cruise.\" Scheduled launch delayed and employee instructed to revert any modifications made.", + id = "Travel ID: 54E4F4C434F59474E454", + departure = "Departure:\nUES Shipping Dock 5,\nRedview,\nMars", + arrival = "Destination:\nUES Shipping Dock 0,\nRedview,\nMars" + }, + swapNemesis = { + name = "Nemesis Survivors", + nameUpper = "NEMESIS SURVIVORS", + description = "Swap to the familiar yet twisted Nemesis survivors." + }, + swapNormal = { + name = "Normal Survivors", + nameUpper = "NORMAL SURVIVORS", + description = "Swap to the regular set of survivors." + } + }, + + skill = { + executionerZ = { + name = "Service Pistol", + description = "Shoot for 100% damage." + }, + executionerX = { + name = "Ion Burst", + description = "Rapidly shoot ionized bullets for 320% damage each.\nGain bullets by slaying enemies." + }, + executionerC = { + name = "Crowd Dispersion", + description = "Dash forward, fearing nearby enemies.\nYou cannot be hit while dashing." + }, + executionerV = { + name = "Execution", + description = "Launch into the air, and slam down with a projected axe for 1000% damage.\nEach successful execution reduces skill cooldowns by one second." + }, + executionerVBoosted = { + name = "Crowd Execution", + description = "Launch into the air, and slam down with a projected axe for 1500% damage, fearing enemies.\nEach successful execution reduces skill cooldowns by one second." + }, + executionerV2 = { + name = "Skullsplitter", + description = "Throw a projected axe in a circular arc for 1500% damage per second. Critically strikes against feared enemies.\nEach successful execution reduces skill cooldowns by one second." + }, + executionerV2Boosted = { + name = "Skullbuster", + description = "Throw two projected axes in a circular arc for 1500% damage per second. Critically strikes against feared enemies.\nEach successful execution reduces skill cooldowns by one second." + }, + muleZ = { + name = "INTERFERENCE REMOVAL", + description = "PUNCH ENEMIES FOR 200% DAMAGE. HOLD TO CHARGE AND SMASH THE GROUND FOR 1400% DAMAGE." + }, + muleX = { + name = "IMMOBILIZE", + description = "FIRE A TRAP FOR 125% DAMAGE. THE TRAP SNARES UP TO 5 ENEMIES IN THE TARGETS' PROXIMITY." + }, + muleC = { + name = "TORQUE CALIBRATION", + description = "SPIN FORWARD FOR 4X100% DAMAGE. THE LAST HIT STUNS." + }, + muleV = { + name = "FAIL-SAFE ASSISTANCE", + description = "DEPLOY A PERSONAL REPAIR DRONE FOR 8 SECONDS. THE DRONE HEALS 5X7% OF YOUR TOTAL HEALTH. AT FULL HEALTH, GRANTS BARRIER INSTEAD." + }, + muleVBoosted = { + name = "FAIL-SAFE ASSISTANCE 2.0", + description = "LAUNCH A HEALING DRONE LASTING 8 SECONDS. THE DRONE HEALS 5X10% OF YOUR TOTAL HEALTH. AT FULL HEALTH, GRANTS BARRIER INSTEAD." + }, + nemesisCommandoZ = { + name = "Blade of Cessation", + description = "Cut through nearby enemies for 120% damage, wounding them for 6 seconds. Cannot be stacked.\nWounded enemies take an extra 50% damage from all sources." + }, + nemesisCommandoX = { + name = "Single Tap", + description = "Shoot an enemy for 200% damage.\nHold up to 4. Reload 2 charges when rolling." + }, + nemesisCommandoX2 = { + name = "Distant Gash", + description = "Slash in a line forward for 90% damage, wounding enemies for 6 seconds." + }, + nemesisCommandoC = { + name = "Tactical Roll", + description = "Quickly roll forward a short distance.\nYou cannot be hit while rolling. Hold up to 2." + }, + nemesisCommandoV = { + name = "Flush Out", + description = "Throw a grenade for 700% damage. Hold to cook the grenade.\nPress the down direction to toss at a low arc instead." + }, + nemesisCommandoVBoosted = { + name = "Cluster Bomb", + description = "Throw a grenade for 700% damage, stunning enemies and spitting into 3 grenades for 3x150% damage.\nHold to cook the grenade. Press the down direction to toss at a low arc instead." + }, + nemesisCommandoV2 = { + name = "Devastator", + description = "Fire a rocket for 1000% damage to a single enemy. Surrounding enemies take 50% damage and are stunned.\nIf used airborne, fire downward at an angle." + }, + nemesisCommandoV2Boosted = { + name = "Spanker", + description = "Fire a rocket for 1000% damage to a single enemy. Surrounding enemies take 50% damage and are scattered.\nIf used airborne, fire downward at an angle.\nHold up to 2." + }, + nemesisMercenaryZ = { + name = "Lascerate", + description = "Quickly slash enemies in front of you for 100% damage." + }, + nemesisMercenaryX = { + name = "Quick Trigger", + description = "Fire your shotgun, stunning and damaging enemies for 400% damage." + }, + nemesisMercenaryC = { + name = "Blinding Slide", + description = "Hold to enter a defensive stance before quickly sliding forwards. \nGetting hit whilst in this stance reloads your shotgun. \nYou can attack whilst sliding." + }, + nemesisMercenaryV = { + name = "Devitalize", + description = "Target the weakest enemy near you, dealing 700% damage. \nPrioritizes and critically strikes stunned enemies. \nKilling an enemy with this skill refreshes it." + }, + nemesisMercenaryVBoosted = { + name = "Absolute Devitalization", + description = "Target the weakest enemy near you, dealing 700% damage. \nPrioritizes and critically strikes stunned enemies. \nInstantly kills enemies under 25% health." + }, + technicianZ = { + name = "Fine Tune", + description = "Strike forward with a wrench for 180% damage.\nHitting gadgets 3 times temporarily upgrades them for 30 seconds." + }, + technicianZ2 = { + name = "Troubleshoot", + description = "Throw a wrench forward, piercing up to 3 enemies for 180% damage.\nHitting gadgets 3 times temporarily upgrades them for 20 seconds." + }, + technicianX = { + name = "Forced Shutdown", + description = "Toss out a bomb dealing 400% damage on reactivation.\nStuns and passively pulls in enemies when upgraded." + }, + technicianXD = { + name = "The Red Button", + description = "Detonate the dropped bomb for 500% damage.\nStuns enemies when upgraded." + }, + technicianC = { + name = "24/7 Energy", + description = "Deploy a vending machine that gives a movement and attack speed bonus.\nAdditionally increases critical strike chance when upgraded." + }, + technicianC2 = { + name = "Radial Amplifier", + description = "Deploy an antenna that deals 15% additional damage to attacked enemies.\nHas 100% critical strike chance when upgraded." + }, + technicianV = { + name = "Backup Firewall", + description = "Place a turret firing forward for 220% damage per second.\nFires rapidly for 350% damage per second when upgraded." + }, + technicianVBoosted = { + name = "Backup Firewall 2.0", + description = "Place a turret firing rapidly for 350% damage per second.\nFires missiles for 4x100% damage when upgraded." + }, + peace = { + name = "<#a4eae4>Peace!", + description = "Cannot use this skill." + } + }, + + item = { + armedBackpack = { + name = "Armed Backpack", + pickup = "Chance of firing a bullet behind you.", + description = "18.5% (+6.5% per stack) chance on attack to fire a bullet behind you for 150% damage.", + destination = "832B,\nHautenuit,\nEarth", + date = "11/7/2056", + story = "Being secure is always important. I don't want you to get in trouble, so please wear this whenever you go out. 'Specially in the canyons, there's a lot of thieves there!\nI'll send you some extra ammunition later this year, alright?", + priority = "Standard" + }, + brassKnuckles = { + name = "Brass Knuckles", + pickup = "Deal extra damage at close range.", + description = "Deal +35% damage to enemies within 2.8m (+1.4 per stack).", + destination = "UESPD,\nE42,\nUES Halberd B2", + date = "03/01/2056", + story = "We found this at the crime scene. We need to check for DNA against the rest of the evidence. I still cant believe they got away.\nThe back has some kind of symbol, maybe that could give us a clue. I'll be heading to the west of [REDACTED], I know someone who can help us solve the case.", + priority = "Standard/Biological" + }, + detritiveTrematode = { + name = "Detritive Trematode", + pickup = "Low health enemies receive damage over time.", + description = "Enemies below 15% (+5% per stack) health become permanently infected, receiving 100% damage per second.", + destination = "112,\nNeaus 2,\nMars", + date = "03/11/2057", + story = "Eh, this is yours.\nDon't send one of those weird microscopic egg hatcheries EVER again, please.", + priority = "Priority/Biological" + }, + dormantFungus = { + name = "Dormant Fungus", + pickup = "Regenerate health while moving.", + description = "Regenerates 2% (+2% per stack) of your health every two seconds while moving.", + destination = "Gate 1,\nModule 13,\nGea Station", + date = "11/01/2056", + story = "When you commanded me to explore the fungal caves all I expected to find was.. well, fungi, of course. Though I wasn't excited by any means, this specimen caught my eye.\nIt seems to only grow around flowing water, and ever since I collected and contained this sample, both the colouration and the smell faded out quickly.\n\nI hope you can study it further.", + priority = "Standard" + }, + fork = { + name = "Fork", + pickup = "Deal more damage.", + description = "Increases base damage by 3 (+3 per stack).", + destination = "Antima Building - Rooftop,\nBlossom Hills,\nEarth", + date = "09/24/2056", + story = "Surprise! A fork! Hahaha!!!\nYou said any gift was a good gift, HERE YOU GO! Jokes aside, this fork is magical. Trust me Duncan, I once got to eat a salad with it and later that same day I found a cent!\nAlso uh please come play with us someday soon, we miss destroying your build in SS2 (please tell me you changed your main, lol).", + priority = "Standard" + }, + watchMetronome = { + name = "Watch Metronome", + pickup = "Charge the watch by standing still. Move faster at full charge.", + description = "Standing still charges the watch over 3 seconds. At full charge, increase movement speed by 50%, consuming charge over 3 (+1 per stack) seconds.", + destination = "Cage 1,\nG-44 (Underground),\nEarth", + date = "09/05/2056", + story = "Grandpa gave me this as a relic of the past. He says it belonged to our great-great-great-great-great grandfather. I couldn't care less about antiques and family belongings so I think it's best if you have it instead.\nNot like it's of any use anyways..", + priority = "Priority" + }, + wonderHerbs = { + name = "Wonder Herbs", + pickup = "Slightly increase all healing effects.", + description = "Increases healing from all sources by 12% (+12% per stack).", + destination = "2342,\nPlot 3,\nEarth", + date = "11/07/2056", + story = "Rikka! I heard you're working with the EC on making a new recipe. Here at the farm we've been growing some special herbs which we think may come handy. They're known for being very, very useful for treating physical and emotional wounds.\nIf you need some more don't hesitate to call me! XOXO\n- Eden", + priority = "Standard" + }, + coffeeBag = { + name = "Coffee Bag", + pickup = "Activating an interactable boosts movement and attack speed.", + description = "Activating an interactable increases movement and attack speed by 22% for 10 (+5 per stack) seconds.", + destination = "n1,\nThe Capital,\nUES PRIME", + date = "05/22/2056", + story = "Sleeeeeeeeeeepyheaaaaaaaaaaaaaaad idk why u say u dont like coffee its so gooooood for staying up. ik ik theres other other ways to get hyped but oh boy i think u will dig this one, its my favorite!!! give it a try before yelling at me mmkay?", + priority = "Standard" + }, + x4Stimulant = { + name = "X4 Stimulant", + pickup = "Decrease secondary skill cooldown, and heal slightly when using it.", + description = "Decrease your secondary skill cooldown by 10% (+10% per stack). Using your secondary skill increases health regeneration by 2 (+1 per stack) hp per second for 3 seconds.", + destination = "Romeo 33,\nBatang Ai National Park,\nEarth", + date = "4/12/2057", + story = "Encased, including manual. Does the machine show symptoms of a 540? If so you may want to avoid using above 100ml of stimulant.\n\nCheers." + }, + moltenCoin = { + name = "Molten Coin", + pickup = "Chance to ignite enemies on hit and gain gold", + description = "6% chance on hit to incinerate enemies for 6 seconds, dropping $1 (+$1 per stack) gold.", + destination = "Toera 2,\nB44,\nMother Station", + date = "05/22/2056", + story = "Hey! Uh, I'm sorry, I'm really sorry.. I know you really wanted me to keep this coin but I can't take the responsibility any more..\nSee, I accidentally put the coin at the edge of a plasma furnace so.. well.. it's a bit burnt on the side, please don't get mad at me..", + priority = "Standard" + }, + crypticSource = { + name = "Cryptic Source", + pickup = "Changing direction creates bursts of energy.", + description = "Changing movement direction fires chain lightning for 70% (+55% per stack) damage on up to 2 targets.", + destination = "O32,\nLow End,\nEarth", + date = "03/30/2058", + story = "From atoms to sentient beings, everything comes from energy. However, this rather cryptic object seems to emanate energy on its own. High amounts of friction seem to trigger a chain reaction which makes it highly unstable. However, it may also be a manipulable source of (infinite?) energy. Whether this will lead us to the utopian future we crave for is completely uncertain, but this completely changes our previous thoughts about the universe.", + priority = "Volatile" + }, + huntersSigil = { + name = "Hunter's Sigil", + pickup = "Standing still creates a zone that increases armor and critical strike chance.", + description = "After standing still for 1 second, create a small zone that buffs you and allies for 15 (+10 per stack) armor and 25% (+20% per stack) critical chance.", + destination = "2East,\nBeckham Building,\nEarth", + date = "02/02/2056", + story = "Hey Sett, welcome to the club. We've been looking for candidates and now that you're with us, we can begin working next season. We're counting on you.\n\n-Irix out", + priority = "Priority" + }, + roulette = { + name = "Roulette", + pickup = "Roll a random buff every minute.", + description = "Gain a random buff, which gets rerolled every minute. Possible buffs:\n\n: Increase max health by 60 (+24 per stack).\n: Increase health regeneration by 3.6 (+1.8 per stack) hp per second.\n: Increase base damage by 14 (+5 per stack).\n: Increase attack speed by 35% (+15.5% per stack).\n: Increase critical chance by 25% (+10% per stack).\n: Increase movement speed by 20% (+7% per stack).\n: Increase armor by 35 (+14 per stack).", + destination = "PRoom 3.1,\nSecva Casino,\nEarth", + date = "7/17/2057", + story = "Replacement Roulette model 144Bella-1 in follow-up to the recent events that unfolded in the casino. For further inquiries, please contact us at the E-direction given by our representatives.", + priority = "Priority" + }, + judderingEgg = { + name = "Juddering Egg", + pickup = "Gain a little friend!", + description = "Hatch 1 (+1 per stack) young wurm who fights with you, periodically attacking surrounding enemies for 18x80% damage.", + destination = "Gate B,\nLee Compound,\nEarth", + date = "11/21/2056", + story = "Another one from the breeder, this time it's a very strange specimen.\nBe extremely cautious, I've heard juveniles can become highly aggressive (and lethal!) in a matter of seconds if you don't handle them correctly. Shouldn't be too long before it hatches, so you better have the structure ready for it.", + priority = "Fragile" + }, + uraniumHorseshoe = { + name = "Uranium Horseshoe", + pickup = "Increase movement speed and jump height.", + description = "Increases movement speed and jump height by 10% (+10% per stack)", + destination = "6900,\nMartian's Face Horse Care and Farm Supplies,\nMars", + date = "04/20/2056", + story = "We found this weird green horse shoe on the hoof of one of our prized show horses after it mysteriously died late one night.\nOur horse doctor said you guys might know something about this strange horseshoe. Just holding it makes my hand burn. Handle it with care.", + priority = "Standard" + }, + malice = { + name = "Malice", + pickup = "Damage spreads to nearby enemies.", + description = "Attacks

                                                                                              spread to nearby enemies for 45% TOTAL damage on up to 1 (+1 per stack) target(s) within 4.2m (+1m per stack).", + destination = "P24,\nRomeo,\nPol-A Station", + date = "02/11/2056", + story = "Contain at all costs. This specimen can be a massive threat if not handled correctly. You don't know how much we lost already in order to keep this at bay.", + priority = "

                                                                                              Malicious" + }, + manOWar = { + name = "Man-o'-war", + pickup = "Create an electric discharge on kill.", + description = "Killing an enemy fires chain lightning for 100% (+40% per stack) damage on up to 2 targets.", + destination = "Hidden Cubby,\nMt. Creation,\nVenus", + date = "11/20/2056", + story = "Guys, remember the tiny guy I sent over some time ago? Well.. this one is a bit more aggressive.. although she is a funny little gal! She loves spinnin' around and watching me from inside the bottle. Man, if I hadn't rescued her, she would be in some alien stomach right now.\nI kinda want to name her Ann, thoughts?", + priority = "Priority" + }, + guardingAmulet = { + name = "Guarding Amulet", + pickup = "Reduce damage from behind you.", + description = "Damage taken from behind you is reduced by 40% (+40% per stack).", + destination = "C8,\nAzure Garden,\nSanctuary", + date = "03/11/2056", + story = "I know he's somewhere out there, I know you can find him.. but you must be persistent. By the time my soul leaves this shell, the words will be spoken and you will take my place. He will be the one to show you the path to the answers you always wanted from me.\nEmbrace this amulet; you will depend on it, and it will keep you from ever looking back.", + priority = "Standard" + }, + distinctiveStick = { + name = "Distinctive Stick", + pickup = "Heal near the teleporter.", + description = "Grows a tree near Teleporters, healing allies within 10m (+3.75m per stack) for 2.2% of their health every 2 seconds.", + destination = "Dreq Mineli,\nTesaft,\nEarth", + date = "01/15/2056", + story = "Darling, I have something special for you..\nThe other day on my mission, I came across this. It caught my eye because it's unlike any of the branches I've seen in my life! This branch excudes a peculiar energy. It made me feel connected to nature in a way I thought was only possible in stories! In honor of our anniversary, I'm giving it to you.\nI'll arrive home soon with an even greater present. Please, don't lose faith.", + priority = "Standard" + }, + balloon = { + name = "Balloon", + pickup = "Lower gravity outside of danger.", + description = "Decrease gravity while holding the jump button by 35% (-15% per stack). Pops when damaged. Recovers when outside of danger for 7 seconds.", + destination = "55 Mo.,\nPaare Plaza,\nEarth", + date = "06/31/2056", + story = "When I was younger I used to love balloons. They're quite fun to watch in places with an atmosphere. However, this one is of special value to me, as it was of the first ones to be made out of reinforced rubber, back in the '22s. I hope you take care of it, it might cost a fortune in a few more years (if it's still floating, of course!).", + priority = "Priority/Fragile" + }, + iceTool = { + name = "Ice Tool", + pickup = "Gain a wall jump. Climb faster.", + description = "Gain 1 (+1 per stack) extra jump while in contact with a wall. Increase climbing speed by 18% (+18% per stack).", + destination = "Mon's Tower #33,\nSolei Shore,\nEarth", + date = "03/12/2056", + story = "Good day fellow hiking friend, I found the ice tool you lost last time we went to Mt. [REDACTED]. Please keep an eye out for the snow next time!\nI wonder if the ice tool is still usable though, it's been through so much!", + priority = "Standard" + }, + needles = { + name = "Needles", + pickup = "Chance to mark enemies for guaranteed critical strikes.", + description = "3% (+2% per stack) chance on hit to mark enemies for 100% critical chance against them for 3 seconds.", + destination = "E2,\nOren's Loop,\nVenus", + date = "[REDACTED]", + story = "Uh.. madre dice que si puedes leer esto, es porque no eres tan torpe como pensaba, y pues eso. Esperamos que te sirvan estas agujas. Me temo que no son las que pediste, pero no hace mucha diferencia, pienso.\nNo recuerdo si los enviamos bien embalados. Ten cuidado.", + priority = "Piercing" + }, + + midas = { + name = "M.I.D.A.S", + pickup = "Convert half of your health into gold.", + description = "Lose 50% health, gaining $1 gold per health spent, plus the cost of a chest.", + destination = "R A 5-5,\nPrime,\nTitan", + date = "05/04/2056", + story = "\"A miracle from the gods..\"\nYou keep saying it over and over again. Are you out of your mind? You keep treating us like we're ignorant when all we tried to do was to SAVE YOU.\n\nDo us a favour and keep it. Do whatever you want with it, we do NOT want to see you around here anymore. We do NOT want to be part of whatever is going on with you and this thing.\n\nIt's making you a greedy, souless person and I hope you know it.", + priority = "Volatile" + }, + strangeCan = { + name = "Strange Can", + pickup = "Throw a can to mark an enemy. Killing it releases a toxic cloud.", + description = "Throw out a peculiar can that intoxicates the enemy with the highest maximum health. Intoxicated enemies release a toxic cloud on death for 50% of the enemy's maximum health as damage over 5 seconds.", + destination = "1530,\n563,\nA-LC12", + date = "05/22/2056", + story = "These are as delicious as I told you. I just hope it doesn't crack open on the way there like the last one.\nI should get a job...", + priority = "Volatile" + }, + whiteFlag = { + name = "White Flag", + pickup = "Place a white flag. Everyone around it is unable to attack.", + description = "Place down a white flag for 8 seconds. Disables skills for everyone within its radius.\nDoesn't affect drones and flying enemies.", + destination = "Room 2B,\nSomnus Hotel,\nEarth", + date = "10/5/2056", + story = "Save this for me until I get back home, I didn't need it. In fact, we became friends! I can't wait to tell you all about it soon. It's been a long trip and an unexpected series of events. I've told them about you and they want me to invite you over the next time. How's that, huh!? Love you.", + priority = "Volatile" + }, + eclipseGummies = { + name = "Eclipse Gummies", + pickup = "Killing enemies drops buffing gummies.", + description = "Killing an enemy drops a buffing gummy that raises attack and movement speed by 2.5% (+2.5% per stack) up to 25% (+25% per stack) for 10 seconds.", + destination = "199\nAbraham St.\nEarth", + date = "8/16/2056", + story = "Hey Dani! It's Vi, you won't believe what I just found. A box of old Eclipse Gummies! At work we were cleaning out this old ma' and pop convenience store and I found this box unopened in the back. My boss let me keep it since we'd just throw it out otherwise. It has to have been nearly 8 years since I last had one of these. Oh lil' Francesca Frog, Robot Reg and all of you other lil' fruity flavors. For a show that solely existed to sell merch, it sure did leave a mark on my childhood. I'm so glad these gummies taste the same as they did so long ago. Even giving me the same wiggly tingles as back then too. It really makes me think, why did they stop selling such popular gummies? Anyways I can't hog all of these to myself so I'm sending you a bunch to have too. Enjoy :heart:", + priority = "Standard" + } + }, + + artifact = { + multitude = { + pickupName = "Artifact of Multitude", + name = "Multitude", + description = "Enemies come in hordes.", + approaching = "A horde of enemies is approaching.", + arriving = "Prepare yourself...", + }, + displacement = { + pickupName = "Artifact of Displacement", + name = "Displacement", + description = "Stages are shuffled.", + }, + gathering = { + pickupName = "Artifact of Gathering", + name = "Gathering", + description = "Gold drops are doubled but they must be picked up.", + } + }, + + actor = { + Exploder = { + name = "Exploder" + }, + Mimic = { + name = "Security Chest" + }, + Gatekeeper = { + name = "Gatekeeper" + }, + Admonitor = { + name = "Clay Admonitor" + }, + Protector = { + name = "Protector", + text = "Keeper of the Artifact" + } + }, + + monster = { + admonitor = { + name = "Clay Admonitor", + story = "In the distance, a familiar clay 'person' trudged across the temple grounds. However, despite their similar appearances, this creature was not the same type I had seen before. For what it lacked in cunning, unlike its smaller brethren, this potted protector bore two skulking fists, clenched in a vice grip. The stride it assumed almost made it look... confident. I know I would be a fool to assume it couldn't produce intelligent emotions such as conviction. Nevertheless, out of concern for my general wellbeing, I chose not to test the mettle of this swaggering oil slick and hid inside an upturned ruin. \n\nIf I'm to make it off of this planet alive, I'd prefer my femur to be intact." + }, + exploder = { + name = "Exploder", + story = "There's nothing I hate more than being chased down by these creatures. Although they can be easily dealt with alone, whenever they come in packs, I'm better off running away. It's as if their entire bloodstream is filled with this viscous, acidic material that managed to dissolve the circuits of one of my drones with a single drop.\n\nI.. really don't want to know what it would do to my skin." + }, + mimic = { + name = "Security Chest", + story = "Who had the crazy idea of giving chests a failsafe? I know they want to make it harder for pirates, but they probably didn't think about emergencies.\nI'll never get used to chasing a mechanical box because it thinks I stole something.\n\n..Or maybe it's the berries I ate." + } + }, + + interactable = { + MimicInactive = { + text = "..?", + name = "Large Chest?" + }, + ChirrsmasPresent = { + text = "Recieve a gift!", + name = "Gift Box" + } + }, + + elite = { + poison = { + name = "Poisoning %s", + }, + empyrean = { + name = "Empyrean %s", + text = "Harbinger of Judgement" + } + }, + + stage = { + whistlingBasin = { + name = "Whistling Basin", + subname = "Dwindling Oasis", + story = "This planet continues to hold nothing but surprises for me. Considering the actions of its denizens prior to coming here, I would never have thought such sprawling and decadent architecture could be built by such creatures. Incredibly deep ebony pillars and arches constructed of beautiful stone that rise from the ground, all adorned with carvings placed by intentional hands. All along the walls are these amazingly intricate displays of deceptively simple shapes. The most peculiarly turquoise foliage attack the structures, climbing subtly along their bases. It seems the preconceived notion of savagery I held for this planet is waning. Even still, my guard is held high. It's too soon for me to rest, despite protests from my aching muscles.", + }, + torridOutlands = { + name = "Torrid Outlands", + subname = "Silent Sunburn", + story = "Even with my suit, I can feel the pulsing solar rays flash down and bend the atmosphere of this god-forsaken planet. It's getting harder to keep my wits about me what with this heat, and the condensation beginning to cloud my helmet's walls won't help me either. Although, without it, my lungs would be halfway full of all the dust and detritus I've kicked up in my wandering- 'find a silver lining' and whatnot. With the sun high in the sky, even just the idea of surveying my environment with a manner of ease is nothing more than a pipe dream. I'm finding it easy to stumble into cacti and boulders in my delirium, taking brief reprieve inside these sprawling tunnels and caves that litter the surface. With the unwieldly terrain slowing my progression, the sweat trickling along my brow towards my eyes is bound to be the least of my worries." + }, + verdantWoodland = { + name = "Verdant Woodland", + subname = "Breathing Overgrowth", + story = "A maze of the largest trees I've ever seen unwinds in front of me, a constricting natural wonder with scales of bark and teeth like splinters. These pillars of wood rise endlessly, protecting this rich acropolis- their overbearing presence makes me reflect upon my actions as I tear through the overgrowth. I can hardly blame these sharp brambles that line my suit. With each piercing step, they remind me that I am just a guest within their fortress. Teeming bug's nests erupt with their knights, guiding me away from their young just as you or I would with our own. I can hear swarms of raging insects overhead, chirruping like birdsong. It reminds me of home. I want nothing more than to lie down in the lush grass that this incredible planet has provided, but I must keep moving. Melancholic pangs sting at my insides. This endeavouring venom that courses through my veins, giving me the will to keep fighting.. I can't help but to regret its presence.\n\nI fear that this planet is starting to get to my head." + } + }, + + ui = { + options = { + ssr = { + header = "STARSTORM RETURNS", + titleReplacement = "Replace Title", + ["titleReplacement.desc"] = "Whether the game's title screen logo should be replaced with Starstorm's", + enableChars = "Enable Survivors", + ["enableChars.desc"] = "Enables survivors added by Starstorm Returns.", + enableItems = "Enable Items", + ["enableItems.desc"] = "Enables items added by Starstorm Returns.", + enableEnemies = "Enable Enemies", + ["enableEnemies.desc"] = "Enables enemies added by Starstorm Returns.", + enableElites = "Enable Elites", + ["enableElites.desc"] = "Enabless elites added by Starstorm Returns.", + enableTyphoon = "Enable Typhoon", + ["enableTyphoon.desc"] = "Enables typhoon added by Starstorm Returns.", + enableInteractables = "Enable Interactables", + ["enableInteractables.desc"] = "Enables interactables added by Starstorm Returns.", + enableArtifacts = "Enable Artifacts", + ["enableArtifacts.desc"] = "Enables artifacts added by Starstorm Returns.", + enableStages = "Enable Stages", + ["enableStages.desc"] = "Enables stages added by Starstorm Returns.", + enableBeta = "Enable Unfinished Content", + ["enableBeta.desc"] = "Enables work in progress Starstorm Returns content. \nVERY UNFINISHED", + enableChirrsmas = "Enable Chirrsmas Content", + ["enableChirrsmas.desc"] = "Whether Chirrsmas-exclusive content should be present. \nBy default active from December 15th to January 15th. \nRESTART REQUIRED", + ["enableChirrsmas.default"] = "Default", + ["enableChirrsmas.always"] = "Always", + ["enableChirrsmas.never"] = "Never" + } + } + } +} \ No newline at end of file diff --git a/Language/russian.json b/Language/russian.json deleted file mode 100644 index 4c183b15..00000000 --- a/Language/russian.json +++ /dev/null @@ -1,480 +0,0 @@ -{ - "difficulty" : { - "typhoon.name" : "Тайфун", - "typhoon.description" : "Максимальная сложность.\nПланета — кошмар, выживание — всего лишь иллюзия.\nНикто не способен остаться в живых.", - }, - "survivor" : { - "executioner" : { - "name" : "Палач", - "nameUpper" : "ПАЛАЧ", - "description": "Палач — подвижный боец, специализирующийся в подсчете трупов. Используя Ионные проекторы, Палач заставляет противников убегать в страхе, а также проецирует топор для устранения сильнейших из них. Цепочки убийств, сделанные с помощью «Ионной Вспышки» и «Казни» помогут продолжать наносить урон.", - "endQuote": "..и он улетел, с неудовлетворенной жаждой крови.", - "story": "

                                                                                                Сведения о пассажире:\n[Военный]\n\n
                                                                                                  Багаж и снаряжение:\nОфицер поднялся на борт в форме исполняющего обязанности офицера EXN из углеродного волокна, гражданском комбинезоне с пластинами и с выданным служебным пистолетом. На военном контрольно-пропускном пункте было отмечено, что вооружение пассажира имело дополнительную подствольную модификацию и было проверено после того, как было достигнуто соглашение о хранении вооружения в безопасном контейнере. Пассажир также проверил одну батарею серии ION 204X, настроенную с отверстиями для пропульсивной установки. В качестве меры безопасности технологические товары пассажира были временно оснащены электрическими автонуллификаторами и возвращены им.\n\n[ПРИМЕЧАНИЯ СЛУЖБЫ БЕЗОПАСНОСТИ]\n
                                                                                                    [Запись E1a] Несколько сообщений о пассажире заставили весь персонал контрольно-пропускного пункта беспокоиться. Легкое наблюдение будет обязательным до дальнейшего уведомления.\n
                                                                                                      [Запись E1b] Пассажир часто отвечал звуками, а не речью, прежде чем решил использовать универсальный язык жестов.\n
                                                                                                        [Случай E1] Контрактный спецназовец проводит проверку безопасности в каютах пассажиров.\n
                                                                                                          [Случай E1a] Контрактный спецназовец запрашивает круглосуточное частное наблюдение в зале, ведущем в каюты пассажиров.\n
                                                                                                            [Случай E2] Пассажира попросили передвинуть столы во время еды после того, как другой пассажир отправил официальную жалобу в службу безопасности на борту.", - "id" : "Travel ID: 4383354378334FF3D34D", - "departure" : "Пункт отправления:\nСтарый Алькатрас,\nРастборо,\nМарс", - "arrival" : "Пункт назначения:\nСтиллуотер-Бей,\nНьюдредж,\nЕвропа" - }, - "mule" : { - "name" : "МУЛ", - "nameUpper" : "МУЛ", - "description" : "КАЖДАЯ МОДЕЛЬ МУЛА ОБОРУДОВАНА И ГОТОВА К РАБОТЕ. СОЗДАНА ИЗ ВЫСОКОКАЧЕСТВЕННЫХ МАТЕРИАЛОВ ДЛЯ МАКСИМАЛЬНОЙ ПРОИЗВОДИТЕЛЬНОСТИ РАБОТЫ. ДАННАЯ МОДЕЛЬ ВКЛЮЧАЕТ НАБОР ИНСТРУМЕНТОВ ДЛЯ ДОБЫЧИ РЕСУРСОВ, ИДЕАЛЬНАЯ ДЛЯ ОХОТЫ, ИССЛЕДОВАНИЯ И ДРУГИХ ПОРУЧЕНИЙ, ОПАСНЫХ ДЛЯ ОБЫЧНОГО ПОЛЬЗОВАТЕЛЯ. ДЛЯ ИНИЦИАЛИЗАЦИИ УКАЖИТЕ ДИРЕКТИВУ ПРИ ЗАГРУЗКЕ СИСТЕМЫ.", - "endQuote" : "..и он улетел, со скрипом поршней, без директивы.", - "story": "
                                                                                                              Дата истечения гарантии: \n8/16/2041 \n\nЗаметка: \n(ID: #1244C4F4F5D494) \nЗАМЕТКА: МУЛ - МОДЕЛЬ F \nНе хочется попадать в неприятности из-за «порчи корабельного имущества», но недавно я включил этого МУЛа и отвёз его в ремонт. Он просто лежал, поэтому я решил, что никто не будет против, если я его немного подправлю. Слышал разговоры о том, что персонал хочет выбросить его перед взлётом, но я просто не могу позволить так обращаться с такой прекрасной работой... он, конечно, отрада для глаз, но, согласитесь, такого не каждый день увидишь! Многоцелевые модели давно не рассматривались с тех пор, как первая четвероногая модель стала хитом. К тому же, мы все знаем, на что способны подобные боты! С таким шасси, я не сомневаюсь, что этот здоровяк покорил довольно ухабистую местность. Он что-то вроде ходячей капсулы времени, не так ли? \nВсе необходимые кадры прикреплены ниже бла-бла-бла— Слушайте, я буду с вами откровенен. Просто... подумайте о том, чтобы оставить его здесь. Хотя бы на время. Правдивы эти слухи или нет... Думаю, его хозяин был бы рад, ну... знать, что он всё ещё на ходу. \nПросто займите его чем нибудь. Дайте ему ещё один шанс. \n\n[ОТЧЕТ ПО ТЕХОБСЛУЖИВАНИЮ] \n
                                                                                                                [Отчет M1] После включения МУЛ оставался полностью неподвижным примерно до 20 минут, пока не была получена устная команда на запуск вспомогательного дрона. МУЛ продемонстрировал работу в режиме ожидания и протоколов только после получения команды. \n
                                                                                                                  [Отчет M1a] Аккумулятор вспомогательного дрона оказался испорченным. Совместимой замены найти не удалось, поэтому пришлось заряжать. Срок службы аккумулятора не гарантируется. \n
                                                                                                                    [Отчет M2] МУЛ был ознакомлен с пассажирами и персоналом «Контакт Лайт» с помощью распознавания лиц после внезапной попытки связать механика, которого он не смог распознать. Большая часть персонала UES теперь записана в его памяти. \n
                                                                                                                      [Отчет M2a] Удаленный доступ к памяти МУЛа показывает предыдущие записи в базе данных, датируемые 2036 годом. Первоначальные записи в его памяти оказались испорченными или удалёнными." - }, - "nemesisCommando" : { - "name" : "Немезис Коммандо", - "nameUpper" : "НЕМЕЗИС КОММАНДО", - "description" : "Немезис Коммандо — гибкий персонаж с огневой мощью, способной справиться с любой ситуацией. Наносите раны на врагов, используя «Клинок Прекращения», чтобы подготовиться к разрушительному урону от других ваших способностей. Используйте «Тактический Перекат», чтобы входить и выходить из ближнего боя и обращать даже самые опасные ситуации в вашу пользу.", - "endQuote": "..и он улетел, с новым чувством человечности внутри.", - "story": "НЕЗАРЕГИСТРИРОВАННАЯ ЗАПИСЬ ПОЛЕВЫХ ДАННЫХ: ДАННЫЕ ИЗМЕНЕНЫ В [##090??!?] \n[ОШИБКА 0x00000ce: Пакет Данных Поврежден, загрузка с точки восстановления.] \n\n
                                                                                                                        [Запись 1] - Патрульная группа только что подобрала раненого отставшего в нескольких милях от лагеря. Ходят слухи, что он «появился из ниоткуда» (ага, как будто я в такое поверю), и в окрестностях нет ничего, что могло бы объяснить его происхождение. Независимо от обоснованности их заявлений, выживший есть выживший. Пока я пишу это, мы несем его обратно. Он, конечно, не облегчает нам задачу, учитывая огромный вес его снаряжения — он тащит какие-то нетрадиционные модификации стандартной брони коммандо. Похоже, это — или, может быть, изначально была — первоначальная версия модели Mk. 1 (хотя я почти уверен, что они прекратили ее производство много лет назад?). \n\n
                                                                                                                          [Запись 2] - Обновление: у приятеля есть пугающе тяжелая артиллерия для бойца коммандо — ракетная установка, все еще теплая на ощупь, и несколько почти наверняка инопланетных гранат — я чертовски уверен, что ничего подобного не видел. Сейчас он без сознания, и, судя по тому, что мы можем сказать, глядя через его шлем, он изрядно потрепан. Его левая рука кажется особенно жесткой — его ладонь сжата вокруг рукояти его виброклинка. Некоторые из нас только что попытались хорошенько ее рассмотреть, но похоже, что его кулак сросся с этой чертовой штукой. Среди прочего на нем, он полностью покрыт этим корким черным... веществом. Мы не знаем, было ли это результатом драки между несколькими выжившими или свидетельством какой-то самообо??!0049### \n[Конец читаемых данных.] \n\n
                                                                                                                            [Запись 2a] - Я не знаю сколько времени у меня осталось пока я печатаю оно проснулось лагерь РАЗРУШЕН люди текут кровью мы даже не знаем человек это учитывая что он может сделать никто не знает что делать он побежал прямо на Адриана \nмы даже не знаем что его побудило??? что МЫ сделали чтобы заслужить все это??? Почему ЭТО СУЩЕСТВО напало на НА?!??!?##$0959090302???! \n[Конец читаемых данных.] \n\n

                                                                                                                            Ово??ды р?оятся в?округ это?го бла?городного з?ве?ря, п?ока он па?сется. \n\nВаш?а з??араза бу?дет уб??рана от?сюда э??ти?ми тру?дящи??мися рук?а?ми." - }, - "nemesisMercenary" : { - "name" : "Nemesis Mercenary", - "nameUpper" : "NEMESIS MERCENARY", - "description" : "The Nemesis Mercenary was born with a special power.", - "endQuote": "..and so he left, loaded up for more.", - "story": "

                                                                                                                            I DON'T NEED NO FRIENDS\nI DON'T NEED NO PHONE\nJUST A BAG OF SEVERED HEADS\nAND MY NUCLEAR THRONE" - }, - "technician": { - "name": "Техник", - "nameUpper": "ТЕХНИК", - "description": "Техник отлично справляется с созданием и поддержанием зон недопущения противника. «Принудительное Отключение» может принудительно заблокировать область при улучшении. Улучшение гаджетов повышает их эффективность, но будьте осторожны, делая это в опасных ситуациях!", - "endQuote": "...и он улетел, непоправимо поврежденный.", - "story": "

                                                                                                                              Сведения о пассажире:\n[Сотрудник]\n\n
                                                                                                                                Сведения о сотруднике:\nСотрудник, нанятый по контракту для работы на месте в течение всего запланированного рейса UES «Контакт Лайт», для выполнения технического обслуживания оборудования в случае возникновения технических проблем на борту судна. Сотрудник имеет квалификацию с 6-летним практическим опытом в области разработки программного и аппаратного обеспечения.\n\n
                                                                                                                                  Багаж и снаряжение:\nСотрудник поднялся на борт в сварочном скафандре Durarend, рассчитанном на высокую устойчивость к вакууму, а также в одежде из кожи и ткани, предназначенной для планетарного износа. Сотрудник уведомлен о том, что одежда может порваться в случае возникновения вакуума. Охрана отметила большой гаечный ключ, который они держали, как непрактичный для использования, хотя и потенциально опасный. После допроса сотрудник указал, что он был «изготовлен на заказ ... для самых сложных работ». После процедуры гаечный ключ был перемещен в автоматизированный сейф в погрузочном отсеке 1a. Во время осмотра оставшейся ручной клади было обнаружено несколько стандартных гаечных ключей, 3 цветных USB-накопителя, ноутбук GeForm модели 2047, ящики для инструментов и сварочный аппарат. USB-накопители были проверены на принадлежащем компании одноразовом компьютере QPuting, на котором был установлен детектор ReoVirus Detector v1.6.2. Следов вредоносного ПО обнаружено не было, а сохраненные файлы имеют формат UPG и содержат чертежи различных устройств. Сотруднику разрешили пройти без дальнейших трудностей.\n\n[ПРИМЕЧАНИЯ СЛУЖБЫ БЕЗОПАСНОСТИ]\n
                                                                                                                                    [Случай T1] Сотрудника принудительно вывели из кабины во время подготовки к взлету, когда он отказался покинуть ее после многочасового разговора с пилотом корабля.\n
                                                                                                                                      [Случай T2] Сотрудник был замечен крадя газировку из торгового автомата.\n
                                                                                                                                        [Случай T3] Автоматическая блокировка, возникшая в погрузочном отсеке 4a, устранена сотрудником.\n
                                                                                                                                          [Случай T4] Статус спасательной капсулы B-08 изменился с «ОШИБКА» на «ВНИМАНИЕ» после того, как персонал судна попросил сотрудника устранить неполадки. Сотрудникам службы безопасности поручено провести расследование.\n
                                                                                                                                            [Случай T1a] Сотрудник также сообщил о его вмешательстве в работу панели управления кабины, чтобы «облегчить круиз». Запланированный запуск отложен, и сотруднику поручено отменить все внесенные изменения.", - "id" : "Travel ID: 54E4F4C434F59474E454", - "departure" : "Пункт отправления:\nПорт UES 5,\nРедвью,\nМарс", - "arrival" : "Пункт назначения:\nПорт UES 0,\nРедвью,\nМарс" - } - }, - "skill" : { - "executionerZ": { - "name": "Служебный Пистолет", - "description": "Вы стреляете, нанося 100% урона." - }, - "executionerX": { - "name": "Ионный Залп", - "description": "Вы быстро стреляете ионизированными пулями, нанося 320% урона за каждый выстрел.\nВы получаете пули при убийстве врагов." - }, - "executionerC": { - "name": "Рассеивание Толпы", - "description": "Вы делаете рывок вперёд, распугивая ближайших врагов.\nВас нельзя атаковать во время рывка." - }, - "executionerV": { - "name": "Казнь", - "description": "Вы подлетаете в воздух, и бросаетесь вниз с проецируемом топором, нанося 1000% урона.\nКазни ускоряют перезарядку навыков на одну секунду." - }, - "executionerVBoosted": { - "name": "Казнь Толпы", - "description": "Вы подлетаете в воздух, и бросаетесь вниз с проецируемом топором, нанося 1500% урона и распугивая врагов.\nКазни ускоряют перезарядку навыков на одну секунду." - }, - "executionerV2": { - "name": "Черепокрамсатель", - "description": "Вы кидаете проецируемый топор дугой, нанося 1500% урона в секунду. Урон удвоен против испуганных врагов.\nКазни ускоряют перезарядку навыков на одну секунду." - }, - "executionerV2Boosted": { - "name": "Черепоразрушитель", - "description": "Вы кидаете два проецируемых топора дугой, нанося 1500% урона в секунду. Урон удвоен против испуганных врагов.\nКазни ускоряют перезарядку навыков на одну секунду." - }, - "muleZ": { - "name": "УДАЛЕНИЕ ПОМЕХ", - "description": "ВЫ БЬЁТЕ ВРАГОВ, НАНОСЯ 200% УРОНА. УДЕРЖИВАЙТЕ, ЧТОБЫ ЗАРЯДИТЬ УДАР ОБ ЗЕМЛЮ, НАНОСЯЩИЙ 1400% УРОНА." - }, - "muleX": { - "name": "СВЯЗЫВАНИЕ", - "description": "ВЫ СТРЕЛЯЕТЕ ЛОВУШКОЙ, НАНОСЯ 125% УРОНА. ЛОВУШКА ОБЕЗДВИЖИВАЕТ ДО 5 ВРАГОВ РЯДОМ С ЦЕЛЬЮ." - }, - "muleC": { - "name": "КАЛИБРАЦИЯ ПОРШНЕЙ", - "description": "ВЫ КРУТИТЕСЬ ВПЕРЕД, НАНОСЯ 4X100% УРОНА. ПОСЛЕДНИЙ УДАР ОГЛУШАЕТ." - }, - "muleV": { - "name": "АВАРИЙНАЯ ПОМОЩЬ", - "description": "ВЫ ЗАПУСКАЕТЕ РЕМОНТНЫЙ ДРОН НА 8 СЕКУНД, ИСЦЕЛЯЮЩИЙ ВАС НА 5X7% ОТ ВАШЕГО МАКСИМАЛЬНОГО ЗДОРОВЬЯ. ПРИ ПОЛНОМ ЗДОРОВЬЕ, ДАЕТ БАРЬЕР ВМЕСТО ИСЦЕЛЕНИЯ." - }, - "muleVBoosted": { - "name": "АВАРИЙНАЯ ПОМОЩЬ 2.0", - "description": "ВЫ ЗАПУСКАЕТЕ РЕМОНТНЫЙ ДРОН НА 8 СЕКУНД, ИСЦЕЛЯЮЩИЙ ВАС НА 5X10% ОТ ВАШЕГО МАКСИМАЛЬНОГО ЗДОРОВЬЯ. ПРИ ПОЛНОМ ЗДОРОВЬЕ, ДАЕТ БАРЬЕР ВМЕСТО ИСЦЕЛЕНИЯ." - }, - "nemesisCommandoZ": { - "name": "Клинок Прекращения", - "description": "Вы режете врагов поблизости, нанося 120% урона, раня их на 6 секунд. Раны не складываются.\nРаненные враги получают +50% урона от всех источников." - }, - "nemesisCommandoX": { - "name": "Одиночный Выстрел", - "description": "Вы стреляете, нанося 200% урона.\nЗарядите до 4 патронов в магазин. Перезарядите 2 патрона при перекате." - }, - "nemesisCommandoX2": { - "name": "Дальняя Рана", - "description": "Вы взмахиваете прямо по линии, нанося 90% урона, раня врагов на 6 секунд." - }, - "nemesisCommandoC": { - "name": "Тактический Перекат", - "description": "Вы быстро перекатываетесь вперёд на короткую дистанцию.\nВы неуязвимы во время переката. Можно перекатиться до 2 раз." - }, - "nemesisCommandoV": { - "name": "Раскрытие", - "description": "Вы кидаете гранату, нанося 700% урона. Удерживайте, чтобы готовить гранату.\nНажмите вниз, чтобы сбросить гранату ниже." - }, - "nemesisCommandoVBoosted": { - "name": "Кассетная Бомба", - "description": "Вы кидаете гранату, нанося 700% урона и оглушая врагов. Граната ррасколывается на 3 бомбы, нанося 3x150% урона.\nУдерживайте, чтобы готовить гранату.\nНажмите вниз, чтобы сбросить гранату ниже." - }, - "nemesisCommandoV2": { - "name": "Опустошитель", - "description": "Вы запускаете ракету, нанося 1000% урона одному врагу. Враги поблизости получают 50% урона и оглушаются.\nВ воздухе вы запускаете ракету диагонально вниз." - }, - "nemesisCommandoV2Boosted": { - "name": "Спанкер", - "description": "Вы запускаете ракету, нанося 1000% урона одному врагу. Враги поблизости получают 50% урона и оглушаются.\nВ воздухе вы запускаете ракету диагонально вниз.\nДержите до 2 ракет." - }, - "nemesisMercenaryZ": { - "name": "Lascerate", - "description": "Injure enemies with your shotgun's attachment for 130% damage." - }, - "nemesisMercenaryX": { - "name": "Quick Trigger", - "description": "Fire your shotgun, stunning and hitting enemies nearby for 600% damage." - }, - "nemesisMercenaryC": { - "name": "Blinding Slide", - "description": "Quickly slide forwards. Getting hit at the start reloads your shotgun. \nYou can attack while sliding." - }, - "nemesisMercenaryV": { - "name": "Devitalize", - "description": "Target the weakest enemy in front of you, attacking them for 850% damage, dealing +50% TOTAL damage to stunned enemies. \nYou cannot be hit for the duration." - }, - "nemesisMercenaryVBoosted": { - "name": "Absolute Devitalization", - "description": "Target the weakest enemy in front of you, attacking them for 1100% damage, dealing +50% TOTAL damage to stunned enemies. \nYou cannot be hit for the duration." - }, - "technicianZ": { - "name": "Тонкая Настройка", - "description": "Вы ударяете гаечным ключом, нанося 180% урона.\nВременно улучшите гаджеты на 30 секунд, ударив по ним 3 раза." - }, - "technicianZ2": { - "name": "Устранение Неполадок", - "description": "Вы кидаете гаечный ключ, проходя до 3 врагов насквозь, нанося 180% урона.\nВременно улучшите гаджеты на 20 секунд, ударив по ним 3 раза." - }, - "technicianX": { - "name": "Принудительное Выключение", - "description": "Вы кидаете оглушающую бомбу, нанося 700% урона при активации.\nЗатягивает врагов когда улучшена." - }, - "technicianX2": { - "name": "Красная Кнопка", - "description": "Вы взрываете выкинутую бомбу, нанося 700% урона и оглушая врагов." - }, - "technicianC": { - "name": "Заряд 24/7", - "description": "Вы ставите торговый автомат, дающий бонус к скорости скорости передвижения и атаки.\nУвеличивает шанс критического удара когда улучшен." - }, - "technicianC2": { - "name": "Радиальный Усилитель", - "description": "Вы ставите антенну, наносящую 15% бонусного урона атакованным врагам.\nУвеличивает шанс критического удара на 100% когда улучшен." - }, - "technicianV": { - "name": "Резервный Брандмауэр", - "description": "Вы ставите турель, которая наносит 220% урона в секунду.\nСтреляет быстрее, нанося 350% урона в секунду когда улучшена." - }, - "technicianVBoosted": { - "name": "Резервный Брандмауэр 2.0", - "description": "Вы ставите турель, стреляющую быстро, нанося 350% урона в секунду.\nВыпускает ракеты, нанося 4x100% урона когда улучшена." - }, - "peace": { - "name": "<#a4eae4>Мир!", - "description": "Вы не можете использовать этот навык." - } - }, - - "item" : { - "armedBackpack" : { - "name" : "Рюкзак с Вооружением", - "pickup": "Дает вероятность выстрелить позади вас.", - "description": "Дает 18.5% (+6.5% за шт.) вероятность при атаке выстерлить позади вас, нанося 150% урона.", - "destination": "832B,\nАутнуи,\nЗемля", - "date": "07.11.2056", - "story": "Быть в безопасности всегда важно. Мне не хочется, чтобы ты попал в неприятности, поэтому, пожалуйста, надевай его, когда выходишь из дома. Особенно в каньонах, там много воров!\nЯ отправлю тебе немного запасных патронов позже в этом году, хорошо?", - "priority": "стандартный" - }, - "brassKnuckles" : { - "name": "Кастет", - "pickup" : "Наносит дополнительный урок врагам вблизи.", - "description": "Наносит +35% урона врагам в радиусе 5 м (+2.5 м за шт.).", - "destination": "Полиция UES,\nE42,\nUES Aлебарда B2", - "date": "01.03.2056", - "story" : "Мы нашли это на месте преступления. Нам нужно сравнить ДНК с остальными уликами. Я все еще не могу поверить, что им удалось уйти от ответственности.\nНа обратной стороне есть какой-то символ, может быть он даст нам подсказку. Я направляюсь на запад [УДАЛЕНО], я знаю кого-то, кто может помочь нам раскрыть это дело.", - "priority": "стандартный/биоматериал" - }, - "detritiveTrematode" : { - "name": "Детритивная Трематода", - "pickup" : "Враги с низким запасом здоровья получают урон за промежуток времени", - "description": "Враги, у которых меньше 10% (+5% за шт.) здоровья становятся зараженными, получая 2% от макс. здоровья в качестве урона в секунду.", - "destination": "112,\nНеус 2,\nМарс", - "date": "11.03.2057", - "story" : "Ех, это твое.\nНИКОГДА больше не отправляй мне эти странные инкубаторы для микроскопических яиц, пожалуйста.", - "priority" : "приоритетный/биоматериал" - }, - "dormantFungus" : { - "name" : "Дремлегриб", - "pickup" : "Востанавливает здоровье во время движения.", - "description": "Востанавливает 2% (+2% за шт.) от вашего здоровья каждые 2 секунды во время движения.", - "destination": "Шлюз 1,\nМодуль 13,\nСтанция Геа", - "date": "01.11.2056", - "story" : "Когда вы велели мне исследовать грибные пещеры, я ожидал найти только... ну, грибы, разумеется. Хоть я не был особо в восторге, этот образец смог привлечь мое внимание.\nПохоже, он растет только около текущей воды, и с тех пор как я собрал и поместил этот образец, его цвет и запах быстро исчезли.\n\nНадеюсь, вы сможете изучить его дальше.", - "priority": "стандартный" - }, - "fork" : { - "name" : "Вилка", - "pickup" : "Увеличивает урон.", - "description": "Увеличивает базовый урон на 3 (+3 за шт.).", - "destination": "Здание Антимы - Крыша,\nЦветущие Холмы,\nЗемля", - "date": "24.09.2056", - "story" : "Сюрприз! Вилка! Ха-ха-ха!!!\nТы сказал, что любой подарок — это хороший подарок, НУ ТАК ВОТ! Шутки шутками, а вилка эта — волшебная. Поверь мне, Дункан, однажды мне удалось съесть ею салат, а позже в тот же день я нашел цент!\nИ, эм, пожалуйста, поиграй с нами еще как-нибудь, мы соскучились по уничтожению твоей сборки в SS2 (пожалуйста, скажи что ты поменял своего мэйна, лол).", - "priority": "стандартный" - }, - "watchMetronome" : { - "name" : "Часовой Метроном", - "pickup" : "Зарядите часы, постояв неподвижны. Ускоряет передвижение при полном заряде.", - "description": "Постояв неподвижны, зарядите часы за 3 секунды. При полном заряде, увеличьте скорость передвижения на 50%. Заряд спадывает за 3 секунды (+1 за шт.).", - "destination": "Клетка 1,\nG-44 (Подземелье),\nЗемля", - "date": "05.09.2056", - "story" : "Дедушка подарил мне их как пережиток прошлого.. Он говорит, что они принадлежали нашему пра-пра-пра-прадеду. Мне все равно на антиквариат и семейные вещи, так что думаю, будет лучше, если ты заберешь их.\nНе то чтобы они вообще могли где-то пригодится..", - "priority": "приоритетный" - }, - "wonderHerbs" : { - "name" : "Чудо-травы", - "pickup" : "Немного увеличивает все исцеляющие эффекты.", - "description": "Увеличивает исцеление от всех источников на 12% (+12% за шт.).", - "destination": "2342,\nУчасток 3,\nЗемля", - "date": "07.11.2056", - "story" : "Рикка! Я слышал, ты работаешь с НП над созданием нового рецепта. Здесь, на ферме, мы выращиваем специальные травы, которые, мы думаем, могут пригодиться. Они известны своей очень-очень полезной способностью лечить физические и эмоциональные раны.\nЕсли тебе нужно больше, не стесняйся звонить мне! Люблю, целую.\n— Эден", - "priority": "стандартный" - }, - "coffeeBag" : { - "name" : "Пакетик с Кофе", - "pickup" : "При взаимодействии с объектами увеличивает скорость передвижения и скорость атаки.", - "description": "При взаимодействии с объектами увеличивает скорость передвижения и скорость атаки на 22% на 10 (+5 за шт.) секунд.", - "destination": "n1,\nСтолица,\nUES PRIME", - "date": "22.05.2056", - "story" : "Соооооооооооняяяяяяяяяяяяяяя хз поч ты говоришь что тебе не нравится кофе оно так бодрит. знаю знаю есть другие другие способы взбодрится но бож я думаю тебе оно зайдет, оно мое любимое!!! попробуй перед тем как кричать на меня лаадно?", - "priority": "стандартный" - }, - "x4Stimulant" : { - "name" : "X4 Стимулятор", - "pickup" : "Уменьшает время перезарядки дополнительного навыка, и немного исцеляет вас при его использовании.", - "description": "Уменьшает время перезарядки дополнительного навыка на 10% (+10% за шт.). Использование вашего дополнительного навыка увеличивает восстановление здоровья на 2 (+1 pза шт.) ед. здоровья в секундy на 3 секунды.", - "destination": "Ромео 33,\nНациональный парк Батанг Ай,\nЗемля", - "date": "12.04.2057", - "story" : "Упакован с мануалом. Показывает ли машина симптомы 540? Если да, то лучше избегать использования более 100 мл стимулятора.\n\nС уважением.", - "priority": "стандартный" - }, - "moltenCoin" : { - "name" : "Расплавленная Монета", - "pickup" : "Дает вероятность при попадании поджечь врагов и дать вам золото", - "description": "Дает 6%-ю вероятность при попадании поджечь врагов на 6 секунд, принося $1 (+$1 за шт.) золота.", - "destination": "Тоэра 2,\nB44,\nМатеринская Станция", - "date": "22.05.2056", - "story" : "Привет! Эм, извини, мне очень жаль.. Я знаю, ты очень хотел, чтобы я оставил эту монету себе, но я больше не могу брать на себя эту ответственность..\nВидишь ли, я случайно положил монету на край плазменной печи, и.. ну.. она немного подгорела с боку, пожалуйста, не злись на меня..", - "priority": "стандартный" - }, - "crypticSource" : { - "name": "Загадочный Источник", - "pickup" : "Создает вспышку энергии при смене направления движения.", - "description": "При смене направления движения выпускает цепную молнию, наносящую 70% (+55% за шт.) урона по максимум 2 целям.", - "destination" : "O32,\nНижняя Часть,\nЗемля", - "date" : "30.03.2058", - "story" : "Все происходит из энергии, от атомов до разумных существ. Однако этот довольно загадочный объект, похоже, излучает энергию сам по себе. Высокие уровни трения, похоже, запускают цепную реакцию, делая его крайне нестабильным. Однако он также может быть управляемым источником (бесконечной?) энергии. Приведет ли это нас к утопическому будущему, о котором мы мечтаем, совершенно неясно, но это полностью меняет наши предыдущие представления о вселенной.", - "priority" : "нестабильный предмет" - }, - "huntersSigil" : { - "name": "Символ Охотника", - "pickup" : "Постояв неподвижны, увеличьте броню и шанс критического удара.", - "description": "После того, как вы постоите неподвижно 1 секунду, получите 15 (+10 за шт.) брони и 25% (+20% за шт.) шанса критического удара.", - "destination" : "2Восток,\nЗдание Бекхэм,\nЗемля", - "date" : "02.02.2056", - "story" : "Привет, Сетт, добро пожаловать в клуб. Мы искали кандидатов, и так как теперь ты с нами, мы сможем начать работать в следующем сезоне. Мы рассчитываем на тебя.\n\n— Ирикс, конец связи.", - "priority": "приоритетный" - }, - "roulette" : { - "name": "Рулетка", - "pickup" : "Получите случайное усиление каждую минуту.", - "description": "Получите случайное усиление, которое перебрасывается каждую минуту. Возможные усиления:\n\n: Увеличьте максимальный запас здоровья на 60 (+24 за шт.).\n: Увеличьте восстановление здоровья на 3.6 (+1.8 за шт.) ед. здоровья в секунду.\n: Увеличьте базовый урон на 14 (+5 за шт.).\n: Увеличьте скорость атаки на 35% (+15.5% за шт.).\n: Увеличьте вероятность критического удара на 25% (+10% за шт.).\n: Увеличьте скорость передвижения на 20% (+7% за шт.).\n: Увеличьте броню на 35 (+14 за шт.).", - "destination" : "ПКомн. 3.1,\nКазино Секва,\nЗемля", - "date" : "17.07.2057", - "story" : "Замена Рулетки модели 144Bella-1 в связи с недавними событиями, произошедшими в казино. Для получения дополнительной информации, пожалуйста, свяжитесь с нами по электронному адресу, указанному нашим представителем", - "priority": "приоритетный" - }, - "judderingEgg" : { - "name": "Дрожащее Яйцо", - "pickup" : "Получите маленького друга!", - "description": "Вы можете вылупите 1 (+1 за шт.) молодого змея, который сражается за вас и периодически атакует ближних врагов, нанося 18x80% урона.", - "destination" : "Ворота B,\nКомпаунд Ли,\nЗемля", - "date" : "21.11.2056", - "story" : "Еще один от заводчика, на этот раз это очень странный экземпляр.\nБудьте предельно осторожны, я слышал, что молодые особи могут стать очень агрессивными (и смертоносными!) за считанные секунды, если с ними неправильно обращаться. Не должно пройти много времени, пока он вылупится, так что лучше подготовьте для него структуру.", - "priority": "хрупкий предмет" - }, - "uraniumHorseshoe" : { - "name": "Урановая Подкова", - "pickup" : "Ускоряет передвижение и увеличивает высоту прыжка.", - "description": "Увеличивает скорость передвижение и высоту прыжка на 10% (+10% за шт.)", - "destination" : "6900,\nУход за лошадьми и фермерские принадлежности от Марсианского Лица,\nМарс", - "date" : "20.04.2056", - "story" : "Мы нашли эту странную зеленую подкову на копыте одной из наших призовых выставочных лошадей после того, как она загадочно умерла поздно ночью.\nНаш конный врач сказал, что вы, ребята, можете знать что-то об этой странной подкове. Мне руку жжет, когда я просто держу ее. Обращайтесь с ней осторожно.", - "priority": "стандарт" - }, - "blastKnuckles" : { - "name": "Blast Knuckles", - "pickup" : "Your attacks explode at close range!", - "description": "Hitting an enemy within 4.5m also blasts behind them for 220% (+220% per stack) damage. Hold up to 5 charges. Gain a charge every 3 seconds.", - "destination" : "todo", - "date" : "todo", - "story" : "todo", - "priority": "Priority" - }, - "malice" : { - "name": "Злоба", - "pickup" : "Распространяет урон по врагам поблизости.", - "description": "Атаки

                                                                                                                                            распространяются по врагам поблизости, нанося 50% ОБЩЕГО урона 1 (+1 за шт.) врагу в радиусе 4,2м. (+1м. за шт.).", - "destination" : "P24,\nRomeo,\nPol-A Station", - "date" : "11.02.2056", - "story" : "Сдерживайте любой ценой. Этот экземпляр может быть огромной угрозой, если с ним не обращаться правильно. Вы не знаете, сколько мы уже потеряли чтобы сдержать его.", - "priority": "

                                                                                                                                            злобный" - }, - "manOWar" : { - "name": "Кораблик", - "pickup" : "Выпускает электрический разряд при убийстве.", - "description": "При убистве врага выпускает электрический разряд, наносящий 100% (+40% за шт.) урона по 2 врагам.", - "destination" : "Скрытый уголок,\nГора Сотворения,\nВенера", - "date" : "20.11.2056", - "story" : "Ребята, помните того маленького парня, которого я прислал не так давно? Ну... эта немного агрессивнее... хотя она забавная девчушка! Ей нравится крутиться и наблюдать за мной из бутылки. Господи, если бы я ее не спас, она бы сейчас была в каком-нибудь инопланетном желудке.\nЯ как-то хочу назвать ее Энн, как считаете?", - "priority": "приоритетный" - }, - "guardingAmulet" : { - "name": "Охраняющий Амулет", - "pickup" : "Уменьшает урон сзади.", - "description": "Урон, полученный сзади уменьшен на 40% (+40% за шт.).", - "destination" : "C8,\nЛазурный Сад,\nСвятилище", - "date" : "11.03.2056", - "story" : "Я знаю, он где-то там, я знаю, что ты можешь найти его... но ты должен быть настойчивым. К тому времени, как моя душа покинет эту оболочку, слова будут сказаны, и ты займешь мое место. Он будет тем, кто покажет тебе путь к ответам, которые ты всегда хотел получить от меня.\nПрими этот амулет; ты будешь зависеть от него, и он не даст тебе никогда оглянуться назад.", - "priority": "стандартный" - }, - "distinctiveStick" : { - "name": "Отличительная палка", - "pickup" : "Исцеляет рядом с телепортом.", - "description": "Выращивает дерево возле телепорта, исцеляеющее союзников в 10м. (+3,75м. за шт.) от него на 2,2% от их здоровья каждые 2 секунды.", - "destination" : "Дрек Минели,\nтесафт,\nЗемля", - "date" : "15.01.2056", - "story" : "Дорогая, у меня есть для тебя что-то особенное..\nНа днях во время своей миссии я наткнулся на нее. Она привлекло мое внимание, потому что она не похоже ни на одну из веток, которые я видел в своей жизни! Эта ветка излучает особую энергию. Она заставила меня почувствовать себя связанным с природой так, как я думал, это возможно только в историях! В честь нашей годовщины я дарю ее тебе.\nСкоро я вернусь домой с еще большим подарком. Пожалуйста, не теряй веру.", - "priority": "стандартный" - }, - "balloon" : { - "name": "Шарик", - "pickup" : "Уменьшает гравитацию вне опасности.", - "description": "Уменьшает гравитацию пока вы держите кнопку прыжка на 35% (-15% за шт.). Лопается при получении урона. Востанавливается, если вы не получаете урон в течение 7 секунд.", - "destination" : "55 Mo.,\nПааре Плаза,\nЗемля", - "date" : "31.06.2056", - "story" : "Когда я был моложе, я любил воздушные шары. За ними довольно забавно наблюдать в местах с атмосферой. Однако этот имеет для меня особую ценность, так как он был одним из первых, сделанных из армированной резины, еще в 22-х годах. Надеюсь, ты побережешь его, через несколько лет он может стоить целое состояние (если все еще будет летать, конечно!).", - "priority": "приоритетный/хрупкий предмет" - }, - "iceTool" : { - "name": "Ледоруб", - "pickup" : "Дает прыжок от стены. Лазайте быстрее.", - "description": "Дает 1 (+1 за шт.) дополнительный прыжок, пока вы возле стены. Увеличивает скорость лазания на 18% (+18% за шт.).", - "destination" : "Башня Мона #33,\nСолей Шор,\nЗемля", - "date" : "12.03.2056", - "story" : "Добрый день, товарищ по походу, я нашел ледоруб, который ты потерял в прошлый раз, когда мы ходили на гору [УДАЛЕНО]. Пожалуйста, следи за снегом в следующий раз!\nИнтересно, можно ли его еще использовать, он столько всего пережил!", - "priority": "стандартный" - }, - "needles" : { - "name": "Иголки", - "pickup" : "Дает вероятность при попадании пометить врага, гарантируя критические удары против него", - "description": "Дает 3%-ю (+2% за шт.) вероятность при попадании пометить врага, увеличивая шанс критического удара на 100% против него на 3 секунды.", - "destination" : "E2,\nПетля Орена,\nВенера", - "date" : "[УДАЛЕНО]", - "story" : "Uh.. madre dice que si puedes leer esto, es porque no eres tan torpe como pensaba, y pues eso. Esperamos que te sirvan estas agujas. Me temo que no son las que pediste, pero no hace mucha diferencia, pienso.\nNo recuerdo si los enviamos bien embalados. Ten cuidado.", - "priority": "пронизывающий" - }, - "midas" : { - "name" : "М.И.Д.А.С", - "pickup" : "Превращает половину вашего здоровья в золото.", - "description" : "Пожертвовав 50% здоровья, вы получаете $1 за единицу потерянного здоровья, плюс стоимость сундука.", - "destination" : "R A 5-5,\nПрайм,\nТитан", - "date" : "04.05.2056", - "story" : "\"Чудо от богов..\"\nТы продолжаешь повторяться снова и снова. С ума сошел? Ты продолжаешь обращаться с нами, как с невеждами, когда все, что мы пытались сделать, это СПАСТИ ТЕБЯ.\n\nСделай нам одолжение и забери его. Делай с ним что хочешь, мы больше НЕ хотим тебя здесь видеть. Мы НЕ хотим быть частью того, что происходит с тобой и этой штукой.\n\nОн делает тебя жадным, бездушным человеком, и я надеюсь, что ты это знаешь.", - "priority": "нестабильный предмет" - }, - "strangeCan" : { - "name": "Странная Банка", - "pickup" : "Кидает банку, помечая врага. При убийстве помеченного врага, выпускает токсичное облако.", - "description": "Кидает необычную банку, которая отравляет врага с наивысшим максимальным запасом здоровья. Отравленные враги выпускают токсичное облако при убийстве, нанося 50% максимального запаса здоровья врага в качестве урона за 5 секунд.", - "destination" : "1530,\n563,\nA-LC12", - "date" : "22.05.2056", - "story" : "Такая же вкуснотища, как я тебе и говорил. Надеюсь, что она не треснет по пути, как последняя.\nНайти бы мне работу...", - "priority": "нестабильный предмет" - }, - "whiteFlag" : { - "name": "Белый Флаг", - "pickup": "Ставит белый флаг. Никто вокруг него не может атаковать.", - "description": "Ставит белый флаг на 8 секунд. Отключает навыки всем в его радиусе.\nНе влияет на дронов и летающих врагов.", - "destination" : "Комната 2B,\nОтель Сомнус,\nЗемля", - "date" : "5.10.2056", - "story" : "Похрани его у себя, пока я не вернусь домой, мне он не понадобился. На самом деле, мы даже подружились! Не могу дождаться, чтобы рассказать тебе обо всем этом поскорее. Эта поездка была долгой, а череда событий неожиданная. Я рассказал им о тебе, и они хотят, чтобы я пригласил тебя в следующий раз. Как тебе это, а!? Люблю тебя.", - "priority": "нестабильный предмет" - } - }, - - "artifact" : { - "multitude" : { - "pickupName" : "Артефакт Множества", - "name" : "Множество", - "description": "Враги наступают ордами.", - "approaching": "Орда врагов приближается.", - "arriving": "Приготовтесь...", - }, - "displacement" : { - "pickupName" : "Артефакт Перемещения", - "name" : "Перемещение", - "description": "Окружения перетасованы.", - }, - "gathering" : { - "pickupName" : "Артефакт Собирания", - "name" : "Собирание", - "description": "Золото удвоенно, но должно быть собрано.", - } - }, - - - "actor" : { - "Exploder.name" : "Взрыватель", - "Mimic.name" : "Охранный Сундук", - "Admonitor.name" : "Глиняный Наставник", - "Gatekeeper.name" : "Сторож", - "Protector.name" : "Защитник", - "Protector.text" : "Хранитель Артефакта" - }, - - "monster" : { - "admonitor.name" : "Глиняный Наставник", - "admonitor.story" : "Вдалеке знакомый глинянный «человек» плелся по территории храма. Однако, несмотря на схожесть внешности, это существо было не одним из тех, что я видел раньше. В отличие от своих меньших собратьев, ему не хватало ловкости, но этот горшечный защитник имел два стиснутых кулака. Его походка придавала ему почти... уверенный вид. Знаю, глупо было бы полагать, что оно не способно на разумные эмоции вроде убеждённости. Тем не менее, заботясь о своём благополучии, я решил не испытывать на прочность это качающееся нефтяное пятно и спрятался в раскопанных руинах. \n\nЕсли я хочу выбраться с этой планеты живым, я предпочту, чтобы моя бедренная кость была цела." - }, - - "interactable" : { - "MimicInactive.text" : "..?", - "MimicInactive.name" : "Большой Сундук?" - }, - - "elite" : { - "poison.name" : "%s яда", - "empyrean.name" : "%s Эмпиреи", - "empyrean.text" : "Предвестник Суда", - "empyrean.worm" : "Призматический Левиафан" - }, - "stage" : { - "whistlingBasin.name" : "Свистящий Бассейн", - "whistlingBasin.subname" : "Исчезающий Оазис", - "whistlingBasin.story" : "Эта планета продолжает приносить мне одни лишь сюрпризы. Учитывая действия ее обитателей до прибытия сюда, я бы никогда не подумал, что такие существа могли построить такую размашистую и декадентскую архитектуру. Невероятно глубокие эбеновые колонны и арки, построенные из прекрасного камня, которые возвышаются над землей, все украшены резьбой, нанесенной намеренно руками. Вдоль всех стен тянутся эти удивительно замысловатые проявления обманчиво простых форм. Самая необычная бирюзовая листва атакует сооружения, тонко поднимаясь по их основаниям. Кажется, предвзятое представление о дикости, которое я питал к этой планете, ослабевает. И все же моя бдительность высока. Мне еще слишком рано отдыхать, несмотря на протесты моих ноющих мышц.", - - "torridOutlands.name" : "Жаркие Запределья", - "torridOutlands.subname" : "Тихий Ожог", - "torridOutlands.story" : "Даже в костюме я чувствую, как пульсирующие солнечные лучи пронзают и изгибают атмосферу этой чёртовой планеты. Мне становится все труднее сохранять ясность ума в такой жаре, а конденсат, начинающий покрывать стенки моего шлема, тоже не помогает. Хотя без него мои легкие были бы наполовину заполнены всей пылью и мусором, которые я поднял во время своих скитаний — «найти золотую середину» и тому подобное. Когда солнце высоко в небе, даже сама мысль о том, чтобы с легкостью обозревать окружающую среду, не более чем несбыточная мечта. В своем бреду я легко натыкаюсь на кактусы и валуны, находя краткую передышку внутри этих раскинувшихся туннелей и пещер, усеивающих поверхность. Поскольку неуклюжая местность замедляет мое продвижение, пот, стекающий по моему лбу к моим глазам, наверняка будет наименьшей из моих забот." - } -} diff --git a/Language/russian.lua b/Language/russian.lua new file mode 100644 index 00000000..fb34566f --- /dev/null +++ b/Language/russian.lua @@ -0,0 +1,566 @@ +return { + difficulty = { + typhoon = { + name = "Тайфун", + description = "Максимальная сложность.\nПланета — кошмар, выживание — всего лишь иллюзия.\nНикто не способен остаться в живых.", + }, + }, + + survivor = { + executioner = { + name = "Палач", + nameUpper = "ПАЛАЧ", + description = "Палач — подвижный боец, специализирующийся в подсчете трупов. Используя Ионные проекторы, Палач заставляет противников убегать в страхе, а также проецирует топор для устранения сильнейших из них. Цепочки убийств, сделанные с помощью «Ионной Вспышки» и «Казни» помогут продолжать наносить урон.", + endQuote = "..и он улетел, с неудовлетворенной жаждой крови.", + story = "

                                                                                                                                              Сведения о пассажире:\n[Военный]\n\n
                                                                                                                                                Багаж и снаряжение:\nОфицер поднялся на борт в форме исполняющего обязанности офицера EXN из углеродного волокна, гражданском комбинезоне с пластинами и с выданным служебным пистолетом. На военном контрольно-пропускном пункте было отмечено, что вооружение пассажира имело дополнительную подствольную модификацию и было проверено после того, как было достигнуто соглашение о хранении вооружения в безопасном контейнере. Пассажир также проверил одну батарею серии ION 204X, настроенную с отверстиями для пропульсивной установки. В качестве меры безопасности технологические товары пассажира были временно оснащены электрическими автонуллификаторами и возвращены им.\n\n[ПРИМЕЧАНИЯ СЛУЖБЫ БЕЗОПАСНОСТИ]\n
                                                                                                                                                  [Запись E1a] Несколько сообщений о пассажире заставили весь персонал контрольно-пропускного пункта беспокоиться. Легкое наблюдение будет обязательным до дальнейшего уведомления.\n
                                                                                                                                                    [Запись E1b] Пассажир часто отвечал звуками, а не речью, прежде чем решил использовать универсальный язык жестов.\n
                                                                                                                                                      [Случай E1] Контрактный спецназовец проводит проверку безопасности в каютах пассажиров.\n
                                                                                                                                                        [Случай E1a] Контрактный спецназовец запрашивает круглосуточное частное наблюдение в зале, ведущем в каюты пассажиров.\n
                                                                                                                                                          [Случай E2] Пассажира попросили передвинуть столы во время еды после того, как другой пассажир отправил официальную жалобу в службу безопасности на борту.", + id = "Travel ID: 4383354378334FF3D34D", + departure = "Пункт отправления:\nСтарый Алькатрас,\nРастборо,\nМарс", + arrival = "Пункт назначения:\nСтиллуотер-Бей,\nНьюдредж,\nЕвропа", + }, + mule = { + name = "МУЛ", + nameUpper = "МУЛ", + description = "КАЖДАЯ МОДЕЛЬ МУЛА ОБОРУДОВАНА И ГОТОВА К РАБОТЕ. СОЗДАНА ИЗ ВЫСОКОКАЧЕСТВЕННЫХ МАТЕРИАЛОВ ДЛЯ МАКСИМАЛЬНОЙ ПРОИЗВОДИТЕЛЬНОСТИ РАБОТЫ. ДАННАЯ МОДЕЛЬ ВКЛЮЧАЕТ НАБОР ИНСТРУМЕНТОВ ДЛЯ ДОБЫЧИ РЕСУРСОВ, ИДЕАЛЬНАЯ ДЛЯ ОХОТЫ, ИССЛЕДОВАНИЯ И ДРУГИХ ПОРУЧЕНИЙ, ОПАСНЫХ ДЛЯ ОБЫЧНОГО ПОЛЬЗОВАТЕЛЯ. ДЛЯ ИНИЦИАЛИЗАЦИИ УКАЖИТЕ ДИРЕКТИВУ ПРИ ЗАГРУЗКЕ СИСТЕМЫ.", + endQuote = "..и он улетел, со скрипом поршней, без директивы.", + story = "
                                                                                                                                                            Дата истечения гарантии: \n8/16/2041 \n\nЗаметка: \n(ID: #1244C4F4F5D494) \nЗАМЕТКА: МУЛ - МОДЕЛЬ F \nНе хочется попадать в неприятности из-за «порчи корабельного имущества», но недавно я включил этого МУЛа и отвёз его в ремонт. Он просто лежал, поэтому я решил, что никто не будет против, если я его немного подправлю. Слышал разговоры о том, что персонал хочет выбросить его перед взлётом, но я просто не могу позволить так обращаться с такой прекрасной работой... он, конечно, отрада для глаз, но, согласитесь, такого не каждый день увидишь! Многоцелевые модели давно не рассматривались с тех пор, как первая четвероногая модель стала хитом. К тому же, мы все знаем, на что способны подобные боты! С таким шасси, я не сомневаюсь, что этот здоровяк покорил довольно ухабистую местность. Он что-то вроде ходячей капсулы времени, не так ли? \nВсе необходимые кадры прикреплены ниже бла-бла-бла— Слушайте, я буду с вами откровенен. Просто... подумайте о том, чтобы оставить его здесь. Хотя бы на время. Правдивы эти слухи или нет... Думаю, его хозяин был бы рад, ну... знать, что он всё ещё на ходу. \nПросто займите его чем нибудь. Дайте ему ещё один шанс. \n\n[ОТЧЕТ ПО ТЕХОБСЛУЖИВАНИЮ] \n
                                                                                                                                                              [Отчет M1] После включения МУЛ оставался полностью неподвижным примерно до 20 минут, пока не была получена устная команда на запуск вспомогательного дрона. МУЛ продемонстрировал работу в режиме ожидания и протоколов только после получения команды. \n
                                                                                                                                                                [Отчет M1a] Аккумулятор вспомогательного дрона оказался испорченным. Совместимой замены найти не удалось, поэтому пришлось заряжать. Срок службы аккумулятора не гарантируется. \n
                                                                                                                                                                  [Отчет M2] МУЛ был ознакомлен с пассажирами и персоналом «Контакт Лайт» с помощью распознавания лиц после внезапной попытки связать механика, которого он не смог распознать. Большая часть персонала UES теперь записана в его памяти. \n
                                                                                                                                                                    [Отчет M2a] Удаленный доступ к памяти МУЛа показывает предыдущие записи в базе данных, датируемые 2036 годом. Первоначальные записи в его памяти оказались испорченными или удалёнными.", + }, + nemesisCommando = { + name = "Немезис Коммандо", + nameUpper = "НЕМЕЗИС КОММАНДО", + description = "Немезис Коммандо — гибкий персонаж с огневой мощью, способной справиться с любой ситуацией. Наносите раны на врагов, используя «Клинок Прекращения», чтобы подготовиться к разрушительному урону от других ваших способностей. Используйте «Тактический Перекат», чтобы входить и выходить из ближнего боя и обращать даже самые опасные ситуации в вашу пользу.", + endQuote = "..и он улетел, с новым чувством человечности внутри.", + story = "НЕЗАРЕГИСТРИРОВАННАЯ ЗАПИСЬ ПОЛЕВЫХ ДАННЫХ: ДАННЫЕ ИЗМЕНЕНЫ В [##090??!?] \n[ОШИБКА 0x00000ce: Пакет Данных Поврежден, загрузка с точки восстановления.] \n\n
                                                                                                                                                                      [Запись 1] - Патрульная группа только что подобрала раненого отставшего в нескольких милях от лагеря. Ходят слухи, что он «появился из ниоткуда» (ага, как будто я в такое поверю), и в окрестностях нет ничего, что могло бы объяснить его происхождение. Независимо от обоснованности их заявлений, выживший есть выживший. Пока я пишу это, мы несем его обратно. Он, конечно, не облегчает нам задачу, учитывая огромный вес его снаряжения — он тащит какие-то нетрадиционные модификации стандартной брони коммандо. Похоже, это — или, может быть, изначально была — первоначальная версия модели Mk. 1 (хотя я почти уверен, что они прекратили её производство много лет назад?). \n\n
                                                                                                                                                                        [Запись 2] - Обновление: у приятеля есть пугающе тяжелая артиллерия для бойца коммандо — ракетная установка, все еще теплая на ощупь, и несколько почти наверняка инопланетных гранат — я чертовски уверен, что ничего подобного не видел. Сейчас он без сознания, и, судя по тому, что мы можем сказать, глядя через его шлем, он изрядно потрепан. Его левая рука кажется особенно жесткой — его ладонь сжата вокруг рукояти его виброклинка. Некоторые из нас только что попытались хорошенько его рассмотреть, но похоже, что его кулак сросся с этой чертовой штукой. Среди прочего на нем, он полностью покрыт этим корким черным... веществом. Мы не знаем, было ли это результатом драки между несколькими выжившими или свидетельством какой-то самообо??!0049### \n[Конец читаемых данных.] \n\n
                                                                                                                                                                          [Запись 2a] - Я не знаю сколько времени у меня осталось пока я печатаю оно проснулось лагерь РАЗРУШЕН люди текут кровью мы даже не знаем человек это учитывая что он может сделать никто не знает что делать он побежал прямо на Адриана \nмы даже не знаем что его побудило??? что МЫ сделали чтобы заслужить все это??? Почему ЭТО СУЩЕСТВО напало на НА?!??!?##$0959090302???! \n[Конец читаемых данных.] \n\n

                                                                                                                                                                          Ово??ды р?оятся в?округ это?го бла?городного з?ве?ря, п?ока он па?сется. \n\nВаш?а з??араза бу?дет уб??рана от?сюда э??ти?ми тру?дящи??мися рук?а?ми.", + }, + nemesisMercenary = { + name = "Немезис Наёмник", + nameUpper = "НЕМЕЗИС НАЁМНИК", + description = "Немезис Наёмник использует грязные приёмы и безжалостную скорость, чтобы сокрушить наступающих врагов. Ослабляйте противников с помощью «Быстрого Курка», восстанавливайте боеприпасы с помощью «Ослепляющего Скольжения» и без труда уничтожайте оставшихся целей с помощью «Добивания».", + endQuote = "..и он улетел, щедро заплаченный кровью и опустошением.", + story = "

                                                                                                                                                                          I DON'T NEED NO FRIENDS\nI DON'T NEED NO PHONE\nJUST A BAG OF SEVERED HEADS\nAND MY NUCLEAR THRONE" + }, + technician = { + name = "Техник", + nameUpper = "ТЕХНИК", + description = "Техник отлично справляется с созданием и поддержанием зон недопущения противника. «Принудительное Отключение» может принудительно заблокировать область при улучшении. Улучшение гаджетов повышает их эффективность, но будьте осторожны, делая это в опасных ситуациях!", + endQuote = "...и он улетел, непоправимо поврежденный.", + story = "

                                                                                                                                                                            Сведения о пассажире:\n[Сотрудник]\n\n
                                                                                                                                                                              Сведения о сотруднике:\nСотрудник, нанятый по контракту для работы на месте в течение всего запланированного рейса UES «Контакт Лайт», для выполнения технического обслуживания оборудования в случае возникновения технических проблем на борту судна. Сотрудник имеет квалификацию с 6-летним практическим опытом в области разработки программного и аппаратного обеспечения.\n\n
                                                                                                                                                                                Багаж и снаряжение:\nСотрудник поднялся на борт в сварочном скафандре Durarend, рассчитанном на высокую устойчивость к вакууму, а также в одежде из кожи и ткани, предназначенной для планетарного износа. Сотрудник уведомлен о том, что одежда может порваться в случае возникновения вакуума. Охрана отметила большой гаечный ключ, который они держали, как непрактичный для использования, хотя и потенциально опасный. После допроса сотрудник указал, что он был «изготовлен на заказ ... для самых сложных работ». После процедуры гаечный ключ был перемещен в автоматизированный сейф в погрузочном отсеке 1a. Во время осмотра оставшейся ручной клади было обнаружено несколько стандартных гаечных ключей, 3 цветных USB-накопителя, ноутбук GeForm модели 2047, ящики для инструментов и сварочный аппарат. USB-накопители были проверены на принадлежащем компании одноразовом компьютере QPuting, на котором был установлен детектор ReoVirus Detector v1.6.2. Следов вредоносного ПО обнаружено не было, а сохраненные файлы имеют формат UPG и содержат чертежи различных устройств. Сотруднику разрешили пройти без дальнейших трудностей.\n\n[ПРИМЕЧАНИЯ СЛУЖБЫ БЕЗОПАСНОСТИ]\n
                                                                                                                                                                                  [Случай T1] Сотрудника принудительно вывели из кабины во время подготовки к взлету, когда он отказался покинуть её после многочасового разговора с пилотом корабля.\n
                                                                                                                                                                                    [Случай T2] Сотрудник был замечен крадя газировку из торгового автомата.\n
                                                                                                                                                                                      [Случай T3] Автоматическая блокировка, возникшая в погрузочном отсеке 4a, устранена сотрудником.\n
                                                                                                                                                                                        [Случай T4] Статус спасательной капсулы B-08 изменился с «ОШИБКА» на «ВНИМАНИЕ» после того, как персонал судна попросил сотрудника устранить неполадки. Сотрудникам службы безопасности поручено провести расследование.\n
                                                                                                                                                                                          [Случай T1a] Сотрудник также сообщил о его вмешательстве в работу панели управления кабины, чтобы «облегчить круиз». Запланированный запуск отложен, и сотруднику поручено отменить все внесенные изменения.", + id = "Travel ID: 54E4F4C434F59474E454", + departure = "Пункт отправления:\nПорт UES 5,\nРедвью,\nМарс", + arrival = "Пункт назначения:\nПорт UES 0,\nРедвью,\nМарс", + }, + swapNemesis = { + name = "Nemesis Survivors", + nameUpper = "NEMESIS SURVIVORS", + description = "Swap to the familiar yet twisted Nemesis survivors." + }, + swapNormal = { + name = "Normal Survivors", + nameUpper = "NORMAL SURVIVORS", + description = "Swap to the regular set of survivors." + } + }, + + skill = { + executionerZ = { + name = "Служебный Пистолет", + description = "Вы стреляете, нанося 100% урона.", + }, + executionerX = { + name = "Ионный Залп", + description = "Вы быстро стреляете ионизированными пулями, нанося 320% урона за каждый выстрел.\nВы получаете пули при убийстве врагов.", + }, + executionerC = { + name = "Рассеивание Толпы", + description = "Вы делаете рывок вперёд, распугивая ближайших врагов.\nВас нельзя атаковать во время рывка.", + }, + executionerV = { + name = "Казнь", + description = "Вы подлетаете в воздух, и бросаетесь вниз с проецируемом топором, нанося 1000% урона.\nКазни ускоряют перезарядку навыков на одну секунду.", + }, + executionerVBoosted = { + name = "Казнь Толпы", + description = "Вы подлетаете в воздух, и бросаетесь вниз с проецируемом топором, нанося 1500% урона и распугивая врагов.\nКазни ускоряют перезарядку навыков на одну секунду.", + }, + executionerV2 = { + name = "Черепокромсатель", + description = "Вы кидаете проецируемый топор дугой, нанося 1500% урона в секунду. Урон удвоен против испуганных врагов.\nКазни ускоряют перезарядку навыков на одну секунду.", + }, + executionerV2Boosted = { + name = "Черепоразрушитель", + description = "Вы кидаете два проецируемых топора дугой, нанося 1500% урона в секунду. Урон удвоен против испуганных врагов.\nКазни ускоряют перезарядку навыков на одну секунду.", + }, + muleZ = { + name = "УДАЛЕНИЕ ПОМЕХ", + description = "ВЫ БЬЁТЕ ВРАГОВ, НАНОСЯ 200% УРОНА. УДЕРЖИВАЙТЕ, ЧТОБЫ ЗАРЯДИТЬ УДАР ОБ ЗЕМЛЮ, НАНОСЯЩИЙ 1400% УРОНА.", + }, + muleX = { + name = "СВЯЗЫВАНИЕ", + description = "ВЫ СТРЕЛЯЕТЕ ЛОВУШКОЙ, НАНОСЯ 125% УРОНА. ЛОВУШКА ОБЕЗДВИЖИВАЕТ ДО 5 ВРАГОВ РЯДОМ С ЦЕЛЬЮ.", + }, + muleC = { + name = "КАЛИБРАЦИЯ ПОРШНЕЙ", + description = "ВЫ КРУТИТЕСЬ ВПЕРЕД, НАНОСЯ 4X100% УРОНА. ПОСЛЕДНИЙ УДАР ОГЛУШАЕТ.", + }, + muleV = { + name = "АВАРИЙНАЯ ПОМОЩЬ", + description = "ВЫ ЗАПУСКАЕТЕ РЕМОНТНЫЙ ДРОН НА 8 СЕКУНД, ИСЦЕЛЯЮЩИЙ ВАС НА 5X7% ОТ ВАШЕГО МАКСИМАЛЬНОГО ЗДОРОВЬЯ. ПРИ ПОЛНОМ ЗДОРОВЬЕ, ДАЕТ БАРЬЕР ВМЕСТО ИСЦЕЛЕНИЯ.", + }, + muleVBoosted = { + name = "АВАРИЙНАЯ ПОМОЩЬ 2.0", + description = "ВЫ ЗАПУСКАЕТЕ РЕМОНТНЫЙ ДРОН НА 8 СЕКУНД, ИСЦЕЛЯЮЩИЙ ВАС НА 5X10% ОТ ВАШЕГО МАКСИМАЛЬНОГО ЗДОРОВЬЯ. ПРИ ПОЛНОМ ЗДОРОВЬЕ, ДАЕТ БАРЬЕР ВМЕСТО ИСЦЕЛЕНИЯ.", + }, + nemesisCommandoZ = { + name = "Клинок Прекращения", + description = "Вы режете врагов поблизости, нанося 120% урона, раня их на 6 секунд. Раны не складываются.\nРаненные враги получают +50% урона от всех источников.", + }, + nemesisCommandoX = { + name = "Одиночный Выстрел", + description = "Вы стреляете, нанося 200% урона.\nЗарядите до 4 патронов в магазин. Перезарядите 2 патрона при перекате.", + }, + nemesisCommandoX2 = { + name = "Дальняя Рана", + description = "Вы взмахиваете прямо по линии, нанося 90% урона, раня врагов на 6 секунд.", + }, + nemesisCommandoC = { + name = "Тактический Перекат", + description = "Вы быстро перекатываетесь вперёд на короткую дистанцию.\nВы неуязвимы во время переката. Можно перекатиться до 2 раз.", + }, + nemesisCommandoV = { + name = "Раскрытие", + description = "Вы кидаете гранату, нанося 700% урона. Удерживайте, чтобы готовить гранату.\nНажмите вниз, чтобы сбросить гранату ниже.", + }, + nemesisCommandoVBoosted = { + name = "Кассетная Бомба", + description = "Вы кидаете гранату, нанося 700% урона и оглушая врагов. Граната ррасколывается на 3 бомбы, нанося 3x150% урона.\nУдерживайте, чтобы готовить гранату.\nНажмите вниз, чтобы сбросить гранату ниже.", + }, + nemesisCommandoV2 = { + name = "Опустошитель", + description = "Вы запускаете ракету, нанося 1000% урона одному врагу. Враги поблизости получают 50% урона и оглушаются.\nВ воздухе вы запускаете ракету диагонально вниз.", + }, + nemesisCommandoV2Boosted = { + name = "Спанкер", + description = "Вы запускаете ракету, нанося 1000% урона одному врагу. Враги поблизости получают 50% урона и оглушаются.\nВ воздухе вы запускаете ракету диагонально вниз.\nДержите до 2 ракет.", + }, + nemesisMercenaryZ = { + name = "Резание", + description = "Вы быстро взмахивайте перед собой, нанося 100% урона.", + }, + nemesisMercenaryX = { + name = "Быстрый Курок", + description = "Вы стреляйте из дробовика, оглушая врагов и нанося им 400% урона.", + }, + nemesisMercenaryC = { + name = "Ослепительное Скольжение", + description = "Удерживайте кнопку, чтобы оборониться и скользнуть вперед.\nБлокировка атак при обороне перезаряжает ваш дробовик.\nВы можете атаковать во время скольжения.", + }, + nemesisMercenaryV = { + name = "Добивание", + description = "Вы атакуете самого слабого врага вблизи, нанося 700% урона.\nПриоритизирует и критически аттакует оглушенных врагов.\nУбийство врага с помощью этого навыка перезаряжает его.", + }, + nemesisMercenaryVBoosted = { + name = "Абсолютное Добивание", + description = "Вы атакуете самого слабого врага вблизи, нанося 700% урона.\nПриоритизирует и критически аттакует оглушенных врагов.\nМгновенно убивает врагов, если у них меньше 25% здоровья.", + }, + technicianZ = { + name = "Тонкая Настройка", + description = "Вы ударяете гаечным ключом, нанося 180% урона.\nВременно улучшите гаджеты на 30 секунд, ударив по ним 3 раза.", + }, + technicianZ2 = { + name = "Устранение Неполадок", + description = "Вы кидаете гаечный ключ, проходя до 3 врагов насквозь, нанося 180% урона.\nВременно улучшите гаджеты на 20 секунд, ударив по ним 3 раза.", + }, + technicianX = { + name = "Принудительное Выключение", + description = "Вы кидаете оглушающую бомбу, нанося 700% урона при активации.\nЗатягивает врагов когда улучшена.", + }, + technicianXD = { + name = "Красная Кнопка", + description = "Вы взрываете выкинутую бомбу, нанося 700% урона и оглушая врагов.", + }, + technicianC = { + name = "Заряд 24/7", + description = "Вы ставите торговый автомат, дающий бонус к скорости скорости передвижения и атаки.\nУвеличивает шанс критического удара когда улучшен.", + }, + technicianC2 = { + name = "Радиальный Усилитель", + description = "Вы ставите антенну, наносящую 15% бонусного урона атакованным врагам.\nУвеличивает шанс критического удара на 100% когда улучшен.", + }, + technicianV = { + name = "Резервный Брандмауэр", + description = "Вы ставите турель, которая наносит 220% урона в секунду.\nСтреляет быстрее, нанося 350% урона в секунду когда улучшена.", + }, + technicianVBoosted = { + name = "Резервный Брандмауэр 2.0", + description = "Вы ставите турель, стреляющую быстро, нанося 350% урона в секунду.\nВыпускает ракеты, нанося 4x100% урона когда улучшена.", + }, + peace = { + name = "<#a4eae4>Перемирие!", + description = "Вы не можете использовать этот навык.", + }, + }, + + item = { + armedBackpack = { + name = "Вооруженный Рюкзак", + pickup = "Дает вероятность выстрелить позади вас.", + description = "Дает 18.5%-ю (+6.5% за шт.) вероятность при атаке выстерлить позади вас, нанося 150% урона.", + destination = "832B,\nАутнуи,\nЗемля", + date = "07.11.2056", + story = "Быть в безопасности всегда важно. Мне не хочется, чтобы ты попал в неприятности, поэтому, пожалуйста, надевай его, когда выходишь из дома. Особенно в каньонах, там много воров!\nЯ отправлю тебе немного запасных патронов позже в этом году, хорошо?", + priority = "стандартный", + }, + brassKnuckles = { + name = "Кастет", + pickup = "Наносит дополнительный урок врагам вблизи.", + description = "Наносит +35% урона врагам в радиусе 5 м (+2.5 м за шт.).", + destination = "Полиция UES,\nE42,\nUES Aлебарда B2", + date = "01.03.2056", + story = "Мы нашли это на месте преступления. Нам нужно сравнить ДНК с остальными уликами. Я все еще не могу поверить, что им удалось уйти от ответственности.\nНа обратной стороне есть какой-то символ, может быть он даст нам подсказку. Я направляюсь на запад [УДАЛЕНО], я знаю кого-то, кто может помочь нам раскрыть это дело.", + priority = "стандартный/биоматериал", + }, + detritiveTrematode = { + name = "Детритивная Трематода", + pickup = "Враги с низким запасом здоровья получают урон за промежуток времени", + description = "Враги, у которых меньше 10% (+5% за шт.) здоровья становятся зараженными, получая 2% от макс. здоровья в качестве урона в секунду.", + destination = "112,\nНеус 2,\nМарс", + date = "11.03.2057", + story = "Ех, это твое.\nНИКОГДА больше не отправляй мне эти странные инкубаторы для микроскопических яиц, пожалуйста.", + priority = "приоритетный/биоматериал", + }, + dormantFungus = { + name = "Дремлегриб", + pickup = "Востанавливает здоровье во время движения.", + description = "Востанавливает 2% (+2% за шт.) от вашего здоровья каждые 2 секунды во время движения.", + destination = "Шлюз 1,\nМодуль 13,\nСтанция Геа", + date = "01.11.2056", + story = "Когда вы велели мне исследовать грибные пещеры, я ожидал найти только... ну, грибы, разумеется. Хоть я не был особо в восторге, этот образец смог привлечь мое внимание.\nПохоже, он растет только около текущей воды, и с тех пор как я собрал и поместил этот образец, его цвет и запах быстро исчезли.\n\nНадеюсь, вы сможете изучить его дальше.", + priority = "стандартный", + }, + fork = { + name = "Вилка", + pickup = "Увеличивает урон.", + description = "Увеличивает базовый урон на 3 (+3 за шт.).", + destination = "Здание Антимы - Крыша,\nЦветущие Холмы,\nЗемля", + date = "24.09.2056", + story = "Сюрприз! Вилка! Ха-ха-ха!!!\nТы сказал, что любой подарок — это хороший подарок, НУ ТАК ВОТ! Шутки шутками, а вилка эта — волшебная. Поверь мне, Дункан, однажды мне удалось съесть ею салат, а позже в тот же день я нашел цент!\nИ, эм, пожалуйста, поиграй с нами еще как-нибудь, мы соскучились по уничтожению твоей сборки в SS2 (пожалуйста, скажи что ты поменял своего мэйна, лол).", + priority = "стандартный", + }, + watchMetronome = { + name = "Часовой Метроном", + pickup = "Зарядите часы, постояв неподвижно. Ускоряет передвижение при полном заряде.", + description = "Постояв неподвижно, зарядите часы за 3 секунды. При полном заряде, увеличьте скорость передвижения на 50%. Разряжается за 3 секунды (+1 за шт.).", + destination = "Клетка 1,\nG-44 (Подземелье),\nЗемля", + date = "05.09.2056", + story = "Дедушка подарил мне их как пережиток прошлого.. Он говорит, что они принадлежали нашему пра-пра-пра-прадеду. Мне все равно на антиквариат и семейные вещи, так что думаю, будет лучше, если ты заберешь их.\nНе то чтобы они вообще могли где-то пригодится..", + priority = "приоритетный", + }, + wonderHerbs = { + name = "Чудо-травы", + pickup = "Немного увеличивает все исцеляющие эффекты.", + description = "Увеличивает исцеление от всех источников на 12% (+12% за шт.).", + destination = "2342,\nУчасток 3,\nЗемля", + date = "07.11.2056", + story = "Рикка! Я слышал, ты работаешь с НП над созданием нового рецепта. Здесь, на ферме, мы выращиваем специальные травы, которые, мы думаем, могут пригодиться. Они известны своей очень-очень полезной способностью лечить физические и эмоциональные раны.\nЕсли тебе нужно больше, не стесняйся звонить мне! Люблю, целую.\n— Эден", + priority = "стандартный", + }, + coffeeBag = { + name = "Пакетик с Кофе", + pickup = "При взаимодействии с объектами увеличивает скорость передвижения и скорость атаки.", + description = "При взаимодействии с объектами увеличивает скорость передвижения и скорость атаки на 22% на 10 (+5 за шт.) секунд.", + destination = "n1,\nСтолица,\nUES PRIME", + date = "22.05.2056", + story = "Соооооооооооняяяяяяяяяяяяяяя хз поч ты говоришь что тебе не нравится кофе оно так бодрит. знаю знаю есть другие другие способы взбодрится но бож я думаю тебе оно зайдет, оно мое любимое!!! попробуй перед тем как кричать на меня лаадно?", + priority = "стандартный", + }, + x4Stimulant = { + name = "Стимулятор X-4", + pickup = "Уменьшает время перезарядки дополнительного навыка, и немного исцеляет вас при его использовании.", + description = "Уменьшает время перезарядки дополнительного навыка на 10% (+10% за шт.). Использование вашего дополнительного навыка увеличивает восстановление здоровья на 2 (+1 pза шт.) ед. здоровья в секундy на 3 секунды.", + destination = "Ромео 33,\nНациональный парк Батанг Ай,\nЗемля", + date = "12.04.2057", + story = "Упакован с мануалом. Показывает ли машина симптомы 540? Если да, то лучше избегать использования более 100 мл стимулятора.\n\nС уважением.", + priority = "стандартный", + }, + moltenCoin = { + name = "Расплавленная Монета", + pickup = "Дает вероятность при попадании поджечь врагов и дать вам золото", + description = "Дает 6%-ю вероятность при попадании поджечь врагов на 6 секунд, принося $1 (+$1 за шт.) золота.", + destination = "Тоэра 2,\nB44,\nМатеринская Станция", + date = "22.05.2056", + story = "Привет! Эм, извини, мне очень жаль.. Я знаю, ты очень хотел, чтобы я оставил эту монету себе, но я больше не могу брать на себя эту ответственность..\nВидишь ли, я случайно положил монету на край плазменной печи, и.. ну.. она немного подгорела с боку, пожалуйста, не злись на меня..", + priority = "стандартный", + }, + crypticSource = { + name = "Загадочный Источник", + pickup = "Создает вспышку энергии при смене направления движения.", + description = "При смене направления движения выпускает цепную молнию, наносящую 70% (+55% за шт.) урона по максимум 2 целям.", + destination = "O32,\nНижняя Часть,\nЗемля", + date = "30.03.2058", + story = "Все происходит из энергии, от атомов до разумных существ. Однако этот довольно загадочный объект, похоже, излучает энергию сам по себе. Высокие уровни трения, похоже, запускают цепную реакцию, делая его крайне нестабильным. Однако он также может быть управляемым источником (бесконечной?) энергии. Приведет ли это нас к утопическому будущему, о котором мы мечтаем, совершенно неясно, но это полностью меняет наши предыдущие представления о вселенной.", + priority = "нестабильный предмет", + }, + huntersSigil = { + name = "Символ Охотника", + pickup = "Постояв неподвижно, увеличьте броню и шанс критического удара.", + description = "После того, как вы постоите неподвижно 1 секунду, получите 15 (+10 за шт.) брони и 25% (+20% за шт.) шанса критического удара.", + destination = "2Восток,\nЗдание Бекхэм,\nЗемля", + date = "02.02.2056", + story = "Привет, Сетт, добро пожаловать в клуб. Мы искали кандидатов, и так как теперь ты с нами, мы сможем начать работать в следующем сезоне. Мы рассчитываем на тебя.\n\n— Ирикс, конец связи.", + priority = "приоритетный", + }, + roulette = { + name = "Рулетка", + pickup = "Получите случайное усиление каждую минуту.", + description = "Получите случайное усиление, которое перебрасывается каждую минуту. Возможные усиления:\n\n: Увеличьте максимальный запас здоровья на 60 (+24 за шт.).\n: Увеличьте восстановление здоровья на 3.6 (+1.8 за шт.) ед. здоровья в секунду.\n: Увеличьте базовый урон на 14 (+5 за шт.).\n: Увеличьте скорость атаки на 35% (+15.5% за шт.).\n: Увеличьте вероятность критического удара на 25% (+10% за шт.).\n: Увеличьте скорость передвижения на 20% (+7% за шт.).\n: Увеличьте броню на 35 (+14 за шт.).", + destination = "ПКомн. 3.1,\nКазино Секва,\nЗемля", + date = "17.07.2057", + story = "Замена Рулетки модели 144Bella-1 в связи с недавними событиями, произошедшими в казино. Для получения дополнительной информации, пожалуйста, свяжитесь с нами по электронному адресу, указанному нашим представителем", + priority = "приоритетный", + }, + judderingEgg = { + name = "Дрожащее Яйцо", + pickup = "Вылупите маленького друга!", + description = "Dылупите 1 (+1 за шт.) молодого змея, который сражается за вас и периодически атакует ближних врагов, нанося 18x80% урона.", + destination = "Ворота B,\nКомпаунд Ли,\nЗемля", + date = "21.11.2056", + story = "Еще один от заводчика, на этот раз это очень странный экземпляр.\nБудьте предельно осторожны, я слышал, что молодые особи могут стать очень агрессивными (и смертоносными!) за считанные секунды, если с ними неправильно обращаться. Не должно пройти много времени, пока он вылупится, так что лучше подготовьте для него структуру.", + priority = "хрупкий предмет", + }, + uraniumHorseshoe = { + name = "Урановая Подкова", + pickup = "Ускоряет передвижение и увеличивает высоту прыжка.", + description = "Увеличивает скорость передвижения и высоту прыжка на 10% (+10% за шт.)", + destination = "6900,\nУход за лошадьми и фермерские принадлежности от Марсианского Лица,\nМарс", + date = "20.04.2056", + story = "Мы нашли эту странную зеленую подкову на копыте одной из наших призовых выставочных лошадей после того, как она загадочно умерла поздно ночью.\nНаш конный врач сказал, что вы, ребята, можете знать что-то об этой странной подкове. Мне руку жжет, когда я просто держу её. Обращайтесь с ней осторожно.", + priority = "стандарт", + }, + malice = { + name = "Злоба", + pickup = "Распространяет урон по врагам поблизости.", + description = "Атаки

                                                                                                                                                                                          распространяются по врагам поблизости, нанося 50% ОБЩЕГО урона 1 (+1 за шт.) врагу в радиусе 4,2м. (+1м. за шт.).", + destination = "P24,\nRomeo,\nPol-A Station", + date = "11.02.2056", + story = "Сдерживайте любой ценой. Этот экземпляр может быть огромной угрозой, если с ним не обращаться правильно. Вы не знаете, сколько мы уже потеряли чтобы сдержать его.", + priority = "

                                                                                                                                                                                          злобный", + }, + manOWar = { + name = "Медуза-Кораблик", + pickup = "Выпускает электрический разряд при убийстве.", + description = "При убистве врага выпускает электрический разряд, наносящий 100% (+40% за шт.) урона по 2 врагам.", + destination = "Скрытый уголок,\nГора Сотворения,\nВенера", + date = "20.11.2056", + story = "Ребята, помните того маленького парня, которого я прислал не так давно? Ну... эта немного агрессивнее... хотя она забавная девчушка! Ей нравится крутиться и наблюдать за мной из бутылки. Господи, если бы я её не спас, она бы сейчас была в каком-нибудь инопланетном желудке.\nЯ как-то хочу назвать её Энн, как считаете?", + priority = "приоритетный", + }, + guardingAmulet = { + name = "Охраняющий Амулет", + pickup = "Уменьшает урон сзади.", + description = "Урон, полученный сзади уменьшен на 40% (+40% за шт.).", + destination = "C8,\nЛазурный Сад,\nСвятилище", + date = "11.03.2056", + story = "Я знаю, он где-то там, я знаю, что ты можешь найти его... но ты должен быть настойчивым. К тому времени, как моя душа покинет эту оболочку, слова будут сказаны, и ты займешь мое место. Он будет тем, кто покажет тебе путь к ответам, которые ты всегда хотел получить от меня.\nПрими этот амулет; ты будешь зависеть от него, и он не даст тебе никогда оглянуться назад.", + priority = "стандартный", + }, + distinctiveStick = { + name = "Отличительная Палка", + pickup = "Исцеляет рядом с телепортом.", + description = "Выращивает дерево возле телепорта, исцеляеющее союзников в 10 м (+3,75 м за шт.) от него на 2,2% от их здоровья каждые 2 секунды.", + destination = "Дрек Минели,\nтесафт,\nЗемля", + date = "15.01.2056", + story = "Дорогая, у меня есть для тебя что-то особенное..\nНа днях во время своей миссии я наткнулся на неё. Она привлекла мое внимание, потому что она не похожа ни на одну из веток, которые я видел в своей жизни! Эта ветка излучает особую энергию. Она заставила меня почувствовать себя связанным с природой так, как я думал, это возможно только в историях! В честь нашей годовщины я дарю её тебе.\nСкоро я вернусь домой с еще большим подарком. Пожалуйста, не теряй веру.", + priority = "стандартный", + }, + balloon = { + name = "Шарик", + pickup = "Уменьшает гравитацию вне опасности.", + description = "Уменьшает гравитацию пока вы держите кнопку прыжка на 35% (-15% за шт.). Лопается при получении урона. Востанавливается, если вы не получаете урон в течение 7 секунд.", + destination = "55 Mo.,\nПааре Плаза,\nЗемля", + date = "31.06.2056", + story = "Когда я был моложе, я любил воздушные шары. За ними довольно забавно наблюдать в местах с атмосферой. Однако этот имеет для меня особую ценность, так как он был одним из первых, сделанных из армированной резины, еще в 22-х годах. Надеюсь, ты побережешь его, через несколько лет он может стоить целое состояние (если все еще будет летать, конечно!).", + priority = "приоритетный/хрупкий предмет", + }, + iceTool = { + name = "Ледоруб", + pickup = "Дает прыжок от стены. Лазайте быстрее.", + description = "Дает 1 (+1 за шт.) дополнительный прыжок, пока вы возле стены. Увеличивает скорость лазания на 18% (+18% за шт.).", + destination = "Башня Мона #33,\nСолей Шор,\nЗемля", + date = "12.03.2056", + story = "Добрый день, товарищ по походу, я нашел ледоруб, который ты потерял в прошлый раз, когда мы ходили на гору [УДАЛЕНО]. Пожалуйста, следи за снегом в следующий раз!\nИнтересно, можно ли его еще использовать, он столько всего пережил!", + priority = "стандартный", + }, + needles = { + name = "Иголки", + pickup = "Дает вероятность при попадании пометить врага, гарантируя критические удары против него", + description = "Дает 3%-ю (+2% за шт.) вероятность при попадании пометить врага, увеличивая шанс критического удара на 100% против него на 3 секунды.", + destination = "E2,\nПетля Орена,\nВенера", + date = "[УДАЛЕНО]", + story = "Uh.. madre dice que si puedes leer esto, es porque no eres tan torpe como pensaba, y pues eso. Esperamos que te sirvan estas agujas. Me temo que no son las que pediste, pero no hace mucha diferencia, pienso.\nNo recuerdo si los enviamos bien embalados. Ten cuidado.", + priority = "пронизывающий", + }, + midas = { + name = "М.И.Д.А.С", + pickup = "Превращает половину вашего здоровья в золото.", + description = "Пожертвовав 50% здоровья, вы получаете $1 за единицу потерянного здоровья, плюс стоимость одного сундука.", + destination = "R A 5-5,\nПрайм,\nТитан", + date = "04.05.2056", + story = "\"Чудо от богов..\"\nТы продолжаешь повторяться снова и снова. С ума сошел? Ты продолжаешь обращаться с нами, как с невеждами, когда все, что мы пытались сделать, это СПАСТИ ТЕБЯ.\n\nСделай нам одолжение и забери его. Делай с ним что хочешь, мы больше НЕ хотим тебя здесь видеть. Мы НЕ хотим быть частью того, что происходит с тобой и этой штукой.\n\nОн делает тебя жадным, бездушным человеком, и я надеюсь, что ты это знаешь.", + priority = "нестабильный предмет", + }, + strangeCan = { + name = "Странная Консерва", + pickup = "Кидает консерву, помечая врага. При убийстве помеченного врага, выпускает токсичное облако.", + description = "Кидает необычную банку, которая отравляет врага с наивысшим максимальным запасом здоровья. Отравленные враги выпускают токсичное облако при убийстве, нанося 50% максимального запаса здоровья врага в качестве урона за 5 секунд.", + destination = "1530,\n563,\nA-LC12", + date = "22.05.2056", + story = "Такая же вкуснотища, как я тебе и говорил. Надеюсь, что она не треснет по пути, как последняя.\nНайти бы мне работу...", + priority = "нестабильный предмет", + }, + whiteFlag = { + name = "Белый Флаг", + pickup = "Ставит белый флаг. Никто вокруг него не может атаковать.", + description = "Ставит белый флаг на 8 секунд. Отключает навыки всем в его радиусе.\nНе влияет на дронов и летающих врагов.", + destination = "Комната 2B,\nОтель Сомнус,\nЗемля", + date = "5.10.2056", + story = "Похрани его у себя, пока я не вернусь домой, мне он не понадобился. На самом деле, мы даже подружились! Не могу дождаться, чтобы рассказать тебе обо всем этом поскорее. Эта поездка была долгой, а череда событий неожиданная. Я рассказал им о тебе, и они хотят, чтобы я пригласил тебя в следующий раз. Как тебе это, а!? Люблю тебя.", + priority = "нестабильный предмет", + }, + eclipseGummies = { + name = "Мармеладки Затмение", + pickup = "Выпускает усиливающие мармеладки при убийстве.", + description = "При убистве врага выпускает усиливающую мармеладку, повыщающую скорость аттаки и передвижения на 2.5% (+2.5% за шт.) до 25% (+25% за шт.) на 10 секунд.", + destination = "199\nАвраам-стрит\nЗемля", + date = "16.8.2056", + story = "Привет, Дани! Это Ви, ты не поверишь, что я только что нашла!. Коробка старых Мармеладок Затмение! На работе мы убирались в старом семейном магазине, и я нашла эту нераскрытую коробку на складе. Начальник разрешил мне оставить её, иначе мы бы просто её выбросили. Прошло почти 8 лет с тех пор, как я пробовала их. О, маленькая Франческа Фрог, Робот Рег и все остальные фруктовые вкусы. Для шоу, сделанного исключительно для продажи мерча, оно оставило след в моём детстве. Я так рада, что эти конфеты на вкус такие же, как и тогда. Даже вызывают те же приятные ощущения, что и тогда. Заставляет меня задуматься, почему они перестали продавать такие популярные мармеладки? В любом случае, я не могу забрать все себе, поэтому отправляю немного тебе, чтобы тебе тоже попробовать. Наслаждайся! :heart:", + priority = "стандартный" + } + }, + + artifact = { + multitude = { + pickupName = "Артефакт Множества", + name = "Множество", + description = "Враги наступают ордами.", + approaching = "Орда врагов приближается.", + arriving = "Приготовтесь...", + }, + displacement = { + pickupName = "Артефакт Перемещения", + name = "Перемещение", + description = "Окружения перетасованы.", + }, + gathering = { + pickupName = "Артефакт Собирания", + name = "Собирание", + description = "Золото удвоенно, но должно быть собрано.", + }, + }, + + + actor = { + Exploder = { + name = "Взрыватель" + }, + Mimic = { + name = "Охранный Сундук" + }, + Admonitor = { + name = "Глиняный Наставник" + }, + Gatekeeper = { + name = "Сторож" + }, + Protector = { + name = "Защитник", + text = "Хранитель Артефакта" + } + }, + + monster = { + admonitor = { + name = "Глиняный Наставник", + story = "Вдалеке знакомый глинянный «человек» плелся по территории храма. Однако, несмотря на схожесть внешности, это существо было не одним из тех, что я видел раньше. В отличие от своих меньших собратьев, ему не хватало ловкости, но этот горшечный защитник имел два стиснутых кулака. Его походка придавала ему почти... уверенный вид. Знаю, глупо было бы полагать, что оно не способно на разумные эмоции вроде убеждённости. Тем не менее, заботясь о своём благополучии, я решил не испытывать на прочность это качающееся смоляное пятно и спрятался в раскопанных руинах. \n\nЕсли я хочу выбраться с этой планеты живым, я предпочту, чтобы моя бедренная кость была цела." + }, + exploder = { + name = "Взрыватель", + story = "Нет ничего, что я ненавижу больше, чем когда меня преследуют эти существа. Хотя с ними легко справиться в одиночку, когда они появляются стаями, мне лучше убежать. Как будто вся их кровь пропитана вязкой, кислотной субстанцией, которая одной каплей смогла растворить схемы одного из моих дронов. Я... даже не хочу знать, что она сделает с моей кожей." + }, + mimic = { + name = "Охранный Сундук", + story = "Кому вообще пришла в голову эта безумная идея — сделать сундуки с системой защиты от сбоев? Я понимаю, они хотят усложнить жизнь пиратам, но, наверное, не подумали о чрезвычайных ситуациях. Я никогда не привыкну гоняться за механическим ящиком, потому что он думает, что я что-то украл. Или, может, дело в ягодах, которые я съел." + } + }, + + interactable = { + MimicInactive = { + text = "..?", + name = "Большой Сундук?" + }, + ChirrsmasPresent = { + text = "Откройте подарок!", + name = "Подарок" + } + }, + + elite = { + poison = { + name = "%s яда", + }, + empyrean = { + name = "%s Эмпиреи", + text = "Предвестник Суда", + worm = "Призматический Левиафан" + } + }, + + stage = { + whistlingBasin = { + name = "Свистящий Бассейн", + subname = "Исчезающий Оазис", + story = "Эта планета продолжает приносить мне одни лишь сюрпризы. Учитывая действия её обитателей до прибытия сюда, я бы никогда не подумал, что такие существа могли построить такую размашистую и декадентскую архитектуру. Невероятно глубокие эбеновые колонны и арки, построенные из прекрасного камня, которые возвышаются над землей, все украшены резьбой, нанесенной намеренно руками. Вдоль всех стен тянутся эти удивительно замысловатые проявления обманчиво простых форм. Самая необычная бирюзовая листва атакует сооружения, тонко поднимаясь по их основаниям. Кажется, предвзятое представление о дикости, которое я питал к этой планете, ослабевает. И все же моя бдительность высока. Мне еще слишком рано отдыхать, несмотря на протесты моих ноющих мышц." + }, + torridOutlands = { + name = "Жаркие Запределья", + subname = "Тихий Ожог", + story = "Даже в костюме я чувствую, как пульсирующие солнечные лучи пронзают и изгибают атмосферу этой чёртовой планеты. Мне становится все труднее сохранять ясность ума в такой жаре, а конденсат, начинающий покрывать стенки моего шлема, тоже не помогает. Хотя без него мои легкие были бы наполовину заполнены всей пылью и мусором, которые я поднял во время своих скитаний — «найти золотую середину» и тому подобное. Когда солнце высоко в небе, даже сама мысль о том, чтобы с легкостью обозревать окружающую среду, не более чем несбыточная мечта. В своем бреду я легко натыкаюсь на кактусы и валуны, находя краткую передышку внутри этих раскинувшихся туннелей и пещер, усеивающих поверхность. Поскольку неуклюжая местность замедляет мое продвижение, пот, стекающий по моему лбу к моим глазам, наверняка будет наименьшей из моих забот." + }, + verdantWoodland = { + name = "Зеленые Леса", + subname = "Дышащие Растание", + story = "Передо мной раскинулся лабиринт из самых больших деревьев, которые я когда-либо видел, — сковывающее меня чудо природы, покрытое чешуйками коры и зубцами, похожими на занозы. Эти столбы древесины бесконечно возвышаются, защищая этот богатый акрополь — их властное присутствие заставляет меня задуматься о своих действиях, когда я пробираюсь сквозь заросли. Я едва ли могу винить эти острые колючки, обвивающие мой костюм. С каждым пронзительным шагом они напоминают мне, что я всего лишь гость в их крепости. Кипящие гнезда насекомых извергают своих рыцарей, уводя меня от своих детенышей, как мы с вами отводили бы своих собственных. Я слышу над головой рои разъяренных насекомых, щебечущих, как птицы. Это напоминает мне о доме. Я больше всего на свете хочу лечь на сочную траву, которую подарила мне эта невероятная планета, но я должен двигаться дальше. Меланхоличные муки жгут меня изнутри. Этот ядовитый порыв, циркулирующий в моих венах и дающий мне волю продолжать бороться... Я не могу не сожалеть о его присутствии.\n\nБоюсь, что эта планета начинает вскруживать мне голову." + } + }, + + ui = { + options = { + ssr = { + header = "STARSTORM RETURNS", + titleReplacement = "Заменить Логотип", + ["titleReplacement.desc"] = "Если параметр включен, то логотип на заставке игры будет заменен на логотип Starstorm.", + enableChars = "Enable Survivors", + ["enableChars.desc"] = "Enables survivors added by Starstorm Returns.", + enableItems = "Enable Items", + ["enableItems.desc"] = "Enables items added by Starstorm Returns.", + enableEnemies = "Enable Enemies", + ["enableEnemies.desc"] = "Enables enemies added by Starstorm Returns.", + enableElites = "Enable Elites", + ["enableElites.desc"] = "Enabless elites added by Starstorm Returns.", + enableTyphoon = "Enable Typhoon", + ["enableTyphoon.desc"] = "Enables typhoon added by Starstorm Returns.", + enableInteractables = "Enable Interactables", + ["enableInteractables.desc"] = "Enables interactables added by Starstorm Returns.", + enableArtifacts = "Enable Artifacts", + ["enableArtifacts.desc"] = "Enables artifacts added by Starstorm Returns.", + enableStages = "Enable Stages", + ["enableStages.desc"] = "Enables stages added by Starstorm Returns.", + enableBeta = "Enable Unfinished Content", + ["enableBeta.desc"] = "Enables work in progress Starstorm Returns content. \nVERY UNFINISHED", + enableChirrsmas = "Включить Новогодний Контент", + ["enableChirrsmas.desc"] = "Если параметр включен, то будет включен Новогодний контент. \nПо умолчанию действует с 15 декабря по 15 января. \nТРЕБУЕТСЯ ПЕРЕЗАПУСК", + ["enableChirrsmas.default"] = "По умолчанию", + ["enableChirrsmas.always"] = "Всегда", + ["enableChirrsmas.never"] = "Никогда" + } + } + } +} \ No newline at end of file diff --git a/Language/schinese.json b/Language/schinese.json deleted file mode 100644 index c11d6721..00000000 --- a/Language/schinese.json +++ /dev/null @@ -1,497 +0,0 @@ -{ - "difficulty" : { - "typhoon.name" : "台风", - "typhoon.description" : "极限挑战。\n这颗星球就是噩梦,生存只是幻象。\n无人能担当此任。" - }, - "survivor" : { - "executioner" : { - "name" : "处刑者", - "nameUpper" : "处刑者", - "description" : "处刑者是一位机动战士,专精于猎首。使用离子投射器时,处刑者制造幻象使敌人恐惧逃窜,同时投影一把斧头解决最强敌人。务必通过连续使用 离子爆发 和 处决 来保持伤害输出源源不断。", - "endQuote" : "..于是,他离开了,杀戮欲求仍未餍足。", - "story": "

                                                                                                                                                                                            乘客详情:\n[军职等级]\n\n
                                                                                                                                                                                              行李与装备:\n军官身着EXN代理军官碳纤维制服、民用级防护外套并携带一把配发勤务手枪登舰。在军事检查站,乘客武器被发现存在额外下挂式改装件。达成存储于安全容器的协议后放行。乘客还携带了一块经改装推进喷口的ION 204X系列电池。作为安全措施,乘客的电子设备被临时安装了电子自抑器后归还。\n\n[安全回溯记录]\n\n
                                                                                                                                                                                                [备注 E1a] 多份报告称该乘客令整个检查站人员感到不安。在另行通知前需实施轻度监视。\n
                                                                                                                                                                                                  [备注 E1b] 该乘客在改用通用手语前常以发声代替言语。\n
                                                                                                                                                                                                    [事件 E1] 签约突击队员在乘客舱室执行安检。\n
                                                                                                                                                                                                      [事件 E1a] 签约突击队员要求对通往乘客舱室的走廊实施24小时私人监控。\n
                                                                                                                                                                                                        [事件 E2] 因其他乘客正式投诉,安保要求该乘客在用餐时间更换餐桌位置。", - "id" : "旅行证件:4383354378334FF3D34D", - "departure" : "出发地:\n旧阿尔卡特拉斯,\n锈城,\n火星", - "arrival" : "目的地:\n死水湾,\n新掘镇,\n木卫二" - }, - "mule" : { - "name" : "骡子", - "nameUpper" : "骡子", - "description" : "每一台 骡子 单位均配备充足并准备就绪。采用高品质材料打造以实现最佳工作效能。此型号包含 觅食者工具套件,非常适合普通用户执行 狩猎、探索 及其他危险任务。请在系统启动时下达指令以初始化。", - "endQuote" : "..于是,它离开了,活塞嘎吱作响,指令仍未明示。", - "story": "通告:骡子单位既无意识,亦非宠物。称呼它们‘莫莉’不会影响其程序运行。", - "id" : "", - "departure" : "", - "arrival" : "" - }, - "nemesisCommando" : { - "name" : "复仇者突击兵", - "nameUpper" : "复仇者突击兵", - "description" : "复仇者突击兵是一位多面手,拥有应对各种情况的火力。使用 止息之刃 创伤敌人,为你的其他能力奠定毁灭性打击基础。运用 战术翻滚 在近战范围内穿梭进退,将最危险的局势也扭转为有利态势。", - "endQuote": "..于是,他离开了,内心承载着新生的凡人情感。", - "story": "未登记现场数据日志:数据最后修改于[##090??!?] \n[错误 0x00000ce:数据包损坏,正在查看还原点。] \n\n
                                                                                                                                                                                                          [记录 1] - 巡逻队在营地外几英里处发现一名受伤落单者。传言他“凭空出现”(反正我是不信),周围区域完全无法追溯其来源。无论其说辞真实性如何,幸存者总是幸存者。写此日志时,我们正带他返回。不过他沉重的装备着实让我们不轻松——他携带了一套标准突击兵装甲的非正统改装件。看起来像是(或曾经是)Mk.1型的初始型号(尽管我记得几年前就已停产?)。\n\n
                                                                                                                                                                                                            [记录 2] - 更新——这位突击兵伙计带着些吓人的重武器——一把摸起来还温热的火箭筒和几颗几乎肯定是外星产的手榴弹——我敢打赌从未见过此类物品。他此刻仍处于昏迷,透过面罩观察显然伤势严重。其左臂尤其僵硬——手掌紧握震动刀刃手柄握把。刚才我们中有人想仔细查看,但他的拳头似乎与这该死的东西熔铸在一起了。他携带物上布满了这层干涸的黑色..物质。我们不知道这仅是几位幸存者间争斗的结果,还是某种自我防御的证据??!0049### \n[可读数据结束。] \n\n
                                                                                                                                                                                                              [记录 2a] - 我不知道还剩多少时间 它苏醒了营地已被摧毁 人们血流不止 甚至无法确认它是否是人类 以它所行之事 无人知晓当下该如何应对 它径直扑向艾德里安\n 我们甚至不知何故触发??? 我们做了什么活该遭此??? 那东西为何攻击我们!??!?##$0959090302???! \n[可读数据结束。] \n\n

                                                                                                                                                                                                              苍蝇群缠绕着这高贵的野兽,它正在吃草。\n\n你的灾疠在其表面将被这些辛劳之手抹去。" - }, - "nemesisMercenary" : { - "name" : "复仇者佣兵", - "nameUpper" : "复仇者佣兵", - "description" : "复仇者佣兵生来就拥有特殊力量。", - "endQuote": "..于是,他离开了,满载而去。", - "story": "

                                                                                                                                                                                                              我无需朋友\n我无需电话\n只需一袋断首\n与我那核武王座" - }, - "technician": { - "name": "技师", - "nameUpper": "技师", - "description": "技师极其擅长建立并维持敌区封锁。强制关机在升级后能 强行封锁一片区域。升级装置能提高其效能,但在险境中升级时需小心谨慎!", - "endQuote": "..于是,他离开了,方法是将飞船关闭再重启。", - "story": "

                                                                                                                                                                                                                乘客详情:\n[雇员等级]\n\n
                                                                                                                                                                                                                  雇员详情:\n该雇员签约在整个UES光明接触号飞船的预定航程期间进行现场维护,以在飞船出现技术问题时维修机器设备。雇员拥有6年软硬件工程实践操作经验资质。\n\n
                                                                                                                                                                                                                    行李与装备:\n雇员携带一套具备高耐真空性的Durarend焊接太空服登舰,另有供行星穿着的皮革和织物服装。雇员被告知衣物可能在真空事件中撕裂。安保注意到其所持的巨大扳手因看似不实用且具潜在危险性而被标记。经询问,雇员表示它是“为最艰巨任务……量身定制的”。按规程,该扳手被移至1a号装载区的自动锁箱。对其随身行李的进一步检查发现了数把标准扳手、3个彩色编码U盘、一台2047款GeForm笔记本电脑、工具箱及一把焊接工具。U盘在公司配备装有ReoVirus Detector v1.6.2的QPuting一次性电脑上检查。未检测到恶意软件痕迹,所存文件为包含各种装置蓝图的UPG格式。雇员获准通行,未遇更多困难。\n\n[安全回溯记录]\n
                                                                                                                                                                                                                      [事件 T1] 在起飞准备期间,雇员因与飞船驾驶员交谈数小时后拒绝离开客舱而被强制请出。\n
                                                                                                                                                                                                                        [事件 T2] 观察到雇员从自动售货机偷取苏打水。\n
                                                                                                                                                                                                                          [事件 T3] 源起4a号装载区的自动封锁被该雇员解决。\n
                                                                                                                                                                                                                            [事件 T4] 在飞船工作人员要求雇员排除故障后,逃生舱B-08状态由错误转为警报。指示安保人员调查。\n
                                                                                                                                                                                                                              [事件 T1a] 雇员被额外报告称其篡改了客舱控制面板,使飞船“更便于巡航”。预定发射延迟,雇员受命还原所有修改。", - "id" : "旅行证件:54E4F4C434F59474E454", - "departure" : "出发地:\nUES 5号船坞,\n红景镇,\n火星", - "arrival" : "目的地:\nUES 0号船坞,\n红景镇,\n火星" - }, - "swapNemesis": { - "name": "复仇者幸存者", - "nameUpper": "复仇者幸存者", - "description": "切换至熟悉却又扭曲的 复仇者幸存者。" - }, - "swapNormal": { - "name": "标准幸存者", - "nameUpper": "标准幸存者", - "description": "切换回标准幸存者集合。" - } - }, - "skill" : { - "executionerZ": { - "name": "勤务手枪", - "description": "射击造成 100% 伤害。" - }, - "executionerX": { - "name": "离子爆发", - "description": "快速射出离子化子弹,每发造成 320% 伤害。\n通过击杀敌人获取子弹。" - }, - "executionerC": { - "name": "驱散人群", - "description": "向前冲刺,使附近敌人恐惧。\n冲刺期间 无法被击中。" - }, - "executionerV": { - "name": "处决", - "description": "跃至空中,用投影巨斧猛击地面,造成 1000% 伤害。\n每次成功处决 使所有技能冷却缩减一秒。" - }, - "executionerVBoosted": { - "name": "群体处决", - "description": "跃至空中,用投影巨斧猛击地面,造成 1500% 伤害, 并使敌人恐惧。\n每次成功处决 使所有技能冷却缩减一秒。" - }, - "executionerV2": { - "name": "裂颅者", - "description": "投掷一把沿弧形轨迹飞行的投影巨斧,每秒造成 1500% 伤害。对 恐惧 敌人造成 暴击。\n每次成功处决 使所有技能冷却缩减一秒。" - }, - "executionerV2Boosted": { - "name": "碎颅者", - "description": "投掷两把沿弧形轨迹飞行的投影巨斧,每秒造成 1500% 伤害。对 恐惧 敌人造成 暴击。\n每次成功处决 使所有技能冷却缩减一秒。" - }, - "muleZ": { - "name": "排除干扰", - "description": "用拳攻击敌人造成 200% 伤害。蓄力后砸击地面造成 1400% 伤害。" - }, - "muleX": { - "name": "禁锢", - "description": "发射陷阱造成 125% 伤害。陷阱能 束缚 最多 5名 目标范围内的敌人。" - }, - "muleC": { - "name": "扭矩校准", - "description": "向前旋转 造成 4X100% 伤害。最后一击 造成眩晕。" - }, - "muleV": { - "name": "失效保护援助", - "description": "部署持续8秒的个人维修无人机。该机每秒 治疗你总生命值的5X7%。生命值全满时,改为提供护盾。" - }, - "muleVBoosted": { - "name": "失效保护援助 2.0", - "description": "发射一架持续8秒的治疗无人机。该机 治疗你总生命值的5X10%。生命值全满时,改为提供护盾。" - }, - "nemesisCommandoZ": { - "name": "止息之刃", - "description": "切割附近敌人造成 120% 伤害,并使其 受创 6秒。不可叠加。\n受创 敌人从 所有来源 承受额外 50% 伤害。" - }, - "nemesisCommandoX": { - "name": "单发射击", - "description": "射击一名敌人造成 200% 伤害。\n最多储存4次。翻滚 时补充 2 次使用次数。" - }, - "nemesisCommandoX2": { - "name": "遥切", - "description": "向前挥砍一条直线,造成 90% 伤害,使敌人 受创 6秒。" - }, - "nemesisCommandoC": { - "name": "战术翻滚", - "description": "短距离快速 向前翻滚。\n翻滚期间 无法被击中。最多储存2次。" - }, - "nemesisCommandoV": { - "name": "清场", - "description": "投掷一颗 手雷 造成 700% 伤害。按住可 预热 手雷。\n按住 下方向键 可改为以 低抛物线投掷。" - }, - "nemesisCommandoVBoosted": { - "name": "集束炸弹", - "description": "投掷一颗 手雷 造成 700% 伤害,击晕敌人并 分裂为3颗手雷,每颗造成 3x150% 伤害。\n按住可 预热 手雷。按住 下方向键 可改为以 低抛物线投掷。" - }, - "nemesisCommandoV2": { - "name": "毁灭者", - "description": "对单个敌人发射火箭造成 1000% 伤害。周围敌人承受 50% 伤害并被 击晕。\n若在 空中使用,则向下倾斜发射。" - }, - "nemesisCommandoV2Boosted": { - "name": "惩戒者", - "description": "对单个敌人发射火箭造成 1000% 伤害。周围敌人承受 50% 伤害并被 击退。\n若在 空中使用,则向下倾斜发射。\n最多储存2次。" - }, - "nemesisMercenaryZ": { - "name": "撕裂", - "description": "用霰弹枪配件撕裂敌人,造成 130% 伤害。" - }, - "nemesisMercenaryX": { - "name": "快拔", - "description": "发射霰弹枪,击晕 并攻击附近敌人,造成 600% 伤害。" - }, - "nemesisMercenaryC": { - "name": "致盲滑铲", - "description": "快速向前滑行。滑行初始被击中会重新装填霰弹枪。\n滑行时可进行攻击。" - }, - "nemesisMercenaryV": { - "name": "生命夺取", - "description": "瞄准前方最弱敌人进行攻击,造成 850% 伤害,对 击晕 敌人造成额外 +50% 总伤害。\n技能持续期间无法被击中。" - }, - "nemesisMercenaryVBoosted": { - "name": "绝对生命汲取", - "description": "瞄准前方最弱敌人进行攻击,造成 1100% 伤害,对 击晕 敌人造成额外 +50% 总伤害。\n技能持续期间无法被击中。" - }, - "technicianZ": { - "name": "微调", - "description": "用扳手向前攻击,造成 180% 伤害。\n击中装置 3次可 暂时升级 它们30秒。" - }, - "technicianZ2": { - "name": "故障排查", - "description": "向前投掷扳手,穿透最多3名敌人,造成 180% 伤害。\n击中装置 3次可 暂时升级 它们20秒。" - }, - "technicianX": { - "name": "强制关机", - "description": "掷出一个炸弹,激活时造成 400% 伤害。\n升级后击晕 并被动牵引敌人。" - }, - "technicianXD": { - "name": "红色按钮", - "description": "引爆炸弹,造成 500% 伤害。\n升级后 可击晕 敌人。" - }, - "technicianX2": { - "name": "", - "description": "" - }, - "technicianC": { - "name": "全天候能量", - "description": "部署一台 自动售货机,提供 移动速度 和 攻击速度加成。\n升级后额外提升 暴击率。" - }, - "technicianC2": { - "name": "辐射放大器", - "description": "部署一根 天线,对被攻击敌人造成 15% 额外伤害。\n升级后拥有 100% 暴击率。" - }, - "technicianV": { - "name": "后备防火墙", - "description": "放置一座朝前射击的炮台,每秒造成 220% 伤害。\n升级后每秒快速造成 350% 伤害。" - }, - "technicianVBoosted": { - "name": "后备防火墙 2.0", - "description": "放置一座炮台快速射击,每秒造成 350% 伤害。\n升级后发射导弹,造成 4x100% 伤害。" - }, - "peace": { - "name": "<#a4eae4>和平!", - "description": "无法使用此技能。" - } - }, - - "item" : { - "armedBackpack" : { - "name" : "武装背包", - "pickup": "概率向身后发射子弹。", - "description": "攻击时 18.5% (每层+6.5%) 概率 向身后发射一颗子弹,造成 150% 伤害。", - "destination": "832B区,\n霍特努特,\n地球", - "date": "2056/11/7", - "story": "保持警惕总是很重要。我不想你惹上麻烦,所以每次外出请务必佩戴它。特别是那些峡谷,那里有很多小偷!\n今年晚些时候我会再给你寄些额外的弹药,好吗?", - "priority": "标准" - }, - "brassKnuckles" : { - "name": "黄铜指虎", - "pickup" : "近距离造成额外伤害。", - "description": "对 2.8米 (每层+1.4米) 范围内敌人造成 +35% 伤害。", - "destination": "UESPD,\nE42区,\nUES 长戟号 B2", - "date": "2056/3/1", - "story" : "我们在犯罪现场发现此物。我们需要针对其他证据核查DNA。我仍不敢相信他们逃脱了。\n背面有某种标记,或许能提供线索。我将前往[信息删除]西部,我知道有人能帮我们破案。", - "priority": "标准/生物" - }, - "detritiveTrematode" : { - "name": "食屑吸虫", - "pickup" : "低生命值敌人承受持续伤害。", - "description": "生命值 低于15% (每层+5%) 的 敌人会永久 感染,每秒承受 100% 伤害。", - "destination": "112区,\n尼乌斯2号,\n火星", - "date": "2057/3/11", - "story" : "嘿,这是你的东西。\n请别再寄那种诡异的微缩卵解孵装置了,拜托。", - "priority" : "优先/生物" - }, - "dormantFungus" : { - "name" : "蛰伏菌簇", - "pickup" : "移动时恢复生命。", - "description": "移动时 每两秒恢复 2% (每层+2%) 生命值。", - "destination": "1号闸门,\n13号模块,\n盖亚空间站", - "date": "2056/11/1", - "story" : "你命令我探索菌类洞穴时,我本以为只会找到菌类。虽说兴致不高,但这个标本吸引了我。\n它似乎只在流动水域旁生长。自我采集并封存样本后,其色泽与气味都迅速消褪。\n\n希望你能深入研究它。", - "priority": "标准" - }, - "fork" : { - "name" : "餐叉", - "pickup" : "造成更多伤害。", - "description": "基础伤害 增加 3点 (每层+3点)。", - "destination": "安蒂玛大厦 - 屋顶,\n繁花山丘,\n地球", - "date": "2056/9/24", - "story" : "惊喜!一把叉子!哈哈哈!!!\n你说过任何礼物都是好礼物,喏,给你!开玩笑啦,这叉子其实挺神奇。相信我邓肯,我曾用它吃过沙拉,然后当天就捡到一分钱!\n对了,有空来找我们玩呗,可想在SS2里锤爆你的构筑了(拜托告诉我你换了主玩角色,哈哈)。", - "priority": "标准" - }, - "watchMetronome" : { - "name" : "腕表节拍器", - "pickup" : "站立不动为腕表充能。满充时移动更快。", - "description": "站立不动可为腕表充能,需时 3 秒。满充时提升 移动速度 50%,充能在 3 (每层+1秒) 秒 内消耗殆尽。", - "destination": "1号笼区,\nG-44(地下),\n地球", - "date": "2056/9/5", - "story" : "祖父将此物作为过去时代的遗物交予我。他说这是六世祖父的遗物。我对古董和家族物品毫无兴趣,所以认为还是交给你为佳。\n无论如何它已无任何用处..", - "priority": "优先" - }, - "wonderHerbs" : { - "name" : "神奇药草", - "pickup" : "略微提升所有治疗效果。", - "description": "所有来源的 治疗 效果提升 12% (每层+12%)。", - "destination": "2342区,\n3号地块,\n地球", - "date": "2056/11/7", - "story" : "莉卡!听说你在与EC合作研发新配方。我们农场在培植一些特殊药草,或许能派上用场。它们以治疗身心创伤奇效而闻名。\n若需更多,请随时来电!拥抱吻安\n- 伊甸", - "priority": "标准" - }, - "coffeeBag" : { - "name" : "咖啡袋", - "pickup" : "激活可交互物提升移动和攻击速度。", - "description": "激活可交互物 提升移动速度和攻击速度 22%,持续 10 (每层+5秒) 秒。", - "destination": "n1区,\n首府区,\nUES 主星", - "date": "2056/5/22", - "story" : "瞌睡虫~~~ 我不知道你为什么说不喜欢咖啡 提神它可是杠杠滴。呃呃 我知道还有别的方法可以嗨 不过兄弟 我想你会爱上这个 我的最爱!!!先试试再骂我 行不?", - "priority": "标准" - }, - "x4Stimulant" : { - "name" : "X4兴奋剂", - "pickup" : "减少次要技能冷却时间,使用次要技能时略微恢复生命。", - "description": "次要技能冷却时间 减少 10% (每层+10%)。使用次要技能提升 生命恢复 为每秒 2点 (每层+1点) 生命值,持续3秒。", - "destination": "罗密欧33区,\n巴塘艾国家公园,\n地球", - "date": "2057/4/12", - "story" : "内含说明书封装。机器是否出现540型症状?如是,应避免使用超过100毫升兴奋剂。\n\n祝好。" - }, - "moltenCoin" : { - "name" : "熔融硬币", - "pickup" : "概率点燃敌人并获得金币", - "description": "6% 概率击中时 灼烧敌人6秒,掉落 $1金币 (每层+$1)。", - "destination": "托伊拉2区,\nB44区,\n母站", - "date": "2056/5/22", - "story" : "嘿!就如我说的那样美味。只是希望别再像上次那样在路上开裂。\n我该去找份工作……", - "priority": "标准" - }, - "crypticSource" : { - "name": "秘源", - "pickup" : "改变方向时释放能量脉冲。", - "description": "改变移动方向发射 连锁闪电,对最多 2 个目标造成 70% (每层+55%) 伤害。", - "destination" : "O32区,\n底层区,\n地球", - "date" : "2058/3/30", - "story" : "从原子到智慧生命,万物皆源于能量。然而,这件神秘物品似乎自行散发着能量。剧烈摩擦似乎会引发连锁反应,使其极不稳定。但它也可能是(无限的?)可控能量源。这能否引领我们走向渴求的乌托邦未来完全未知,但这彻底改变了我们先前对宇宙的认知。", - "priority" : "不稳定" - }, - "huntersSigil" : { - "name": "猎者符印", - "pickup" : "站立不动产生增益区域,提升护甲和暴击率。", - "description": "站立不动1秒后,生成一个 小型区域,为你和盟友提供 15 (每层+10) 点护甲 和 25% (每层+20%) 暴击率 增益。", - "destination" : "东2区,\n贝克汉姆大厦,\n地球", - "date" : "2056/2/2", - "story" : "嘿,赛特,欢迎入会。我们一直在寻觅人选,既然你来了,我们下季就能开工。我们指望你了。\n\n- 艾瑞克斯 启", - "priority": "优先" - }, - "roulette" : { - "name": "轮盘", - "pickup" : "每分钟获取随机增益。", - "description": "获得 一种随机增益,每 分钟 重新轮换一次。可能增益如下:\n\n最大生命值 提升 60 (每层+24)。\n生命恢复 提升 3.6 (每层+1.8) 生命值 每秒。\n基础伤害 提升 14 (每层+5)。\n攻击速度 提升 35% (每层+15.5%)。\n暴击率 提升 25% (每层+10%)。\n移动速度 提升 20% (每层+7%)。\n护甲 提升 35 (每层+14)。", - "destination" : "3.1号贵宾厅,\n赛卡赌场,\n地球", - "date" : "2057/7/17", - "story" : "144贝拉-1型替换轮盘,以应对赌场近期发生的事件。如需进一步咨询,请联系我方代表提供的E向地址。", - "priority": "优先" - }, - "judderingEgg" : { - "name": "颤栗之卵", - "pickup" : "获得一个小朋友!", - "description": "孵化 1条 (每层+1)幼生蠕虫 为你而战,间歇性 攻击周围敌人,共造成 18x80% 伤害。", - "destination" : "B闸门,\n李氏庄园,\n地球", - "date" : "2056/11/21", - "story" : "又一份来自饲育者的样本,这次是个非常奇特的种。\n极度谨慎,我听说幼体若处理不当,可在几秒内变得极具攻击性(甚至致命!)。距其孵化应不久,你最好准备好安置构造。", - "priority": "易碎" - }, - "uraniumHorseshoe" : { - "name": "铀制马蹄铁", - "pickup" : "提升移动速度和跳跃高度。", - "description": "移动速度 和 跳跃高度 提升 10% (每层+10%)", - "destination" : "6900号,\n“火星人脸”马具与农场用品店,\n火星", - "date" : "2056/4/20", - "story" : "我们在一匹珍贵表演马的蹄上发现这件古怪的绿色马蹄铁,此前它某晚神秘死亡。\n马医认为你们可能了解这个奇怪马蹄铁。光是拿着它就让我手掌灼痛。请谨慎处理。", - "priority": "标准" - }, - "blastKnuckles" : { - "name": "爆裂指虎", - "pickup" : "你的攻击在近距离引发爆炸!", - "description": "击中 4.5米 范围内敌人时,同时 在其身后引发爆炸,造成 220% (每层+220%) 伤害。最多储存 5次。每 3秒 获得一次使用次数。", - "destination" : "todo", - "date" : "todo", - "story" : "todo", - "priority": "优先" - }, - "malice" : { - "name": "恶意", - "pickup" : "伤害扩散至附近敌人。", - "description": "攻击

                                                                                                                                                                                                                              扩散至附近敌人,在 4.2米 (每层+1米) 范围内对最多 1 (每层+1) 个目标 造成 50% 总伤害。", - "destination" : "P24区,\n罗密欧,\n波尔-A空间站", - "date" : "2056/2/11", - "story" : "不惜一切代价收容。此样本如处理不当将成为巨大威胁。你不知我们为遏制它已付出多少代价。", - "priority": "

                                                                                                                                                                                                                              恶意" - }, - "manOWar" : { - "name": "僧帽水母", - "pickup" : "击杀时产生电涌。", - "description": "击杀敌人释放 连锁闪电,对最多 2 个目标造成 100% (每层+40%) 伤害。", - "destination" : "隐秘隔间,\n创世山,\n金星", - "date" : "2056/11/20", - "story" : "兄弟们,还记得我上次寄的小家伙吗?嗯..这只攻击性更强..虽然是个有趣的小家伙!它喜欢在瓶子里转圈圈看着我。要不是我救了它,它早就进了外星生物的肚子里。\n我想给她取名安,觉得如何?", - "priority": "优先" - }, - "guardingAmulet" : { - "name": "守护护符", - "pickup" : "减少来自背后的伤害。", - "description": "身后 承受的伤害减少 40% (每层+40%)。", - "destination" : "C8区,\n蔚蓝花园,\n圣所", - "date" : "2056/3/11", - "story" : "我知道他在某处,我知道你能找到他..但你必须坚持不懈。待吾灵魂脱壳之时,箴言将启,你应继吾位。他将是那个向你揭示你对我所有疑问答案的人。\n接受此护符;你将依赖它,它将使你永不回首。", - "priority": "标准" - }, - "distinctiveStick" : { - "name": "奇木之杖", - "pickup" : "在传送器附近治疗。", - "description": "在传送器附近生长一棵树,治疗 10米 (每层+3.75米) 范围内盟友,每2秒 恢复 2.2% 其 生命值。", - "destination" : "德雷克·米内利,\n泰萨夫特,\n地球", - "date" : "2056/1/15", - "story" : "亲爱的,我有件特别的东西给你..\n前几日执行任务时,我遇见了此物。它吸引了我,因与我此生所见任何枝桠都不同!这根树枝散发着奇特能量,让我感觉与自然的联系超乎想象,曾以为只存在于故事中!值此周年,我将其赠你。\n我很快就到家,还有一份更棒礼物。请,不要失去信念。", - "priority": "标准" - }, - "balloon" : { - "name": "气球", - "pickup" : "在安全区降低重力。", - "description": "按住跳跃键时 重力 降低 35% (每层-15%)。被击中时爆破。在安全区持续 7 秒后恢复。", - "destination" : "55大道,\n帕尔广场,\n地球", - "date" : "2056/6/31", - "story" : "年少时我喜爱气球。在存在大气的场所观赏它们十分有趣。但此球对我意义非凡,它是由强化橡胶制成的最早一批气球之一,可追溯至22年代。望君善加保管,再过几年它可能价值连城(当然,前提是它仍飘在空中!)。", - "priority": "优先/易碎" - }, - "iceTool" : { - "name": "冰镐", - "pickup" : "获得蹬墙跳能力。更快攀爬。", - "description": "接触墙壁时获得 1次 (每层+1)额外跳跃。攀爬速度提升 18% (每层+18%)。", - "destination" : "33号蒙塔,\n索莱海岸,\n地球", - "date" : "2056/3/12", - "story" : "徒步好友日安,我找到你上次去[信息删除]山时丢失的冰镐。下次请留意积雪!\n不知冰镐还能否使用,它可是历经沧桑!", - "priority": "标准" - }, - "needles" : { - "name": "刺针", - "pickup" : "概率标记敌人,使其承受必然暴击。", - "description": "3% (每层+2%) 概率击中时 标记敌人,使其在3秒内承受针对其的 100% 暴击率。", - "destination" : "E2区,\n奥伦回环,\n金星", - "date" : "[信息删除]", - "story" : "呃..母亲说若你能读到此信,说明你不似她所想那般笨拙。我们希望这些刺针于你有用。恐怕这不是你订的那种,但我想差别不大。\n不记得是否妥善打包寄出。请务必小心。", - "priority": "穿刺" - }, - - "midas" : { - "name" : "M.I.D.A.S", - "pickup" : "将一半生命值转化为金币。", - "description" : "失去 50%生命值,转化为每点生命值获得 $1金币,外加一个 宝箱 的价值。", - "destination" : "R A 5-5区,\n主星,\n土卫六", - "date" : "2056/5/4", - "story" : "\"众神的奇迹..\"\n你反复絮叨着。你疯了吗?我们只想救你,你却视我们如无物。\n\n帮个忙,收下它。随你怎么处置,我们不想在这附近看到你。我们不想卷入你和此物之间的任何事端。\n\n它使你变成了贪婪无魂之人,愿你有自知之明。", - "priority": "不稳定" - }, - "strangeCan" : { - "name": "异样罐头", - "pickup" : "投掷罐头标记敌人。击杀后释放毒云。", - "description": "扔出一个 奇特罐头,污染 最大生命值最高的敌人。 被污染 敌人死亡时释放一团 毒云,在5秒内造成相当于该敌人 50% 最大生命值的伤害。", - "destination" : "1530区,\n563区,\nA-LC12", - "date" : "2056/5/22", - "story" : "如我所言,非常美味。只希望别再像上次那样在路上裂开。\n我该找份工作了...", - "priority": "不稳定" - }, - "whiteFlag" : { - "name": "白旗", - "pickup": "放置白旗。范围内所有人无法攻击。", - "description": "放置一面持续 8 秒的白旗。白旗半径内 所有人 技能被禁用。\n不影响无人机和飞行敌人。", - "destination" : "2B房,\n索姆纳斯酒店,\n地球", - "date" : "2056/10/5", - "story" : "替我保管此物直到归家,我并未使用。实际上我们成了朋友!等不及要告诉你了。旅途漫长且经历了一系列意外事件。我向他们提及过你,他们希望下次能邀你前来。意下如何?爱你。", - "priority": "不稳定" - } - }, - - "artifact" : { - "multitude" : { - "pickupName" : "群体神器", - "name" : "群体", - "description": "敌人大规模成群结队。", - "approaching": "成群的敌人正在逼近。", - "arriving": "做好迎战准备……" - }, - "displacement" : { - "pickupName" : "错位神器", - "name" : "错位", - "description": "关卡顺序被打乱。" - }, - "gathering" : { - "pickupName" : "集聚神器", - "name" : "集聚", - "description": "金币掉落翻倍但必须手动拾取。" - } - }, - - - "actor" : { - "Exploder.name" : "爆破者", - "Mimic.name" : "安保宝箱", - "Gatekeeper.name" : "守门人", - "Admonitor.name" : "陶土训诫者", - "Protector.name" : "守护者", - "Protector.text" : "神器看守者" - }, - - "monster" : { - "admonitor.name" : "陶土训诫者", - "admonitor.story" : "远处,一个熟悉的陶土'人'在神殿地面上艰难跋涉。然而,尽管外观相似,这个生物与我之前见过的类型不同。与它较小的同类不同,这个盆栽守护者缺乏狡诈,却拥有两个潜伏的拳头,紧握成钳状。它迈出的步伐几乎让它看起来...自信。我知道如果假设它无法产生诸如信念这样的智能情感,我就是个傻瓜。尽管如此,出于对我整体健康的担忧,我选择不测试这个趾高气扬的油滑家伙的勇气,而是躲在一座翻倒的废墟里。\n\n如果我要活着离开这个星球,我希望我的股骨保持完整。" - }, - - "interactable" : { - "MimicInactive.text" : "..?", - "MimicInactive.name" : "大宝箱?" - }, - - "elite" : { - "poison.name" : "剧毒 %s", - "empyrean.name" : "天界 %s", - "empyrean.text" : "审判先驱", - "empyrean.worm" : "棱镜利维坦" - }, - "stage" : { - "whistlingBasin.name" : "啸风盆地", - "whistlingBasin.subname" : "渐涸绿洲", - "whistlingBasin.story" : "这颗星球持续带给我意外之惊奇。鉴于其原住民之前的行径,我从未想过这般颓败而奢华的建筑竟能由这等生灵所造。令人惊叹的深黑立柱与优美石料构成的拱门拔地而起,尽由匠心之手雕琢装饰。遍布墙壁的是这些绝妙复杂、表象简朴的装饰。奇异的蓝绿色植被侵袭着建筑,悄沿其基攀爬。看来我对这颗星球持有的野蛮成见正在消散。即便如此,我仍高度戒备。尽管酸痛的肌肉在抗议,但此刻休息对我而言为时尚早。", - - "torridOutlands.name" : "焦热荒地", - "torridOutlands.subname" : "死寂灼痕", - "torridOutlands.story" : "即便有防护服,我也能感受到脉动的太阳射线直射而下,扭曲着这颗背弃之行星的大气层。高温使我难以保持清醒,凝结在头盔内壁的水汽更是于事无补。不过,若没有它,在游荡中踢起的灰尘碎屑恐怕已半填满我的肺腑——算是“寻找一线光明”聊以自慰吧。烈日当空,哪怕仅仅是轻松勘察周围环境的想法也不过是白日梦。神志恍惚间,我极易踉跄绊入仙人掌和巨石丛中,只能暂时躲进遍布地表的蜿蜒隧道与洞穴中稍作喘息。崎岖地势拖慢脚步,顺眉滴入眼中的汗水终将成为我最小忧虑之一。" - } -} diff --git a/Language/schinese.lua b/Language/schinese.lua new file mode 100644 index 00000000..c3b5a8ee --- /dev/null +++ b/Language/schinese.lua @@ -0,0 +1,524 @@ +return { + difficulty = { + + typhoon = { + name = "台风", + description = "极限挑战。\n这颗星球就是噩梦,生存只是幻象。\n无人能担当此任。", + }, + + }, + survivor = { + executioner = { + name = "处刑者", + nameUpper = "处刑者", + description = "处刑者是一位机动战士,专精于猎首。使用离子投射器时,处刑者制造幻象使敌人恐惧逃窜,同时投影一把斧头解决最强敌人。务必通过连续使用 离子爆发 和 处决 来保持伤害输出源源不断。", + endQuote = "..于是,他离开了,杀戮欲求仍未餍足。", + story = "

                                                                                                                                                                                                                                乘客详情:\n[军职等级]\n\n
                                                                                                                                                                                                                                  行李与装备:\n军官身着EXN代理军官碳纤维制服、民用级防护外套并携带一把配发勤务手枪登舰。在军事检查站,乘客武器被发现存在额外下挂式改装件。达成存储于安全容器的协议后放行。乘客还携带了一块经改装推进喷口的ION 204X系列电池。作为安全措施,乘客的电子设备被临时安装了电子自抑器后归还。\n\n[安全回溯记录]\n\n
                                                                                                                                                                                                                                    [备注 E1a] 多份报告称该乘客令整个检查站人员感到不安。在另行通知前需实施轻度监视。\n
                                                                                                                                                                                                                                      [备注 E1b] 该乘客在改用通用手语前常以发声代替言语。\n
                                                                                                                                                                                                                                        [事件 E1] 签约突击队员在乘客舱室执行安检。\n
                                                                                                                                                                                                                                          [事件 E1a] 签约突击队员要求对通往乘客舱室的走廊实施24小时私人监控。\n
                                                                                                                                                                                                                                            [事件 E2] 因其他乘客正式投诉,安保要求该乘客在用餐时间更换餐桌位置。", + id = "旅行证件:4383354378334FF3D34D", + departure = "出发地:\n旧阿尔卡特拉斯,\n锈城,\n火星", + arrival = "目的地:\n死水湾,\n新掘镇,\n木卫二", + }, + mule = { + name = "骡子", + nameUpper = "骡子", + description = "每一台 骡子 单位均配备充足并准备就绪。采用高品质材料打造以实现最佳工作效能。此型号包含 觅食者工具套件,非常适合普通用户执行 狩猎、探索 及其他危险任务。请在系统启动时下达指令以初始化。", + endQuote = "..于是,它离开了,活塞嘎吱作响,指令仍未明示。", + story = "通告:骡子单位既无意识,亦非宠物。称呼它们‘莫莉’不会影响其程序运行。", + id = "", + departure = "", + arrival = "", + }, + nemesisCommando = { + name = "复仇者突击兵", + nameUpper = "复仇者突击兵", + description = "复仇者突击兵是一位多面手,拥有应对各种情况的火力。使用 止息之刃 创伤敌人,为你的其他能力奠定毁灭性打击基础。运用 战术翻滚 在近战范围内穿梭进退,将最危险的局势也扭转为有利态势。", + endQuote = "..于是,他离开了,内心承载着新生的凡人情感。", + story = "未登记现场数据日志:数据最后修改于[##090??!?] \n[错误 0x00000ce:数据包损坏,正在查看还原点。] \n\n
                                                                                                                                                                                                                                              [记录 1] - 巡逻队在营地外几英里处发现一名受伤落单者。传言他“凭空出现”(反正我是不信),周围区域完全无法追溯其来源。无论其说辞真实性如何,幸存者总是幸存者。写此日志时,我们正带他返回。不过他沉重的装备着实让我们不轻松——他携带了一套标准突击兵装甲的非正统改装件。看起来像是(或曾经是)Mk.1型的初始型号(尽管我记得几年前就已停产?)。\n\n
                                                                                                                                                                                                                                                [记录 2] - 更新——这位突击兵伙计带着些吓人的重武器——一把摸起来还温热的火箭筒和几颗几乎肯定是外星产的手榴弹——我敢打赌从未见过此类物品。他此刻仍处于昏迷,透过面罩观察显然伤势严重。其左臂尤其僵硬——手掌紧握震动刀刃手柄握把。刚才我们中有人想仔细查看,但他的拳头似乎与这该死的东西熔铸在一起了。他携带物上布满了这层干涸的黑色..物质。我们不知道这仅是几位幸存者间争斗的结果,还是某种自我防御的证据??!0049### \n[可读数据结束。] \n\n
                                                                                                                                                                                                                                                  [记录 2a] - 我不知道还剩多少时间 它苏醒了营地已被摧毁 人们血流不止 甚至无法确认它是否是人类 以它所行之事 无人知晓当下该如何应对 它径直扑向艾德里安\n 我们甚至不知何故触发??? 我们做了什么活该遭此??? 那东西为何攻击我们!??!?##$0959090302???! \n[可读数据结束。] \n\n

                                                                                                                                                                                                                                                  苍蝇群缠绕着这高贵的野兽,它正在吃草。\n\n你的灾疠在其表面将被这些辛劳之手抹去。", + }, + nemesisMercenary = { + name = "复仇者佣兵", + nameUpper = "复仇者佣兵", + description = "复仇者佣兵生来就拥有特殊力量。", + endQuote = "..于是,他离开了,满载而去。", + story = "

                                                                                                                                                                                                                                                  我无需朋友\n我无需电话\n只需一袋断首\n与我那核武王座", + }, + technician = { + name = "技师", + nameUpper = "技师", + description = "技师极其擅长建立并维持敌区封锁。强制关机在升级后能 强行封锁一片区域。升级装置能提高其效能,但在险境中升级时需小心谨慎!", + endQuote = "..于是,他离开了,方法是将飞船关闭再重启。", + story = "

                                                                                                                                                                                                                                                    乘客详情:\n[雇员等级]\n\n
                                                                                                                                                                                                                                                      雇员详情:\n该雇员签约在整个UES光明接触号飞船的预定航程期间进行现场维护,以在飞船出现技术问题时维修机器设备。雇员拥有6年软硬件工程实践操作经验资质。\n\n
                                                                                                                                                                                                                                                        行李与装备:\n雇员携带一套具备高耐真空性的Durarend焊接太空服登舰,另有供行星穿着的皮革和织物服装。雇员被告知衣物可能在真空事件中撕裂。安保注意到其所持的巨大扳手因看似不实用且具潜在危险性而被标记。经询问,雇员表示它是“为最艰巨任务……量身定制的”。按规程,该扳手被移至1a号装载区的自动锁箱。对其随身行李的进一步检查发现了数把标准扳手、3个彩色编码U盘、一台2047款GeForm笔记本电脑、工具箱及一把焊接工具。U盘在公司配备装有ReoVirus Detector v1.6.2的QPuting一次性电脑上检查。未检测到恶意软件痕迹,所存文件为包含各种装置蓝图的UPG格式。雇员获准通行,未遇更多困难。\n\n[安全回溯记录]\n
                                                                                                                                                                                                                                                          [事件 T1] 在起飞准备期间,雇员因与飞船驾驶员交谈数小时后拒绝离开客舱而被强制请出。\n
                                                                                                                                                                                                                                                            [事件 T2] 观察到雇员从自动售货机偷取苏打水。\n
                                                                                                                                                                                                                                                              [事件 T3] 源起4a号装载区的自动封锁被该雇员解决。\n
                                                                                                                                                                                                                                                                [事件 T4] 在飞船工作人员要求雇员排除故障后,逃生舱B-08状态由错误转为警报。指示安保人员调查。\n
                                                                                                                                                                                                                                                                  [事件 T1a] 雇员被额外报告称其篡改了客舱控制面板,使飞船“更便于巡航”。预定发射延迟,雇员受命还原所有修改。", + id = "旅行证件:54E4F4C434F59474E454", + departure = "出发地:\nUES 5号船坞,\n红景镇,\n火星", + arrival = "目的地:\nUES 0号船坞,\n红景镇,\n火星", + }, + swapNemesis = { + name = "复仇者幸存者", + nameUpper = "复仇者幸存者", + description = "切换至熟悉却又扭曲的 复仇者幸存者。" + }, + swapNormal = { + name = "标准幸存者", + nameUpper = "标准幸存者", + description = "切换回标准幸存者集合。", + }, + }, + skill = { + executionerZ = { + name = "勤务手枪", + description = "射击造成 100% 伤害。", + }, + executionerX = { + name = "离子爆发", + description = "快速射出离子化子弹,每发造成 320% 伤害。\n通过击杀敌人获取子弹。" + }, + executionerC = { + name = "驱散人群", + description = "向前冲刺,使附近敌人恐惧。\n冲刺期间 无法被击中。", + }, + executionerV = { + name = "处决", + description = "跃至空中,用投影巨斧猛击地面,造成 1000% 伤害。\n每次成功处决 使所有技能冷却缩减一秒。", + }, + executionerVBoosted = { + name = "群体处决", + description = "跃至空中,用投影巨斧猛击地面,造成 1500% 伤害, 并使敌人恐惧。\n每次成功处决 使所有技能冷却缩减一秒。", + }, + executionerV2 = { + name = "裂颅者", + description = "投掷一把沿弧形轨迹飞行的投影巨斧,每秒造成 1500% 伤害。对 恐惧 敌人造成 暴击。\n每次成功处决 使所有技能冷却缩减一秒。", + }, + executionerV2Boosted = { + name = "碎颅者", + description = "投掷两把沿弧形轨迹飞行的投影巨斧,每秒造成 1500% 伤害。对 恐惧 敌人造成 暴击。\n每次成功处决 使所有技能冷却缩减一秒。", + }, + muleZ = { + name = "排除干扰", + description = "用拳攻击敌人造成 200% 伤害。蓄力后砸击地面造成 1400% 伤害。", + }, + muleX = { + name = "禁锢", + description = "发射陷阱造成 125% 伤害。陷阱能 束缚 最多 5名 目标范围内的敌人。", + }, + muleC = { + name = "扭矩校准", + description = "向前旋转 造成 4X100% 伤害。最后一击 造成眩晕。", + }, + muleV = { + name = "失效保护援助", + description = "部署持续8秒的个人维修无人机。该机每秒 治疗你总生命值的5X7%。生命值全满时,改为提供护盾。", + }, + muleVBoosted = { + name = "失效保护援助 2.0", + description = "发射一架持续8秒的治疗无人机。该机 治疗你总生命值的5X10%。生命值全满时,改为提供护盾。", + }, + nemesisCommandoZ = { + name = "止息之刃", + description = "切割附近敌人造成 120% 伤害,并使其 受创 6秒。不可叠加。\n受创 敌人从 所有来源 承受额外 50% 伤害。", + }, + nemesisCommandoX = { + name = "单发射击", + description = "射击一名敌人造成 200% 伤害。\n最多储存4次。翻滚 时补充 2 次使用次数。", + }, + nemesisCommandoX2 = { + name = "遥切", + description = "向前挥砍一条直线,造成 90% 伤害,使敌人 受创 6秒。", + }, + nemesisCommandoC = { + name = "战术翻滚", + description = "短距离快速 向前翻滚。\n翻滚期间 无法被击中。最多储存2次。", + }, + nemesisCommandoV = { + name = "清场", + description = "投掷一颗 手雷 造成 700% 伤害。按住可 预热 手雷。\n按住 下方向键 可改为以 低抛物线投掷。", + }, + nemesisCommandoVBoosted = { + name = "集束炸弹", + description = "投掷一颗 手雷 造成 700% 伤害,击晕敌人并 分裂为3颗手雷,每颗造成 3x150% 伤害。\n按住可 预热 手雷。按住 下方向键 可改为以 低抛物线投掷。", + }, + nemesisCommandoV2 = { + name = "毁灭者", + description = "对单个敌人发射火箭造成 1000% 伤害。周围敌人承受 50% 伤害并被 击晕。\n若在 空中使用,则向下倾斜发射。", + }, + nemesisCommandoV2Boosted = { + name = "惩戒者", + description = "对单个敌人发射火箭造成 1000% 伤害。周围敌人承受 50% 伤害并被 击退。\n若在 空中使用,则向下倾斜发射。\n最多储存2次。", + }, + nemesisMercenaryZ = { + name = "撕裂", + description = "用霰弹枪配件撕裂敌人,造成 130% 伤害。", + }, + nemesisMercenaryX = { + name = "快拔", + description = "发射霰弹枪,击晕 并攻击附近敌人,造成 600% 伤害。", + }, + nemesisMercenaryC = { + name = "致盲滑铲", + description = "快速向前滑行。滑行初始被击中会重新装填霰弹枪。\n滑行时可进行攻击。", + }, + nemesisMercenaryV = { + name = "生命夺取", + description = "瞄准前方最弱敌人进行攻击,造成 850% 伤害,对 击晕 敌人造成额外 +50% 总伤害。\n技能持续期间无法被击中。", + }, + nemesisMercenaryVBoosted = { + name = "绝对生命汲取", + description = "瞄准前方最弱敌人进行攻击,造成 1100% 伤害,对 击晕 敌人造成额外 +50% 总伤害。\n技能持续期间无法被击中。", + }, + technicianZ = { + name = "微调", + description = "用扳手向前攻击,造成 180% 伤害。\n击中装置 3次可 暂时升级 它们30秒。", + }, + technicianZ2 = { + name = "故障排查", + description = "向前投掷扳手,穿透最多3名敌人,造成 180% 伤害。\n击中装置 3次可 暂时升级 它们20秒。", + }, + technicianX = { + name = "强制关机", + description = "掷出一个炸弹,激活时造成 400% 伤害。\n升级后击晕 并被动牵引敌人。", + }, + technicianXD = { + name = "红色按钮", + description = "引爆炸弹,造成 500% 伤害。\n升级后 可击晕 敌人。", + }, + technicianX2 = { + name = "", + description = "", + }, + technicianC = { + name = "全天候能量", + description = "部署一台 自动售货机,提供 移动速度 和 攻击速度加成。\n升级后额外提升 暴击率。", + }, + technicianC2 = { + name = "辐射放大器", + description = "部署一根 天线,对被攻击敌人造成 15% 额外伤害。\n升级后拥有 100% 暴击率。", + }, + technicianV = { + name = "后备防火墙", + description = "放置一座朝前射击的炮台,每秒造成 220% 伤害。\n升级后每秒快速造成 350% 伤害。", + }, + technicianVBoosted = { + name = "后备防火墙 2.0", + description = "放置一座炮台快速射击,每秒造成 350% 伤害。\n升级后发射导弹,造成 4x100% 伤害。", + }, + peace = { + name = "<#a4eae4>和平!", + description = "无法使用此技能。", + }, + }, + + item = { + armedBackpack = { + name = "武装背包", + pickup = "概率向身后发射子弹。", + description = "攻击时 18.5% (每层+6.5%) 概率 向身后发射一颗子弹,造成 150% 伤害。", + destination = "832B区,\n霍特努特,\n地球", + date = "2056/11/7", + story = "保持警惕总是很重要。我不想你惹上麻烦,所以每次外出请务必佩戴它。特别是那些峡谷,那里有很多小偷!\n今年晚些时候我会再给你寄些额外的弹药,好吗?", + priority = "标准", + }, + brassKnuckles = { + name = "黄铜指虎", + pickup = "近距离造成额外伤害。", + description = "对 2.8米 (每层+1.4米) 范围内敌人造成 +35% 伤害。", + destination = "UESPD,\nE42区,\nUES 长戟号 B2", + date = "2056/3/1", + story = "我们在犯罪现场发现此物。我们需要针对其他证据核查DNA。我仍不敢相信他们逃脱了。\n背面有某种标记,或许能提供线索。我将前往[信息删除]西部,我知道有人能帮我们破案。", + priority = "标准/生物", + }, + detritiveTrematode = { + name = "食屑吸虫", + pickup = "低生命值敌人承受持续伤害。", + description = "生命值 低于15% (每层+5%) 的 敌人会永久 感染,每秒承受 100% 伤害。", + destination = "112区,\n尼乌斯2号,\n火星", + date = "2057/3/11", + story = "嘿,这是你的东西。\n请别再寄那种诡异的微缩卵解孵装置了,拜托。", + priority = "优先/生物", + }, + dormantFungus = { + name = "蛰伏菌簇", + pickup = "移动时恢复生命。", + description = "移动时 每两秒恢复 2% (每层+2%) 生命值。", + destination = "1号闸门,\n13号模块,\n盖亚空间站", + date = "2056/11/1", + story = "你命令我探索菌类洞穴时,我本以为只会找到菌类。虽说兴致不高,但这个标本吸引了我。\n它似乎只在流动水域旁生长。自我采集并封存样本后,其色泽与气味都迅速消褪。\n\n希望你能深入研究它。", + priority = "标准", + }, + fork = { + name = "餐叉", + pickup = "造成更多伤害。", + description = "基础伤害 增加 3点 (每层+3点)。", + destination = "安蒂玛大厦 - 屋顶,\n繁花山丘,\n地球", + date = "2056/9/24", + story = "惊喜!一把叉子!哈哈哈!!!\n你说过任何礼物都是好礼物,喏,给你!开玩笑啦,这叉子其实挺神奇。相信我邓肯,我曾用它吃过沙拉,然后当天就捡到一分钱!\n对了,有空来找我们玩呗,可想在SS2里锤爆你的构筑了(拜托告诉我你换了主玩角色,哈哈)。", + priority = "标准", + }, + watchMetronome = { + name = "腕表节拍器", + pickup = "站立不动为腕表充能。满充时移动更快。", + description = "站立不动可为腕表充能,需时 3 秒。满充时提升 移动速度 50%,充能在 3 (每层+1秒) 秒 内消耗殆尽。", + destination = "1号笼区,\nG-44(地下),\n地球", + date = "2056/9/5", + story = "祖父将此物作为过去时代的遗物交予我。他说这是六世祖父的遗物。我对古董和家族物品毫无兴趣,所以认为还是交给你为佳。\n无论如何它已无任何用处..", + priority = "优先", + }, + wonderHerbs = { + name = "神奇药草", + pickup = "略微提升所有治疗效果。", + description = "所有来源的 治疗 效果提升 12% (每层+12%)。", + destination = "2342区,\n3号地块,\n地球", + date = "2056/11/7", + story = "莉卡!听说你在与EC合作研发新配方。我们农场在培植一些特殊药草,或许能派上用场。它们以治疗身心创伤奇效而闻名。\n若需更多,请随时来电!拥抱吻安\n- 伊甸", + priority = "标准", + }, + coffeeBag = { + name = "咖啡袋", + pickup = "激活可交互物提升移动和攻击速度。", + description = "激活可交互物 提升移动速度和攻击速度 22%,持续 10 (每层+5秒) 秒。", + destination = "n1区,\n首府区,\nUES 主星", + date = "2056/5/22", + story = "瞌睡虫~~~ 我不知道你为什么说不喜欢咖啡 提神它可是杠杠滴。呃呃 我知道还有别的方法可以嗨 不过兄弟 我想你会爱上这个 我的最爱!!!先试试再骂我 行不?", + priority = "标准", + }, + x4Stimulant = { + name = "X4兴奋剂", + pickup = "减少次要技能冷却时间,使用次要技能时略微恢复生命。", + description = "次要技能冷却时间 减少 10% (每层+10%)。使用次要技能提升 生命恢复 为每秒 2点 (每层+1点) 生命值,持续3秒。", + destination = "罗密欧33区,\n巴塘艾国家公园,\n地球", + date = "2057/4/12", + story = "内含说明书封装。机器是否出现540型症状?如是,应避免使用超过100毫升兴奋剂。\n\n祝好。", + priority = "标准", + }, + moltenCoin = { + name = "熔融硬币", + pickup = "概率点燃敌人并获得金币", + description = "6% 概率击中时 灼烧敌人6秒,掉落 $1金币 (每层+$1)。", + destination = "托伊拉2区,\nB44区,\n母站", + date = "2056/5/22", + story = "嘿!就如我说的那样美味。只是希望别再像上次那样在路上开裂。\n我该去找份工作……", + priority = "标准", + }, + crypticSource = { + name = "秘源", + pickup = "改变方向时释放能量脉冲。", + description = "改变移动方向发射 连锁闪电,对最多 2 个目标造成 70% (每层+55%) 伤害。", + destination = "O32区,\n底层区,\n地球", + date = "2058/3/30", + story = "从原子到智慧生命,万物皆源于能量。然而,这件神秘物品似乎自行散发着能量。剧烈摩擦似乎会引发连锁反应,使其极不稳定。但它也可能是(无限的?)可控能量源。这能否引领我们走向渴求的乌托邦未来完全未知,但这彻底改变了我们先前对宇宙的认知。", + priority = "不稳定", + }, + huntersSigil = { + name = "猎者符印", + pickup = "站立不动产生增益区域,提升护甲和暴击率。", + description = "站立不动1秒后,生成一个 小型区域,为你和盟友提供 15 (每层+10) 点护甲 和 25% (每层+20%) 暴击率 增益。", + destination = "东2区,\n贝克汉姆大厦,\n地球", + date = "2056/2/2", + story = "嘿,赛特,欢迎入会。我们一直在寻觅人选,既然你来了,我们下季就能开工。我们指望你了。\n\n- 艾瑞克斯 启", + priority = "优先", + }, + roulette = { + name = "轮盘", + pickup = "每分钟获取随机增益。", + description = "获得 一种随机增益,每 分钟 重新轮换一次。可能增益如下:\n\n最大生命值 提升 60 (每层+24)。\n生命恢复 提升 3.6 (每层+1.8) 生命值 每秒。\n基础伤害 提升 14 (每层+5)。\n攻击速度 提升 35% (每层+15.5%)。\n暴击率 提升 25% (每层+10%)。\n移动速度 提升 20% (每层+7%)。\n护甲 提升 35 (每层+14)。", + destination = "3.1号贵宾厅,\n赛卡赌场,\n地球", + date = "2057/7/17", + story = "144贝拉-1型替换轮盘,以应对赌场近期发生的事件。如需进一步咨询,请联系我方代表提供的E向地址。", + priority = "优先", + }, + judderingEgg = { + name = "颤栗之卵", + pickup = "获得一个小朋友!", + description = "孵化 1条 (每层+1)幼生蠕虫 为你而战,间歇性 攻击周围敌人,共造成 18x80% 伤害。", + destination = "B闸门,\n李氏庄园,\n地球", + date = "2056/11/21", + story = "又一份来自饲育者的样本,这次是个非常奇特的种。\n极度谨慎,我听说幼体若处理不当,可在几秒内变得极具攻击性(甚至致命!)。距其孵化应不久,你最好准备好安置构造。", + priority = "易碎", + }, + uraniumHorseshoe = { + name = "铀制马蹄铁", + pickup = "提升移动速度和跳跃高度。", + description = "移动速度 和 跳跃高度 提升 10% (每层+10%)", + destination = "6900号,\n“火星人脸”马具与农场用品店,\n火星", + date = "2056/4/20", + story = "我们在一匹珍贵表演马的蹄上发现这件古怪的绿色马蹄铁,此前它某晚神秘死亡。\n马医认为你们可能了解这个奇怪马蹄铁。光是拿着它就让我手掌灼痛。请谨慎处理。", + priority = "标准", + }, + blastKnuckles = { + name = "爆裂指虎", + pickup = "你的攻击在近距离引发爆炸!", + description = "击中 4.5米 范围内敌人时,同时 在其身后引发爆炸,造成 220% (每层+220%) 伤害。最多储存 5次。每 3秒 获得一次使用次数。", + destination = "todo", + date = "todo", + story = "todo", + priority = "优先", + }, + malice = { + name = "恶意", + pickup = "伤害扩散至附近敌人。", + description = "攻击

                                                                                                                                                                                                                                                                  扩散至附近敌人,在 4.2米 (每层+1米) 范围内对最多 1 (每层+1) 个目标 造成 50% 总伤害。", + destination = "P24区,\n罗密欧,\n波尔-A空间站", + date = "2056/2/11", + story = "不惜一切代价收容。此样本如处理不当将成为巨大威胁。你不知我们为遏制它已付出多少代价。", + priority = "

                                                                                                                                                                                                                                                                  恶意", + }, + manOWar = { + name = "僧帽水母", + pickup = "击杀时产生电涌。", + description = "击杀敌人释放 连锁闪电,对最多 2 个目标造成 100% (每层+40%) 伤害。", + destination = "隐秘隔间,\n创世山,\n金星", + date = "2056/11/20", + story = "兄弟们,还记得我上次寄的小家伙吗?嗯..这只攻击性更强..虽然是个有趣的小家伙!它喜欢在瓶子里转圈圈看着我。要不是我救了它,它早就进了外星生物的肚子里。\n我想给她取名安,觉得如何?", + priority = "优先", + }, + guardingAmulet = { + name = "守护护符", + pickup = "减少来自背后的伤害。", + description = "身后 承受的伤害减少 40% (每层+40%)。", + destination = "C8区,\n蔚蓝花园,\n圣所", + date = "2056/3/11", + story = "我知道他在某处,我知道你能找到他..但你必须坚持不懈。待吾灵魂脱壳之时,箴言将启,你应继吾位。他将是那个向你揭示你对我所有疑问答案的人。\n接受此护符;你将依赖它,它将使你永不回首。", + priority = "标准", + }, + distinctiveStick = { + name = "奇木之杖", + pickup = "在传送器附近治疗。", + description = "在传送器附近生长一棵树,治疗 10米 (每层+3.75米) 范围内盟友,每2秒 恢复 2.2% 其 生命值。", + destination = "德雷克·米内利,\n泰萨夫特,\n地球", + date = "2056/1/15", + story = "亲爱的,我有件特别的东西给你..\n前几日执行任务时,我遇见了此物。它吸引了我,因与我此生所见任何枝桠都不同!这根树枝散发着奇特能量,让我感觉与自然的联系超乎想象,曾以为只存在于故事中!值此周年,我将其赠你。\n我很快就到家,还有一份更棒礼物。请,不要失去信念。", + priority = "标准", + }, + balloon = { + name = "气球", + pickup = "在安全区降低重力。", + description = "按住跳跃键时 重力 降低 35% (每层-15%)。被击中时爆破。在安全区持续 7 秒后恢复。", + destination = "55大道,\n帕尔广场,\n地球", + date = "2056/6/31", + story = "年少时我喜爱气球。在存在大气的场所观赏它们十分有趣。但此球对我意义非凡,它是由强化橡胶制成的最早一批气球之一,可追溯至22年代。望君善加保管,再过几年它可能价值连城(当然,前提是它仍飘在空中!)。", + priority = "优先/易碎", + }, + iceTool = { + name = "冰镐", + pickup = "获得蹬墙跳能力。更快攀爬。", + description = "接触墙壁时获得 1次 (每层+1)额外跳跃。攀爬速度提升 18% (每层+18%)。", + destination = "33号蒙塔,\n索莱海岸,\n地球", + date = "2056/3/12", + story = "徒步好友日安,我找到你上次去[信息删除]山时丢失的冰镐。下次请留意积雪!\n不知冰镐还能否使用,它可是历经沧桑!", + priority = "标准" + }, + needles = { + name = "刺针", + pickup = "概率标记敌人,使其承受必然暴击。", + description = "3% (每层+2%) 概率击中时 标记敌人,使其在3秒内承受针对其的 100% 暴击率。", + destination = "E2区,\n奥伦回环,\n金星", + date = "[信息删除]", + story = "呃..母亲说若你能读到此信,说明你不似她所想那般笨拙。我们希望这些刺针于你有用。恐怕这不是你订的那种,但我想差别不大。\n不记得是否妥善打包寄出。请务必小心。", + priority = "穿刺", + }, + + midas = { + name = "M.I.D.A.S", + pickup = "将一半生命值转化为金币。", + description = "失去 50%生命值,转化为每点生命值获得 $1金币,外加一个 宝箱 的价值。", + destination = "R A 5-5区,\n主星,\n土卫六", + date = "2056/5/4", + story = "\"众神的奇迹..\"\n你反复絮叨着。你疯了吗?我们只想救你,你却视我们如无物。\n\n帮个忙,收下它。随你怎么处置,我们不想在这附近看到你。我们不想卷入你和此物之间的任何事端。\n\n它使你变成了贪婪无魂之人,愿你有自知之明。", + priority = "不稳定", + }, + strangeCan = { + name = "异样罐头", + pickup = "投掷罐头标记敌人。击杀后释放毒云。", + description = "扔出一个 奇特罐头,污染 最大生命值最高的敌人。 被污染 敌人死亡时释放一团 毒云,在5秒内造成相当于该敌人 50% 最大生命值的伤害。", + destination = "1530区,\n563区,\nA-LC12", + date = "2056/5/22", + story = "如我所言,非常美味。只希望别再像上次那样在路上裂开。\n我该找份工作了...", + priority = "不稳定", + }, + whiteFlag = { + name = "白旗", + pickup = "放置白旗。范围内所有人无法攻击。", + description = "放置一面持续 8 秒的白旗。白旗半径内 所有人 技能被禁用。\n不影响无人机和飞行敌人。", + destination = "2B房,\n索姆纳斯酒店,\n地球", + date = "2056/10/5", + story = "替我保管此物直到归家,我并未使用。实际上我们成了朋友!等不及要告诉你了。旅途漫长且经历了一系列意外事件。我向他们提及过你,他们希望下次能邀你前来。意下如何?爱你。", + priority = "不稳定", + }, + }, + + artifact = { + multitude = { + pickupName = "群体神器", + name = "群体", + description = "敌人大规模成群结队。", + approaching = "成群的敌人正在逼近。", + arriving = "做好迎战准备……", + }, + displacement = { + pickupName = "错位神器", + name = "错位", + description = "关卡顺序被打乱。", + }, + gathering = { + pickupName = "集聚神器", + name = "集聚", + description = "金币掉落翻倍但必须手动拾取。", + }, + }, + + + actor = { + Exploder = { + name = "爆破者" + }, + Mimic = { + name = "安保宝箱" + }, + Gatekeeper = { + name = "守门人" + }, + Admonitor = { + name = "陶土训诫者" + }, + Protector = { + name = "守护者", + text = "神器看守者" + } + }, + + monster = { + admonitor = { + name = "陶土训诫者", + story = "远处,一个熟悉的陶土'人'在神殿地面上艰难跋涉。然而,尽管外观相似,这个生物与我之前见过的类型不同。与它较小的同类不同,这个盆栽守护者缺乏狡诈,却拥有两个潜伏的拳头,紧握成钳状。它迈出的步伐几乎让它看起来...自信。我知道如果假设它无法产生诸如信念这样的智能情感,我就是个傻瓜。尽管如此,出于对我整体健康的担忧,我选择不测试这个趾高气扬的油滑家伙的勇气,而是躲在一座翻倒的废墟里。\n\n如果我要活着离开这个星球,我希望我的股骨保持完整。" + } + }, + + interactable = { + MimicInactive = { + text = "..?", + name = "大宝箱?" + } + }, + + elite = { + poison = { + name = "剧毒 %s" + }, + empyrean = { + name = "天界 %s", + text = "审判先驱", + worm = "棱镜利维坦" + } + }, + + stage = { + whistlingBasin = { + name = "啸风盆地", + subname = "渐涸绿洲", + story = "这颗星球持续带给我意外之惊奇。鉴于其原住民之前的行径,我从未想过这般颓败而奢华的建筑竟能由这等生灵所造。令人惊叹的深黑立柱与优美石料构成的拱门拔地而起,尽由匠心之手雕琢装饰。遍布墙壁的是这些绝妙复杂、表象简朴的装饰。奇异的蓝绿色植被侵袭着建筑,悄沿其基攀爬。看来我对这颗星球持有的野蛮成见正在消散。即便如此,我仍高度戒备。尽管酸痛的肌肉在抗议,但此刻休息对我而言为时尚早。" + }, + torridOutlands = { + name = "焦热荒地", + subname = "死寂灼痕", + story = "即便有防护服,我也能感受到脉动的太阳射线直射而下,扭曲着这颗背弃之行星的大气层。高温使我难以保持清醒,凝结在头盔内壁的水汽更是于事无补。不过,若没有它,在游荡中踢起的灰尘碎屑恐怕已半填满我的肺腑——算是“寻找一线光明”聊以自慰吧。烈日当空,哪怕仅仅是轻松勘察周围环境的想法也不过是白日梦。神志恍惚间,我极易踉跄绊入仙人掌和巨石丛中,只能暂时躲进遍布地表的蜿蜒隧道与洞穴中稍作喘息。崎岖地势拖慢脚步,顺眉滴入眼中的汗水终将成为我最小忧虑之一。" + } + } +} diff --git a/Language/spanish.json b/Language/spanish.json deleted file mode 100644 index ff490c70..00000000 --- a/Language/spanish.json +++ /dev/null @@ -1,493 +0,0 @@ -{ - "difficulty" : { - "typhoon.name" : "Tifón", - "typhoon.description" : "El reto definitivo.\nUna pesadilla en la cual la supervivencia es una mera ilusión.\nNadie está a la altura.", - "easyEthereal.name" : "Diluvio", - "normalEthereal.name" : "Tempesta", - "hardEthereal.name" : "Cyclón", - "typhoonEthereal.name" : "Huracán", - }, - "alert" : { - "difficultyUp" : "Subida de Dificultad", - "storm" : "Una tormena se acerca...", - }, - "survivor" : { - "executioner" : { - "name" : "Verdugo", - "nameUpper" : "VERDUGO", - "description" : "El Verdugo es un luchador ágil especializado en contar cabezas. Usando Proyectores Iónicos, el Verdugo crea ilusiones las cuales hacen huir a los enemigos aterrorizados, al mismo tiempo que proyecta un hacha para acabar con los más fuertes. Asegúrate de encadenar eliminaciones con Ráfaga Iónica y Ejecución para mantener el daño constante.", - "endQuote" : "..y se marchó, pero sin su sed de sangre saciada.", - "story": "

                                                                                                                                                                                                                                                                    Detalles del pasajero:\n[Clase Militar]\n\n
                                                                                                                                                                                                                                                                      Equipaje:\nOficial abordó con uniforme de fibra de carbono del Oficial Actuante de EXN, sobretraje civil con placas de grado básico y una pistola de servicio emitida.\nEn el punto de control militar, se observó que el armamento del pasajero tenía una modificación adicional bajo el cañón, y fue autorizado tras acordar que se almacenaría en un contenedor seguro.\nEl pasajero también fue autorizado con una batería ION serie 204X personalizada con orificios de propulsión.\nComo medida de seguridad, los bienes tecnológicos del pasajero fueron equipados temporalmente con anuladores eléctricos automáticos y luego devueltos.\n\n[REGISTRO DE SEGURIDAD]\n\n
                                                                                                                                                                                                                                                                        [Nota E1a] Se han reportado varios casos en los que el Pasajero pone nervioso al personal completo del punto de control de seguridad. Se requerirá vigilancia ligera hasta nuevo aviso.\n
                                                                                                                                                                                                                                                                          [Nota E1b] El Pasajero respondía frecuentemente con sonidos en lugar de habla antes de optar por utilizar Lengua de Señas Universal.\n
                                                                                                                                                                                                                                                                            [Suceso E1] Comando contratado realiza revisión de seguridad en los aposentos del Pasajero.\n
                                                                                                                                                                                                                                                                              [Suceso EM1a] Comando contratado solicita vigilancia privada de 24 horas en el pasillo que conduce a los aposentos del Pasajero.\n
                                                                                                                                                                                                                                                                                [Suceso E2] Se le pidió al Pasajero que cambiara de mesa durante la comida tras recibir una queja formal de otro Pasajero a la seguridad a bordo.", - "id" : "Travel ID: 4383354378334FF3D34D", - "departure" : "Origen:\nVieja Alcatraz,\nRustborough,\nMarte", - "arrival" : "Destino:\nBahía Stillwater,\nNewdredge,\nEuropa" - }, - "mule" : { - "name" : "MONTACARGAS", - "nameUpper" : "MONTACARGAS", - "description" : "CADA UNIDAD MONTACARGAS ESTÁ DEBIDAMENTE EQUIPADA Y LISTA PARA EL SERVICIO. CONSTRUIDA CON MATERIALES DE ALTA CALIDAD PARA UN RENDIMIENTO MÁXIMO EN EL TRABAJO. ESTE MODELO INCLUYE EL CONJUNTO DE HERRAMIENTAS FORRAJERO, PERFECTO PARA CAZA, EXPLORACIÓN Y OTRAS TAREAS PELIGROSAS DESTINADAS AL USUARIO COMÚN. PARA INICIALIZAR, INDIQUE UNA DIRECTIVA DURANTE EL ARRANQUE DEL SISTEMA.", - "endQuote" : "..y se marchó, pistones chirriando, dirección indefinida.", - "story": "AVISO: Las unidades MONTACARGAS no son conscientes ni mascotas. Llamarlas ''Molly'' no afectará en absoluto la programación de la unidad MONTACARGAS.", - "id" : "", - "departure" : "", - "arrival" : "" - }, - "nemesisCommando" : { - "name" : "Comando Némesis", - "nameUpper" : "COMANDO NÉMESIS", - "description" : "El Comando Némesis es un personaje flexible con el arsenal suficiente para enfrentarse a cualquier situación. Hiere a los enemigos usando Misericordia como preparación para infligir un daño devastador con el resto de tus habilidades. Utiliza Evasión Táctica para entrar y salir del combate cuerpo a cuerpo, y convertir hasta el peor escenario en una ventaja.", - "endQuote": "..y se marchó, llevando consigo un nuevo sentido de humanidad.", - "story" : "REGISTRO DE DATOS DE CAMPO NO REGISTRADO: ÚLTIMA MODIFICACIÓN EN [##090??!?] \n[ERROR 0x00000ce: Paquete de Datos Corrupto, viendo punto de restauración.] \n\n
                                                                                                                                                                                                                                                                                  [Entrada 1] La patrulla recogió a un sobreviviente herido a pocos kilómetros del campamento. Se rumorea que “apareció de la nada” (como si me fuera a creer eso), ya que no hay nada en los alrededores que indique de dónde vino. Sea como sea, un sobreviviente es un sobreviviente. Mientras escribo esto, lo llevamos de vuelta. No lo pone fácil, eso sí, por el peso del equipo que lleva. Tiene unas modificaciones poco comunes en una armadura estándar de Comando. Parece ser, o haber sido, un modelo Mk. 1 de primera emisión (¿Aunque creo recordar que dejaron de producirlo hace años?). \n\n
                                                                                                                                                                                                                                                                                    [Entrada 2] Actualización: el sujeto porta artillería peligrosamente pesada para ser un Comando: Un lanzacohetes aún caliente al tacto y unas granadas que parecen claramente alienígenas, Jamás habia visto algo parecido. Está inconsciente, y por lo que vemos a través del casco, bastante golpeado. Su brazo izquierdo está rígido, tiene la mano cerrada con fuerza sobre la empuñadura de su vibrocuchilla. Intentamos verla mejor, pero es como si su puño estuviera fusionado con esa cosa. Además, está cubierta con una sustancia negra endurecida. No sabemos si fue por una pelea entre sobrevivientes o evidencia de algún tipo de auto defe??!0049### \n[Fin de datos legibles.] \n\n
                                                                                                                                                                                                                                                                                      [Entrada 2a] no sé cuánto tiempo tengo mientras escribo esto se despertó y el campamento está DESTRUIDO la gente no para de sangrar no sabemos ni siquiera si era humano con lo que puede hacer, nadie supo qué hacer fue directo a por Adrian \n no sabemos qué lo provocó??? qué HICIMOS para merecer esto??? por qué esa COSA nos atacó U!??!?##$0959090302???! \n[Fin de datos legibles.] \n\n

                                                                                                                                                                                                                                                                                      Las tábanas se ?reunen s?obre esta no?ble be?stia mie?ntras p?asta. \n\n Tu pla?ga en su s?uperficie s?erá err?adi?cada... \n\n Por es?tas man?os labo?rosas." - }, - "nemesisMercenary" : { - "name" : "Mercenario Némesis", - "nameUpper" : "MERCENARIO NÉMESIS", - "description" : "The Nemesis Mercenary was born with a special power.", - "endQuote": "..y se marchó, equipado para más.", - "story": "

                                                                                                                                                                                                                                                                                      I DON'T NEED NO FRIENDS\nI DON'T NEED NO PHONE\nJUST A BAG OF SEVERED HEADS\nAND MY NUCLEAR THRONE" - }, - "technician": { - "name": "Técnico", - "nameUpper": "TÉCNICO", - "description": "El Técnico es experto en hacer y mantener zonas que bloquean el avance de los enemigos. Cierre Forzado puede bloquear una zona forzadamente al ser mejorado. Mejorar dispositivos aumenta su efectividad, ¡pero ten cuidado haciendolo en entornos peligrosos!", - "endQuote": "..y se marchó, simplemente reiniciando la nave.", - "story" : "

                                                                                                                                                                                                                                                                                        Detalles del Pasajero:\n[Clase Empleado]\n\n
                                                                                                                                                                                                                                                                                          Detalles del Empleado:\nEmpleado contratado para trabajar en el lugar durante toda la duración del viaje programado de la UES Contact Light, con el fin de realizar mantenimiento en maquinaria en caso de que surjan problemas técnicos a bordo de la nave. Empleado calificado con 6 años de experiencia práctica en ingeniería de software y hardware.\n\n
                                                                                                                                                                                                                                                                                            Equipaje:\nEl empleado abordó con un traje espacial de soldadura Durarend, calificado con alta resistencia al vacío, además de ropa de cuero y tela diseñada para uso planetario. Se notificó al empleado que la ropa podría rasgarse en caso de un evento de vacío. Seguridad marcó la llave inglesa de gran tamaño que portaba por parecer poco práctica y potencialmente peligrosa. Al ser cuestionado, el empleado indicó que fue ''hecha a medida... para los trabajos más duros''. Siguiendo el procedimiento, la llave fue trasladada a una caja de seguridad automatizada en la bahía de carga 1a. La inspección restante del equipaje reveló varias llaves inglesas de emisión estándar, 3 USBs codificados por color, un portátil modelo GeForm 2047, cajas de herramientas y una herramienta de soldadura. Los USBs fueron inspeccionados en una computadora desechable QPuting de la compañía equipada con ReoVirus Detector v1.6.2. No se detectaron rastros de malware y los archivos almacenados están en formato UPG, conteniendo planos de varios dispositivos. El empleado fue autorizado a continuar sin más dificultades.\n\n[REGISTRO DE SEGURIDAD]\n
                                                                                                                                                                                                                                                                                              - [Suceso T1] Empleado retirado por la fuerza de la cabina durante los preparativos de despegue al negarse a salir después de conversar con el piloto de la nave durante varias horas.\n
                                                                                                                                                                                                                                                                                                [Suceso T2] Empleado observado robando refrescos de una máquina expendedora.\n
                                                                                                                                                                                                                                                                                                  [Suceso T3] Bloqueo automatizado originado en la bahía de carga 4a fue resuelto por el empleado.\n
                                                                                                                                                                                                                                                                                                    [Suceso T4] El estado de la cápsula de escape B-08 cambió de ERROR a ALERTA después de que el empleado fue solicitado por el personal de la nave para realizar un diagnóstico. Se instruyó al personal de seguridad a investigar.\n
                                                                                                                                                                                                                                                                                                      [Suceso T1a] Se reportó además que el empleado manipuló el panel de control de la cabina para que fuera ''más fácil de manejar''. El lanzamiento programado se retrasó y se instruyó al empleado a revertir cualquier modificación realizada.", - "id" : "Travel ID: 54E4F4C434F59474E454", - "departure" : "Origen:\nMuelle de Carga 5 de la UES,\nRedview,\nMarte", - "arrival" : "Destino:\nMuelle de Carga 0 de la UES,\nRedview,\nMarte" - } - }, - "skill" : { - "executionerZ": { - "name": "Pistola estándar", - "description": "Haz un disparo que inflinge 100% de daño." - }, - "executionerX": { - "name": "Ráfaga Iónica", - "description": "Dispara una ráfaga de balas ionizadas que inflingen 320% de daño cada una.\nConsigue balas matando enemigos." - }, - "executionerC": { - "name": "Disuasión masiva", - "description": "Te lanza hacia delante, asustando a enemigos cercanos.\nNo puedes ser golpeado mientras te desplazas." - }, - "executionerV": { - "name": "Ejecución", - "description": "Te elevas y desciendes con fuerza, impactando con un hacha proyectada que inflinge 1000% de daño.\nCada ejecución exitosa reduce por un segundo la recarga de las habilidades." - }, - "executionerVBoosted": { - "name": "Ejecución colectiva", - "description": "Te elevas y desciendes con fuerza, impactando con un hacha proyectada que inflinge 1500% de daño, aterrorizando a los enemigos.\nCada ejecución exitosa reduce por un segundo la recarga de las habilidades." - }, - "executionerV2": { - "name": "Partecráneos", - "description": "Lanza un hacha proyectada en un arco circular que inflinge 1500% de daño por segundo. Los enemigos aterrorizados reciben el doble de daño.\nCada ejecución exitosa reduce por un segundo la recarga de las habilidades." - }, - "executionerV2Boosted": { - "name": "Revientacráneos", - "description": "Lanza dos hachas proyectadas en un arco circular que inflingen 1500% de daño por segundo. Los enemigos aterrorizados reciben el doble de daño.\nCada ejecución exitosa reduce por un segundo la recarga de las habilidades." - }, - "muleZ": { - "name": "ELIMINACIÓN DE INTERFERENCIAS", - "description": "GOLPEA A LOS ENEMIGOS INFLINGIENDO 200% DE DAÑO. MANTEN PRESIONADO PARA CARGAR Y APLASTAR A LOS ENEMIGOS INFLINGIENDO 1400% DE DAÑO." - }, - "muleX": { - "name": "INMOBILIZAR", - "description": "DISPARA UNA TRAMPA QUE INFLINGE 125% DE DAÑO. LA TRAMPA ATRAPA HASTA 5 ENEMIGOS EN LA PROXIMIDAD DEL OBJETIVO." - }, - "muleC": { - "name": "RECALIBRACIÓN DE ENGRANAJES", - "description": "GIRA HACIA DELANTE INFLINGIENDO 4X100% DE DAÑO. EL ULTIMO GOLPE ATURDE." - }, - "muleV": { - "name": "ASISTENCIA A PRUEBA DE ERRORES", - "description": "DESPLIEGA UN DRON DE REPARACIÓN PERSONAL DURANTE 8 SEGUNDOS. EL DRON CURA 5x7% DE TU SALUD TOTAL. SI ESTÁS A SALUD COMPLETA, OTORGA BARRERA EN SU LUGAR." - }, - "muleVBoosted": { - "name": "ASISTENCIA A PRUEBA DE ERRORES 2.0", - "description": "LANZA UN DRON DE REPARACIÓN DURANTE 8 SEGUNDOS. EL DRON CURA 5x10% DE TU SALUD TOTAL. SI ESTÁS A SALUD COMPLETA, OTORGA BARRERA EN SU LUGAR.." - }, - "nemesisCommandoZ": { - "name": "Misericordia", - "description": "Desgarra a los enemigos cercanos con un tajo que inflinge 120% de daño, hiriendolos durante 6 segundos. No se puede acumular.\nLos enemigos heridos reciben un 50% más de daño de todas las fuentes." - }, - "nemesisCommandoX": { - "name": "Toque Singular", - "description": "Dispara a un enemigo inflingiendo 200% de daño.\nPuedes acumular hasta 4. Recarga 2 balas cuando ruedes." - }, - "nemesisCommandoX2": { - "name": "Tajo Distante", - "description": "Corta en línea recta hacia delante e inflige 90% de daño, hiriendo a los enemigos golpeados durante 6 segundos." - }, - "nemesisCommandoC": { - "name": "Evasión Táctica", - "description": "Rapidamente rueda hacia delante una corta distancia.\nNo puedes ser dañado mientras estes rodando. Puedes acumular hasta 2." - }, - "nemesisCommandoV": { - "name": "Desalojo", - "description": "Lanza una granada que inflinge 700% de daño. Manten presionado para cocinar la granada.\nPulsa la dirección abajo para lanzarla en un arco bajo." - }, - "nemesisCommandoVBoosted": { - "name": "Bombardeo", - "description": "Lanza una granada que inflinge 700% de daño, aturdiendo enemigos y dividiendose en 3 granadas que inflingen 3x150% de daño.\nManten presionado para cocinar la granada.\nPulsa la dirección abajo para lanzarla en un arco bajo." - }, - "nemesisCommandoV2": { - "name": "Devastador", - "description": "Dispara un cohete que inflinge 1000% de daño a un solo enemigo. Los enemigos de alrededor reciben 50% de daño y son aturdidos.\nSi se utiliza en el aire, dispara en diagonal hacia abajo." - }, - "nemesisCommandoV2Boosted": { - "name": "Azotador", - "description": "Dispara un cohete que inflinge 1000% de daño a un solo enemigo. os enemigos de alrededor reciben 50% de daño y son esparcidos.\nSi se utiliza en el aire, dispara en diagonal hacia abajo.\nPuedes acumular hasta 2." - }, - "nemesisMercenaryZ": { - "name": "Lascerate", - "description": "Injure enemies with your shotgun's attachment for 130% damage." - }, - "nemesisMercenaryX": { - "name": "Quick Trigger", - "description": "Fire your shotgun, stunning and hitting enemies nearby for 600% damage." - }, - "nemesisMercenaryC": { - "name": "Blinding Slide", - "description": "Quickly slide forwards. Getting hit at the start reloads your shotgun. \nYou can attack while sliding." - }, - "nemesisMercenaryV": { - "name": "Devitalize", - "description": "Target the weakest enemy in front of you, attacking them for 850% damage, dealing +50% TOTAL damage to stunned enemies. \nYou cannot be hit for the duration." - }, - "nemesisMercenaryVBoosted": { - "name": "Absolute Devitalization", - "description": "Target the weakest enemy in front of you, attacking them for 1100% damage, dealing +50% TOTAL damage to stunned enemies. \nYou cannot be hit for the duration." - }, - "technicianZ": { - "name": "Calibrar", - "description": "Golpea hacia delante con una llave inglesa que inflinge 180% de daño.\nGolpear dispositivos 3 veces los mejorará temporalmente durante 30 segundos." - }, - "technicianZ2": { - "name": "Contramedidas", - "description": "Lanza una llave inglesa hacia adelante, atravesando hasta 3 enemigos inflingiendo un 180% de daño.\nGolpear dispositivos 3 veces los mejorará temporalmente durante 20 segundos." - }, - "technicianX": { - "name": "Cierre Forzado", - "description": "Lanza una bomba que inflinge 500% de daño cuando es reactivada.\n Aturde y atrae a los enemigos pasivamente cuando es mejorada." - }, - "technicianX2": { - "name": "El Botón Rojo", - "description": "Detona la bomba lanzada inflingiendo 700% de daño y aturde a los enemigos." - }, - "technicianC": { - "name": "Energia Refrescante", - "description": "Coloca una máquina expendedora que mejora tu movimiento y velocidad de ataque.\nAdicionalmente aumenta la probabilidad de ataque crítico cuando es mejorada." - }, - "technicianC2": { - "name": "Radial Amplifier", - "description": "Deploy an antenna that deals 15% additional damage to attacked enemies.\nHas 100% critical strike chance when upgraded." - }, - "technicianV": { - "name": "Cortafuegos Auxiliar", - "description": "Coloca una torreta disparando hacia delante que inflinge 220% de daño por segundo.\nDispara rápidamente e inflinge 350% de daño por segundo cuando es mejorada." - }, - "technicianVBoosted": { - "name": "Cortafuegos Auxiliar 2.0", - "description": "Coloca una torreta disparando rápidamente que inflinge 350% de daño por segundo.\nDispara misiles que inflingen 4x100% de daño cuando es mejorada." - }, - "peace": { - "name": "<#a4eae4>Paz!", - "description": "No puedes usar esta habilidad." - } - }, - - "item" : { - "armedBackpack" : { - "name" : "Mochila Armada", - "pickup": "Probabilidad de disparar una bala detras de ti.", - "description": "18.5% (+6.5% por acumulación) de probabilidad de al atacar disparar una bala detras de ti que inflinge 150% de daño.", - "destination": "832B,\nHautenuit,\nTierra", - "date": "11/7/2056", - "story": "Estar seguro siempre es importante. No quiero que te metas en problemas, así que por favor lleva esto cuando salgas. ¡Especialmente en los cañones, que hay muchos ladrones por allí!\nTe enviaré munición extra más adelante este año, ¿vale?", - "priority": "Estandar" - }, - "brassKnuckles" : { - "name": "Puños Americanos", - "pickup" : "Realiza daño extra a corta distancia.", - "description": "Realiza +35% de daño a enemigos en un rango de 2.8m (+1.4 por acumulación).", - "destination": "UESPD,\nE42,\nUES Alabarda B2", - "date": "03/01/2056", - "story" : "Encontramos esto en la escena del crimen. Tenemos que analizar el ADN para compararlo con el resto de las pruebas. Aún no puedo creer que hayan escapado.\nEn la parte trasera hay un símbolo, tal vez nos dé alguna pista. Me dirigiré al oeste de [REDACTADO], conozco a alguien que puede ayudarnos a resolver el caso.", - "priority": "Estandar/Biológico" - }, - "detritiveTrematode" : { - "name": "Trematodo Detritívoro", - "pickup" : "Los enemigos con baja salud sufren daño con el tiempo.", - "description": "Los enemigos con menos del 15% (+5% por acumulación) de salud quedan infectados de forma permanente, recibiendo un 100% de daño por segundo.", - "destination": "112,\nNeaus 2,\nMarte", - "date": "03/11/2057", - "story" : "Eh, esto es tuyo.\nPor favor, no vuelvas a enviar una de esas raras incubadoras microscópicas de huevos, ¡jamás!.", - "priority" : "Priority/Biological" - }, - "dormantFungus" : { - "name" : "Hongo Latente", - "pickup" : "Regenera salud mientras estás en movimiento.", - "description": "Regenera un 2% (+2% por acumulación) de tu salud cada dos segundos mientras te mueves.", - "destination": "Puerta 1,\nModulo 13,\nEstación Gea", - "date": "11/01/2056", - "story" : "Cuando me ordenaste explorar las cuevas de hongos, lo único que esperaba encontrar era... bueno, hongos, por supuesto. Aunque no estaba para nada entusiasmado, este espécimen llamó mi atención.\nParece crecer únicamente alrededor de agua corriente, y desde que recogí y almacené esta muestra, tanto el color como el olor han desaparecido rápidamente.\n\nEspero que puedas estudiarlo más a fond.", - "priority": "Estandar" - }, - "fork" : { - "name" : "Tenedor", - "pickup" : "Haz más daño.", - "description": "Aumenta el daño base en 3 (+3 por acumulación).", - "destination": "Edificio Antima - Tejado,\nColinas Florales,\nTierra", - "date": "09/24/2056", - "story" : "¡Sorpresa! ¡Un tenedor! ¡Jajaja!!!\nDijiste que cualquier regalo era un buen regalo, ¡AQUÍ TIENES! Bromas aparte, este tenedor es mágico. Confía en mí, Duncan, una vez comí una ensalada con él y ese mismo día encontré una moneda.\n\nAdemás, eh, por favor ven a jugar con nosotros pronto, echamos de menos destrozar tu build en SS2 (por favor dime que has cambiado de main, lol).", - "priority": "Estandar" - }, - "watchMetronome" : { - "name" : "Metrónomo de Reloj", - "pickup" : "Carga el reloj mateniendote quieto. Gana velocidad con la carga entera.", - "description": "Permanecer quieto carga el reloj durante 3 segundos. Al estar completamente cargado, aumenta la velocidad de movimiento en un 50%, consumiendo la carga en 3 (+1 por acumulación) segundos.", - "destination": "Caja 1,\nG-44 (Bajo tierra),\nTierra", - "date": "09/05/2056", - "story" : "El abuelo me dio esto como reliquia del pasado. Dice que perteneció a nuestro tataratatatatatarabuelo. No me importan los antigüedades ni los objetos familiares, así que creo que es mejor que tú lo tengas.\nDe todas formas, no es que sirva para mucho..", - "priority": "Prioridad" - }, - "wonderHerbs" : { - "name" : "Hierbas Milagrosas", - "pickup" : "Levemente aumenta todos los efectos de curación.", - "description": "Aumenta la curación de todas las fuentes en un 12% (+12% por acumulación).", - "destination": "2342,\nPlot 3,\nTierra", - "date": "11/07/2056", - "story" : "¡Rikka! He oído que estás trabajando con la EC en una nueva receta. Aquí en la granja hemos estado cultivando unas hierbas especiales que creemos que te pueden ser muy útiles. Son conocidas por ser muy, muy efectivas para tratar heridas físicas y emocionales.\nSi necesitas más, no dudes en llamarme. ¡Besos!\n-Eden", - "priority": "Estándar" - }, - "coffeeBag" : { - "name" : "Bolsa de Cadé", - "pickup" : "Activar un interactuable aumenta la velocidad de movimiento y de ataque.", - "description": "Activar un objeto interactivo aumenta la velocidad de movimiento y ataque en un 22% durante 10 (+5 por acumulación) segundos..", - "destination": "n1,\nLa Capital,\nUES PRIMORDIAL", - "date": "05/22/2056", - "story" : "Doooooooormiloooooon no sé por qué dices que no te gusta el café, ¡es taaaan bueno para mantenerse despierto! Ya sé, ya sé, hay otras maneras de animarse, pero oh, creo que te va a molar esta, ¡es mi favorita! Pruébala antes de gritarme, ¿vale?", - "priority": "Estándar" - }, - "x4Stimulant" : { - "name" : "Estimulante X4", - "pickup" : "Reduce el tiempo de recarga de la habilidad secundaria y cura ligeramente al usarla.", - "description": "Reduce el tiempo de recarga de la habilidad secundaria en un 10% (+10% por acumulación). Usar la habilidad secundaria aumenta la regeneración de salud en 2 (+1 por acumulación) puntos de vida por segundo durante 3 segundos..", - "destination": "Romeo 33,\nParque Nacional Batang Ai,\nTierra", - "date": "4/12/2057", - "story" : "Empaquetado, incluye manual. ¿La máquina muestra síntomas de un 540? Si es así, quizá debas evitar usar más de 100 ml de estimulante.\n\nSaludos." - }, - "moltenCoin" : { - "name" : "Moneda Derretida", - "pickup" : "Probabilidad de incendiar a los enemigos al golpear y ganar oro", - "description": "6% de probabilidad al golpear de incinerar a los enemigos durante 6 segundos, haciendo que suelten $1 (+$1 por acumulación) de oro.", - "destination": "Toera 2,\nB44,\nEstación Madre", - "date": "05/22/2056", - "story" : "¡Hey! Eh, lo siento, de verdad lo siento... Sé que querías que guardara esta moneda, pero ya no puedo hacerme responsable...\nVerás, la puse accidentalmente en el borde de un horno de plasma y... bueno... se ha quemado un poco por un lado, por favor no te enfades conmigo..", - "priority": "Estándar" - }, - "crypticSource" : { - "name": "Fuente Críptica", - "pickup" : "Cambiar de dirección crea ráfagas de energía.", - "description": "Cambiar la dirección del movimiento dispara un rayo en cadena que inflige un 70% (+55% por acumulación) de daño a hasta 2 objetivos.", - "destination" : "O32,\nPerfil Bajo,\nTierra", - "date" : "03/30/2058", - "story" : "Desde los átomos hasta los seres conscientes, todo proviene de la energía. Sin embargo, este objeto, bastante críptico, parece emanar energía por sí mismo. Grandes cantidades de fricción parecen desencadenar una reacción en cadena que lo hace altamente inestable. No obstante, también podría ser una fuente manipulable de energía (¿infinita?).\nNo sabemos si esto nos llevará al futuro utópico que anhelamos, pero sin duda cambia por completo nuestras ideas previas sobre el universo.", - "priority" : "Volátil" - }, - "huntersSigil" : { - "name": "Sigilo del Cazasor", - "pickup" : "Permanecer quieto crea una zona que aumenta la armadura y la probabilidad de golpe crítico.", - "description": "Después de estar quieto 1 segundo, crea una zona pequeña que otorga a ti y a tus aliados 15 (+10 por acumulación) de armadura y 25% (+20% por acumulación) de probabilidad de crítico.", - "destination" : "2Este,\nEdificio Beckham,\nTierra", - "date" : "02/02/2056", - "story" : "Hola Sett, bienvenido al club. Hemos estado buscando candidatos y ahora que estás con nosotros, podemos empezar a trabajar la próxima temporada. Contamos contigo.\n\n- Irix, fuera", - "priority": "Prioridad" - }, - "roulette" : { - "name": "Ruleta", - "pickup" : "Obtén un beneficio aleatorio cada minuto.", - "description": "Obtén un beneficio aleatorio, que se reinicia cada minuto. Beneficios posibles:\n\n: Aumenta la vida máxima en 60 (+24 por acumulación).\n: Aumenta la regeneración de vida en 3,6 (+1,8 por acumulación) puntos por segundo.\n: Aumenta el daño base en 14 (+5 por acumulación).\n: Aumenta la velocidad de ataque en 35% (+15,5% por acumulación).\n: Aumenta la probabilidad de crítico en 25% (+10% por acumulación).\n: Aumenta la velocidad de movimiento en 20% (+7% por acumulación).\n: Aumenta la armadura en 35 (+14 por acumulación).", - "destination" : "PHabitación 3.1,\nCasino Secva,\nTierra", - "date" : "7/17/2057", - "story" : "Modelo de reemplazo de la Ruleta 144Bella-1, en seguimiento a los recientes eventos ocurridos en el casino. Para más consultas, por favor contacte con nosotros en la dirección electrónica proporcionada por nuestros representantes.", - "priority": "Prioridad" - }, - "judderingEgg" : { - "name": "Huevo del Juicio", - "pickup" : "Consigue un pequeño amigo!", - "description": "Incuba 1 (+1 por acumulación) joven wurm que lucha contigo, atacando periódicamente a los enemigos cercanos con un daño de 18x80%.", - "destination" : "Puerta B,\nCompuesto Lee,\nTierra", - "date" : "11/21/2056", - "story" : "Otro más del criador, esta vez es un ejemplar muy extraño.\nTen mucho cuidado, he oído que los juveniles pueden volverse altamente agresivos (¡y letales!) en cuestión de segundos si no los manejas correctamente. No debería tardar mucho en eclosionar, así que más vale que tengas la estructura lista para ello.", - "priority": "Fragil" - }, - "uraniumHorseshoe" : { - "name": "Herradura Radioactiva", - "pickup" : "Aumenta la velocidad de movimiento y la altura de salto.", - "description": "Aumenta la velocidad de movimiento y la altura de salto en un 10% (+10% por acumulación)Estándar" - }, - "blastKnuckles" : { - "name": "Blast Knuckles", - "pickup" : "Your attacks explode at close range!", - "description": "Hitting an enemy within 4.5m also blasts behind them for 220% (+220% per stack) damage. Hold up to 5 charges. Gain a charge every 3 seconds.", - "destination" : "todo", - "date" : "todo", - "story" : "todo", - "priority": "Priority" - }, - "malice" : { - "name": "Malicia", - "pickup" : "El Daño se propaga a enemigos cercanos.", - "description": "Los ataques

                                                                                                                                                                                                                                                                                                      se propagan a enemigos cercanos infligiendo un 50% de daño TOTAL a hasta 1 (+1 por acumulación) objetivo(s) dentro de 4,2 m (+1 m por acumulación)Malicioso" - }, - "manOWar" : { - "name": "Medusa Embotellada", - "pickup" : "Crea una descarga eléctrica al matar a un enemigo.", - "description": "Matar a un enemigo dispara un rayo en cadena que inflige un 100% (+40% por acumulación) de daño a hasta 2 objetivos.", - "destination" : "Cubículo Escondido,\nMt. de la Creación,\nVenus", - "date" : "11/20/2056", - "story" : "Chicos, ¿se acuerdan del pequeñín que les envié hace un tiempo? Bueno... este es un poco más agresivo... aunque es una chica muy simpática. ¡Le encanta dar vueltas y mirarme desde dentro de la botella! Tío, si no la hubiera rescatado, ahora estaría en el estómago de algún extraterrestre.\nEstoy pensando en llamarla Ann, ¿qué opinan?", - "priority": "Prioridad" - }, - "guardingAmulet" : { - "name": "Amuleto Protector", - "pickup" : "Reduce daño recibido por la espalda.", - "description": "El daño recibido por detrás se reduce en un 40% (+40% por acumulación).", - "destination" : "C8,\nJardín Celeste,\nSantuario", - "date" : "03/11/2056", - "story" : "Sé que está en algún lugar allá afuera, sé que puedes encontrarlo... pero debes ser persistente. Para cuando mi alma abandone este cuerpo, las palabras serán pronunciadas y tú tomarás mi lugar. Él será quien te muestre el camino hacia las respuestas que siempre quisiste de mí.\nAbraza este amuleto; dependerás de él, y te impedirá mirar atrás jamás.", - "priority": "Estándar" - }, - "distinctiveStick" : { - "name": "Palo Distintivo", - "pickup" : "Curate cerca del teletransportador.", - "description": "Hace crecer un árbol cerca de los Teletransportadores, curando a los aliados en un radio de 10 m (+3,75 m por acumulación) por un 2,2% de su salud cada 2 segundos.", - "destination" : "Dreq Mineli,\nTesaft,\nTierra", - "date" : "01/15/2056", - "story" : "Cariño, tengo algo especial para ti..\nEl otro día, en mi misión, me encontré con esto. Me llamó la atención porque es diferente a cualquier rama que haya visto en mi vida. ¡Esta rama desprende una energía peculiar! Me hizo sentir conectado con la naturaleza de una forma que creía posible solo en los cuentos. En honor a nuestro aniversario, te la regalo.\nPronto llegaré a casa con un regalo aún mejor. Por favor, no pierdas la fe.", - "priority": "Estándar" - }, - "balloon" : { - "name": "Globo", - "pickup" : "Disminuye tu gravedad fuera de peligro.", - "description": "Reduce la gravedad mientras mantienes pulsado el botón de salto en un 35% (-15% por acumulación). Se desactiva al recibir daño. Se recupera tras estar fuera de peligro durante 7 segundos.", - "destination" : "55 Mo.,\nPlaza Paare,\nTierra", - "date" : "06/31/2056", - "story" : "Cuando era joven me encantaban los globos. Son muy divertidos de ver en lugares con atmósfera. Sin embargo, este tiene un valor especial para mí, ya que fue uno de los primeros hechos con caucho reforzado, allá por los años 22. Espero que lo cuides, podría valer una fortuna dentro de unos años (¡si sigue flotando, claro!).", - "priority": "Prioridad/Fragil" - }, - "iceTool" : { - "name": "Picahielos", - "pickup" : "Obtén un salto en pared. Escala más rápido.", - "description": "Obtén 1 (+1 por acumulación) salto extra mientras estés en contacto con una pared. Aumenta la velocidad de escalada en un 18% (+18% por acumulación).", - "destination" : "Torre de Mon #33,\nCosta Solei,\nTierra", - "date" : "03/12/2056", - "story" : "Buen día, compañero de senderismo. Encontré la herramienta de hielo que perdiste la última vez que fuimos al Mt. [REDACTADO]. ¡Por favor, ten cuidado con la nieve la próxima vez!\nMe pregunto si la herramienta de hielo todavía será útil, ¡ha pasado por tanto!", - "priority": "Estándar" - }, - "needles" : { - "name": "Agujas", - "pickup" : "Probabilidad de marcar enemigos para golpes críticos garantizados.", - "description": "3% (+2% por acumulación) de probabilidad al impactar de marcar a los enemigos para que reciban un 100% de probabilidad de crítico durante 3 segundos.", - "destination" : "E2,\nBucle de Oren,\nVenus", - "date" : "[REDACTADO]", - "story" : "Uh.. madre dice que si puedes leer esto, es porque no eres tan torpe como pensaba, y pues eso. Esperamos que te sirvan estas agujas. Me temo que no son las que pediste, pero no hace mucha diferencia, pienso.\nNo recuerdo si los enviamos bien embalados. Ten cuidado.", - "priority": "Punzante" - }, - - "midas" : { - "name" : "M.I.D.A.S", - "pickup" : "Convierte la mitad de tu salud en oro.", - "description" : "Pierde 50% de salud, ganando $1 de oro por cada punto de salud gastado, más el costo de un cofre.", - "destination" : "R A 5-5,\nPrimordial,\nTitan", - "date" : "05/04/2056", - "story" : "\"Un milagro de los dioses...\"\nLo repites una y otra vez. ¿Estás loco? Nos sigues tratando como si fuéramos ignorantes cuando todo lo que intentamos fue SALVARTE.\n\nHaznos un favor y quédate con eso. Haz lo que quieras con ello, pero NO queremos verte por aquí nunca más. NO queremos ser parte de lo que sea que esté pasando contigo y esa cosa.\n\nTe está volviendo una persona codiciosa y sin alma, y espero que lo sepas..", - "priority": "Volatile" - }, - "strangeCan" : { - "name": "Lata Extraña", - "pickup" : "Lanza una lata para marcar a un enemigo. Matarlo libera una nube tóxica.", - "description": "Lanza una lata peculiar que intoxica al enemigo con la mayor salud máxima. Los enemigos intoxicados liberan una nube tóxica al morir que inflige un 50% de la salud máxima del enemigo como daño durante 5 segundos.", - "destination" : "1530,\n563,\nA-LC12", - "date" : "05/22/2056", - "story" : "Estos son tan deliciosos como te dije. Solo espero que no se rompa en el camino como la última vez.\nDebería buscar trabajo...", - "priority": "Volátil" - }, - "whiteFlag" : { - "name": "Bandera Blanca", - "pickup": "Coloca una bandera blanca. Todos a su alrededor quedan incapacitados para atacar.", - "description": "Coloca una bandera blanca durante 8 segundos. Desactiva las habilidades de todos dentro de su radio.\nNo afecta a drones ni enemigos voladores.", - "destination" : "Habitación 2B,\nHotel Somnus,\nTierra", - "date" : "10/5/2056", - "story" : "Guárdalo para mí hasta que regrese a casa, no lo necesitaba. De hecho, ¡nos hicimos amigos! No puedo esperar para contarte todo pronto. Ha sido un viaje largo y una serie de eventos inesperados. Les hablé de ti y quieren que te invite la próxima vez. ¿Qué te parece, eh? ¡Te quiero!", - "priority": "Volátil" - } - }, - - "artifact" : { - "multitude" : { - "pickupName" : "Artefacto de Multitud", - "name" : "Multitud", - "description": "Vienen hordas de enemigos.", - "approaching": "Una horda de enemigos se acerca.", - "arriving": "Preparate...", - }, - "displacement" : { - "pickupName" : "Artefacto de Desarraigo", - "name" : "Desarraigo", - "description": "Los escenarios se mezclan.", - }, - "gathering" : { - "pickupName" : "Artefacto de Recolecta", - "name" : "Recolecta", - "description": "El oro soltado se duplica, pero es necesario recogerlo.", - } - }, - - - "actor" : { - "Exploder.name" : "Estallador", - "Mimic.name" : "Cofre de seguridad", - "Admonitor.name" : "Admonitor de Arcilla", - "Gatekeeper.name" : "Centinela", - "Protector.name" : "Protector", - "Protector.text" : "Guardián del Artefacto", - "nemmandoEnemy.name" : "Comando Némesis", - "nemmandoEnemy.text" : "Eco Solitario", - "nemmercEnemy.name" : "Mercenario Némesis", - "nemmercEnemy.text" : "Desertor Vengativo", - "nemforcerEnemy.name" : "Agente Némesis", - "nemforcerEnemy.text" : "Sombra Incorruptible", - "nemloaderEnemy.name" : "Cargador Némesis", - "nemloaderEnemy.text" : "Mastodonte Hidráulico" - }, - - "interactable" : { - "TeleporterEthereal.text" : "Activar Teletransportador Etéreo.", - "TeleporterEthereal.text2" : "¡Esto incrementara la dificultad radicalmente!", - "MimicInactive.text" : "..?", - "MimicInactive.name" : "Cofre Grande..?" - }, - - "elite" : { - "poison.name" : "Venenoso %s" - }, - "stage" : { - "whistlingBasin.name" : "Cuenca Silbante", - "whistlingBasin.subname" : "Oasis Menguante", - "whistlingBasin.story" : "Este planeta sigue reservándome sorpresas. Teniendo en cuenta las acciones de sus habitantes antes de llegar aquí, nunca habría imaginado que tales criaturas pudieran construir una arquitectura tan extensa y decadente. Impresionantes pilares de ébano profundo y arcos hechos de piedra hermosa que se alzan desde el suelo, todos adornados con grabados realizados por manos deliberadas. A lo largo de las paredes, hay unas exhibiciones increíblemente intrincadas de formas que, aunque parecen simples, esconden complejidad. Un follaje turquesa, extrañamente peculiar, ataca las estructuras, trepando sutilmente por sus bases. Parece que la idea preconcebida de salvajismo que tenía sobre este planeta está desapareciendo. Aun así, mantengo la guardia alta. Es demasiado pronto para descansar, a pesar de las protestas de mis músculos doloridos.", - - "torridOutlands.name" : "Tierras Torridas", - "torridOutlands.subname" : "Quemadura Silenciosa", - "torridOutlands.story" : "Incluso con mi traje, puedo sentir los pulsantes rayos solares impactar y doblar la atmósfera de este planeta maldito por los dioses. Cada vez me cuesta más mantener la cordura con este calor, y la condensación que empieza a empañar las paredes de mi casco tampoco ayuda. Aunque, sin él, mis pulmones estarían a medio llenar de todo el polvo y los restos que he levantado durante mis andanzas-“buscar el lado positivo”, como dicen. Con el sol en lo alto, siquiera imaginar recorrer mi entorno con cierta facilidad es un sueño imposible. Me resulta fácil tropezar con cactus y rocas en mi delirio, encontrando breves refugios dentro de esos extensos túneles y cuevas que salpican la superficie. Con el terreno tan abrupto ralentizando mi avance, el sudor que me gotea por la frente hacia los ojos será, con seguridad, el menor de mis problemas." - } -} diff --git a/Language/spanish.lua b/Language/spanish.lua new file mode 100644 index 00000000..a909280d --- /dev/null +++ b/Language/spanish.lua @@ -0,0 +1,522 @@ +return { + difficulty = { + + typhoon = { + name = "Tifón", + description = "El reto definitivo.\nUna pesadilla en la cual la supervivencia es una mera ilusión.\nNadie está a la altura.", + }, + + easyEthereal = { + name = "Diluvio", + }, + + normalEthereal = { + name = "Tempesta", + }, + + hardEthereal = { + name = "Cyclón", + }, + + typhoonEthereal = { + name = "Huracán", + }, + + }, + alert = { + difficultyUp = "Subida de Dificultad", + storm = "Una tormena se acerca...", + }, + survivor = { + executioner = { + name = "Verdugo", + nameUpper = "VERDUGO", + description = "El Verdugo es un luchador ágil especializado en contar cabezas. Usando Proyectores Iónicos, el Verdugo crea ilusiones las cuales hacen huir a los enemigos aterrorizados, al mismo tiempo que proyecta un hacha para acabar con los más fuertes. Asegúrate de encadenar eliminaciones con Ráfaga Iónica y Ejecución para mantener el daño constante.", + endQuote = "..y se marchó, pero sin su sed de sangre saciada.", + story = "

                                                                                                                                                                                                                                                                                                        Detalles del pasajero:\n[Clase Militar]\n\n
                                                                                                                                                                                                                                                                                                          Equipaje:\nOficial abordó con uniforme de fibra de carbono del Oficial Actuante de EXN, sobretraje civil con placas de grado básico y una pistola de servicio emitida.\nEn el punto de control militar, se observó que el armamento del pasajero tenía una modificación adicional bajo el cañón, y fue autorizado tras acordar que se almacenaría en un contenedor seguro.\nEl pasajero también fue autorizado con una batería ION serie 204X personalizada con orificios de propulsión.\nComo medida de seguridad, los bienes tecnológicos del pasajero fueron equipados temporalmente con anuladores eléctricos automáticos y luego devueltos.\n\n[REGISTRO DE SEGURIDAD]\n\n
                                                                                                                                                                                                                                                                                                            [Nota E1a] Se han reportado varios casos en los que el Pasajero pone nervioso al personal completo del punto de control de seguridad. Se requerirá vigilancia ligera hasta nuevo aviso.\n
                                                                                                                                                                                                                                                                                                              [Nota E1b] El Pasajero respondía frecuentemente con sonidos en lugar de habla antes de optar por utilizar Lengua de Señas Universal.\n
                                                                                                                                                                                                                                                                                                                [Suceso E1] Comando contratado realiza revisión de seguridad en los aposentos del Pasajero.\n
                                                                                                                                                                                                                                                                                                                  [Suceso EM1a] Comando contratado solicita vigilancia privada de 24 horas en el pasillo que conduce a los aposentos del Pasajero.\n
                                                                                                                                                                                                                                                                                                                    [Suceso E2] Se le pidió al Pasajero que cambiara de mesa durante la comida tras recibir una queja formal de otro Pasajero a la seguridad a bordo.", + id = "Travel ID: 4383354378334FF3D34D", + departure = "Origen:\nVieja Alcatraz,\nRustborough,\nMarte", + arrival = "Destino:\nBahía Stillwater,\nNewdredge,\nEuropa", + }, + mule = { + name = "MONTACARGAS", + nameUpper = "MONTACARGAS", + description = "CADA UNIDAD MONTACARGAS ESTÁ DEBIDAMENTE EQUIPADA Y LISTA PARA EL SERVICIO. CONSTRUIDA CON MATERIALES DE ALTA CALIDAD PARA UN RENDIMIENTO MÁXIMO EN EL TRABAJO. ESTE MODELO INCLUYE EL CONJUNTO DE HERRAMIENTAS FORRAJERO, PERFECTO PARA CAZA, EXPLORACIÓN Y OTRAS TAREAS PELIGROSAS DESTINADAS AL USUARIO COMÚN. PARA INICIALIZAR, INDIQUE UNA DIRECTIVA DURANTE EL ARRANQUE DEL SISTEMA.", + endQuote = "..y se marchó, pistones chirriando, dirección indefinida.", + story = "AVISO: Las unidades MONTACARGAS no son conscientes ni mascotas. Llamarlas ''Molly'' no afectará en absoluto la programación de la unidad MONTACARGAS.", + id = "", + departure = "", + arrival = "", + }, + nemesisCommando = { + name = "Comando Némesis", + nameUpper = "COMANDO NÉMESIS", + description = "El Comando Némesis es un personaje flexible con el arsenal suficiente para enfrentarse a cualquier situación. Hiere a los enemigos usando Misericordia como preparación para infligir un daño devastador con el resto de tus habilidades. Utiliza Evasión Táctica para entrar y salir del combate cuerpo a cuerpo, y convertir hasta el peor escenario en una ventaja.", + endQuote = "..y se marchó, llevando consigo un nuevo sentido de humanidad.", + story = "REGISTRO DE DATOS DE CAMPO NO REGISTRADO: ÚLTIMA MODIFICACIÓN EN [##090??!?] \n[ERROR 0x00000ce: Paquete de Datos Corrupto, viendo punto de restauración.] \n\n
                                                                                                                                                                                                                                                                                                                      [Entrada 1] La patrulla recogió a un sobreviviente herido a pocos kilómetros del campamento. Se rumorea que “apareció de la nada” (como si me fuera a creer eso), ya que no hay nada en los alrededores que indique de dónde vino. Sea como sea, un sobreviviente es un sobreviviente. Mientras escribo esto, lo llevamos de vuelta. No lo pone fácil, eso sí, por el peso del equipo que lleva. Tiene unas modificaciones poco comunes en una armadura estándar de Comando. Parece ser, o haber sido, un modelo Mk. 1 de primera emisión (¿Aunque creo recordar que dejaron de producirlo hace años?). \n\n
                                                                                                                                                                                                                                                                                                                        [Entrada 2] Actualización: el sujeto porta artillería peligrosamente pesada para ser un Comando: Un lanzacohetes aún caliente al tacto y unas granadas que parecen claramente alienígenas, Jamás habia visto algo parecido. Está inconsciente, y por lo que vemos a través del casco, bastante golpeado. Su brazo izquierdo está rígido, tiene la mano cerrada con fuerza sobre la empuñadura de su vibrocuchilla. Intentamos verla mejor, pero es como si su puño estuviera fusionado con esa cosa. Además, está cubierta con una sustancia negra endurecida. No sabemos si fue por una pelea entre sobrevivientes o evidencia de algún tipo de auto defe??!0049### \n[Fin de datos legibles.] \n\n
                                                                                                                                                                                                                                                                                                                          [Entrada 2a] no sé cuánto tiempo tengo mientras escribo esto se despertó y el campamento está DESTRUIDO la gente no para de sangrar no sabemos ni siquiera si era humano con lo que puede hacer, nadie supo qué hacer fue directo a por Adrian \n no sabemos qué lo provocó??? qué HICIMOS para merecer esto??? por qué esa COSA nos atacó U!??!?##$0959090302???! \n[Fin de datos legibles.] \n\n

                                                                                                                                                                                                                                                                                                                          Las tábanas se ?reunen s?obre esta no?ble be?stia mie?ntras p?asta. \n\n Tu pla?ga en su s?uperficie s?erá err?adi?cada... \n\n Por es?tas man?os labo?rosas.", + }, + nemesisMercenary = { + name = "Mercenario Némesis", + nameUpper = "MERCENARIO NÉMESIS", + description = "The Nemesis Mercenary was born with a special power.", + endQuote = "..y se marchó, equipado para más.", + story = "

                                                                                                                                                                                                                                                                                                                          I DON'T NEED NO FRIENDS\nI DON'T NEED NO PHONE\nJUST A BAG OF SEVERED HEADS\nAND MY NUCLEAR THRONE", + }, + technician = { + name = "Técnico", + nameUpper = "TÉCNICO", + description = "El Técnico es experto en hacer y mantener zonas que bloquean el avance de los enemigos. Cierre Forzado puede bloquear una zona forzadamente al ser mejorado. Mejorar dispositivos aumenta su efectividad, ¡pero ten cuidado haciendolo en entornos peligrosos!", + endQuote = "..y se marchó, simplemente reiniciando la nave.", + story = "

                                                                                                                                                                                                                                                                                                                            Detalles del Pasajero:\n[Clase Empleado]\n\n
                                                                                                                                                                                                                                                                                                                              Detalles del Empleado:\nEmpleado contratado para trabajar en el lugar durante toda la duración del viaje programado de la UES Contact Light, con el fin de realizar mantenimiento en maquinaria en caso de que surjan problemas técnicos a bordo de la nave. Empleado calificado con 6 años de experiencia práctica en ingeniería de software y hardware.\n\n
                                                                                                                                                                                                                                                                                                                                Equipaje:\nEl empleado abordó con un traje espacial de soldadura Durarend, calificado con alta resistencia al vacío, además de ropa de cuero y tela diseñada para uso planetario. Se notificó al empleado que la ropa podría rasgarse en caso de un evento de vacío. Seguridad marcó la llave inglesa de gran tamaño que portaba por parecer poco práctica y potencialmente peligrosa. Al ser cuestionado, el empleado indicó que fue ''hecha a medida... para los trabajos más duros''. Siguiendo el procedimiento, la llave fue trasladada a una caja de seguridad automatizada en la bahía de carga 1a. La inspección restante del equipaje reveló varias llaves inglesas de emisión estándar, 3 USBs codificados por color, un portátil modelo GeForm 2047, cajas de herramientas y una herramienta de soldadura. Los USBs fueron inspeccionados en una computadora desechable QPuting de la compañía equipada con ReoVirus Detector v1.6.2. No se detectaron rastros de malware y los archivos almacenados están en formato UPG, conteniendo planos de varios dispositivos. El empleado fue autorizado a continuar sin más dificultades.\n\n[REGISTRO DE SEGURIDAD]\n
                                                                                                                                                                                                                                                                                                                                  - [Suceso T1] Empleado retirado por la fuerza de la cabina durante los preparativos de despegue al negarse a salir después de conversar con el piloto de la nave durante varias horas.\n
                                                                                                                                                                                                                                                                                                                                    [Suceso T2] Empleado observado robando refrescos de una máquina expendedora.\n
                                                                                                                                                                                                                                                                                                                                      [Suceso T3] Bloqueo automatizado originado en la bahía de carga 4a fue resuelto por el empleado.\n
                                                                                                                                                                                                                                                                                                                                        [Suceso T4] El estado de la cápsula de escape B-08 cambió de ERROR a ALERTA después de que el empleado fue solicitado por el personal de la nave para realizar un diagnóstico. Se instruyó al personal de seguridad a investigar.\n
                                                                                                                                                                                                                                                                                                                                          [Suceso T1a] Se reportó además que el empleado manipuló el panel de control de la cabina para que fuera ''más fácil de manejar''. El lanzamiento programado se retrasó y se instruyó al empleado a revertir cualquier modificación realizada.", + id = "Travel ID: 54E4F4C434F59474E454", + departure = "Origen:\nMuelle de Carga 5 de la UES,\nRedview,\nMarte", + arrival = "Destino:\nMuelle de Carga 0 de la UES,\nRedview,\nMarte", + }, + }, + skill = { + executionerZ = { + name = "Pistola estándar", + description = "Haz un disparo que inflinge 100% de daño.", + }, + executionerX = { + name = "Ráfaga Iónica", + description = "Dispara una ráfaga de balas ionizadas que inflingen 320% de daño cada una.\nConsigue balas matando enemigos.", + }, + executionerC = { + name = "Disuasión masiva", + description = "Te lanza hacia delante, asustando a enemigos cercanos.\nNo puedes ser golpeado mientras te desplazas.", + }, + executionerV = { + name = "Ejecución", + description = "Te elevas y desciendes con fuerza, impactando con un hacha proyectada que inflinge 1000% de daño.\nCada ejecución exitosa reduce por un segundo la recarga de las habilidades.", + }, + executionerVBoosted = { + name = "Ejecución colectiva", + description = "Te elevas y desciendes con fuerza, impactando con un hacha proyectada que inflinge 1500% de daño, aterrorizando a los enemigos.\nCada ejecución exitosa reduce por un segundo la recarga de las habilidades.", + }, + executionerV2 = { + name = "Partecráneos", + description = "Lanza un hacha proyectada en un arco circular que inflinge 1500% de daño por segundo. Los enemigos aterrorizados reciben el doble de daño.\nCada ejecución exitosa reduce por un segundo la recarga de las habilidades.", + }, + executionerV2Boosted = { + name = "Revientacráneos", + description = "Lanza dos hachas proyectadas en un arco circular que inflingen 1500% de daño por segundo. Los enemigos aterrorizados reciben el doble de daño.\nCada ejecución exitosa reduce por un segundo la recarga de las habilidades.", + }, + muleZ = { + name = "ELIMINACIÓN DE INTERFERENCIAS", + description = "GOLPEA A LOS ENEMIGOS INFLINGIENDO 200% DE DAÑO. MANTEN PRESIONADO PARA CARGAR Y APLASTAR A LOS ENEMIGOS INFLINGIENDO 1400% DE DAÑO.", + }, + muleX = { + name = "INMOBILIZAR", + description = "DISPARA UNA TRAMPA QUE INFLINGE 125% DE DAÑO. LA TRAMPA ATRAPA HASTA 5 ENEMIGOS EN LA PROXIMIDAD DEL OBJETIVO.", + }, + muleC = { + name = "RECALIBRACIÓN DE ENGRANAJES", + description = "GIRA HACIA DELANTE INFLINGIENDO 4X100% DE DAÑO. EL ULTIMO GOLPE ATURDE.", + }, + muleV = { + name = "ASISTENCIA A PRUEBA DE ERRORES", + description = "DESPLIEGA UN DRON DE REPARACIÓN PERSONAL DURANTE 8 SEGUNDOS. EL DRON CURA 5x7% DE TU SALUD TOTAL. SI ESTÁS A SALUD COMPLETA, OTORGA BARRERA EN SU LUGAR.", + }, + muleVBoosted = { + name = "ASISTENCIA A PRUEBA DE ERRORES 2.0", + description = "LANZA UN DRON DE REPARACIÓN DURANTE 8 SEGUNDOS. EL DRON CURA 5x10% DE TU SALUD TOTAL. SI ESTÁS A SALUD COMPLETA, OTORGA BARRERA EN SU LUGAR..", + }, + nemesisCommandoZ = { + name = "Misericordia", + description = "Desgarra a los enemigos cercanos con un tajo que inflinge 120% de daño, hiriendolos durante 6 segundos. No se puede acumular.\nLos enemigos heridos reciben un 50% más de daño de todas las fuentes.", + }, + nemesisCommandoX = { + name = "Toque Singular", + description = "Dispara a un enemigo inflingiendo 200% de daño.\nPuedes acumular hasta 4. Recarga 2 balas cuando ruedes.", + }, + nemesisCommandoX2 = { + name = "Tajo Distante", + description = "Corta en línea recta hacia delante e inflige 90% de daño, hiriendo a los enemigos golpeados durante 6 segundos.", + }, + nemesisCommandoC = { + name = "Evasión Táctica", + description = "Rapidamente rueda hacia delante una corta distancia.\nNo puedes ser dañado mientras estes rodando. Puedes acumular hasta 2.", + }, + nemesisCommandoV = { + name = "Desalojo", + description = "Lanza una granada que inflinge 700% de daño. Manten presionado para cocinar la granada.\nPulsa la dirección abajo para lanzarla en un arco bajo.", + }, + nemesisCommandoVBoosted = { + name = "Bombardeo", + description = "Lanza una granada que inflinge 700% de daño, aturdiendo enemigos y dividiendose en 3 granadas que inflingen 3x150% de daño.\nManten presionado para cocinar la granada.\nPulsa la dirección abajo para lanzarla en un arco bajo.", + }, + nemesisCommandoV2 = { + name = "Devastador", + description = "Dispara un cohete que inflinge 1000% de daño a un solo enemigo. Los enemigos de alrededor reciben 50% de daño y son aturdidos.\nSi se utiliza en el aire, dispara en diagonal hacia abajo.", + }, + nemesisCommandoV2Boosted = { + name = "Azotador", + description = "Dispara un cohete que inflinge 1000% de daño a un solo enemigo. os enemigos de alrededor reciben 50% de daño y son esparcidos.\nSi se utiliza en el aire, dispara en diagonal hacia abajo.\nPuedes acumular hasta 2.", + }, + nemesisMercenaryZ = { + name = "Lascerate", + description = "Injure enemies with your shotgun's attachment for 130% damage.", + }, + nemesisMercenaryX = { + name = "Quick Trigger", + description = "Fire your shotgun, stunning and hitting enemies nearby for 600% damage.", + }, + nemesisMercenaryC = { + name = "Blinding Slide", + description = "Quickly slide forwards. Getting hit at the start reloads your shotgun. \nYou can attack while sliding.", + }, + nemesisMercenaryV = { + name = "Devitalize", + description = "Target the weakest enemy in front of you, attacking them for 850% damage, dealing +50% TOTAL damage to stunned enemies. \nYou cannot be hit for the duration.", + }, + nemesisMercenaryVBoosted = { + name = "Absolute Devitalization", + description = "Target the weakest enemy in front of you, attacking them for 1100% damage, dealing +50% TOTAL damage to stunned enemies. \nYou cannot be hit for the duration.", + }, + technicianZ = { + name = "Calibrar", + description = "Golpea hacia delante con una llave inglesa que inflinge 180% de daño.\nGolpear dispositivos 3 veces los mejorará temporalmente durante 30 segundos.", + }, + technicianZ2 = { + name = "Contramedidas", + description = "Lanza una llave inglesa hacia adelante, atravesando hasta 3 enemigos inflingiendo un 180% de daño.\nGolpear dispositivos 3 veces los mejorará temporalmente durante 20 segundos.", + }, + technicianX = { + name = "Cierre Forzado", + description = "Lanza una bomba que inflinge 500% de daño cuando es reactivada.\n Aturde y atrae a los enemigos pasivamente cuando es mejorada.", + }, + technicianX2 = { + name = "El Botón Rojo", + description = "Detona la bomba lanzada inflingiendo 700% de daño y aturde a los enemigos.", + }, + technicianC = { + name = "Energia Refrescante", + description = "Coloca una máquina expendedora que mejora tu movimiento y velocidad de ataque.\nAdicionalmente aumenta la probabilidad de ataque crítico cuando es mejorada.", + }, + technicianC2 = { + name = "Radial Amplifier", + description = "Deploy an antenna that deals 15% additional damage to attacked enemies.\nHas 100% critical strike chance when upgraded.", + }, + technicianV = { + name = "Cortafuegos Auxiliar", + description = "Coloca una torreta disparando hacia delante que inflinge 220% de daño por segundo.\nDispara rápidamente e inflinge 350% de daño por segundo cuando es mejorada.", + }, + technicianVBoosted = { + name = "Cortafuegos Auxiliar 2.0", + description = "Coloca una torreta disparando rápidamente que inflinge 350% de daño por segundo.\nDispara misiles que inflingen 4x100% de daño cuando es mejorada.", + }, + peace = { + name = "<#a4eae4>Paz!", + description = "No puedes usar esta habilidad.", + }, + }, + + item = { + armedBackpack = { + name = "Mochila Armada", + pickup = "Probabilidad de disparar una bala detras de ti.", + description = "18.5% (+6.5% por acumulación) de probabilidad de al atacar disparar una bala detras de ti que inflinge 150% de daño.", + destination = "832B,\nHautenuit,\nTierra", + date = "11/7/2056", + story = "Estar seguro siempre es importante. No quiero que te metas en problemas, así que por favor lleva esto cuando salgas. ¡Especialmente en los cañones, que hay muchos ladrones por allí!\nTe enviaré munición extra más adelante este año, ¿vale?", + priority = "Estandar", + }, + brassKnuckles = { + name = "Puños Americanos", + pickup = "Realiza daño extra a corta distancia.", + description = "Realiza +35% de daño a enemigos en un rango de 2.8m (+1.4 por acumulación).", + destination = "UESPD,\nE42,\nUES Alabarda B2", + date = "03/01/2056", + story = "Encontramos esto en la escena del crimen. Tenemos que analizar el ADN para compararlo con el resto de las pruebas. Aún no puedo creer que hayan escapado.\nEn la parte trasera hay un símbolo, tal vez nos dé alguna pista. Me dirigiré al oeste de [REDACTADO], conozco a alguien que puede ayudarnos a resolver el caso.", + priority = "Estandar/Biológico", + }, + detritiveTrematode = { + name = "Trematodo Detritívoro", + pickup = "Los enemigos con baja salud sufren daño con el tiempo.", + description = "Los enemigos con menos del 15% (+5% por acumulación) de salud quedan infectados de forma permanente, recibiendo un 100% de daño por segundo.", + destination = "112,\nNeaus 2,\nMarte", + date = "03/11/2057", + story = "Eh, esto es tuyo.\nPor favor, no vuelvas a enviar una de esas raras incubadoras microscópicas de huevos, ¡jamás!.", + priority = "Priority/Biological", + }, + dormantFungus = { + name = "Hongo Latente", + pickup = "Regenera salud mientras estás en movimiento.", + description = "Regenera un 2% (+2% por acumulación) de tu salud cada dos segundos mientras te mueves.", + destination = "Puerta 1,\nModulo 13,\nEstación Gea", + date = "11/01/2056", + story = "Cuando me ordenaste explorar las cuevas de hongos, lo único que esperaba encontrar era... bueno, hongos, por supuesto. Aunque no estaba para nada entusiasmado, este espécimen llamó mi atención.\nParece crecer únicamente alrededor de agua corriente, y desde que recogí y almacené esta muestra, tanto el color como el olor han desaparecido rápidamente.\n\nEspero que puedas estudiarlo más a fond.", + priority = "Estandar", + }, + fork = { + name = "Tenedor", + pickup = "Haz más daño.", + description = "Aumenta el daño base en 3 (+3 por acumulación).", + destination = "Edificio Antima - Tejado,\nColinas Florales,\nTierra", + date = "09/24/2056", + story = "¡Sorpresa! ¡Un tenedor! ¡Jajaja!!!\nDijiste que cualquier regalo era un buen regalo, ¡AQUÍ TIENES! Bromas aparte, este tenedor es mágico. Confía en mí, Duncan, una vez comí una ensalada con él y ese mismo día encontré una moneda.\n\nAdemás, eh, por favor ven a jugar con nosotros pronto, echamos de menos destrozar tu build en SS2 (por favor dime que has cambiado de main, lol).", + priority = "Estandar", + }, + watchMetronome = { + name = "Metrónomo de Reloj", + pickup = "Carga el reloj mateniendote quieto. Gana velocidad con la carga entera.", + description = "Permanecer quieto carga el reloj durante 3 segundos. Al estar completamente cargado, aumenta la velocidad de movimiento en un 50%, consumiendo la carga en 3 (+1 por acumulación) segundos.", + destination = "Caja 1,\nG-44 (Bajo tierra),\nTierra", + date = "09/05/2056", + story = "El abuelo me dio esto como reliquia del pasado. Dice que perteneció a nuestro tataratatatatatarabuelo. No me importan los antigüedades ni los objetos familiares, así que creo que es mejor que tú lo tengas.\nDe todas formas, no es que sirva para mucho..", + priority = "Prioridad", + }, + wonderHerbs = { + name = "Hierbas Milagrosas", + pickup = "Levemente aumenta todos los efectos de curación.", + description = "Aumenta la curación de todas las fuentes en un 12% (+12% por acumulación).", + destination = "2342,\nPlot 3,\nTierra", + date = "11/07/2056", + story = "¡Rikka! He oído que estás trabajando con la EC en una nueva receta. Aquí en la granja hemos estado cultivando unas hierbas especiales que creemos que te pueden ser muy útiles. Son conocidas por ser muy, muy efectivas para tratar heridas físicas y emocionales.\nSi necesitas más, no dudes en llamarme. ¡Besos!\n-Eden", + priority = "Estándar", + }, + coffeeBag = { + name = "Bolsa de Cadé", + pickup = "Activar un interactuable aumenta la velocidad de movimiento y de ataque.", + description = "Activar un objeto interactivo aumenta la velocidad de movimiento y ataque en un 22% durante 10 (+5 por acumulación) segundos..", + destination = "n1,\nLa Capital,\nUES PRIMORDIAL", + date = "05/22/2056", + story = "Doooooooormiloooooon no sé por qué dices que no te gusta el café, ¡es taaaan bueno para mantenerse despierto! Ya sé, ya sé, hay otras maneras de animarse, pero oh, creo que te va a molar esta, ¡es mi favorita! Pruébala antes de gritarme, ¿vale?", + priority = "Estándar", + }, + x4Stimulant = { + name = "Estimulante X4", + pickup = "Reduce el tiempo de recarga de la habilidad secundaria y cura ligeramente al usarla.", + description = "Reduce el tiempo de recarga de la habilidad secundaria en un 10% (+10% por acumulación). Usar la habilidad secundaria aumenta la regeneración de salud en 2 (+1 por acumulación) puntos de vida por segundo durante 3 segundos..", + destination = "Romeo 33,\nParque Nacional Batang Ai,\nTierra", + date = "4/12/2057", + story = "Empaquetado, incluye manual. ¿La máquina muestra síntomas de un 540? Si es así, quizá debas evitar usar más de 100 ml de estimulante.\n\nSaludos.", + priority = "Estándar", + }, + moltenCoin = { + name = "Moneda Derretida", + pickup = "Probabilidad de incendiar a los enemigos al golpear y ganar oro", + description = "6% de probabilidad al golpear de incinerar a los enemigos durante 6 segundos, haciendo que suelten $1 (+$1 por acumulación) de oro.", + destination = "Toera 2,\nB44,\nEstación Madre", + date = "05/22/2056", + story = "¡Hey! Eh, lo siento, de verdad lo siento... Sé que querías que guardara esta moneda, pero ya no puedo hacerme responsable...\nVerás, la puse accidentalmente en el borde de un horno de plasma y... bueno... se ha quemado un poco por un lado, por favor no te enfades conmigo..", + priority = "Estándar", + }, + crypticSource = { + name = "Fuente Críptica", + pickup = "Cambiar de dirección crea ráfagas de energía.", + description = "Cambiar la dirección del movimiento dispara un rayo en cadena que inflige un 70% (+55% por acumulación) de daño a hasta 2 objetivos.", + destination = "O32,\nPerfil Bajo,\nTierra", + date = "03/30/2058", + story = "Desde los átomos hasta los seres conscientes, todo proviene de la energía. Sin embargo, este objeto, bastante críptico, parece emanar energía por sí mismo. Grandes cantidades de fricción parecen desencadenar una reacción en cadena que lo hace altamente inestable. No obstante, también podría ser una fuente manipulable de energía (¿infinita?).\nNo sabemos si esto nos llevará al futuro utópico que anhelamos, pero sin duda cambia por completo nuestras ideas previas sobre el universo.", + priority = "Volátil", + }, + huntersSigil = { + name = "Sigilo del Cazasor", + pickup = "Permanecer quieto crea una zona que aumenta la armadura y la probabilidad de golpe crítico.", + description = "Después de estar quieto 1 segundo, crea una zona pequeña que otorga a ti y a tus aliados 15 (+10 por acumulación) de armadura y 25% (+20% por acumulación) de probabilidad de crítico.", + destination = "2Este,\nEdificio Beckham,\nTierra", + date = "02/02/2056", + story = "Hola Sett, bienvenido al club. Hemos estado buscando candidatos y ahora que estás con nosotros, podemos empezar a trabajar la próxima temporada. Contamos contigo.\n\n- Irix, fuera", + priority = "Prioridad", + }, + roulette = { + name = "Ruleta", + pickup = "Obtén un beneficio aleatorio cada minuto.", + description = "Obtén un beneficio aleatorio, que se reinicia cada minuto. Beneficios posibles:\n\n: Aumenta la vida máxima en 60 (+24 por acumulación).\n: Aumenta la regeneración de vida en 3,6 (+1,8 por acumulación) puntos por segundo.\n: Aumenta el daño base en 14 (+5 por acumulación).\n: Aumenta la velocidad de ataque en 35% (+15,5% por acumulación).\n: Aumenta la probabilidad de crítico en 25% (+10% por acumulación).\n: Aumenta la velocidad de movimiento en 20% (+7% por acumulación).\n: Aumenta la armadura en 35 (+14 por acumulación).", + destination = "PHabitación 3.1,\nCasino Secva,\nTierra", + date = "7/17/2057", + story = "Modelo de reemplazo de la Ruleta 144Bella-1, en seguimiento a los recientes eventos ocurridos en el casino. Para más consultas, por favor contacte con nosotros en la dirección electrónica proporcionada por nuestros representantes.", + priority = "Prioridad", + }, + judderingEgg = { + name = "Huevo del Juicio", + pickup = "Consigue un pequeño amigo!", + description = "Incuba 1 (+1 por acumulación) joven wurm que lucha contigo, atacando periódicamente a los enemigos cercanos con un daño de 18x80%.", + destination = "Puerta B,\nCompuesto Lee,\nTierra", + date = "11/21/2056", + story = "Otro más del criador, esta vez es un ejemplar muy extraño.\nTen mucho cuidado, he oído que los juveniles pueden volverse altamente agresivos (¡y letales!) en cuestión de segundos si no los manejas correctamente. No debería tardar mucho en eclosionar, así que más vale que tengas la estructura lista para ello.", + priority = "Fragil", + }, + uraniumHorseshoe = { + name = "Herradura Radioactiva", + pickup = "Aumenta la velocidad de movimiento y la altura de salto.", + description = "Aumenta la velocidad de movimiento y la altura de salto en un 10% (+10% por acumulación) 255 then - log.warning("CustomTracer.new(): Allocated tracer IDs have exceeded 255. This will cause problems in netplay. Yell at Kris about this!") - end - - local id = free_id - funcs[id] = func - - free_id = free_id + 1 - - local tracer_kind_info = gm["@@NewGMLObject@@"](gm.constants.TracerKindInfo) - gm.array_set(gm.variable_global_get("tracer_info"), id, tracer_kind_info) - return id, tracer_kind_info -end - ---- calls the handler function for the given tracer ID, if one exists ---- mostly only for internal use. -CustomTracer.draw_tracer = function(id, x1, y1, x2, y2, col) - local fn = funcs[id] - if fn then - fn(x1, y1, x2, y2, col) - end -end - ---- utility function that takes a given sprite ID and configures it to not stretch when used in a line tracer. -CustomTracer.make_sprite_tiling = function(sprite) - local nine = gm.sprite_nineslice_create() - gm.variable_struct_set(nine, "enabled", true) - gm.variable_struct_set(nine, "tilemode", gm.array_create(5, 1)) - gm.sprite_set_nineslice(sprite, nine) -end - -gm.post_script_hook(gm.constants.bullet_draw_tracer, function(self, other, result, args) - local kind = args[1].value - if kind <= last_vanilla_id then return end - - local col = args[2].value - local x1, y1, x2, y2 = args[3].value, args[4].value, args[5].value, args[6].value - - CustomTracer.draw_tracer(kind, x1, y1, x2, y2, col) -end) -gm.post_script_hook(gm.constants.bullet_draw_tracer_networked, function(self, other, result, args) - if args[1].value > 63 then - log.warning("CustomTracer: bullet_draw_tracer_networked can't transmit tracer IDs above 63. consult Kris if this is an issue for you.") - end -end) - --- for future reference. --- these are the functions used by bullet_draw_tracer_networked to encode and decode tracers --- supposedly, tracer IDs are encoded into 6 bits -- this is only true for bullet_draw_tracer_networked -- standard attacks encode the ID in 8 bits. --- bullet_draw_tracer_networked is only used by one thing in the vanilla game -- sniper's alt utility skill --- so it's not really worth the time to rewrite it to extend the range .. - ---local net_packet_vfx_tracer_write = gm.constants.__net_packet_vfx_tracer_write___anon__30474_gml_GlobalScript_scr_network_packets_objects ---local net_packet_vfx_tracer_read = gm.constants.__net_packet_vfx_tracer_read___anon__30474_gml_GlobalScript_scr_network_packets_objects diff --git a/Misc/library.lua b/Misc/library.lua new file mode 100644 index 00000000..88be5460 --- /dev/null +++ b/Misc/library.lua @@ -0,0 +1,388 @@ +-- TEMPORARY RETURNS API UNFINISHED THINGS -- +-- basically lifted from rmt since returns api doesnt have them yet, once rapi adds them delete all this shit and use that instead + +-- MONSTER LOGS +function ssr_set_monster_log_boss(self, is_boss) + if type(is_boss) ~= "boolean" then log.error("is_boss should be a boolean, got a "..type(is_boss), 2) return end + + if is_boss then + self.log_backdrop_index = 1 + else + self.log_backdrop_index = 0 + end + + -- Remove previous monster log position (if found) + local monster_log_order = List.wrap(gm.variable_global_get("monster_log_display_list")) + local pos = monster_log_order:find(self) + if pos then monster_log_order:delete(pos) end + + local pos = monster_log_order:size() + for i, id in ipairs(monster_log_order) do + if MonsterLog.wrap(id).log_backdrop_index > self.log_backdrop_index then + pos = i + break + end + end + + monster_log_order:insert(pos - 1, self) +end + +function ssr_create_monster_log(identifier) + -- check if monster_log already exists + local monster_log = MonsterLog.find(identifier) + if monster_log then return monster_log end + + -- create monster_log + monster_log = MonsterLog.wrap(gm.monster_log_create("ssr", identifier)) + + monster_log.sprite_id = gm.constants.sLizardWalk + monster_log.portrait_id = gm.constants.sPortrait + + ssr_set_monster_log_boss(monster_log, false) + + return monster_log +end + +-- ELITES +function ssr_create_elite(identifier) + -- check if monster_log already exists + local elite = Elite.find(identifier) + if elite then return elite end + + -- create monster_log + elite = Elite.wrap(gm.elite_type_create("ssr", identifier)) + + return elite +end + +-- END OF TEMPORARY RETURNS API UNFINISHED THINGS -- + +-- play animation and then fade it out object +local SSREfFadeout = Object.new("SSREfFadeout") + +Callback.add(SSREfFadeout.on_create, function(self) + self.fadeout_rate = 0.2 +end) + +Callback.add(SSREfFadeout.on_step, function(self) + if self.image_index + self.image_speed >= gm.sprite_get_number(self.sprite_index) then + self.image_speed = 0 + end + + if self.image_speed == 0 then + self.image_alpha = self.image_alpha - self.fadeout_rate + if self.image_alpha <= 0 then + self:destroy() + end + end +end) + +function ssr_create_fadeout(x, y, xscale, sprite, animation_speed, rate) + local fadeout = Object.find("SSREfFadeout"):create(x, y) + fadeout.image_xscale = xscale + fadeout.sprite_index = sprite + fadeout.image_speed = animation_speed + fadeout.fadeout_rate = rate + + return fadeout +end + +-- used for skins +obj_sprite_layer = Object.new("sprite_layer") +obj_sprite_layer:set_sprite(gm.constants.sEfChestRain) +obj_sprite_layer:set_depth(1) + +Callback.add(obj_sprite_layer.on_create, function(inst) + inst.image_speed = 0 +end) + +Callback.add(obj_sprite_layer.on_step, function(inst) + if not inst.parent or not Instance.exists(inst.parent) then + inst:destroy() + end +end) + +Callback.add(obj_sprite_layer.on_draw, function(inst) + if inst.skinnable then + inst:actor_skin_skinnable_draw_self() + end +end) + +-- math.approach from rorml +function ssr_approach(current, target, change) + if current < target then + return math.min(target, current + change) + elseif current > target then + return math.max(target, current - change) + end +end + +-- check if a point is colliding with the stage +function ssr_is_point_colliding_stage(x, y, actor) + local collision = actor:collision_point(x, y, gm.constants.pBlock, false, true) + + if not collision or (type(collision) == "number" and collision < 0) then + return false + end + + return true +end + +-- check if an instance is colliding with the stage +function ssr_is_colliding_stage(inst, x, y) + return inst:is_colliding(gm.constants.pBlock, x or inst.x, y or inst.y) +end + +-- move a point in a specified direction until it collides with the stage, or has reached the max amount +-- 90 is down, 270 up, 180 left, and 0/360 right +function ssr_move_point_contact_solid(x, y, angle, amount, actor) + amount = amount or 1000 + + local totalMoved = 0 + local xx = math.cos(math.rad(angle)) + local yy = math.sin(math.rad(angle)) + + while totalMoved < amount do + x = x + xx * 32 + y = y + yy * 32 + + totalMoved = totalMoved + 32 + + if totalMoved >= amount then + x = x - xx * (totalMoved - amount) + y = y - yy * (totalMoved - amount) + break + end + + if ssr_is_point_colliding_stage(x, y, actor) then + for i = 0, 31 do + x = x - xx + y = y - yy + if not ssr_is_point_colliding_stage(x, y, actor) then + break + end + end + break + end + end + + return x, y +end + +-- move a point in a specified direction until it stops colliding with the stage, or has reached the max amount +-- 90 is down, 270 up, 180 left, and 0/360 right +function ssr_move_point_contact_air(x, y, angle, amount, actor) + amount = amount or 1000 + + local totalMoved = 0 + local xx = math.cos(math.rad(angle)) + local yy = math.sin(math.rad(angle)) + + while totalMoved < amount do + x = x + xx * 32 + y = y + yy * 32 + + totalMoved = totalMoved + 32 + + if totalMoved >= amount then + x = x - xx * (totalMoved - amount) + y = y - yy * (totalMoved - amount) + break + end + + if not ssr_is_point_colliding_stage(x, y, actor) then + for i = 0, 31 do + x = x - xx + y = y - yy + + if ssr_is_point_colliding_stage(x, y, actor) then + x = x + xx + y = y + yy + break + end + end + break + end + end + + return x, y +end + +-- move an instance in a specified direction until it collides with the stage, or has reached the max amount +-- 90 is down, 270 up, 180 left, and 0/360 right +function ssr_move_contact_solid(inst, angle, amount) + amount = amount or 1000 + + local totalMoved = 0 + local xx = math.cos(math.rad(angle)) + local yy = math.sin(math.rad(angle)) + + while totalMoved < amount do + inst.x = inst.x + xx * 32 + inst.y = inst.y + yy * 32 + + totalMoved = totalMoved + 32 + + if totalMoved >= amount then + inst.x = inst.x - xx * (totalMoved - amount) + inst.y = inst.y - yy * (totalMoved - amount) + end + + if ssr_is_colliding_stage(inst) then + for i = 0, 31 do + inst.x = inst.x - xx + inst.y = inst.y - yy + + if not ssr_is_colliding_stage(inst) then + break + end + end + break + end + end + + return x, y +end + +-- move an instance in a specified direction until it stops colliding with the stage, or has reached the max amount +-- 90 is down, 270 up, 180 left, and 0/360 right +function ssr_move_contact_air(inst, angle, amount) + amount = amount or 1000 + + local totalMoved = 0 + local xx = math.cos(math.rad(angle)) + local yy = math.sin(math.rad(angle)) + + while totalMoved < amount do + inst.x = inst.x + xx * 32 + inst.y = inst.y + yy * 32 + + totalMoved = totalMoved + 32 + + if totalMoved >= amount then + inst.x = inst.x - xx * (totalMoved - amount) + inst.y = inst.y - yy * (totalMoved - amount) + end + + if not ssr_is_colliding_stage(inst) then + for i = 0, 31 do + inst.x = inst.x - xx + inst.y = inst.y - yy + + if ssr_is_colliding_stage(inst) then + inst.x = inst.x + xx + inst.y = inst.y + yy + break + end + end + break + end + end + + return x, y +end + +-- kinda useless but whatever... feels better anyways +function ssr_get_tiles(x) + return (x or 1) * 32 +end + +-- sets the achievement to the survivors category, locks the survivor behind it, and sets the sprites and unlock text ("X" unlocked.) +function ssr_set_survivor_achievement(achievement, survivor) + achievement.group = Achievement.GROUP.character + achievement.token_unlock_name = survivor.token_name + achievement:set_sprite(survivor.sprite_portrait, 0) + survivor:set_survivor_achievement(achievement) +end + +-- adds X to the achievement's progress (achievement completes when progress reaches the requirement) +function ssr_progress_achievement(achievement, value) + gm.achievement_add_progress(achievement.value, value) +end + +-- makes the attack not proc the damage number yellow +function ssr_set_no_proc(attack_info) + attack_info.proc = false + attack_info.damage_color = Color.from_hex(0xC9B736) +end + +-- return true if there is no collision between the first set of coordinates and the second one +function ssr_line_of_sight(inst, x1, y1, x2, y2, edges) + local list = List.new() + + inst:collision_line_list(x1, y1, x2, y2, gm.constants.pBlock, false, true, list, false) + + local flag = true + + for _, block in ipairs(list) do + if block then + flag = false + break + end + end + + list:destroy() + + return flag +end + +function ssr_instance_line_of_sight(inst, inst2) + local list = List.new() + local flag = false + + local coords = { + {inst2.x, inst2.y}, + {inst2.bbox_left, inst2.bbox_top}, + {inst2.bbox_right, inst2.bbox_top}, + {inst2.bbox_left, inst2.bbox_bottom}, + {inst2.bbox_right, inst2.bbox_bottom} + } + + for _, coord in ipairs(coords) do + inst:collision_line_list(inst.x, inst.y, coord[1], coord[2], gm.constants.pBlock, false, true, list, false) + + local flag2 = true + + for _, block in ipairs(list) do + if block then + flag2 = false + break + end + end + + if flag2 == true then + flag = true + break + end + + list:clear() + end + + list:destroy() + + return flag +end + +function ssr_is_near_ground(inst, x, y, radius) + local flag = false + for _, block in ipairs(inst:get_collisions_circle(gm.constants.oB, radius, x, y)) do + if block then + flag = true + break + end + end + + return flag +end + +function ssr_table_shuffle(tabl) + for i = #tabl, 2, -1 do + local j = math.random(i) + tabl[i], tabl[j] = tabl[j], tabl[i] + end + + return tabl +end + +-- easy shortcut for checking if chirrsmas is active +ssr_chirrsmas_active = ((tonumber(os.date("%m")) == 12 and tonumber(os.date("%d")) >= 15) or (tonumber(os.date("%m")) == 1 and tonumber(os.date("%d")) <= 15) or Settings.chirrsmas == 1) \ No newline at end of file diff --git a/Misc/sharedObjects.lua b/Misc/sharedObjects.lua deleted file mode 100644 index c24e9751..00000000 --- a/Misc/sharedObjects.lua +++ /dev/null @@ -1,18 +0,0 @@ ---Objects used in a lot of parts of the module - -obj_sprite_layer = Object.new(NAMESPACE, "sprite_layer") -obj_sprite_layer:set_sprite(gm.constants.sEfChestRain) -obj_sprite_layer.obj_depth = 1 -obj_sprite_layer:onCreate(function(inst) - inst.image_speed = 0 -end) -obj_sprite_layer:onStep(function(inst) - if not inst.parent or not Instance.exists(inst.parent) then - inst:destroy() - end -end) -obj_sprite_layer:onDraw(function(inst) - if inst.skinnable then - inst:actor_skin_skinnable_draw_self() - end -end) \ No newline at end of file diff --git a/Misc/titleScreen.lua b/Misc/titleScreen.lua index 7290009f..c920d528 100644 --- a/Misc/titleScreen.lua +++ b/Misc/titleScreen.lua @@ -1,5 +1,50 @@ -- starstorm logo -gm.sprite_replace(gm.constants.sTitle, path.combine(PATH, "Sprites/Menu/title.png"), 1, false, false, 346, 82) --- dim purple background .. doesn't look super great on its own ---gm.sprite_replace(gm.constants.bTitlescreen, path.combine(PATH, "Sprites/Menu/background.png"), 1, false, false, 0, 0) +local original_sprite = nil +local is_replaced = false + +-- `true` if the logo should be replaced with Starstorm's, `false` to either keep it as is or revert it. +local function toggle_logo_replacement(enable) + if enable == nil then + enable = not is_replaced + end + + if original_sprite == nil then + original_sprite = gm.sprite_duplicate(gm.constants.sTitle) + end + + if enable and not is_replaced then + -- replace with Starstorm logo + gm.sprite_replace(gm.constants.sTitle, path.combine(PATH, "Sprites/Menu/title.png"), 1, false, false, 364, 82) + is_replaced = true + elseif not enable and is_replaced then + -- revert to original sprite + gm.sprite_assign(gm.constants.sTitle, original_sprite) + is_replaced = false + --- It might be worth deleting the duplicated sprite if there proves to be memory leaks but there shouldn't be + --- since it should only ever be duplicated once, hotloading might prove otherwise which could be just checked + --- for when setting original_sprite = nil + end +end + +-- easier to do the options/config here +local title_replacement_checkbox = Options:add_checkbox("titleReplacement") + +-- check Settings and return what this was set to (true/false) +title_replacement_checkbox:add_getter(function() + --- uncomment this if we wanna check the toml file everytime we check its value if we want to have the ability for editing the toml file live + -- Settings = SettingsFile:read() + return Settings.title_replacement +end) + +-- replace title if the checkbox is checked +title_replacement_checkbox:add_setter(function(value) + Settings.title_replacement = value + toggle_logo_replacement(value) + SettingsFile:write(Settings) +end) + +-- run once during initialization, Settings should have already been read by this point if the toml file exists. +if Settings.title_replacement == true then + toggle_logo_replacement(true) +end diff --git a/Misc/votvlibrary.lua b/Misc/votvlibrary.lua deleted file mode 100644 index 970e0691..00000000 --- a/Misc/votvlibrary.lua +++ /dev/null @@ -1,168 +0,0 @@ ---All my functions! And sometimes some constants - ---Check if a point is colliding with the stage -function is_point_colliding_stage(x, y, actor) - local collision = actor:collision_point(x, y, gm.constants.pBlock, false, true) - if not collision or (type(collision) == "number" and collision < 0) then - return false - end - return true -end - ---Same thing, but with an instance instead -function is_colliding_stage(inst, x, y) - return inst:is_colliding(gm.constants.pBlock, x or inst.x, y or inst.y) -end - ---Move a point in a specified direction until it collides with the stage, or has reached the max amount ----90 is down, 270 up, 180 left, and 0/360 right -function move_point_contact_solid(x, y, angle, amount, actor) - amount = amount or 1000 - local totalMoved = 0 - local xx = math.cos(math.rad(angle)) - local yy = math.sin(math.rad(angle)) - while totalMoved < amount do - x = x + xx * 32 - y = y + yy * 32 - totalMoved = totalMoved + 32 - if totalMoved >= amount then - x = x - xx * (totalMoved - amount) - y = y - yy * (totalMoved - amount) - break - end - if is_point_colliding_stage(x, y, actor) then - for i = 0, 31 do - x = x - xx - y = y - yy - if not is_point_colliding_stage(x, y, actor) then - break - end - end - break - end - end - return x, y -end - ---Same as move_point_contact_solid, but stops after *exiting* terrain instead -function move_point_contact_air(x, y, angle, amount, actor) - amount = amount or 1000 - local totalMoved = 0 - local xx = math.cos(math.rad(angle)) - local yy = math.sin(math.rad(angle)) - while totalMoved < amount do - x = x + xx * 32 - y = y + yy * 32 - totalMoved = totalMoved + 32 - if totalMoved >= amount then - x = x - xx * (totalMoved - amount) - y = y - yy * (totalMoved - amount) - break - end - if not is_point_colliding_stage(x, y, actor) then - for i = 0, 31 do - x = x - xx - y = y - yy - if is_point_colliding_stage(x, y, actor) then - x = x + xx - y = y + yy - break - end - end - break - end - end - return x, y -end - ---Same as move_point_contact_solid, but with an instance instead -function move_contact_solid(inst, angle, amount) - amount = amount or 1000 - local totalMoved = 0 - local xx = math.cos(math.rad(angle)) - local yy = math.sin(math.rad(angle)) - while totalMoved < amount do - inst.x = inst.x + xx * 32 - inst.y = inst.y + yy * 32 - totalMoved = totalMoved + 32 - if totalMoved >= amount then - inst.x = inst.x - xx * (totalMoved - amount) - inst.y = inst.y - yy * (totalMoved - amount) - end - if is_colliding_stage(inst) then - for i = 0, 31 do - inst.x = inst.x - xx - inst.y = inst.y - yy - if not is_colliding_stage(inst) then - break - end - end - break - end - end - return x, y -end - ---Same as move_point_contact_air, but with an instance instead -function move_contact_air(inst, angle, amount) - amount = amount or 1000 - local totalMoved = 0 - local xx = math.cos(math.rad(angle)) - local yy = math.sin(math.rad(angle)) - while totalMoved < amount do - inst.x = inst.x + xx * 32 - inst.y = inst.y + yy * 32 - totalMoved = totalMoved + 32 - if totalMoved >= amount then - inst.x = inst.x - xx * (totalMoved - amount) - inst.y = inst.y - yy * (totalMoved - amount) - end - if not is_colliding_stage(inst) then - for i = 0, 31 do - inst.x = inst.x - xx - inst.y = inst.y - yy - if is_colliding_stage(inst) then - inst.x = inst.x + xx - inst.y = inst.y + yy - break - end - end - break - end - end - return x, y -end - ---Kinda useless but whatever... Feels better anyways -function get_tiles(x) - return (x or 1) * 32 -end - ---Better is_grounded, I think? -function is_grounded(actor) - return not gm.bool(actor.free) -end - ---Retrieves a sprite previously loaded -function sprite_find(namespace, identifier) - return gm.sprite_find(namespace.."-"..identifier) -end - ---Sets the achievement to the Survivors category, locks the survivor behind it, and sets the sprites and unlock text ("X" unlocked.) -function set_survivor_achievement(achievement, survivor) - achievement.group = Achievement.GROUP.character - achievement.token_unlock_name = survivor.token_name - achievement:set_sprite(survivor.sprite_portrait, 0) - survivor:set_survivor_achievement(achievement) -end - ---Adds X to the achievement's progress (Achievement completes when progress reaches the requirement) -function progress_achievement(achievement, value) - gm.achievement_add_progress(achievement.value, value) -end - ---Simple, makes the attack not proc the damage number yellow -function setNoProc(attack_info) - attack_info.proc = false - attack_info.damage_color = Color.from_hex(0xC9B736) -end \ No newline at end of file diff --git a/README.md b/README.md index 8e504046..f18c9c32 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,66 @@ -![SSR Title](https://raw.githubusercontent.com/RobomandosLab/StarstormReturns/refs/heads/main/Sprites/Menu/title.png) +![SSR Title](https://github.com/WinslowRoR/SSR-PromoStuff/blob/main/README%20Stuff/SSR_Logo.png?raw=true) -Starstorm Returns is a work-in-progress mod for RoRR that remasters the original RoR1 Starstorm mod . It adds new survivors, enemies, stages, difficulties, and items. +**Starstorm Returns** is a **work-in-progress** mod for Risk of Rain Returns that re-imagines the original Risk of Rain mod "Starstorm". It currently features **four** survivors, **two** stages, **two** monsters, and over **20** new items to help reshape your runs. -## Feedback, Bug Reports, and Known Issues -- Bugs can be reported on the [GitHub Issues page](https://github.com/RobomandosLab/StarstormReturns) or in the [Starstorm Returns Discord server](https://discord.gg/fGShMVEayr). +![New_Environments_Header](https://github.com/WinslowRoR/testing_grounds1/blob/main/PromoFiles/SSR_Environments.gif?raw=true) + +**New Environments** +* Explore new and improved stages returning from Starstorm. Brave the **Whistling Basin**, a crumbling and desolate visage of what once was, or journey into the **Torrid Outlands**, a barren and scorching dust bowl, to help spice up your runs! + +![New_Items_Header](https://github.com/WinslowRoR/SSR-PromoStuff/blob/main/README%20Stuff/SSR_Items.gif?raw=true) + +**New Items** +* Make use of the refined and enhanced items, old and new, to help you brave this dangerous planet. Watch your back with the **Guarding Amulet** whilst utilizing the electric capabilities of the **Man-o'-War**, with your new beastly friend hatched from the **Juddering Egg**- and more! + +![New_Enemies_Header](https://github.com/WinslowRoR/SSR-PromoStuff/blob/main/README%20Stuff/SSR_Enemies.gif?raw=true) + +**New Enemies** +* Brand new monsters arise to face you as you tear through Petrichor V. Tango with the **Clay Admonitor**, a beefy pot brawler, and watch your step for **Exploders** that- well, explode- and more as you traverse through your runs! + +![New_Survivors_Header](https://github.com/WinslowRoR/SSR-PromoStuff/blob/main/README%20Stuff/SSR_Survivors.gif?raw=true) + +**New Survivors** + +Descend to Petrichor V with reworked, polished and incredibly unique characters. + +* **Executioner**: Armed to the teeth with an ion-powered arsenal, the Executioner makes quick work of the hordes of monsters that stand in his way. + +* **MULE**: The MULE is adequately equipped and ready for duty, packing a rueful piston punch (despite its weathered appearance). + +* **Technician**: With his complex gadgets and steel wrench in tow, the Technician is the man for the industrial job. + +* **???**: Old and eroded, twisted and deceptive- familiar faces all the same. + + +![Bugs](https://github.com/WinslowRoR/SSR-PromoStuff/blob/main/README%20Stuff/SSR_FAQ_and_Bugs.gif?raw=true) + +**Feedback & Bug Reports** + +* Bugs can be reported on the [**GitHub Issues page**](https://github.com/RobomandosLab/StarstormReturns) or in the [**Starstorm Returns Discord server**](https://discord.gg/QfAxXhuFAe) #ssr-bugs-and-feedback channel. +* Before reporting a bug, make sure it has not been reported before! + +![FAQ](https://github.com/WinslowRoR/SSR-PromoStuff/blob/main/README%20Stuff/SSR_FAQ.gif?raw=true) + +**FAQ** -## FAQ ### Is the mod finished? No ### Is it Multiplayer compatible? -Should be! Please report any issues you find on our [Github](https://github.com/RobomandosLab/StarstormReturns) or [Discord](https://discord.gg/fGShMVEayr). +Should be! Please report any issues you find. + + + +![Credits](https://github.com/WinslowRoR/SSR-PromoStuff/blob/main/README%20Stuff/SSR_Credits.gif?raw=true) + +**Credits** + -## Credits - Neik: Creator of Starstorm 1 - Hobart: Morale support - Bread: Github Detective - Buns: Project management +- Kris: Programmer - [Azuline](https://bsky.app/profile/azulineskye.bsky.social): Programmer - Battery: Programmer - [TRYAGAIN211](https://rainfusion.net/mod/ccf124a5-a412-4106-86bb-9b89645d6b31/): Writer, Programmer & Pixel Artist @@ -24,7 +68,7 @@ Should be! Please report any issues you find on our [Github](https://github.com/ - AnAwesomeDudette: Writer & Programmer - Opal: Pixel Artist & Programmer - Altzeus: Music & Programmer -- Redacted: Writer & Pixel Artist +- Redacted: Writer & Lead Pixel Artist - DigBug: Writer & Pixel Artist - [Winslow](https://bsky.app/profile/winslowror.bsky.social): Pixel Artist - isDahMaster: Pixel Artist @@ -35,13 +79,15 @@ Should be! Please report any issues you find on our [Github](https://github.com/ - [RicottaKitten](https://bsky.app/profile/boreeddddd.bsky.social): Pixel Artist - [Gummies](https://bsky.app/profile/gummies139.bsky.social): Pixel Artist -## Special Thanks +![Special_Thanks](https://github.com/WinslowRoR/SSR-PromoStuff/blob/main/README%20Stuff/SSR_SpecialThanks.gif?raw=true) + +**Special Thanks** + - Neik for creating a wonderful mod and community we have loved and enjoyed - Dee for design help and development guidance - Swuff for helping kickstart the codebase - Starstorm 2 team for their design guidance and support - Starstorm fans everywhere -- Kris for helping get SSR development started! - Khlerik, iDeathHD, and other RoRR Modding Toolkit contributors for their work on making modding possible - Risk of Rain Discord, and RoRRModcord for playtesting and feedback -- SwedishFish75, Dragonrster for translations! +- SwedishFish75 for Spanish Translation \ No newline at end of file diff --git a/Sounds/Gameplay/portal_pop_out.ogg b/Sounds/Gameplay/portal_pop_out.ogg new file mode 100644 index 00000000..c5cacf42 Binary files /dev/null and b/Sounds/Gameplay/portal_pop_out.ogg differ diff --git a/Sounds/Items/gummiesBounce.ogg b/Sounds/Items/gummiesBounce.ogg new file mode 100644 index 00000000..1f43b5df Binary files /dev/null and b/Sounds/Items/gummiesBounce.ogg differ diff --git a/Sounds/Items/gummiesBuff.ogg b/Sounds/Items/gummiesBuff.ogg new file mode 100644 index 00000000..46946528 Binary files /dev/null and b/Sounds/Items/gummiesBuff.ogg differ diff --git a/Sounds/Items/gummiesBuffMax.ogg b/Sounds/Items/gummiesBuffMax.ogg new file mode 100644 index 00000000..a417ee1a Binary files /dev/null and b/Sounds/Items/gummiesBuffMax.ogg differ diff --git a/Sounds/Music/musicVerdantWoodland.ogg b/Sounds/Music/musicVerdantWoodland.ogg new file mode 100644 index 00000000..11cfc133 Binary files /dev/null and b/Sounds/Music/musicVerdantWoodland.ogg differ diff --git a/Sounds/Survivors/Executioner/select.ogg b/Sounds/Survivors/Executioner/select.ogg index 45b9586a..edde0284 100644 Binary files a/Sounds/Survivors/Executioner/select.ogg and b/Sounds/Survivors/Executioner/select.ogg differ diff --git a/Sounds/Survivors/NemesisCommando/portal.ogg b/Sounds/Survivors/NemesisCommando/portal.ogg new file mode 100644 index 00000000..467a5ffc Binary files /dev/null and b/Sounds/Survivors/NemesisCommando/portal.ogg differ diff --git a/Sounds/Survivors/NemesisMercenary/disappear.ogg b/Sounds/Survivors/NemesisMercenary/disappear.ogg new file mode 100644 index 00000000..95c15e76 Binary files /dev/null and b/Sounds/Survivors/NemesisMercenary/disappear.ogg differ diff --git a/Sounds/Survivors/NemesisMercenary/portal.ogg b/Sounds/Survivors/NemesisMercenary/portal.ogg new file mode 100644 index 00000000..5b1f855f Binary files /dev/null and b/Sounds/Survivors/NemesisMercenary/portal.ogg differ diff --git a/Sounds/Survivors/NemesisMercenary/shoot2a.ogg b/Sounds/Survivors/NemesisMercenary/shoot2a.ogg index fa0055dd..33f5ad29 100644 Binary files a/Sounds/Survivors/NemesisMercenary/shoot2a.ogg and b/Sounds/Survivors/NemesisMercenary/shoot2a.ogg differ diff --git a/Sounds/Survivors/NemesisMercenary/shoot3a.ogg b/Sounds/Survivors/NemesisMercenary/shoot3a.ogg new file mode 100644 index 00000000..a9d98022 Binary files /dev/null and b/Sounds/Survivors/NemesisMercenary/shoot3a.ogg differ diff --git a/Sounds/Survivors/NemesisMercenary/shoot3b.ogg b/Sounds/Survivors/NemesisMercenary/shoot3b.ogg new file mode 100644 index 00000000..9be738b2 Binary files /dev/null and b/Sounds/Survivors/NemesisMercenary/shoot3b.ogg differ diff --git a/Sounds/Survivors/NemesisMercenary/shoot4.ogg b/Sounds/Survivors/NemesisMercenary/shoot4.ogg index f1fda55d..3378df0a 100644 Binary files a/Sounds/Survivors/NemesisMercenary/shoot4.ogg and b/Sounds/Survivors/NemesisMercenary/shoot4.ogg differ diff --git a/Sounds/Survivors/NemesisMercenary/slide.ogg b/Sounds/Survivors/NemesisMercenary/slide.ogg new file mode 100644 index 00000000..5313ef1f Binary files /dev/null and b/Sounds/Survivors/NemesisMercenary/slide.ogg differ diff --git a/Sounds/Survivors/NemesisMercenary/teleport.ogg b/Sounds/Survivors/NemesisMercenary/teleport.ogg new file mode 100644 index 00000000..70d643f5 Binary files /dev/null and b/Sounds/Survivors/NemesisMercenary/teleport.ogg differ diff --git a/Sprites/Actors/Admonitor/portrait.png b/Sprites/Actors/Admonitor/portrait.png index 6d220519..093e1631 100644 Binary files a/Sprites/Actors/Admonitor/portrait.png and b/Sprites/Actors/Admonitor/portrait.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/attack.png b/Sprites/Actors/ChirrsmasGolem/attack.png new file mode 100644 index 00000000..c0bff352 Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/attack.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/credits.png b/Sprites/Actors/ChirrsmasGolem/credits.png new file mode 100644 index 00000000..bfa9224e Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/credits.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/death.png b/Sprites/Actors/ChirrsmasGolem/death.png new file mode 100644 index 00000000..abeb5bd2 Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/death.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/doodad.png b/Sprites/Actors/ChirrsmasGolem/doodad.png new file mode 100644 index 00000000..23e6bbe5 Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/doodad.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/fall.png b/Sprites/Actors/ChirrsmasGolem/fall.png new file mode 100644 index 00000000..3fc0fe1b Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/fall.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/idle.png b/Sprites/Actors/ChirrsmasGolem/idle.png new file mode 100644 index 00000000..43b34c90 Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/idle.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/jump.png b/Sprites/Actors/ChirrsmasGolem/jump.png new file mode 100644 index 00000000..e9eb34d2 Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/jump.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/jump_peak.png b/Sprites/Actors/ChirrsmasGolem/jump_peak.png new file mode 100644 index 00000000..c7ae19c0 Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/jump_peak.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/palette.png b/Sprites/Actors/ChirrsmasGolem/palette.png new file mode 100644 index 00000000..edd3adf6 Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/palette.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/ping.png b/Sprites/Actors/ChirrsmasGolem/ping.png new file mode 100644 index 00000000..21b8f94b Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/ping.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/spawn.png b/Sprites/Actors/ChirrsmasGolem/spawn.png new file mode 100644 index 00000000..8081fb0d Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/spawn.png differ diff --git a/Sprites/Actors/ChirrsmasGolem/walk.png b/Sprites/Actors/ChirrsmasGolem/walk.png new file mode 100644 index 00000000..aaf9a7e9 Binary files /dev/null and b/Sprites/Actors/ChirrsmasGolem/walk.png differ diff --git a/Sprites/Buffs/blastCharge.png b/Sprites/Buffs/blastCharge.png deleted file mode 100644 index ec33dc63..00000000 Binary files a/Sprites/Buffs/blastCharge.png and /dev/null differ diff --git a/Sprites/Buffs/gummiesBuff.png b/Sprites/Buffs/gummiesBuff.png new file mode 100644 index 00000000..fd8c1490 Binary files /dev/null and b/Sprites/Buffs/gummiesBuff.png differ diff --git a/Sprites/Elites/Empyrean/beam.png b/Sprites/Elites/Empyrean/beam.png index 739e6179..0e47a9ea 100644 Binary files a/Sprites/Elites/Empyrean/beam.png and b/Sprites/Elites/Empyrean/beam.png differ diff --git a/Sprites/Elites/Empyrean/particle.png b/Sprites/Elites/Empyrean/particle.png new file mode 100644 index 00000000..32db3e6f Binary files /dev/null and b/Sprites/Elites/Empyrean/particle.png differ diff --git a/Sprites/Elites/Empyrean/particle2.png b/Sprites/Elites/Empyrean/particle2.png new file mode 100644 index 00000000..44890450 Binary files /dev/null and b/Sprites/Elites/Empyrean/particle2.png differ diff --git a/Sprites/Elites/Empyrean/particle3.png b/Sprites/Elites/Empyrean/particle3.png new file mode 100644 index 00000000..fac1e4e6 Binary files /dev/null and b/Sprites/Elites/Empyrean/particle3.png differ diff --git a/Sprites/Elites/Empyrean/shockwave.png b/Sprites/Elites/Empyrean/shockwave.png new file mode 100644 index 00000000..364d78cc Binary files /dev/null and b/Sprites/Elites/Empyrean/shockwave.png differ diff --git a/Sprites/Elites/Empyrean/splash.png b/Sprites/Elites/Empyrean/splash.png index 32db3e6f..0e39de72 100644 Binary files a/Sprites/Elites/Empyrean/splash.png and b/Sprites/Elites/Empyrean/splash.png differ diff --git a/Sprites/Elites/Empyrean/star_small.png b/Sprites/Elites/Empyrean/star_small.png new file mode 100644 index 00000000..03b32860 Binary files /dev/null and b/Sprites/Elites/Empyrean/star_small.png differ diff --git a/Sprites/Interactables/ChirrsmasPresent/present.png b/Sprites/Interactables/ChirrsmasPresent/present.png new file mode 100644 index 00000000..21829189 Binary files /dev/null and b/Sprites/Interactables/ChirrsmasPresent/present.png differ diff --git a/Sprites/Items/Effects/dungus.png b/Sprites/Items/Effects/dungus.png index be42b9b1..ab068655 100644 Binary files a/Sprites/Items/Effects/dungus.png and b/Sprites/Items/Effects/dungus.png differ diff --git a/Sprites/Items/Effects/gummies.png b/Sprites/Items/Effects/gummies.png new file mode 100644 index 00000000..e1781d02 Binary files /dev/null and b/Sprites/Items/Effects/gummies.png differ diff --git a/Sprites/Items/blastKnuckles.png b/Sprites/Items/blastKnuckles.png deleted file mode 100644 index 8bb2af3e..00000000 Binary files a/Sprites/Items/blastKnuckles.png and /dev/null differ diff --git a/Sprites/Items/eclipseGummies.png b/Sprites/Items/eclipseGummies.png new file mode 100644 index 00000000..81a228d3 Binary files /dev/null and b/Sprites/Items/eclipseGummies.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorBroken.png b/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorBroken.png new file mode 100644 index 00000000..cdbf7d4c Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorBroken.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorLeftFrame.png b/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorLeftFrame.png new file mode 100644 index 00000000..23051049 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorLeftFrame.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorRight.png b/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorRight.png new file mode 100644 index 00000000..5259cf25 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorRight.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyEnvironmentFinal.png b/Sprites/Stages/SnowyRiskOfRain/snowyEnvironmentFinal.png new file mode 100644 index 00000000..74718625 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyEnvironmentFinal.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyGroundStripRiskOfRain.png b/Sprites/Stages/SnowyRiskOfRain/snowyGroundStripRiskOfRain.png new file mode 100644 index 00000000..9120278f Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyGroundStripRiskOfRain.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyRange1.png b/Sprites/Stages/SnowyRiskOfRain/snowyRange1.png new file mode 100644 index 00000000..9fd620df Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyRange1.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyRange2.png b/Sprites/Stages/SnowyRiskOfRain/snowyRange2.png new file mode 100644 index 00000000..597daddb Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyRange2.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyRiskofRainTile.png b/Sprites/Stages/SnowyRiskOfRain/snowyRiskofRainTile.png new file mode 100644 index 00000000..54d4cf6c Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyRiskofRainTile.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyRoR_BG.png b/Sprites/Stages/SnowyRiskOfRain/snowyRoR_BG.png new file mode 100644 index 00000000..8bdfb920 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyRoR_BG.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyRoR_Containers.png b/Sprites/Stages/SnowyRiskOfRain/snowyRoR_Containers.png new file mode 100644 index 00000000..c9beaca9 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyRoR_Containers.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL.png b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL.png new file mode 100644 index 00000000..449bee93 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL_trans.png b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL_trans.png new file mode 100644 index 00000000..aef60a96 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL_trans.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL.png b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL.png new file mode 100644 index 00000000..b36583d7 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_end.png b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_end.png new file mode 100644 index 00000000..d4b0bc6b Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_end.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_trans.png b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_trans.png new file mode 100644 index 00000000..43ce37cc Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_trans.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySunrise2.png b/Sprites/Stages/SnowyRiskOfRain/snowySunrise2.png new file mode 100644 index 00000000..a4c99293 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySunrise2.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds.png b/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds.png new file mode 100644 index 00000000..8436b79b Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds2.png b/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds2.png new file mode 100644 index 00000000..24863909 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds2.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds3.png b/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds3.png new file mode 100644 index 00000000..2d9f211e Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds3.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds4.png b/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds4.png new file mode 100644 index 00000000..7da70a91 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds4.png differ diff --git a/Sprites/Stages/SnowyRiskOfRain/snowyWater_CL.png b/Sprites/Stages/SnowyRiskOfRain/snowyWater_CL.png new file mode 100644 index 00000000..8d513e40 Binary files /dev/null and b/Sprites/Stages/SnowyRiskOfRain/snowyWater_CL.png differ diff --git a/Sprites/Stages/VerdantWoodland/BackTilesWoodland.png b/Sprites/Stages/VerdantWoodland/BackTilesWoodland.png new file mode 100644 index 00000000..5080b790 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/BackTilesWoodland.png differ diff --git a/Sprites/Stages/VerdantWoodland/EnvironmentVerdantWoodland.png b/Sprites/Stages/VerdantWoodland/EnvironmentVerdantWoodland.png new file mode 100644 index 00000000..d6d1cb00 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/EnvironmentVerdantWoodland.png differ diff --git a/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_1.png b/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_1.png new file mode 100644 index 00000000..100721f3 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_1.png differ diff --git a/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_2.png b/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_2.png new file mode 100644 index 00000000..8c611b88 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_2.png differ diff --git a/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_3.png b/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_3.png new file mode 100644 index 00000000..536829fe Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/GroundStripVerdantWoodland_3.png differ diff --git a/Sprites/Stages/VerdantWoodland/Tile16Woodland.png b/Sprites/Stages/VerdantWoodland/Tile16Woodland.png new file mode 100644 index 00000000..4a5a4898 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/Tile16Woodland.png differ diff --git a/Sprites/Stages/VerdantWoodland/TreeTrunk.aseprite b/Sprites/Stages/VerdantWoodland/TreeTrunk.aseprite new file mode 100644 index 00000000..c157faf6 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/TreeTrunk.aseprite differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantBG_PLACEHOLDER.png b/Sprites/Stages/VerdantWoodland/VerdantBG_PLACEHOLDER.png new file mode 100644 index 00000000..d7c3164d Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantBG_PLACEHOLDER.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantGodRays.png b/Sprites/Stages/VerdantWoodland/VerdantGodRays.png new file mode 100644 index 00000000..c0414e26 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantGodRays.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees1.png b/Sprites/Stages/VerdantWoodland/VerdantTrees1.png new file mode 100644 index 00000000..795c5b91 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees1.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees1_OLD.png b/Sprites/Stages/VerdantWoodland/VerdantTrees1_OLD.png new file mode 100644 index 00000000..c358dbc7 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees1_OLD.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees1_PLACEHOLDER.png b/Sprites/Stages/VerdantWoodland/VerdantTrees1_PLACEHOLDER.png new file mode 100644 index 00000000..b31c75be Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees1_PLACEHOLDER.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees2.png b/Sprites/Stages/VerdantWoodland/VerdantTrees2.png new file mode 100644 index 00000000..6f76bf50 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees2.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees2_OLD.png b/Sprites/Stages/VerdantWoodland/VerdantTrees2_OLD.png new file mode 100644 index 00000000..be58b435 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees2_OLD.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees2_PLACEHOLDER.png b/Sprites/Stages/VerdantWoodland/VerdantTrees2_PLACEHOLDER.png new file mode 100644 index 00000000..89ba6297 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees2_PLACEHOLDER.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees3.png b/Sprites/Stages/VerdantWoodland/VerdantTrees3.png new file mode 100644 index 00000000..d6304df5 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees3.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees3_PLACEHOLDER.png b/Sprites/Stages/VerdantWoodland/VerdantTrees3_PLACEHOLDER.png new file mode 100644 index 00000000..c50156fc Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees3_PLACEHOLDER.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees4.png b/Sprites/Stages/VerdantWoodland/VerdantTrees4.png new file mode 100644 index 00000000..6ebfa4b7 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees4.png differ diff --git a/Sprites/Stages/VerdantWoodland/VerdantTrees4_PLACEHOLDER.png b/Sprites/Stages/VerdantWoodland/VerdantTrees4_PLACEHOLDER.png new file mode 100644 index 00000000..153e5724 Binary files /dev/null and b/Sprites/Stages/VerdantWoodland/VerdantTrees4_PLACEHOLDER.png differ diff --git a/Sprites/Survivors/NemesisCommando/portal.png b/Sprites/Survivors/NemesisCommando/portal.png new file mode 100644 index 00000000..84f15536 Binary files /dev/null and b/Sprites/Survivors/NemesisCommando/portal.png differ diff --git a/Sprites/Survivors/NemesisCommando/portal_inside.png b/Sprites/Survivors/NemesisCommando/portal_inside.png new file mode 100644 index 00000000..2336fc0f Binary files /dev/null and b/Sprites/Survivors/NemesisCommando/portal_inside.png differ diff --git a/Sprites/Survivors/NemesisMercenary/boost.png b/Sprites/Survivors/NemesisMercenary/boost.png new file mode 100644 index 00000000..8ca7a3f9 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/boost.png differ diff --git a/Sprites/Survivors/NemesisMercenary/death.png b/Sprites/Survivors/NemesisMercenary/death.png index 90015cc1..b23876ef 100644 Binary files a/Sprites/Survivors/NemesisMercenary/death.png and b/Sprites/Survivors/NemesisMercenary/death.png differ diff --git a/Sprites/Survivors/NemesisMercenary/decoy.png b/Sprites/Survivors/NemesisMercenary/decoy.png index 904c444e..23d6a8a2 100644 Binary files a/Sprites/Survivors/NemesisMercenary/decoy.png and b/Sprites/Survivors/NemesisMercenary/decoy.png differ diff --git a/Sprites/Survivors/NemesisMercenary/drone_idle.png b/Sprites/Survivors/NemesisMercenary/drone_idle.png new file mode 100644 index 00000000..97c2b4eb Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/drone_idle.png differ diff --git a/Sprites/Survivors/NemesisMercenary/drone_shoot.png b/Sprites/Survivors/NemesisMercenary/drone_shoot.png new file mode 100644 index 00000000..afc253ae Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/drone_shoot.png differ diff --git a/Sprites/Survivors/NemesisMercenary/idle.png b/Sprites/Survivors/NemesisMercenary/idle.png index f23e6c23..fc7f9c9c 100644 Binary files a/Sprites/Survivors/NemesisMercenary/idle.png and b/Sprites/Survivors/NemesisMercenary/idle.png differ diff --git a/Sprites/Survivors/NemesisMercenary/idle2.png b/Sprites/Survivors/NemesisMercenary/idle2.png index 7ecc352f..c3a55c3c 100644 Binary files a/Sprites/Survivors/NemesisMercenary/idle2.png and b/Sprites/Survivors/NemesisMercenary/idle2.png differ diff --git a/Sprites/Survivors/NemesisMercenary/idle2l.png b/Sprites/Survivors/NemesisMercenary/idle2l.png new file mode 100644 index 00000000..6eb460c6 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/idle2l.png differ diff --git a/Sprites/Survivors/NemesisMercenary/log.png b/Sprites/Survivors/NemesisMercenary/log.png index 88020fca..8f3c472a 100644 Binary files a/Sprites/Survivors/NemesisMercenary/log.png and b/Sprites/Survivors/NemesisMercenary/log.png differ diff --git a/Sprites/Survivors/NemesisMercenary/portal.png b/Sprites/Survivors/NemesisMercenary/portal.png new file mode 100644 index 00000000..57f17c58 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/portal.png differ diff --git a/Sprites/Survivors/NemesisMercenary/portal_inside.png b/Sprites/Survivors/NemesisMercenary/portal_inside.png new file mode 100644 index 00000000..4b9a353b Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/portal_inside.png differ diff --git a/Sprites/Survivors/NemesisMercenary/portrait.png b/Sprites/Survivors/NemesisMercenary/portrait.png index b3baaac3..f2f26789 100644 Binary files a/Sprites/Survivors/NemesisMercenary/portrait.png and b/Sprites/Survivors/NemesisMercenary/portrait.png differ diff --git a/Sprites/Survivors/NemesisMercenary/portraitSmall.png b/Sprites/Survivors/NemesisMercenary/portraitSmall.png index 2bcd4b1c..c2c47c53 100644 Binary files a/Sprites/Survivors/NemesisMercenary/portraitSmall.png and b/Sprites/Survivors/NemesisMercenary/portraitSmall.png differ diff --git a/Sprites/Survivors/NemesisMercenary/select.png b/Sprites/Survivors/NemesisMercenary/select.png index aafb7194..4ac56ef3 100644 Binary files a/Sprites/Survivors/NemesisMercenary/select.png and b/Sprites/Survivors/NemesisMercenary/select.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot1_1a.png b/Sprites/Survivors/NemesisMercenary/shoot1_1a.png new file mode 100644 index 00000000..725ad066 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot1_1a.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot1_2a.png b/Sprites/Survivors/NemesisMercenary/shoot1_2a.png new file mode 100644 index 00000000..d4b01a0f Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot1_2a.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot1_3a.png b/Sprites/Survivors/NemesisMercenary/shoot1_3a.png new file mode 100644 index 00000000..e28ed4ed Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot1_3a.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot1_4a.png b/Sprites/Survivors/NemesisMercenary/shoot1_4a.png new file mode 100644 index 00000000..0b67ef80 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot1_4a.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot1a.png b/Sprites/Survivors/NemesisMercenary/shoot1a.png deleted file mode 100644 index 8fad23f2..00000000 Binary files a/Sprites/Survivors/NemesisMercenary/shoot1a.png and /dev/null differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot1b.png b/Sprites/Survivors/NemesisMercenary/shoot1b.png index 74052e2a..b8462499 100644 Binary files a/Sprites/Survivors/NemesisMercenary/shoot1b.png and b/Sprites/Survivors/NemesisMercenary/shoot1b.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot1lb.png b/Sprites/Survivors/NemesisMercenary/shoot1lb.png new file mode 100644 index 00000000..dac007d5 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot1lb.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot2a.png b/Sprites/Survivors/NemesisMercenary/shoot2a.png index 784de2b3..7605fa79 100644 Binary files a/Sprites/Survivors/NemesisMercenary/shoot2a.png and b/Sprites/Survivors/NemesisMercenary/shoot2a.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot2b.png b/Sprites/Survivors/NemesisMercenary/shoot2b.png index 19569f9f..738f81d8 100644 Binary files a/Sprites/Survivors/NemesisMercenary/shoot2b.png and b/Sprites/Survivors/NemesisMercenary/shoot2b.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot2lb.png b/Sprites/Survivors/NemesisMercenary/shoot2lb.png new file mode 100644 index 00000000..358f3f63 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot2lb.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot3.png b/Sprites/Survivors/NemesisMercenary/shoot3.png index 529cf7c4..f1c64e10 100644 Binary files a/Sprites/Survivors/NemesisMercenary/shoot3.png and b/Sprites/Survivors/NemesisMercenary/shoot3.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot4.png b/Sprites/Survivors/NemesisMercenary/shoot4.png deleted file mode 100644 index 5093b3a7..00000000 Binary files a/Sprites/Survivors/NemesisMercenary/shoot4.png and /dev/null differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot4_1.png b/Sprites/Survivors/NemesisMercenary/shoot4_1.png new file mode 100644 index 00000000..6fc11a1f Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot4_1.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot4_2a.png b/Sprites/Survivors/NemesisMercenary/shoot4_2a.png new file mode 100644 index 00000000..ce0609b8 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot4_2a.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot4_2b.png b/Sprites/Survivors/NemesisMercenary/shoot4_2b.png new file mode 100644 index 00000000..035bd008 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot4_2b.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot4_2c.png b/Sprites/Survivors/NemesisMercenary/shoot4_2c.png new file mode 100644 index 00000000..a9750591 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot4_2c.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot4_3.png b/Sprites/Survivors/NemesisMercenary/shoot4_3.png new file mode 100644 index 00000000..70224b28 Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/shoot4_3.png differ diff --git a/Sprites/Survivors/NemesisMercenary/shoot5.png b/Sprites/Survivors/NemesisMercenary/shoot5.png deleted file mode 100644 index 92a8cf0d..00000000 Binary files a/Sprites/Survivors/NemesisMercenary/shoot5.png and /dev/null differ diff --git a/Sprites/Survivors/NemesisMercenary/skills.png b/Sprites/Survivors/NemesisMercenary/skills.png index d81b5364..41179564 100644 Binary files a/Sprites/Survivors/NemesisMercenary/skills.png and b/Sprites/Survivors/NemesisMercenary/skills.png differ diff --git a/Sprites/Survivors/NemesisMercenary/slash.png b/Sprites/Survivors/NemesisMercenary/slash.png deleted file mode 100644 index 53d6469b..00000000 Binary files a/Sprites/Survivors/NemesisMercenary/slash.png and /dev/null differ diff --git a/Sprites/Survivors/NemesisMercenary/slash2.png b/Sprites/Survivors/NemesisMercenary/slash2.png deleted file mode 100644 index 1d478ba0..00000000 Binary files a/Sprites/Survivors/NemesisMercenary/slash2.png and /dev/null differ diff --git a/Sprites/Survivors/NemesisMercenary/sparks.png b/Sprites/Survivors/NemesisMercenary/sparks.png new file mode 100644 index 00000000..b41a668f Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/sparks.png differ diff --git a/Sprites/Survivors/NemesisMercenary/sparks2.png b/Sprites/Survivors/NemesisMercenary/sparks2.png new file mode 100644 index 00000000..421a01cd Binary files /dev/null and b/Sprites/Survivors/NemesisMercenary/sparks2.png differ diff --git a/Sprites/Survivors/NemesisMercenary/walkBack.png b/Sprites/Survivors/NemesisMercenary/walkBack.png index 08f56086..9ea7ab11 100644 Binary files a/Sprites/Survivors/NemesisMercenary/walkBack.png and b/Sprites/Survivors/NemesisMercenary/walkBack.png differ diff --git a/Stages/SnowyRiskOfRain/riskOfRain1_snowy.rorlvl b/Stages/SnowyRiskOfRain/riskOfRain1_snowy.rorlvl new file mode 100644 index 00000000..034c2cf1 Binary files /dev/null and b/Stages/SnowyRiskOfRain/riskOfRain1_snowy.rorlvl differ diff --git a/Stages/SnowyRiskOfRain/riskOfRain2_snowy.rorlvl b/Stages/SnowyRiskOfRain/riskOfRain2_snowy.rorlvl new file mode 100644 index 00000000..623b72ca Binary files /dev/null and b/Stages/SnowyRiskOfRain/riskOfRain2_snowy.rorlvl differ diff --git a/Stages/SnowyRiskOfRain/snowyRiskOfRain.lua b/Stages/SnowyRiskOfRain/snowyRiskOfRain.lua new file mode 100644 index 00000000..df9a5409 --- /dev/null +++ b/Stages/SnowyRiskOfRain/snowyRiskOfRain.lua @@ -0,0 +1,191 @@ +if not ssr_chirrsmas_active then return end -- christmas lasts from december 15th to january 15th +if Settings.chirrsmas == 2 then return end -- if chirrsmas is disabled in the config then we dont do anything + +-- Stage resources +Sprite.new("RiskofRainTile_Snowy", path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyRiskofRainTile.png"), 2, 0, 0) +Sprite.new("RoR_Containers_Snowy", path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyRoR_Containers.png"), 2, 0, 0) +Sprite.new("Spacer_M_CL_Snowy", path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL.png"), 1, 0, 0) +Sprite.new("Spacer_M2_CL_Snowy", path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL.png"), 1, 0, 0) + +gm.sprite_replace(gm.constants.bRoR_BG, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyRoR_BG.png"), 2, false, false, 0, 0) +gm.sprite_replace(gm.constants.bRange1, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyRange1.png"), 4, false, false, 0, 0) +gm.sprite_replace(gm.constants.bRange2, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyRange2.png"), 4, false, false, 0, 0) +gm.sprite_replace(gm.constants.bWater_CL, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyWater_CL.png"), 4, false, false, 0, 0) +gm.sprite_replace(gm.constants.bSunsetClouds, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds.png"), 2, false, false, 0, 0) +gm.sprite_replace(gm.constants.bSunsetClouds2, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds2.png"), 2, false, false, 0, 0) +gm.sprite_replace(gm.constants.bSunsetClouds3, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds3.png"), 2, false, false, 0, 0) +gm.sprite_replace(gm.constants.bSunsetClouds4, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySunsetClouds4.png"), 4, false, false, 0, 0) +gm.sprite_replace(gm.constants.bSunrise2, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySunrise2.png"), 2, false, false, 0, 0) + +gm.sprite_replace(gm.constants.sBlastdoorBroken, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorBroken.png"), 2, false, false, -6, 484) +gm.sprite_replace(gm.constants.sBlastdoorLeftFrame, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorLeftFrame.png"), 4, false, false, 0, 512) +gm.sprite_replace(gm.constants.sBlastdoorRight, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyBlastdoorRight.png"), 2, false, false, 0, 512) + +GM.sprite_set_speed(Sprite.find("Spacer_M_CL_Snowy"), 1, 2) +GM.sprite_set_speed(Sprite.find("Spacer_M2_CL_Snowy"), 1, 2) +GM.background_set_megasprite(gm.constants.bRange1) +GM.background_set_megasprite(gm.constants.bRange2) +GM.background_set_megasprite(gm.constants.bWater_CL) +GM.background_set_megasprite(gm.constants.bSunsetClouds4) +GM.tile_render_init_final(Sprite.find("RiskofRainTile_Snowy")) +GM.tile_render_init_final(Sprite.find("RoR_Containers_Snowy")) + +-- Menu Resources +local GroundStripSnowyRiskOfRain = Sprite.new("GroundStripRiskOfRain_Snowy", path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyGroundStripRiskOfRain.png"), 1, 0, 0) + +-- Stage +local cl_stage = Stage.new("riskOfRainSnowy") +cl_stage.music_id = Stage.find("riskOfRain").music_id +cl_stage.token_name = Stage.find("riskOfRain").token_name +cl_stage.token_subname = Stage.find("riskOfRain").token_subname +cl_stage.teleporter_index = Stage.find("riskOfRain").teleporter_index +cl_stage.interactable_spawn_points = Stage.find("riskOfRain").interactable_spawn_points +cl_stage.spawn_interactable_rarity = Stage.find("riskOfRain").spawn_interactable_rarity +cl_stage.spawn_enemies = Stage.find("riskOfRain").spawn_enemies +cl_stage.spawn_enemies_loop = Stage.find("riskOfRain").spawn_enemies_loop +cl_stage.spawn_interactables = Stage.find("riskOfRain").spawn_interactables +cl_stage.spawn_interactables_loop = Stage.find("riskOfRain").spawn_interactables_loop +cl_stage.allow_mountain_shrine_spawn = Stage.find("riskOfRain").allow_mountain_shrine_spawn +cl_stage.is_new_stage = Stage.find("riskOfRain").is_new_stage +cl_stage.populate_biome_properties = Stage.find("riskOfRain").populate_biome_properties + +-- Spawn list +cl_stage:add_interactable(InteractableCard.find("chirrsmasPresent")) +cl_stage:add_monster(MonsterCard.find("golem")) +cl_stage:remove_monster(MonsterCard.find("exploder")) -- green and ruins christmas. grinch basically + +-- Rooms +Stage.add_room(cl_stage, path.combine(PATH.."/Stages/SnowyRiskOfRain", "riskOfRain1_snowy.rorlvl")) +Stage.add_room(cl_stage, path.combine(PATH.."/Stages/SnowyRiskOfRain", "riskOfRain2_snowy.rorlvl")) + +-- Main Menu +cl_stage:set_title_screen_properties(GroundStripSnowyRiskOfRain) + +-- Log +local stage_log = EnvironmentLog.new_from_stage(cl_stage) +stage_log.spr_icon = Sprite.new("EnvironmentFinal_Snowy", path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowyEnvironmentFinal.png"), 1, 0, 0) +stage_log:set_initial_camera_position(9591, 2818) +stage_log.token_name = EnvironmentLog.find("riskOfRain").token_name +stage_log.token_story = EnvironmentLog.find("riskOfRain").token_story + +-- Special +local snow = Object.new("SnowyRORSnow") + +Callback.add(snow.on_create, function(self) + local data = Instance.get_data(self) + data.quality = Global.__pref_graphics_quality + data.timer = 0 + data.toggle = 1 + data.setup = 0 + + Particle.find("Snow"):set_life(240, 840) +end) + +Callback.add(snow.on_step, function(self) + local data = Instance.get_data(self) + + if data.timer <= 5 then + data.timer = data.timer + 1 + else + data.timer = 0 + end + + if data.setup == 0 then + data.setup = 1 + + if Net.host then + local amount = math.random(3) + for _, barrel in ipairs(ssr_table_shuffle(Instance.find_all(Object.find("Barrel3")))) do + barrel.save = false + + if amount > 0 then + barrel.save = true + amount = amount - 1 + end + + if not barrel.save then + barrel:destroy() + end + end + + for _, cab in ipairs(Instance.find_all(Object.find("Medcab"))) do + if Util.chance(0.66) then + cab:destroy() + end + end + + for _, gun in ipairs(Instance.find_all(Object.find("Gunchest"))) do + if Util.chance(0.66) then + gun:destroy() + end + end + + for _, rift in ipairs(Instance.find_all(Object.find("RiftChest1"))) do + if gm.save_get_rift_chest_content() ~= -1 or not Global.__game_lobby.rulebook.game_style.new_interactables then + rift:destroy() + end + end + else + for _, barrel in ipairs(ssr_table_shuffle(Instance.find_all(Object.find("Barrel3")))) do + barrel:destroy() + end + + for _, cab in ipairs(Instance.find_all(Object.find("Medcab"))) do + cab:destroy() + end + + for _, gun in ipairs(Instance.find_all(Object.find("Gunchest"))) do + gun:destroy() + end + + for _, rift in ipairs(Instance.find_all(Object.find("RiftChest1"))) do + rift:destroy() + end + end + end + + if data.quality >= 2 and data.timer % 5 == 0 then + for i=0, data.quality do + Particle.find("Snow"):create((Global.___view_l_x - (Global.___view_l_w / 2)) + math.random(Global.___view_l_w * 1.5), Global.___view_l_y, data.quality - 1) + end + end + + local toggle = Global.tileset_final_toggle + + if toggle == 0 and data.toggle ~= toggle then + gm.sprite_replace(Sprite.find("Spacer_M_CL_Snowy").value, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL.png"), 1, false, false, 0, 0) + gm.sprite_replace(Sprite.find("Spacer_M2_CL_Snowy").value, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL.png"), 1, false, false, 0, 0) + data.toggle = toggle + elseif toggle == 1 and data.toggle ~= toggle then + gm.sprite_replace(Sprite.find("Spacer_M_CL_Snowy").value, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_end.png"), 1, false, false, 0, 0) + gm.sprite_replace(Sprite.find("Spacer_M2_CL_Snowy").value, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_end.png"), 1, false, false, 0, 0) + data.toggle = toggle + elseif toggle > 0 and toggle < 1 and data.toggle ~= 3 then + gm.sprite_replace(Sprite.find("Spacer_M_CL_Snowy").value, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySpacer_M_CL_trans.png"), 13, false, false, 0, 0) + gm.sprite_replace(Sprite.find("Spacer_M2_CL_Snowy").value, path.combine(PATH, "Sprites/Stages/SnowyRiskOfRain/snowySpacer_M2_CL_trans.png"), 13, false, false, 0, 0) + data.toggle = 3 + end +end) + +Callback.add(Callback.ON_STAGE_START, function() + if Global.stage_id ~= cl_stage.value then return end + + snow:create(0, 0) +end) + +Hook.add_post(gm.constants.stage_roll_next, function(self, other, result, args) + print(result.value, Stage.find("riskOfRain").value, cl_stage.value) + if result.value == Stage.find("riskOfRain").value then + result.value = cl_stage.value + end +end) + +if HOTLOADING then return end + +local list = Global.environment_log_display_list + +if list:find(EnvironmentLog.find("riskOfRain").value) then + list:insert(list:find(EnvironmentLog.find("riskOfRain").value), stage_log.value) + list:delete_value(EnvironmentLog.find("riskOfRain").value) + list:delete((#list) - 1) +end \ No newline at end of file diff --git a/Stages/TorridOutlands/torridOutlands.lua b/Stages/TorridOutlands/torridOutlands.lua new file mode 100644 index 00000000..5f921292 --- /dev/null +++ b/Stages/TorridOutlands/torridOutlands.lua @@ -0,0 +1,85 @@ +local SPRITE_PATH = path.combine(PATH, "Sprites/Stages/TorridOutlands") +local SOUND_PATH = path.combine(PATH, "Sounds/Music") + +-- Stage Resources +Sprite.new("Tile16Outlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Tile16Outlands.png"), 1, 0, 0) +Sprite.new("BackTilesOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "BackTilesOutlands.png"), 1, 0, 0) +Sprite.new("MoonOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "MoonOutlands.png"), 1, 0, 0) +Sprite.new("Arch2TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Arch2TorridOutlands.png"), 1, 0, 0) +Sprite.new("Arch1TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Arch1TorridOutlands.png"), 1, 0, 0) +Sprite.new("Arch3TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Arch3TorridOutlands.png"), 1, 0, 0) +Sprite.new("CanyonsBack2TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "CanyonsBack2TorridOutlands.png"), 1, 0, 0) +Sprite.new("Clouds1TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Clouds1TorridOutlands.png"), 1, 0, 0) +Sprite.new("CanyonsBack1TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "CanyonsBack1TorridOutlands.png"), 1, 0, 0) +Sprite.new("CanyonsBack3TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "CanyonsBack3TorridOutlands.png"), 1, 0, 0) +Sprite.new("EelBone", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "skeelton.png"), 1, 0, 0) + +-- Menu Resources +local EnvironmentTorridOutlands = Sprite.new("EnvironmentTorridOutlands", path.combine(SPRITE_PATH, "EnvironmentTorridOutlands.png")) +local GroundStripTorridOutlands = Sprite.new("GroundStripTorridOutlands", path.combine(SPRITE_PATH, "GroundStripTorridOutlands.png")) + +-- Stage +local outlands_stage = Stage.new("torridOutlands") +outlands_stage.music_id = Sound.new("musicTorridOutlands", path.combine(SOUND_PATH, "musicTorridOutlands.ogg")) +outlands_stage.token_name = gm.translate("stage.torridOutlands.name") +outlands_stage.token_subname = gm.translate("stage.torridOutlands.subname") +outlands_stage.teleporter_index = 0 +outlands_stage.interactable_spawn_points = 900 +outlands_stage:set_tier(3) + +-- Rooms +Stage.add_room(outlands_stage, path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands1.rorlvl")) +Stage.add_room(outlands_stage, path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands2.rorlvl")) +Stage.add_room(outlands_stage, path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands3.rorlvl")) +Stage.add_room(outlands_stage, path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands4.rorlvl")) +Stage.add_room(outlands_stage, path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands5.rorlvl")) +Stage.add_room(outlands_stage, path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands6.rorlvl")) + +-- Spawn list +outlands_stage:add_monster({ + "wisp", + "imp", + "bison", + "spitter", + "tuber", + "colossus", + "clayMan", + "toxicBeast", + "scavenger", + MonsterCard.find("admonitor") +}) + +outlands_stage:add_monster_loop({ + "greaterWisp", + "archaicWisp", +}) + +outlands_stage:add_interactable({ + "barrel1", + "barrelEquipment", + "chest1", + "chest4", + "shop1", + "drone6", + "drone4", + "shrine2", + "equipmentActivator", + "droneRecycler", + "droneUpgrader" +}) + +outlands_stage:add_interactable_loop({ + "shrine3S" +}) + +if ssr_chirrsmas_active then + outlands_stage:add_interactable(InteractableCard.find("chirrsmasPresent")) +end + +-- Main Menu +outlands_stage:set_title_screen_properties(GroundStripTorridOutlands) + +-- Environment Log +local stage_log = EnvironmentLog.new_from_stage(outlands_stage) +stage_log.spr_icon = EnvironmentTorridOutlands +stage_log:set_initial_camera_position(9247, 2300) \ No newline at end of file diff --git a/Stages/TorridOutlands/torridOutlands1.rorlvl b/Stages/TorridOutlands/torridOutlands1.rorlvl index d8f99f99..4072f480 100644 Binary files a/Stages/TorridOutlands/torridOutlands1.rorlvl and b/Stages/TorridOutlands/torridOutlands1.rorlvl differ diff --git a/Stages/TorridOutlands/torridOutlands3.rorlvl b/Stages/TorridOutlands/torridOutlands3.rorlvl index aeda1edf..6dae752f 100644 Binary files a/Stages/TorridOutlands/torridOutlands3.rorlvl and b/Stages/TorridOutlands/torridOutlands3.rorlvl differ diff --git a/Stages/TorridOutlands/torridOutlands4.rorlvl b/Stages/TorridOutlands/torridOutlands4.rorlvl index 1fac3a2b..840c6f63 100644 Binary files a/Stages/TorridOutlands/torridOutlands4.rorlvl and b/Stages/TorridOutlands/torridOutlands4.rorlvl differ diff --git a/Stages/VerdantWoodland/VerdantWoodland_test.rorlvl b/Stages/VerdantWoodland/VerdantWoodland_test.rorlvl new file mode 100644 index 00000000..d248eaef Binary files /dev/null and b/Stages/VerdantWoodland/VerdantWoodland_test.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland.lua b/Stages/VerdantWoodland/verdantWoodland.lua new file mode 100644 index 00000000..fc2e2624 --- /dev/null +++ b/Stages/VerdantWoodland/verdantWoodland.lua @@ -0,0 +1,97 @@ +local SPRITE_PATH = path.combine(PATH, "Sprites/Stages/VerdantWoodland") +local SOUND_PATH = path.combine(PATH, "Sounds/Music") + +-- Stage Resources +Sprite.new("Tile16Woodland", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "Tile16Woodland.png"), 1, 0, 0) +Sprite.new("BackTilesWoodland", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "BackTilesWoodland.png"), 1, 0, 0) +Sprite.new("VerdantTrees1", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "VerdantTrees1.png"), 1, 0, 0) +Sprite.new("VerdantTrees2", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "VerdantTrees2.png"), 1, 0, 0) +Sprite.new("VerdantTrees3", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "VerdantTrees3.png"), 1, 0, 0) +Sprite.new("VerdantTrees4", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "VerdantTrees4.png"), 1, 0, 0) +Sprite.new("VerdantGodRays", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "VerdantGodRays.png"), 1, 0, 0) + +-- Menu Resources +local EnvironmentVerdantWoodland = Sprite.new("EnvironmentVerdantWoodland", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "EnvironmentVerdantWoodland.png")) +local GroundStripVerdantWoodland_1 = Sprite.new("GroundStripVerdantWoodland_1", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "GroundStripVerdantWoodland_1.png")) +local GroundStripVerdantWoodland_2 = Sprite.new("GroundStripVerdantWoodland_2", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "GroundStripVerdantWoodland_2.png")) +local GroundStripVerdantWoodland_3 = Sprite.new("GroundStripVerdantWoodland_3", path.combine(PATH.."/Sprites/Stages/VerdantWoodland", "GroundStripVerdantWoodland_3.png")) + +local grounds = { + GroundStripVerdantWoodland_1, + GroundStripVerdantWoodland_2, + GroundStripVerdantWoodland_3, +} + +-- Stage +local woodland_stage = Stage.new("verdantWoodland") +woodland_stage.music_id = Sound.new("musicVerdantWoodland", path.combine(SOUND_PATH, "musicVerdantWoodland.ogg")) +woodland_stage.token_name = gm.translate("stage.verdantWoodland.name") +woodland_stage.token_subname = gm.translate("stage.verdantWoodland.subname") +woodland_stage.teleporter_index = 0 +woodland_stage.interactable_spawn_points = 920 +woodland_stage:set_tier(4) + +Stage.add_room(woodland_stage, path.combine(PATH.."/Stages/VerdantWoodland", "verdantWoodland1.rorlvl")) +Stage.add_room(woodland_stage, path.combine(PATH.."/Stages/VerdantWoodland", "verdantWoodland2.rorlvl")) +Stage.add_room(woodland_stage, path.combine(PATH.."/Stages/VerdantWoodland", "verdantWoodland3.rorlvl")) +Stage.add_room(woodland_stage, path.combine(PATH.."/Stages/VerdantWoodland", "verdantWoodland4.rorlvl")) +Stage.add_room(woodland_stage, path.combine(PATH.."/Stages/VerdantWoodland", "verdantWoodland5.rorlvl")) +Stage.add_room(woodland_stage, path.combine(PATH.."/Stages/VerdantWoodland", "verdantWoodland6.rorlvl")) + +-- Spawn list +woodland_stage:add_monster({ + "mushrum", + "greaterWisp", + "wanderingVagrant", + "lynxTribe", + "impOverlord", + "jellyfish", + "golem", + "gup", + "archerBug", + "spitter", + "bramble", + "scavenger" +}) + +woodland_stage:add_monster_loop({ + "impOverlord", + "lemrider" +}) + +woodland_stage:add_interactable({ + "barrel2", + "barrelEquipment", + "chest1", + "chest2", + "shop1", + "drone7", + "equipmentActivator", + "shrine2", + "chestHealing1", + "droneUpgrader" +}) + +woodland_stage:add_interactable_loop({ + "chestHealing2", + "shrine3S", + "chest5", + "equipmentActivator", + "droneRecycler" +}) + +if ssr_chirrsmas_active then + woodland_stage:add_interactable(InteractableCard.find("chirrsmasPresent")) +end + +-- Main Menu +woodland_stage:set_title_screen_properties(grounds[math.random(3)]) + +Callback.add(Callback.ON_GAME_START, function() + woodland_stage:set_title_screen_properties(grounds[math.random(3)]) +end) + +-- Environment Log +local stage_log = EnvironmentLog.new_from_stage(woodland_stage) +stage_log.spr_icon = EnvironmentVerdantWoodland +stage_log:set_initial_camera_position(2591, 4075) \ No newline at end of file diff --git a/Stages/VerdantWoodland/verdantWoodland1.rorlvl b/Stages/VerdantWoodland/verdantWoodland1.rorlvl new file mode 100644 index 00000000..e59acedd Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland1.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland2.rorlvl b/Stages/VerdantWoodland/verdantWoodland2.rorlvl new file mode 100644 index 00000000..a6e02225 Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland2.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland2_OLD.rorlvl b/Stages/VerdantWoodland/verdantWoodland2_OLD.rorlvl new file mode 100644 index 00000000..e81eb508 Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland2_OLD.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland3.rorlvl b/Stages/VerdantWoodland/verdantWoodland3.rorlvl new file mode 100644 index 00000000..58f85368 Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland3.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland4.rorlvl b/Stages/VerdantWoodland/verdantWoodland4.rorlvl new file mode 100644 index 00000000..5e17b046 Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland4.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland5.rorlvl b/Stages/VerdantWoodland/verdantWoodland5.rorlvl new file mode 100644 index 00000000..8a8d6a0f Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland5.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland6.rorlvl b/Stages/VerdantWoodland/verdantWoodland6.rorlvl new file mode 100644 index 00000000..d101f76b Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland6.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland_Base.rorlvl b/Stages/VerdantWoodland/verdantWoodland_Base.rorlvl new file mode 100644 index 00000000..8221dafd Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland_Base.rorlvl differ diff --git a/Stages/VerdantWoodland/verdantWoodland_Base2.rorlvl b/Stages/VerdantWoodland/verdantWoodland_Base2.rorlvl new file mode 100644 index 00000000..897f5c65 Binary files /dev/null and b/Stages/VerdantWoodland/verdantWoodland_Base2.rorlvl differ diff --git a/Stages/WhistlingBasin/whistlingBasin.lua b/Stages/WhistlingBasin/whistlingBasin.lua new file mode 100644 index 00000000..1b2efe46 --- /dev/null +++ b/Stages/WhistlingBasin/whistlingBasin.lua @@ -0,0 +1,86 @@ +local SPRITE_PATH = path.combine(PATH, "Sprites/Stages/WhistlingBasin") +local SOUND_PATH = path.combine(PATH, "Sounds/Music") + +-- Stage Resources +Sprite.new("tile16basin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "tile16basin.png"), 1, 0, 0) +Sprite.new("BackTilesModded2", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "BackTilesModded2.png"), 1, 0, 0) +Sprite.new("LandCloudWhistlingBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "LandCloudWhistlingBasin.png"), 1, 0, 0) +Sprite.new("SkyBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "SkyBasin.png"), 1, 0, 0) +Sprite.new("MoonBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "MoonBasin.png"), 1, 0, 0) +Sprite.new("MountainsBasinNew", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "MountainsBasinNew.png"), 1, 0, 0) +Sprite.new("MountainsBasinNew2", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "MountainsBasinNew2.png"), 1, 0, 0) +Sprite.new("LandCloud4WhistlingBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "LandCloud4WhistlingBasin.png"), 1, 0, 0) +Sprite.new("LandCloud5WhistlingBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "LandCloud5WhistlingBasin.png"), 1, 0, 0) + +-- Menu Resources +local EnvironmentWhistlingBasin = Sprite.new("EnvironmentWhistlingBasin", path.combine(SPRITE_PATH, "EnvironmentWhistlingBasin.png")) +local GroundStripWhistlingBasin = Sprite.new("GroundStripWhistlingBasin", path.combine(SPRITE_PATH, "GroundStripWhistlingBasin.png")) + +-- Stage +local basin_stage = Stage.new("whistlingBasin") +basin_stage.music_id = Sound.new("musicWhistlingBasin", path.combine(SOUND_PATH, "musicWhistlingBasin.ogg")) +basin_stage.token_name = gm.translate("stage.whistlingBasin.name") +basin_stage.token_subname = gm.translate("stage.whistlingBasin.subname") +basin_stage.teleporter_index = 0 +basin_stage:set_tier(2) + +-- Rooms +Stage.add_room(basin_stage, path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin1.rorlvl")) +Stage.add_room(basin_stage, path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin2.rorlvl")) +Stage.add_room(basin_stage, path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin3.rorlvl")) +Stage.add_room(basin_stage, path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin4.rorlvl")) +Stage.add_room(basin_stage, path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin5.rorlvl")) +Stage.add_room(basin_stage, path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin6.rorlvl")) + +-- Spawn list +basin_stage:add_monster({ + "lemurian", + "wisp", + "imp", + "crab", + "greaterWisp", + "ancientWisp", + "archaicWisp", + "bramble", + "scavenger" +}) + +basin_stage:add_monster_loop({ + "impOverlord", + "lemrider", + MonsterCard.find("admonitor") +}) + +basin_stage:add_interactable({ + "barrel1", + "barrelEquipment", + "chest1", + "chest2", + "shop1", + "drone3", + "drone4", + "shrine2", + "chestHealing1" +}) + +basin_stage:add_interactable_loop({ + "chestHealing2", + "chest4", + "shrine3S", + "barrel2", + "chest5", + "equipmentActivator", + "droneRecycler" +}) + +if ssr_chirrsmas_active then + basin_stage:add_interactable(InteractableCard.find("chirrsmasPresent")) +end + +-- Main Menu +basin_stage:set_title_screen_properties(GroundStripWhistlingBasin) + +-- Environment Log +local stage_log = EnvironmentLog.new_from_stage(basin_stage) +stage_log.spr_icon = EnvironmentWhistlingBasin +stage_log:set_initial_camera_position(3013, 2279) \ No newline at end of file diff --git a/Stages/WhistlingBasin/whistlingBasin1.rorlvl b/Stages/WhistlingBasin/whistlingBasin1.rorlvl index 1ef729aa..d499ae18 100644 Binary files a/Stages/WhistlingBasin/whistlingBasin1.rorlvl and b/Stages/WhistlingBasin/whistlingBasin1.rorlvl differ diff --git a/Stages/WhistlingBasin/whistlingBasin2.rorlvl b/Stages/WhistlingBasin/whistlingBasin2.rorlvl index 5dc0fcbe..97bcfc41 100644 Binary files a/Stages/WhistlingBasin/whistlingBasin2.rorlvl and b/Stages/WhistlingBasin/whistlingBasin2.rorlvl differ diff --git a/Stages/WhistlingBasin/whistlingBasin3.rorlvl b/Stages/WhistlingBasin/whistlingBasin3.rorlvl index 1f007e31..2660f219 100644 Binary files a/Stages/WhistlingBasin/whistlingBasin3.rorlvl and b/Stages/WhistlingBasin/whistlingBasin3.rorlvl differ diff --git a/Stages/WhistlingBasin/whistlingBasin4.rorlvl b/Stages/WhistlingBasin/whistlingBasin4.rorlvl index 0b9cb57c..0ea911b2 100644 Binary files a/Stages/WhistlingBasin/whistlingBasin4.rorlvl and b/Stages/WhistlingBasin/whistlingBasin4.rorlvl differ diff --git a/Stages/WhistlingBasin/whistlingBasin5.rorlvl b/Stages/WhistlingBasin/whistlingBasin5.rorlvl index a5fbaacd..f88fa9ab 100644 Binary files a/Stages/WhistlingBasin/whistlingBasin5.rorlvl and b/Stages/WhistlingBasin/whistlingBasin5.rorlvl differ diff --git a/Stages/WhistlingBasin/whistlingBasin6.rorlvl b/Stages/WhistlingBasin/whistlingBasin6.rorlvl index 98666447..6b6bcdb8 100644 Binary files a/Stages/WhistlingBasin/whistlingBasin6.rorlvl and b/Stages/WhistlingBasin/whistlingBasin6.rorlvl differ diff --git a/Survivors/chirr.lua b/Survivors/chirr.lua.rmt similarity index 100% rename from Survivors/chirr.lua rename to Survivors/chirr.lua.rmt diff --git a/Survivors/executioner.lua b/Survivors/executioner.lua index a31b86a3..15bf8da6 100644 --- a/Survivors/executioner.lua +++ b/Survivors/executioner.lua @@ -2,77 +2,77 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Survivors/Executioner") local SOUND_PATH = path.combine(PATH, "Sounds/Survivors/Executioner") -- sprites. -local sprite_loadout = Resources.sprite_load(NAMESPACE, "ExecutionerSelect", path.combine(SPRITE_PATH, "select.png"), 23, 28, 0) -local sprite_portrait = Resources.sprite_load(NAMESPACE, "ExecutionerPortrait", path.combine(SPRITE_PATH, "portrait.png"), 3) -local sprite_portrait_small = Resources.sprite_load(NAMESPACE, "ExecutionerPortraitSmall", path.combine(SPRITE_PATH, "portraitSmall.png")) -local sprite_skills = Resources.sprite_load(NAMESPACE, "ExecutionerSkills", path.combine(SPRITE_PATH, "skills.png"), 11) -local sprite_credits = Resources.sprite_load(NAMESPACE, "CreditsSurvivorExecutioner", path.combine(SPRITE_PATH, "credits.png"), 1, 6, 12) - -local sprite_idle = Resources.sprite_load(NAMESPACE, "ExecutionerIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 12, 17) -local sprite_idle_half = Resources.sprite_load(NAMESPACE, "ExecutionerIdleHalf", path.combine(SPRITE_PATH, "idleHalf.png"), 1, 12, 17) -local sprite_walk = Resources.sprite_load(NAMESPACE, "ExecutionerWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 14, 18) -local sprite_walk_half = Resources.sprite_load(NAMESPACE, "ExecutionerWalkHalf", path.combine(SPRITE_PATH, "walkHalf.png"), 8, 14, 18) -local sprite_walk_back = Resources.sprite_load(NAMESPACE, "ExecutionerWalkBack", path.combine(SPRITE_PATH, "walkBack.png"), 8, 9, 16) -local sprite_jump = Resources.sprite_load(NAMESPACE, "ExecutionerJump", path.combine(SPRITE_PATH, "jump.png"), 1, 12, 15) -local sprite_jump_half = Resources.sprite_load(NAMESPACE, "ExecutionerJumpHalf", path.combine(SPRITE_PATH, "jumpHalf.png"), 1, 12, 15) -local sprite_jump_peak = Resources.sprite_load(NAMESPACE, "ExecutionerJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 12, 14) -local sprite_jump_peak_half = Resources.sprite_load(NAMESPACE, "ExecutionerJumpPeakHalf", path.combine(SPRITE_PATH, "jumpPeakHalf.png"), 1, 12, 14) -local sprite_fall = Resources.sprite_load(NAMESPACE, "ExecutionerFall", path.combine(SPRITE_PATH, "fall.png"), 1, 12, 13) -local sprite_fall_half = Resources.sprite_load(NAMESPACE, "ExecutionerFallHalf", path.combine(SPRITE_PATH, "fallHalf.png"), 1, 12, 13) -local sprite_climb = Resources.sprite_load(NAMESPACE, "ExecutionerClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 12, 18) -local sprite_death = Resources.sprite_load(NAMESPACE, "ExecutionerDeath", path.combine(SPRITE_PATH, "death.png"), 11, 38, 17) -local sprite_decoy = Resources.sprite_load(NAMESPACE, "ExecutionerDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 16, 18) -local sprite_palette = Resources.sprite_load(NAMESPACE, "ExecutionerPalette", path.combine(SPRITE_PATH, "palette.png")) - -local sprite_shoot1 = Resources.sprite_load(NAMESPACE, "ExecutionerShoot1", path.combine(SPRITE_PATH, "shoot1.png"), 5, 10, 17) -local sprite_shoot1_half = Resources.sprite_load(NAMESPACE, "ExecutionerShoot1Half", path.combine(SPRITE_PATH, "shoot1Half.png"), 5, 10, 17) -local sprite_shoot2a = Resources.sprite_load(NAMESPACE, "ExecutionerShoot2a", path.combine(SPRITE_PATH, "shoot2a.png"), 6, 12, 25) -local sprite_shoot2b = Resources.sprite_load(NAMESPACE, "ExecutionerShoot2b", path.combine(SPRITE_PATH, "shoot2b.png"), 6, 12, 25) -local sprite_shoot3 = Resources.sprite_load(NAMESPACE, "ExecutionerShoot3", path.combine(SPRITE_PATH, "shoot3.png"), 10, 68, 82) - -local sprite_shoot4PreGround= Resources.sprite_load(NAMESPACE, "ExecutionerShoot4PreGround", path.combine(SPRITE_PATH, "shoot4PreGround.png"), 5, 39, 63) -local sprite_shoot4PreAir = Resources.sprite_load(NAMESPACE, "ExecutionerShoot4PreAir", path.combine(SPRITE_PATH, "shoot4PreAir.png"), 5, 39, 63) -local sprite_shoot4PreSlide = Resources.sprite_load(NAMESPACE, "ExecutionerShoot4PreSlide", path.combine(SPRITE_PATH, "shoot4PreSlide.png"), 5, 39, 63) -local sprite_shoot4 = Resources.sprite_load(NAMESPACE, "ExecutionerShoot4", path.combine(SPRITE_PATH, "shoot4.png"), 18, 70, 82) - -local sprite_shoot5PreGround= Resources.sprite_load(NAMESPACE, "ExecutionerShoot5PreGround", path.combine(SPRITE_PATH, "shoot5PreGround.png"), 5, 39, 63) -local sprite_shoot5PreAir = Resources.sprite_load(NAMESPACE, "ExecutionerShoot5PreAir", path.combine(SPRITE_PATH, "shoot5PreAir.png"), 5, 39, 63) -local sprite_shoot5PreSlide = Resources.sprite_load(NAMESPACE, "ExecutionerShoot5PreSlide", path.combine(SPRITE_PATH, "shoot5PreSlide.png"), 5, 39, 63) -local sprite_shoot5 = Resources.sprite_load(NAMESPACE, "ExecutionerShoot5", path.combine(SPRITE_PATH, "shoot5.png"), 18, 70, 82) - -local sprite_shoot4b = Resources.sprite_load(NAMESPACE, "ExecutionerShoot4B", path.combine(SPRITE_PATH, "shoot4b.png"), 9, 36, 33) -local sprite_shoot5b = Resources.sprite_load(NAMESPACE, "ExecutionerShoot5B", path.combine(SPRITE_PATH, "shoot5b.png"), 9, 36, 33) -local sprite_axe_projectile = Resources.sprite_load(NAMESPACE, "ExecutionerAxeProjectile", path.combine(SPRITE_PATH, "axeProjectile.png"), 4, 56, 51) -local sprite_axe_projectileS= Resources.sprite_load(NAMESPACE, "ExecutionerAxeProjectileS", path.combine(SPRITE_PATH, "axeProjectileS.png"), 4, 56, 51) - -local sprite_drone_idle = Resources.sprite_load(NAMESPACE, "DronePlayerExecutionerIdle", path.combine(SPRITE_PATH, "droneIdle.png"), 5, 11, 14) -local sprite_drone_shoot = Resources.sprite_load(NAMESPACE, "DronePlayerExecutionerShoot", path.combine(SPRITE_PATH, "droneShoot.png"), 5, 33, 13) - -local sprite_ion_sparks = Resources.sprite_load(NAMESPACE, "ExecutionerIonSparks", path.combine(SPRITE_PATH, "ionSparks.png"), 4, 24, 14) -local sprite_ion_sparks2 = Resources.sprite_load(NAMESPACE, "ExecutionerIonSparks2s", path.combine(SPRITE_PATH, "ionSparks2.png"), 4, 21, 21) -local sprite_ion_tracer = Resources.sprite_load(NAMESPACE, "ExecutionerIonTracer", path.combine(SPRITE_PATH, "ionTracer.png"), 5, 0, 2) -local sprite_ion_particle = Resources.sprite_load(NAMESPACE, "ExecutionerIonParticle", path.combine(SPRITE_PATH, "ionParticle.png"), 5, 8, 8) -local sprite_ion_particleS = Resources.sprite_load(NAMESPACE, "ExecutionerIonParticleS", path.combine(SPRITE_PATH, "ionParticleS.png"), 5, 8, 8) - -local sprite_log = Resources.sprite_load(NAMESPACE, "ExecutionerLog", path.combine(SPRITE_PATH, "log.png")) +local sprite_loadout = Sprite.new("ExecutionerSelect", path.combine(SPRITE_PATH, "select.png"), 23, 28, 0) +local sprite_portrait = Sprite.new("ExecutionerPortrait", path.combine(SPRITE_PATH, "portrait.png"), 3) +local sprite_portrait_small = Sprite.new("ExecutionerPortraitSmall", path.combine(SPRITE_PATH, "portraitSmall.png")) +local sprite_skills = Sprite.new("ExecutionerSkills", path.combine(SPRITE_PATH, "skills.png"), 11) +local sprite_credits = Sprite.new("CreditsSurvivorExecutioner", path.combine(SPRITE_PATH, "credits.png"), 1, 6, 12) + +local sprite_idle = Sprite.new("ExecutionerIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 12, 17) +local sprite_idle_half = Sprite.new("ExecutionerIdleHalf", path.combine(SPRITE_PATH, "idleHalf.png"), 1, 12, 17) +local sprite_walk = Sprite.new("ExecutionerWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 14, 18) +local sprite_walk_half = Sprite.new("ExecutionerWalkHalf", path.combine(SPRITE_PATH, "walkHalf.png"), 8, 14, 18) +local sprite_walk_back = Sprite.new("ExecutionerWalkBack", path.combine(SPRITE_PATH, "walkBack.png"), 8, 9, 16) +local sprite_jump = Sprite.new("ExecutionerJump", path.combine(SPRITE_PATH, "jump.png"), 1, 12, 15) +local sprite_jump_half = Sprite.new("ExecutionerJumpHalf", path.combine(SPRITE_PATH, "jumpHalf.png"), 1, 12, 15) +local sprite_jump_peak = Sprite.new("ExecutionerJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 12, 14) +local sprite_jump_peak_half = Sprite.new("ExecutionerJumpPeakHalf", path.combine(SPRITE_PATH, "jumpPeakHalf.png"), 1, 12, 14) +local sprite_fall = Sprite.new("ExecutionerFall", path.combine(SPRITE_PATH, "fall.png"), 1, 12, 13) +local sprite_fall_half = Sprite.new("ExecutionerFallHalf", path.combine(SPRITE_PATH, "fallHalf.png"), 1, 12, 13) +local sprite_climb = Sprite.new("ExecutionerClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 12, 18) +local sprite_death = Sprite.new("ExecutionerDeath", path.combine(SPRITE_PATH, "death.png"), 11, 38, 17) +local sprite_decoy = Sprite.new("ExecutionerDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 16, 18) +local sprite_palette = Sprite.new("ExecutionerPalette", path.combine(SPRITE_PATH, "palette.png")) + +local sprite_shoot1 = Sprite.new("ExecutionerShoot1", path.combine(SPRITE_PATH, "shoot1.png"), 5, 10, 17) +local sprite_shoot1_half = Sprite.new("ExecutionerShoot1Half", path.combine(SPRITE_PATH, "shoot1Half.png"), 5, 10, 17) +local sprite_shoot2a = Sprite.new("ExecutionerShoot2a", path.combine(SPRITE_PATH, "shoot2a.png"), 6, 12, 25) +local sprite_shoot2b = Sprite.new("ExecutionerShoot2b", path.combine(SPRITE_PATH, "shoot2b.png"), 6, 12, 25) +local sprite_shoot3 = Sprite.new("ExecutionerShoot3", path.combine(SPRITE_PATH, "shoot3.png"), 10, 68, 82) + +local sprite_shoot4PreGround= Sprite.new("ExecutionerShoot4PreGround", path.combine(SPRITE_PATH, "shoot4PreGround.png"), 5, 39, 63) +local sprite_shoot4PreAir = Sprite.new("ExecutionerShoot4PreAir", path.combine(SPRITE_PATH, "shoot4PreAir.png"), 5, 39, 63) +local sprite_shoot4PreSlide = Sprite.new("ExecutionerShoot4PreSlide", path.combine(SPRITE_PATH, "shoot4PreSlide.png"), 5, 39, 63) +local sprite_shoot4 = Sprite.new("ExecutionerShoot4", path.combine(SPRITE_PATH, "shoot4.png"), 18, 70, 82) + +local sprite_shoot5PreGround= Sprite.new("ExecutionerShoot5PreGround", path.combine(SPRITE_PATH, "shoot5PreGround.png"), 5, 39, 63) +local sprite_shoot5PreAir = Sprite.new("ExecutionerShoot5PreAir", path.combine(SPRITE_PATH, "shoot5PreAir.png"), 5, 39, 63) +local sprite_shoot5PreSlide = Sprite.new("ExecutionerShoot5PreSlide", path.combine(SPRITE_PATH, "shoot5PreSlide.png"), 5, 39, 63) +local sprite_shoot5 = Sprite.new("ExecutionerShoot5", path.combine(SPRITE_PATH, "shoot5.png"), 18, 70, 82) + +local sprite_shoot4b = Sprite.new("ExecutionerShoot4B", path.combine(SPRITE_PATH, "shoot4b.png"), 9, 36, 33) +local sprite_shoot5b = Sprite.new("ExecutionerShoot5B", path.combine(SPRITE_PATH, "shoot5b.png"), 9, 36, 33) +local sprite_axe_projectile = Sprite.new("ExecutionerAxeProjectile", path.combine(SPRITE_PATH, "axeProjectile.png"), 4, 56, 51) +local sprite_axe_projectileS= Sprite.new("ExecutionerAxeProjectileS", path.combine(SPRITE_PATH, "axeProjectileS.png"), 4, 56, 51) + +local sprite_drone_idle = Sprite.new("DronePlayerExecutionerIdle", path.combine(SPRITE_PATH, "droneIdle.png"), 5, 11, 14) +local sprite_drone_shoot = Sprite.new("DronePlayerExecutionerShoot", path.combine(SPRITE_PATH, "droneShoot.png"), 5, 33, 13) + +local sprite_ion_sparks = Sprite.new("ExecutionerIonSparks", path.combine(SPRITE_PATH, "ionSparks.png"), 4, 24, 14) +local sprite_ion_sparks2 = Sprite.new("ExecutionerIonSparks2s", path.combine(SPRITE_PATH, "ionSparks2.png"), 4, 21, 21) +local sprite_ion_tracer = Sprite.new("ExecutionerIonTracer", path.combine(SPRITE_PATH, "ionTracer.png"), 5, 0, 2) +local sprite_ion_particle = Sprite.new("ExecutionerIonParticle", path.combine(SPRITE_PATH, "ionParticle.png"), 5, 8, 8) +local sprite_ion_particleS = Sprite.new("ExecutionerIonParticleS", path.combine(SPRITE_PATH, "ionParticleS.png"), 5, 8, 8) + +local sprite_log = Sprite.new("ExecutionerLog", path.combine(SPRITE_PATH, "log.png")) -- sounds. -local sound_select = Resources.sfx_load(NAMESPACE, "UISurvivorsExecutioner", path.combine(SOUND_PATH, "select.ogg")) -local sound_shoot1 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot1", path.combine(SOUND_PATH, "skill1.ogg")) -local sound_shoot2 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot2", path.combine(SOUND_PATH, "skill2.ogg")) -local sound_shoot3 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot3", path.combine(SOUND_PATH, "skill3.ogg")) -local sound_shoot4_1 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot4_1", path.combine(SOUND_PATH, "skill4_1.ogg")) -local sound_shoot4_2 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot4_2", path.combine(SOUND_PATH, "skill4_2.ogg")) -local sound_shoot4_3 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot4_3", path.combine(SOUND_PATH, "skill4_3.ogg")) -local sound_shoot4b_1 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot4B_1", path.combine(SOUND_PATH, "skill4b_1.ogg")) -local sound_shoot4b_2 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot4B_2", path.combine(SOUND_PATH, "skill4b_2.ogg")) +local sound_select = Sound.new("UISurvivorsExecutioner", path.combine(SOUND_PATH, "select.ogg")) +local sound_shoot1 = Sound.new("ExecutionerShoot1", path.combine(SOUND_PATH, "skill1.ogg")) +local sound_shoot2 = Sound.new("ExecutionerShoot2", path.combine(SOUND_PATH, "skill2.ogg")) +local sound_shoot3 = Sound.new("ExecutionerShoot3", path.combine(SOUND_PATH, "skill3.ogg")) +local sound_shoot4_1 = Sound.new("ExecutionerShoot4_1", path.combine(SOUND_PATH, "skill4_1.ogg")) +local sound_shoot4_2 = Sound.new("ExecutionerShoot4_2", path.combine(SOUND_PATH, "skill4_2.ogg")) +local sound_shoot4_3 = Sound.new("ExecutionerShoot4_3", path.combine(SOUND_PATH, "skill4_3.ogg")) +local sound_shoot4b_1 = Sound.new("ExecutionerShoot4B_1", path.combine(SOUND_PATH, "skill4b_1.ogg")) +local sound_shoot4b_2 = Sound.new("ExecutionerShoot4B_2", path.combine(SOUND_PATH, "skill4b_2.ogg")) -- particles. -- used for ion burst tracer -local particleWispGTracer = Particle.find("ror", "WispGTracer") +local particleWispGTracer = Particle.find("WispGTracer") -- used for crowd dispersion and execution -local ionParticle = Particle.new(NAMESPACE, "ion") +local ionParticle = Particle.new("ion") ionParticle:set_sprite(sprite_ion_particle, true, true, false) ionParticle:set_life(15, 60) ionParticle:set_orientation(0, 360, 0, 0, false) @@ -81,7 +81,7 @@ ionParticle:set_size(0.6, 1, 0, 0.01) ionParticle:set_direction(0, 360, 0, 0) -- used for scepter-boosted execution -local ionParticleS = Particle.new(NAMESPACE, "ionS") +local ionParticleS = Particle.new("ionS") ionParticleS:set_sprite(sprite_ion_particleS, true, true, false) ionParticleS:set_life(15, 60) ionParticleS:set_orientation(0, 360, 0, 0, false) @@ -89,97 +89,111 @@ ionParticleS:set_speed(0.2, 0.5, -0.02, 0) ionParticleS:set_size(0.6, 1, 0, 0.01) ionParticleS:set_direction(0, 360, 0, 0) -local buffFear = Buff.find("ror", "fear") -local buffShadowClone = Buff.find("ror", "shadowClone") +local buffFear = Buff.find("fear") +local buffShadowClone = Buff.find("shadowClone") -- Okay, let's start Executing, some Monsters .. -local executioner = Survivor.new(NAMESPACE, "executioner") -local executioner_id = executioner.value +local executioner = Survivor.new("executioner") executioner:set_stats_base({ - maxhp = 95, + health = 95, damage = 14, regen = 0.008, }) + executioner:set_stats_level({ - maxhp = 24, + health = 24, damage = 3, regen = 0.0012, armor = 2, }) -executioner:set_animations({ - idle = sprite_idle, - walk = sprite_walk, - jump = sprite_jump, - jump_peak = sprite_jump_peak, - fall = sprite_fall, - climb = sprite_climb, - death = sprite_death, - decoy = sprite_decoy, - drone_idle = sprite_drone_idle, - drone_shoot = sprite_drone_shoot, -}) +local executioner_log = SurvivorLog.new_from_survivor(executioner) +executioner_log.portrait_id = sprite_log +executioner_log.sprite_id = sprite_walk +executioner_log.sprite_icon_id = sprite_portrait -executioner:set_cape_offset(0, -8, 0, -5) -executioner:set_primary_color(Color.from_rgb(175, 113, 126)) -executioner.select_sound_id = sound_select +executioner.primary_color = Color.from_rgb(175, 113, 126) executioner.sprite_loadout = sprite_loadout executioner.sprite_portrait = sprite_portrait executioner.sprite_portrait_small = sprite_portrait_small + executioner.sprite_idle = sprite_idle -- used by skin systen for idle sprite executioner.sprite_title = sprite_walk -- also used by skin system for walk sprite executioner.sprite_credits = sprite_credits -executioner:set_palettes(sprite_palette, sprite_palette, sprite_palette) +executioner.sprite_palette = sprite_palette +executioner.sprite_portrait_palette = sprite_palette +executioner.sprite_loadout_palette = sprite_palette + +executioner.select_sound_id = sound_select +executioner.cape_offset = Array.new({0, -8, 0, -5}) + +--[[ --skins -executioner:add_skin("Grass Green", 1, Resources.sprite_load(NAMESPACE, "ExecutionerSelect2", path.combine(SPRITE_PATH, "select2.png"), 23, 28, 0), -Resources.sprite_load(NAMESPACE, "ExecutionerPortrait2", path.combine(SPRITE_PATH, "portrait2.png"), 3), -Resources.sprite_load(NAMESPACE, "ExecutionerPortraitSmall2", path.combine(SPRITE_PATH, "portraitSmall2.png"))) +executioner:add_skin("Grass Green", 1, Sprite.new("ExecutionerSelect2", path.combine(SPRITE_PATH, "select2.png"), 23, 28, 0), +Sprite.new("ExecutionerPortrait2", path.combine(SPRITE_PATH, "portrait2.png"), 3), +Sprite.new("ExecutionerPortraitSmall2", path.combine(SPRITE_PATH, "portraitSmall2.png"))) -executioner:add_skin("Blood Red", 2, Resources.sprite_load(NAMESPACE, "ExecutionerSelect3", path.combine(SPRITE_PATH, "select3.png"), 23, 28, 0), -Resources.sprite_load(NAMESPACE, "ExecutionerPortrait3", path.combine(SPRITE_PATH, "portrait3.png"), 3), -Resources.sprite_load(NAMESPACE, "ExecutionerPortraitSmall3", path.combine(SPRITE_PATH, "portraitSmall3.png"))) +executioner:add_skin("Blood Red", 2, Sprite.new("ExecutionerSelect3", path.combine(SPRITE_PATH, "select3.png"), 23, 28, 0), +Sprite.new("ExecutionerPortrait3", path.combine(SPRITE_PATH, "portrait3.png"), 3), +Sprite.new("ExecutionerPortraitSmall3", path.combine(SPRITE_PATH, "portraitSmall3.png"))) -executioner:add_skin("Royal Purple", 3, Resources.sprite_load(NAMESPACE, "ExecutionerSelect4", path.combine(SPRITE_PATH, "select4.png"), 23, 28, 0), -Resources.sprite_load(NAMESPACE, "ExecutionerPortrait4", path.combine(SPRITE_PATH, "portrait4.png"), 3), -Resources.sprite_load(NAMESPACE, "ExecutionerPortraitSmall4", path.combine(SPRITE_PATH, "portraitSmall4.png"))) +executioner:add_skin("Royal Purple", 3, Sprite.new("ExecutionerSelect4", path.combine(SPRITE_PATH, "select4.png"), 23, 28, 0), +Sprite.new("ExecutionerPortrait4", path.combine(SPRITE_PATH, "portrait4.png"), 3), +Sprite.new("ExecutionerPortraitSmall4", path.combine(SPRITE_PATH, "portraitSmall4.png"))) +]]-- -executioner:clear_callbacks() -executioner:onInit(function(actor) +Callback.add(executioner.on_init, function(actor) -- setup half-sprite nonsense - actor.sprite_idle_half = Array.new({sprite_idle, sprite_idle_half, 0}) - actor.sprite_walk_half = Array.new({sprite_walk, sprite_walk_half, 0, sprite_walk_back}) - actor.sprite_jump_half = Array.new({sprite_jump, sprite_jump_half, 0}) - actor.sprite_jump_peak_half = Array.new({sprite_jump_peak, sprite_jump_peak_half, 0}) - actor.sprite_fall_half = Array.new({sprite_fall, sprite_fall_half, 0}) + actor.sprite_idle_half = Array.new({sprite_idle, sprite_idle_half, 0}) + actor.sprite_walk_half = Array.new({sprite_walk, sprite_walk_half, 0, sprite_walk_back}) + actor.sprite_jump_half = Array.new({sprite_jump, sprite_jump_half, 0}) + actor.sprite_jump_peak_half = Array.new({sprite_jump_peak, sprite_jump_peak_half, 0}) + actor.sprite_fall_half = Array.new({sprite_fall, sprite_fall_half, 0}) + + actor.sprite_idle = sprite_idle + actor.sprite_walk = sprite_walk + actor.sprite_jump = sprite_jump + actor.sprite_jump_peak = sprite_jump_peak + actor.sprite_fall = sprite_fall + actor.sprite_climb = sprite_climb + actor.sprite_death = sprite_death + actor.sprite_decoy = sprite_decoy + actor.sprite_drone_idle = sprite_drone_idle + actor.sprite_drone_shoot = sprite_drone_shoot actor:survivor_util_init_half_sprites() end) -local executionerPrimary = executioner:get_primary() -local executionerSecondary = executioner:get_secondary() -local executionerUtility = executioner:get_utility() -local executionerSpecial = executioner:get_special() +local primary = executioner:get_skills(Skill.Slot.PRIMARY)[1] +local secondary = executioner:get_skills(Skill.Slot.SECONDARY)[1] +local utility = executioner:get_skills(Skill.Slot.UTILITY)[1] +local special = executioner:get_skills(Skill.Slot.SPECIAL)[1] +local specialS = Skill.new("executionerVBoosted") + +-- alt special +local special2 = Skill.new("executionerV2") +local special2S = Skill.new("executionerV2Boosted") +executioner:add_skill(Skill.Slot.SPECIAL, special2) -- Service Pistol -executionerPrimary:set_skill_icon(sprite_skills, 0) -executionerPrimary.cooldown = 5 -executionerPrimary.damage = 1.0 -executionerPrimary.require_key_press = false -executionerPrimary.hold_facing_direction = true -executionerPrimary.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.any +primary.sprite = sprite_skills +primary.subimage = 0 +primary.cooldown = 5 +primary.damage = 1 +primary.require_key_press = false +primary.hold_facing_direction = true +primary.required_interrupt_priority = ActorState.InterruptPriority.ANY -local stateExecutionerPrimary = State.new(NAMESPACE, "executionerPrimary") +local statePrimary = ActorState.new("executionerPrimary") -executionerPrimary:clear_callbacks() -executionerPrimary:onActivate(function(actor) - actor:enter_state(stateExecutionerPrimary) +Callback.add(primary.on_activate, function(actor, skill, slot) + actor:set_state(statePrimary) end) -stateExecutionerPrimary:clear_callbacks() -stateExecutionerPrimary:onEnter(function(actor, data) +Callback.add(statePrimary.on_enter, function(actor, data) actor.image_index2 = 0 data.fired = 0 -- gamemaker bools are a pain to deal with in lua, so just use numbers instead @@ -187,7 +201,7 @@ stateExecutionerPrimary:onEnter(function(actor, data) actor:skill_util_strafe_turn_init() end) -stateExecutionerPrimary:onStep(function(actor, data) +Callback.add(statePrimary.on_step, function(actor, data) actor.sprite_index2 = sprite_shoot1_half -- first arg: speed for attack animation, in sprite frames per game frame @@ -200,11 +214,13 @@ stateExecutionerPrimary:onStep(function(actor, data) if actor.sprite_index == actor.sprite_walk_half[2] then local walk_offset = 0 local leg_frame = math.floor(actor.image_index) + if leg_frame == 1 or leg_frame == 5 then walk_offset = 1 elseif leg_frame == 3 or leg_frame == 7 then walk_offset = -1 end + actor.ydisp = walk_offset -- ydisp controls upper body offset end @@ -214,12 +230,12 @@ stateExecutionerPrimary:onStep(function(actor, data) actor:sound_play(sound_shoot1, 1, 0.9 + math.random() * 0.2) if actor:is_authority() then - local damage = actor:skill_get_damage(executionerPrimary) + local damage = actor:skill_get_damage(primary) local dir = actor:skill_util_facing_direction() if not GM.skill_util_update_heaven_cracker(actor, damage, actor.image_xscale) then - for i=0, actor:buff_stack_count(buffShadowClone) do - local attack = actor:fire_bullet(actor.x, actor.y, 1000, dir, damage, nil, gm.constants.sSparks1, Attack_Info.TRACER.commando1) + for i=0, actor:buff_count(buffShadowClone) do + local attack = actor:fire_bullet(actor.x, actor.y, 1000, dir, damage, nil, gm.constants.sSparks1, Tracer.COMMANDO1) attack.climb = i * 8 * 1.35 end end @@ -230,78 +246,82 @@ stateExecutionerPrimary:onStep(function(actor, data) actor:skill_util_reset_activity_state() end end) -stateExecutionerPrimary:onExit(function(actor, data) + +Callback.add(statePrimary.on_exit, function(actor, data) actor:skill_util_strafe_exit() end) -stateExecutionerPrimary:onGetInterruptPriority(function(actor, data) + +Callback.add(statePrimary.on_get_interrupt_priority, function(actor, data) -- enables extremely high attack rates -- allow interrupting if the next frame is calculated to reach the end of the anim - -- FIXME: breaks strafe turn queuing - if actor.image_index2 + 0.33 * actor.attack_speed >= gm.sprite_get_number(actor.sprite_index2)+1 then - return State.ACTOR_STATE_INTERRUPT_PRIORITY.any + -- FIXME: breaks strafe turn queuing -- lmao "fixme" nobodys gonna fix you bozo + if actor.image_index2 + 0.33 * actor.attack_speed >= gm.sprite_get_number(actor.sprite_index2) + 1 then + return ActorState.InterruptPriority.ANY end + if actor.image_index2 > 2 then - return State.ACTOR_STATE_INTERRUPT_PRIORITY.skill_interrupt_period + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD else - return State.ACTOR_STATE_INTERRUPT_PRIORITY.priority_skill + return ActorState.InterruptPriority.PRIORITY_SKILL end end) -- Ion Burst -executionerSecondary:set_skill_icon(sprite_skills, 2) -executionerSecondary.cooldown = -1 -executionerSecondary.damage = 3.2 -executionerSecondary.max_stock = 10 -executionerSecondary.auto_restock = false -executionerSecondary.start_with_stock = false -executionerSecondary.use_delay = 30 -executionerSecondary.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill - -local ION_TRACER_COLOR = Color.from_rgb(110, 129, 195) - -local ion_tracer, ion_tracer_info = CustomTracer.new(function(x1, y1, x2, y2) +secondary.sprite = sprite_skills +secondary.subimage = 2 +secondary.cooldown = -1 +secondary.damage = 3.2 +secondary.max_stock = 10 +secondary.auto_restock = false +secondary.start_with_stock = false +secondary.use_delay = 30 +secondary.required_interrupt_priority = ActorState.InterruptPriority.SKILL + +local tracer = Tracer.new("executionerIonBurst") +tracer.sparks_offset_y = -5 + +tracer:set_callback(function(x1, y1, x2, y2) if x1 < x2 then x1 = x1 + 16 else x1 = x1 - 16 end y1 = y1 - 5 y2 = y2 - 5 -- line tracer - local tracer = gm.instance_create(x1, y1, gm.constants.oEfLineTracer) - - tracer.xend = x2 - tracer.yend = y2 - tracer.sprite_index = sprite_ion_tracer - tracer.image_speed = 1 - tracer.rate = 0.1 - tracer.blend_1 = Color.from_rgb(255, 255, 255) - tracer.blend_2 = ION_TRACER_COLOR - tracer.blend_rate = 0.2 - tracer.image_alpha = 1.5 - tracer.bm = 1 - tracer.width = 3 + local inst = Object.find("EfLineTracer"):create(x1, y1) + + inst.xend = x2 + inst.yend = y2 + inst.sprite_index = sprite_ion_tracer + inst.image_speed = 1 + inst.rate = 0.1 + inst.blend_1 = Color.from_rgb(255, 255, 255) + inst.blend_2 = Color.from_rgb(110, 129, 195) + inst.blend_rate = 0.2 + inst.image_alpha = 1.5 + inst.bm = 1 + inst.width = 3 -- particles - local dist = gm.point_distance(x1, y1, x2, y2) - local dir = gm.point_direction(x1, y1, x2, y2) + local dist = Math.distance(x1, y1, x2, y2) + local dir = Math.direction(x1, y1, x2, y2) particleWispGTracer:set_direction(dir, dir, 0, 0) local px = x1 local i = 0 while i < dist do - particleWispGTracer:create_colour(px, y1 + gm.random_range(-8, 8), ION_TRACER_COLOR, 1) + particleWispGTracer:create_colour(px, y1 + gm.random_range(-8, 8), Color.from_rgb(110, 129, 195), 1) px = px + gm.lengthdir_x(20, dir) i = i + 20 end end) -ion_tracer_info.sparks_offset_y = -5 -local stateExecutionerSecondary = State.new(NAMESPACE, "executionerSecondary") +local stateSecondary = ActorState.new("executionerSecondary") -executionerSecondary:clear_callbacks() -executionerSecondary:onActivate(function(actor, skill) - actor:enter_state(stateExecutionerSecondary) +Callback.add(secondary.on_activate, function(actor, skill, slot) + actor:set_state(stateSecondary) end) -executionerSecondary:onStep(function(actor, skill) + +Callback.add(secondary.on_step, function(actor, skill, slot) -- update ion burst's skill icon depending on how many rounds it has local ion_rounds = skill.stock local frame = 1 @@ -320,15 +340,16 @@ executionerSecondary:onStep(function(actor, skill) skill.subimage = frame end) -stateExecutionerSecondary:clear_callbacks() -stateExecutionerSecondary:onEnter(function(actor, data) - actor.image_index = 0 - data.ion_rounds = actor:get_active_skill(Skill.SLOT.secondary).stock + 1 -- compensate for first stock being decremented already + +Callback.add(stateSecondary.on_enter, function(actor, data) + actor.image_index = 0 + data.ion_rounds = actor:get_active_skill(Skill.Slot.SECONDARY).stock + 1 -- compensate for first stock being decremented already data.should_fire = 1 data.is_first_shot = 1 data.sprite = sprite_shoot2a end) -stateExecutionerSecondary:onStep(function(actor, data) + +Callback.add(stateSecondary.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(data.sprite, 0.33) @@ -350,18 +371,18 @@ stateExecutionerSecondary:onStep(function(actor, data) actor:screen_shake(2) if actor:is_authority() then - local damage = actor:skill_get_damage(executionerSecondary) + local damage = actor:skill_get_damage(secondary) local dir = actor:skill_util_facing_direction() - for i=0, actor:buff_stack_count(buffShadowClone) do - local attack_info = actor:fire_bullet(actor.x, actor.y, 1000, dir, damage, nil, sprite_ion_sparks, ion_tracer).attack_info - attack_info:set_stun(1.0) + for i=0, actor:buff_count(buffShadowClone) do + local attack_info = actor:fire_bullet(actor.x, actor.y, 1000, dir, damage, nil, sprite_ion_sparks, tracer).attack_info + attack_info.stun = 1.5 attack_info.climb = i * 8 * 1.35 end end if data.is_first_shot == 0 then - local skill = actor:get_active_skill(Skill.SLOT.secondary) + local skill = actor:get_active_skill(Skill.Slot.SECONDARY) skill.stock = skill.stock - 1 else data.is_first_shot = 0 @@ -370,19 +391,19 @@ stateExecutionerSecondary:onStep(function(actor, data) actor:skill_util_exit_state_on_anim_end() end) -stateExecutionerSecondary:onGetInterruptPriority(function(actor, data) + +Callback.add(stateSecondary.on_get_interrupt_priority, function(actor, data) if actor.image_index > 3 then - return State.ACTOR_STATE_INTERRUPT_PRIORITY.skill_interrupt_period + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD else - return State.ACTOR_STATE_INTERRUPT_PRIORITY.priority_skill + return ActorState.InterruptPriority.PRIORITY_SKILL end end) -local objIonOrb = Object.new(NAMESPACE, "ExecutionerOrb") -objIonOrb.obj_depth = -280 +local objIonOrb = Object.new("ExecutionerOrb") +objIonOrb:set_depth(-280) -objIonOrb:clear_callbacks() -objIonOrb:onCreate(function(self) +Callback.add(objIonOrb.on_create, function(self) self.target = -4 self.counter = 30 self.vspeed = -6 + math.random() * 3 @@ -391,28 +412,27 @@ objIonOrb:onCreate(function(self) self:instance_sync() end) -objIonOrb:onStep(function(self) - if not Instance.exists(self.target) then - self:destroy() - return - end + +Callback.add(objIonOrb.on_step, function(self) + if not Instance.exists(self.target) then self:destroy() return end + if self.counter > 0 then self.speed = math.max(0, self.speed - 0.2) self.counter = self.counter - 1 else local target = self.target - self.direction = gm.point_direction(self.x, self.y, target.x, target.y) + self.direction = Math.direction(self.x, self.y, target.x, target.y) self.speed = math.min(12, self.speed + 0.4) if self:distance_to_object(target) < 8 then - if gm._mod_net_isHost() then - for i=1, self.charges do - GM.actor_skill_add_stock_networked(target, Skill.SLOT.secondary) + if Net.host then + for i = 1, self.charges do + GM.actor_skill_add_stock_networked(target, Skill.Slot.SECONDARY) end end - local flash = GM.instance_create(0, 0, gm.constants.oEfFlash) + local flash = Object.find("EfFlash"):create(0, 0) flash.parent = target flash.rate = 0.08 @@ -420,43 +440,53 @@ objIonOrb:onStep(function(self) end end end) -objIonOrb:onDraw(function(self) + +Callback.add(objIonOrb.on_draw, function(self) local radius = 6 + self.charges * 4 radius = radius + math.sin(Global._current_frame * 0.2) * 0.5 gm.gpu_set_blendmode(1) - gm.draw_set_colour(ION_TRACER_COLOR) + gm.draw_set_colour(Color.from_rgb(110, 129, 195)) gm.draw_set_alpha(0.6) gm.draw_circle(self.x, self.y, radius, false) --gm.draw_circle(self.x, self.y, radius-2, true) gm.draw_set_alpha(1) gm.draw_set_colour(Color.WHITE) - for i=1, self.charges do + + for i = 1, self.charges do gm.draw_circle(self.x, self.y, radius + 3 - (i * 3), true) end + gm.draw_circle(self.x, self.y, radius * 0.33, false) gm.gpu_set_blendmode(0) end) -objIonOrb:onSerialize(function(self, buffer) + +local serializer = function(self, buffer) buffer:write_instance(self.target) buffer:write_float(self.vspeed) buffer:write_byte(self.charges) -end) -objIonOrb:onDeserialize(function(self, buffer) +end + +local deserializer = function(self, buffer) self.target = buffer:read_instance() self.vspeed = buffer:read_float() self.charges = buffer:read_byte() -end) +end -Callback.add(Callback.TYPE.onKillProc, "SSIonCharge", function(victim, killer) - if killer.object_index == gm.constants.oP and killer.class == executioner_id then +Object.add_serializers(objIonOrb, serializer, deserializer) + +Callback.add(Callback.ON_KILL_PROC, function(victim, killer) + if killer.object_index == gm.constants.oP and killer.class == executioner.value then local charges = 1 + if GM.actor_is_elite(victim) then charges = charges * 2 end + if GM.actor_is_boss(victim) then charges = charges * 5 end + local orb = objIonOrb:create(victim.x, victim.y) orb.target = killer orb.charges = charges @@ -464,33 +494,34 @@ Callback.add(Callback.TYPE.onKillProc, "SSIonCharge", function(victim, killer) end) -- Crowd Dispersion -executionerUtility:set_skill_icon(sprite_skills, 6) -executionerUtility.cooldown = 7 * 60 -executionerUtility.is_utility = true -executionerUtility.override_strafe_direction = true -executionerUtility.ignore_aim_direction = true +utility.sprite = sprite_skills +utility.subimage = 6 +utility.cooldown = 7 * 60 +utility.is_utility = true +utility.override_strafe_direction = true +utility.ignore_aim_direction = true -local stateExecutionerUtility = State.new(NAMESPACE, "executionerUtility") -stateExecutionerUtility.activity_flags = State.ACTIVITY_FLAG.allow_rope_cancel +local stateUtility = ActorState.new("executionerUtility") +stateUtility.activity_flags = ActorState.ActivityFlag.ALLOW_ROPE_CANCEL -executionerUtility:clear_callbacks() -executionerUtility:onActivate(function(actor) - actor:enter_state(stateExecutionerUtility) +Callback.add(utility.on_activate, function(actor, skill, slot) + actor:set_state(stateUtility) end) -stateExecutionerUtility:clear_callbacks() -stateExecutionerUtility:onEnter(function(actor, data) + +Callback.add(stateUtility.on_enter, function(actor, data) actor.image_index = 0 data.feared = 0 end) -stateExecutionerUtility:onStep(function(actor, data) + +Callback.add(stateUtility.on_step, function(actor, data) actor.sprite_index = sprite_shoot3 actor.image_speed = 0.25 actor.pHspeed = actor.pHmax * 2.2 * actor.image_xscale - actor:set_immune(8) + actor.invincible = math.max(8, actor.invincible) if math.random() < 0.5 then - ionParticle:create(actor.x - 20 + math.random() * 40, actor.y - 10 + math.random() * 20, 1, Particle.SYSTEM.below) + ionParticle:create(actor.x - 20 + math.random() * 40, actor.y - 10 + math.random() * 20, 1, Particle.System.BELOW) end if data.feared == 0 then @@ -498,14 +529,12 @@ stateExecutionerUtility:onStep(function(actor, data) actor:sound_play(sound_shoot3, 1.0, 1.0) end - local fear = Buff.find("ror", "fear") - local victims = List.new() - actor:collision_rectangle_list(actor.x - 100, actor.y - 48, actor.x + 100, actor.y + 48, gm.constants.pActor, false, true, victims, false) - - for _, victim in ipairs(victims) do + local fear = Buff.find("fear") + + for _, victim in ipairs(actor:get_collisions_rectangle(gm.constants.pActor, actor.x - 100, actor.y - 48, actor.x + 100, actor.y + 48)) do if victim.team ~= actor.team then -- buff application is host-only. - if victim:buff_stack_count(fear) == 0 then + if victim:buff_count(fear) == 0 then victim:buff_apply(fear, 2 * 60) else -- when buffs are re-applied, their duration is extended, which gets networked @@ -515,51 +544,47 @@ stateExecutionerUtility:onStep(function(actor, data) end end - victims:destroy() - actor:skill_util_exit_state_on_anim_end() end) -- Execution -executionerSpecial:set_skill_icon(sprite_skills, 7) -executionerSpecial.cooldown = 8 * 60 -executionerSpecial.damage = 10 -executionerSpecial.require_key_press = true -executionerSpecial.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill +special.sprite = sprite_skills +special.subimage = 7 +special.cooldown = 8 * 60 +special.damage = 10 +special.require_key_press = true +special.required_interrupt_priority = ActorState.InterruptPriority.SKILL +special.upgrade_skill = specialS --- Crowd Execution -local executionerSpecialScepter = Skill.new(NAMESPACE, "executionerVBoosted") -executionerSpecial:set_skill_upgrade(executionerSpecialScepter) +specialS.sprite = sprite_skills +specialS.subimage = 8 +specialS.cooldown = 8 * 60 +specialS.damage = 15 +specialS.require_key_press = true +specialS.required_interrupt_priority = ActorState.InterruptPriority.SKILL -executionerSpecialScepter:set_skill_icon(sprite_skills, 8) -executionerSpecialScepter.cooldown = 8 * 60 -executionerSpecialScepter.damage = 15 -executionerSpecialScepter.require_key_press = true -executionerSpecialScepter.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill +local stateSpecialPre = ActorState.new("executionerSpecialPre") +local stateSpecial = ActorState.new("executionerSpecial") -local stateExecutionerSpecialPre = State.new(NAMESPACE, "executionerSpecialPre") -local stateExecutionerSpecial = State.new(NAMESPACE, "executionerSpecial") - -executionerSpecial:clear_callbacks() -executionerSpecial:onActivate(function(actor) - actor:enter_state(stateExecutionerSpecialPre) +Callback.add(special.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecialPre) end) -executionerSpecialScepter:clear_callbacks() -executionerSpecialScepter:onActivate(function(actor) - actor:enter_state(stateExecutionerSpecialPre) + +Callback.add(specialS.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecialPre) end) -stateExecutionerSpecialPre:clear_callbacks() -stateExecutionerSpecialPre:onEnter(function(actor, data) +Callback.add(stateSpecialPre.on_enter, function(actor, data) actor.image_index = 0 data.previous_frame = 0 data.fired = 0 - data.scepter = actor:item_stack_count(Item.find("ror", "ancientScepter")) + data.scepter = actor:item_count(Item.find("ancientScepter")) end) -stateExecutionerSpecialPre:onStep(function(actor, data) + +Callback.add(stateSpecialPre.on_step, function(actor, data) local drifting = math.abs(actor.pHspeed) > actor.pHmax - local true_speed = math.max(1, 2 - (1 / actor.attack_speed) ) -- see stateExecutionerSpecial:onStep for why this is + local true_speed = math.max(1, 2 - (1 / actor.attack_speed)) -- see stateExecutionerSpecial:onStep for why this is if data.fired == 0 then data.fired = 1 @@ -585,7 +610,7 @@ stateExecutionerSpecialPre:onStep(function(actor, data) local sprite = animation.ground -- when `free` is true, we are in the air - if gm.bool(actor.free) then + if Util.bool(actor.free) then sprite = animation.air else if drifting then @@ -596,22 +621,22 @@ stateExecutionerSpecialPre:onStep(function(actor, data) end actor:actor_animation_set(sprite, 0.25 * true_speed, false) - actor:set_immune(8) + actor.invincible = math.max(8, actor.invincible) if actor.image_index + 0.25 * true_speed >= actor.image_number then - actor:enter_state(stateExecutionerSpecial) + actor:set_state(stateSpecial) end end) -stateExecutionerSpecial:clear_callbacks() -stateExecutionerSpecial:onEnter(function(actor, data) +Callback.add(stateSpecial.on_enter, function(actor, data) data.substate = 0 - data.scepter = actor:item_stack_count(Item.find("ror", "ancientScepter")) + data.scepter = actor:item_count(Item.find("ancientScepter")) data.aoe_height = 0 data.recovery_attempts = 0 actor.activity_free = 1 end) -stateExecutionerSpecial:onStep(function(actor, data) + +Callback.add(stateSpecial.on_step, function(actor, data) actor:skill_util_fix_hspeed() local animation = sprite_shoot4 @@ -638,7 +663,7 @@ stateExecutionerSpecial:onStep(function(actor, data) actor.pVspeed = math.min(actor.pVspeed + deceleration, 0) if math.random() < 0.25 then - particle:create(actor.x - 16 + math.random() * 32, actor.y - math.random() * 32, 1, Particle.SYSTEM.below) + particle:create(actor.x - 16 + math.random() * 32, actor.y - math.random() * 32, 1, Particle.System.BELOW) end if actor.image_index >= 5 then @@ -657,7 +682,7 @@ stateExecutionerSpecial:onStep(function(actor, data) actor.image_index = actor.image_index - 4 end - particle:create(actor.x - 16 + math.random() * 32, actor.y + math.random() * 32, 1, Particle.SYSTEM.below) + particle:create(actor.x - 16 + math.random() * 32, actor.y + math.random() * 32, 1, Particle.System.BELOW) if actor.pVspeed < 0 then -- something launched us up, handle this interruption data.recovery_attempts = data.recovery_attempts + 1 @@ -671,7 +696,7 @@ stateExecutionerSpecial:onStep(function(actor, data) else actor.pVspeed = 30 * true_speed -- water slows exe down and without gravity he's left stuck, so always force to max speed' - if not gm.bool(actor.free) then + if not Util.bool(actor.free) then data.substate = 4 actor.image_index = 11 actor.activity_type = 1 -- return to standard state physics @@ -684,22 +709,23 @@ stateExecutionerSpecial:onStep(function(actor, data) actor:sound_play(gm.constants.wLightning, 1.0, 1.0) end - for i=1, 9 do - particle:create(actor.x - 80 + math.random() * 160, actor.y - 60 + math.random() * 80, 1, Particle.SYSTEM.below) + for i = 1, 9 do + particle:create(actor.x - 80 + math.random() * 160, actor.y - 60 + math.random() * 80, 1, Particle.System.BELOW) end -- usually attacks use is_authority, but in this case we have to always run it host-side -- this is because onAttackHandleEnd only runs on the host, in which we check for the execution variable - if not GM._mod_net_isClient() then + if not Net.client then local ax = actor.x + 32 * actor.image_xscale local ay = actor.y + 24 - data.aoe_height * 0.5 - local damage = actor:skill_get_damage(executionerSpecial) + local damage = actor:skill_get_damage(special) + if data.scepter > 0 then - damage = actor:skill_get_damage(executionerSpecialScepter) + damage = actor:skill_get_damage(specialS) end - for i=0, actor:buff_stack_count(buffShadowClone) do + for i=0, actor:buff_count(buffShadowClone) do local attack_info = actor:fire_explosion(ax, ay, 160, 32 + data.aoe_height, damage, nil, sprite_ion_sparks2).attack_info attack_info.climb = i * 8 * 1.35 attack_info.y = actor.y @@ -707,16 +733,11 @@ stateExecutionerSpecial:onStep(function(actor, data) end if data.scepter > 0 then - local victims = List.new() - actor:collision_rectangle_list(ax - 120, actor.y - 30, ax + 120, actor.y + 30, gm.constants.pActor, false, true, victims, false) - - for _, victim in ipairs(victims) do + for _, victim in ipairs(actor:get_collisions_rectangle(gm.constants.pActor, ax - 120, actor.y - 30, ax + 120, actor.y + 30)) do if victim.team ~= actor.team then victim:buff_apply(buffFear, 2 * 60) end end - - victims:destroy() end end end @@ -724,57 +745,53 @@ stateExecutionerSpecial:onStep(function(actor, data) end if data.substate < 4 then - actor:set_immune(8) + actor.invincible = math.max(8, actor.invincible) end + actor:skill_util_exit_state_on_anim_end() end) -Callback.add(Callback.TYPE.onAttackHandleEnd, "SSExecutionCDR", function(attack_info) +Callback.add(Callback.ON_ATTACK_HANDLE_END, function(attack_info) if attack_info.execution == 1 then local kill_count = attack_info.kill_number GM.actor_skill_reset_cooldowns(attack_info.parent, -60 * kill_count, true, false, true) end end) -local executionerSpecial2 = Skill.new(NAMESPACE, "executionerV2") -executionerSpecial2:set_skill_icon(sprite_skills, 9) -executionerSpecial2.cooldown = 8 * 60 -executionerSpecial2.damage = 2.5 -executionerSpecial2.override_strafe_direction = true -executionerSpecial2.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill - -executioner:add_special(executionerSpecial2) - -local executionerSpecial2Scepter = Skill.new(NAMESPACE, "executionerV2Boosted") -executionerSpecial2Scepter:set_skill_icon(sprite_skills, 10) -executionerSpecial2Scepter.cooldown = 8 * 60 -executionerSpecial2Scepter.damage = 2.5 -executionerSpecial2Scepter.override_strafe_direction = true -executionerSpecial2Scepter.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill +special2.sprite = sprite_skills +special2.subimage = 9 +special2.cooldown = 8 * 60 +special2.damage = 2.5 +special2.override_strafe_direction = true +special2.required_interrupt_priority = ActorState.InterruptPriority.SKILL +special2.upgrade_skill = special2S -executionerSpecial2:set_skill_upgrade(executionerSpecial2Scepter) +special2S.sprite = sprite_skills +special2S.subimage = 10 +special2S.cooldown = 8 * 60 +special2S.damage = 2.5 +special2S.override_strafe_direction = true +special2S.required_interrupt_priority = ActorState.InterruptPriority.SKILL -local stateExecutionerSpecial2 = State.new(NAMESPACE, "executionerSpecial2") +local stateSpecial2 = ActorState.new("executionerSpecial2") -local objExecutionerAxe = Object.new(NAMESPACE, "ExecutionerAxe") -objExecutionerAxe.obj_sprite = sprite_axe_projectile -objExecutionerAxe.obj_depth = -500 +local objExecutionerAxe = Object.new("ExecutionerAxe") +objExecutionerAxe:set_sprite(sprite_axe_projectile) +objExecutionerAxe:set_depth(-500) -local objEfAxeAfterImage = Object.new(NAMESPACE, "EfAxeAfterImage") +local objEfAxeAfterImage = Object.new("EfAxeAfterImage") -executionerSpecial2:clear_callbacks() -executionerSpecial2:onActivate(function(actor) - actor:enter_state(stateExecutionerSpecial2) +Callback.add(special2.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial2) end) -executionerSpecial2Scepter:clear_callbacks() -executionerSpecial2Scepter:onActivate(function(actor) - actor:enter_state(stateExecutionerSpecial2) + +Callback.add(special2S.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial2) end) -stateExecutionerSpecial2:clear_callbacks() -stateExecutionerSpecial2:onEnter(function(actor, data) +Callback.add(stateSpecial2.on_enter, function(actor, data) data.fired = 0 - data.scepter = actor:item_stack_count(Item.find("ror", "ancientScepter")) + data.scepter = actor:item_count(Item.find("ancientScepter")) actor.image_index = 0 actor:sound_play(sound_shoot4b_1, 1, 1) @@ -784,44 +801,45 @@ stateExecutionerSpecial2:onEnter(function(actor, data) actor.sprite_index = sprite_shoot4b end end) -stateExecutionerSpecial2:onStep(function(actor, data) + +Callback.add(stateSpecial2.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(actor.sprite_index, 0.2) if data.fired == 0 and actor.image_index >= 4 then data.fired = 1 - local damage = actor:skill_get_damage(executionerSpecial2) + local damage = actor:skill_get_damage(special2) if data.scepter > 0 then - damage = actor:skill_get_damage(executionerSpecial2Scepter) + damage = actor:skill_get_damage(special2S) end - for i=0, actor:buff_stack_count(buffShadowClone) do - local projectile = objExecutionerAxe:create(actor.x + 30 * actor.image_xscale, actor.y - 30) - projectile.parent = actor - projectile.team = actor.team - projectile.direction = 90 - actor.image_xscale * 90 - projectile.image_xscale = actor.image_xscale - projectile.damage_coeff = damage - projectile:actor_skin_skinnable_set_skin(actor) + for i = 0, actor:buff_count(buffShadowClone) do + local inst = objExecutionerAxe:create(actor.x + 30 * actor.image_xscale, actor.y - 30) + inst.parent = actor + inst.team = actor.team + inst.direction = 90 - actor.image_xscale * 90 + inst.image_xscale = actor.image_xscale + inst.damage_coeff = damage + inst:actor_skin_skinnable_set_skin(actor) - projectile.tX = actor.x + 270 * actor.image_xscale - projectile.tY = actor.y + inst.tX = actor.x + 270 * actor.image_xscale + inst.tY = actor.y if data.scepter > 0 then - projectile.sprite_index = sprite_axe_projectileS - - projectile = objExecutionerAxe:create(actor.x + 30 * actor.image_xscale, actor.y - 30) - projectile.parent = actor - projectile.team = actor.team - projectile.direction = 90 - actor.image_xscale * 90 - projectile.image_xscale = actor.image_xscale - projectile.image_yscale = -1 - projectile.sprite_index = sprite_axe_projectileS - projectile.damage_coeff = damage - - projectile.tX = actor.x + 270 * actor.image_xscale - projectile.tY = actor.y + inst.sprite_index = sprite_axe_projectileS + + inst = objExecutionerAxe:create(actor.x + 30 * actor.image_xscale, actor.y - 30) + inst.parent = actor + inst.team = actor.team + inst.direction = 90 - actor.image_xscale * 90 + inst.image_xscale = actor.image_xscale + inst.image_yscale = -1 + inst.sprite_index = sprite_axe_projectileS + inst.damage_coeff = damage + + inst.tX = actor.x + 270 * actor.image_xscale + inst.tY = actor.y end end @@ -834,8 +852,7 @@ stateExecutionerSpecial2:onStep(function(actor, data) actor:skill_util_exit_state_on_anim_end() end) -objExecutionerAxe:clear_callbacks() -objExecutionerAxe:onCreate(function(self) +Callback.add(objExecutionerAxe.on_create, function(self) self.parent = -4 self.team = 1 self.damage_coeff = 1 @@ -847,26 +864,24 @@ objExecutionerAxe:onCreate(function(self) self.time = 0.0 - self:get_data().already_hit = {} + Instance.get_data(self).already_hit = {} self:actor_skin_skinnable_init() end) -objExecutionerAxe:onStep(function(self) - if not Instance.exists(self.parent) then - self:destroy() - return - end + +Callback.add(objExecutionerAxe.on_step, function(self) + if not Instance.exists(self.parent) then self:destroy() return end local attack_rate = math.ceil(10 / self.parent.attack_speed) if self.hitstop == 0 then local actors = self:get_collisions(gm.constants.pActorCollisionBase) local did_hit = false - local already_hit = self:get_data().already_hit + local already_hit = Instance.get_data(self).already_hit for _, hit in ipairs(actors) do if self:attack_collision_canhit(hit) then did_hit = true - if gm._mod_net_isHost() then + if Net.host then local dmg = self.damage_coeff local proc = not already_hit[hit.id] -- only proc once per hit enemy @@ -875,7 +890,7 @@ objExecutionerAxe:onStep(function(self) attack_info.stun = 0.5 local actor = GM.attack_collision_resolve(hit) - if actor:buff_stack_count(buffFear) > 0 then + if actor:buff_count(buffFear) > 0 then attack_info:set_critical(true) end @@ -901,21 +916,23 @@ objExecutionerAxe:onStep(function(self) end if Global._current_frame % 3 == 0 then - local ef = gm.instance_create(self.x, self.y, gm.constants.oEfTrail) - ef.sprite_index = self.sprite_index - ef.image_index = self.image_index - ef.image_xscale = self.image_xscale - ef.image_blend = ION_TRACER_COLOR - ef.rate = 0.08 - ef.depth = self.depth + 1 + local inst = Object.find("EfTrail"):create(self.x, self.y) + inst.sprite_index = self.sprite_index + inst.image_index = self.image_index + inst.image_xscale = self.image_xscale + inst.image_blend = Color.from_rgb(110, 129, 195) + inst.rate = 0.08 + inst.depth = self.depth + 1 end if math.random() < 0.5 then local particle = ionParticle + if self.sprite_index == sprite_axe_projectileS then particle = ionParticleS end - particle:create(self.x + math.random(-48, 48), self.y + math.random(-48, 48), 1, Particle.SYSTEM.below) + + particle:create(self.x + math.random(-48, 48), self.y + math.random(-48, 48), 1, Particle.System.BELOW) end if self.hitstop > 0 then @@ -963,7 +980,7 @@ objExecutionerAxe:onStep(function(self) self.x = b[1] self.y = b[2] - if self.time > 2 or gm.point_distance(self.parent.x, self.parent.y, self.x, self.y) < 40 then + if self.time > 2 or Math.distance(self.parent.x, self.parent.y, self.x, self.y) < 40 then self:sound_play(gm.constants.wLizardR_Spear_2, 0.8, 1.3) self:destroy() return @@ -977,8 +994,6 @@ objExecutionerAxe:onStep(function(self) end end) -objExecutionerAxe:onDraw(function(self) +Callback.add(objExecutionerAxe.on_draw, function(self) self:actor_skin_skinnable_draw_self() end) - -local executionerLog = Survivor_Log.new(executioner, sprite_log) diff --git a/Survivors/knight.lua b/Survivors/knight.lua.rmt similarity index 100% rename from Survivors/knight.lua rename to Survivors/knight.lua.rmt diff --git a/Survivors/mule.lua b/Survivors/mule.lua index df544623..60b5538a 100644 --- a/Survivors/mule.lua +++ b/Survivors/mule.lua @@ -1,66 +1,67 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Survivors/MULE") local SOUND_PATH = path.combine(PATH, "Sounds/Survivors/MULE") -local sprite_loadout = Resources.sprite_load(NAMESPACE, "MuleSelect", path.combine(SPRITE_PATH, "select.png"), 15, 56, 0) -local sprite_portrait = Resources.sprite_load(NAMESPACE, "MulePortrait", path.combine(SPRITE_PATH, "portrait.png"), 4) -local sprite_portrait_small = Resources.sprite_load(NAMESPACE, "MulePortraitSmall", path.combine(SPRITE_PATH, "portraitSmall.png")) -local sprite_palette = Resources.sprite_load(NAMESPACE, "MulePalette", path.combine(SPRITE_PATH, "palette.png")) -local sprite_skills = Resources.sprite_load(NAMESPACE, "MuleSkills", path.combine(SPRITE_PATH, "skills.png"), 5) -local sprite_credits = Resources.sprite_load(NAMESPACE, "MuleCredits", path.combine(SPRITE_PATH, "credits.png"), 1, 12, 22) -local sprite_log = Resources.sprite_load(NAMESPACE, "MuleLog", path.combine(SPRITE_PATH, "log.png"), 1) -local sprite_wave_mask = Resources.sprite_load(NAMESPACE, "MuleShockwaveMask", path.combine(SPRITE_PATH, "wave_mask.png"), 1, 8, 8) - -local sprite_idle = Resources.sprite_load(NAMESPACE, "MuleIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 20, 26) -local sprite_idle_half = Resources.sprite_load(NAMESPACE, "MuleIdleHalf", path.combine(SPRITE_PATH, "idle_half.png"), 1, 7, 26) -local sprite_walk = Resources.sprite_load(NAMESPACE, "MuleWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 22, 28) -local sprite_walk_back = Resources.sprite_load(NAMESPACE, "MuleWalkBack", path.combine(SPRITE_PATH, "walk_back.png"), 8, 22, 27) -local sprite_walk_half = Resources.sprite_load(NAMESPACE, "MuleWalkHalf", path.combine(SPRITE_PATH, "walk_half.png"), 8, 10, 28) -local sprite_climb = Resources.sprite_load(NAMESPACE, "MuleClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 25, 51) -local sprite_jump = Resources.sprite_load(NAMESPACE, "MuleJump", path.combine(SPRITE_PATH, "jump_start.png"), 1, 22, 32) -local sprite_jump_half = Resources.sprite_load(NAMESPACE, "MuleJumpHalf", path.combine(SPRITE_PATH, "jump_start_half.png"), 1, 9, 28) -local sprite_jump_peak = Resources.sprite_load(NAMESPACE, "MuleJumpPeak", path.combine(SPRITE_PATH, "jump_peak.png"), 1, 22, 32) -local sprite_jump_peak_half = Resources.sprite_load(NAMESPACE, "MuleJumpPeakHalf", path.combine(SPRITE_PATH, "jump_peak_half.png"), 1, 9, 28) -local sprite_fall = Resources.sprite_load(NAMESPACE, "MuleFall", path.combine(SPRITE_PATH, "jump_fall.png"), 1, 22, 32) -local sprite_fall_half = Resources.sprite_load(NAMESPACE, "MuleFallHalf", path.combine(SPRITE_PATH, "jump_fall_half.png"), 1, 9, 28) -local sprite_death = Resources.sprite_load(NAMESPACE, "MuleDeath", path.combine(SPRITE_PATH, "death.png"), 10, 45, 55) -local sprite_decoy = Resources.sprite_load(NAMESPACE, "MuleDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 17, 17) - -local sprite_shoot1a = Resources.sprite_load(NAMESPACE, "MuleShoot1_1", path.combine(SPRITE_PATH, "shoot1_1.png"), 6, 21, 34) -local sprite_shoot1b = Resources.sprite_load(NAMESPACE, "MuleShoot1_2", path.combine(SPRITE_PATH, "shoot1_2.png"), 6, 21, 34) -local sprite_shoot1c = Resources.sprite_load(NAMESPACE, "MuleShoot1_3", path.combine(SPRITE_PATH, "shoot1_3.png"), 9, 39, 36) -local sprite_shoot2 = Resources.sprite_load(NAMESPACE, "MuleShoot2", path.combine(SPRITE_PATH, "shoot2.png"), 7, 30, 33) -local sprite_shoot1charge = Resources.sprite_load(NAMESPACE, "MuleShoot1Charge", path.combine(SPRITE_PATH, "shoot1_charge.png"), 6, 17, 38) -local sprite_shoot3 = Resources.sprite_load(NAMESPACE, "MuleShoot3", path.combine(SPRITE_PATH, "shoot3.png"), 15, 48, 33) -local sprite_shoot4 = Resources.sprite_load(NAMESPACE, "MuleShoot4", path.combine(SPRITE_PATH, "shoot4.png"), 15, 23, 27) -local sprite_shoot4boosted = Resources.sprite_load(NAMESPACE, "MuleShoot4Boosted", path.combine(SPRITE_PATH, "shoot4boosted.png"), 15, 23, 27) -local sprite_shoot4drone = Resources.sprite_load(NAMESPACE, "MuleShoot4Drone", path.combine(SPRITE_PATH, "drone.png"), 5, 9, 12) -local sprite_shoot4heal = Resources.sprite_load(NAMESPACE, "MuleShoot4Heal", path.combine(SPRITE_PATH, "droneheal.png"), 5, 16, 14) -local sprite_shoot4droneboosted = Resources.sprite_load(NAMESPACE, "MuleShoot4DroneBoosted", path.combine(SPRITE_PATH, "droneboosted.png"), 5, 14, 14) -local sprite_shoot4healboosted = Resources.sprite_load(NAMESPACE, "MuleShoot4HealBoosted", path.combine(SPRITE_PATH, "dronehealboosted.png"), 5, 17, 14) -local sprite_drone_idle = Resources.sprite_load(NAMESPACE, "MulePlayerDroneIdle", path.combine(SPRITE_PATH, "playerdrone.png"), 5, 15, 13) -local sprite_drone_shoot = Resources.sprite_load(NAMESPACE, "MulePlayerDroneShoot", path.combine(SPRITE_PATH, "playerdroneshoot.png"), 5, 33, 13) -local sprite_trap_debuff = Resources.sprite_load(NAMESPACE, "MuleTrapDebuff", path.combine(SPRITE_PATH, "trap_debuff.png"), 8, 18, 13) -local sprite_snare_debuff = Resources.sprite_load(NAMESPACE, "MuleSnareDebuff", path.combine(SPRITE_PATH, "snare_debuff.png"), 1, 18, 12) -local sprite_sparks1 = Resources.sprite_load(NAMESPACE, "MuleSparks1", path.combine(SPRITE_PATH, "sparks1.png"), 3, 13, 25) -local sprite_sparks2 = Resources.sprite_load(NAMESPACE, "MuleSparks2", path.combine(SPRITE_PATH, "sparks2.png"), 4, 27, 24) -local sprite_sparks3 = Resources.sprite_load(NAMESPACE, "MuleSparks3", path.combine(SPRITE_PATH, "sparks3.png"), 4, 22, 16) - -local sound_select = Resources.sfx_load(NAMESPACE, "MuleSelect", path.combine(SOUND_PATH, "select.ogg")) -local sound_shoot1a = Resources.sfx_load(NAMESPACE, "MuleShoot1a", path.combine(SOUND_PATH, "skill1a.ogg")) -local sound_shoot1b = Resources.sfx_load(NAMESPACE, "MuleShoot1b", path.combine(SOUND_PATH, "skill1b.ogg")) -local sound_shoot1c = Resources.sfx_load(NAMESPACE, "MuleShoot1c", path.combine(SOUND_PATH, "skill1c.ogg")) -local sound_shoot2a = Resources.sfx_load(NAMESPACE, "MuleShoot2a", path.combine(SOUND_PATH, "skill2a.ogg")) -local sound_shoot2b = Resources.sfx_load(NAMESPACE, "MuleShoot2b", path.combine(SOUND_PATH, "skill2b.ogg")) -local sound_shoot3 = Resources.sfx_load(NAMESPACE, "MuleShoot3", path.combine(SOUND_PATH, "skill3.ogg")) -local sound_shoot4a = Resources.sfx_load(NAMESPACE, "MuleShoot4a", path.combine(SOUND_PATH, "skill4a.ogg")) -local sound_shoot4b = Resources.sfx_load(NAMESPACE, "MuleShoot4b", path.combine(SOUND_PATH, "skill4b.ogg")) -local sound_shoot4c = Resources.sfx_load(NAMESPACE, "MuleShoot4c", path.combine(SOUND_PATH, "skill4c.ogg")) -local sound_shoot4d = Resources.sfx_load(NAMESPACE, "MuleShoot4d", path.combine(SOUND_PATH, "skill4d.ogg")) -local sound_shoot4e = Resources.sfx_load(NAMESPACE, "MuleShoot4e", path.combine(SOUND_PATH, "skill4e.ogg")) -local sound_drone_death = Resources.sfx_load(NAMESPACE, "MuleDroneDeath", path.combine(SOUND_PATH, "drone_death.ogg")) - -local par_fire4 = Particle.find("ror", "Fire4") -local par_debris = Particle.new(NAMESPACE, "Debris") +local sprite_loadout = Sprite.new("MuleSelect", path.combine(SPRITE_PATH, "select.png"), 15, 56, 0) +local sprite_portrait = Sprite.new("MulePortrait", path.combine(SPRITE_PATH, "portrait.png"), 4) +local sprite_portrait_small = Sprite.new("MulePortraitSmall", path.combine(SPRITE_PATH, "portraitSmall.png")) +local sprite_palette = Sprite.new("MulePalette", path.combine(SPRITE_PATH, "palette.png")) +local sprite_skills = Sprite.new("MuleSkills", path.combine(SPRITE_PATH, "skills.png"), 5) +local sprite_credits = Sprite.new("MuleCredits", path.combine(SPRITE_PATH, "credits.png"), 1, 12, 22) +local sprite_log = Sprite.new("MuleLog", path.combine(SPRITE_PATH, "log.png"), 1) +local sprite_wave_mask = Sprite.new("MuleShockwaveMask", path.combine(SPRITE_PATH, "wave_mask.png"), 1, 8, 8) + +local sprite_idle = Sprite.new("MuleIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 20, 26) +local sprite_idle_half = Sprite.new("MuleIdleHalf", path.combine(SPRITE_PATH, "idle_half.png"), 1, 7, 26) +local sprite_walk = Sprite.new("MuleWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 22, 28) +local sprite_walk_back = Sprite.new("MuleWalkBack", path.combine(SPRITE_PATH, "walk_back.png"), 8, 22, 27) +local sprite_walk_half = Sprite.new("MuleWalkHalf", path.combine(SPRITE_PATH, "walk_half.png"), 8, 10, 28) +local sprite_climb = Sprite.new("MuleClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 25, 51) +local sprite_jump = Sprite.new("MuleJump", path.combine(SPRITE_PATH, "jump_start.png"), 1, 22, 32) +local sprite_jump_half = Sprite.new("MuleJumpHalf", path.combine(SPRITE_PATH, "jump_start_half.png"), 1, 9, 28) +local sprite_jump_peak = Sprite.new("MuleJumpPeak", path.combine(SPRITE_PATH, "jump_peak.png"), 1, 22, 32) +local sprite_jump_peak_half = Sprite.new("MuleJumpPeakHalf", path.combine(SPRITE_PATH, "jump_peak_half.png"), 1, 9, 28) +local sprite_fall = Sprite.new("MuleFall", path.combine(SPRITE_PATH, "jump_fall.png"), 1, 22, 32) +local sprite_fall_half = Sprite.new("MuleFallHalf", path.combine(SPRITE_PATH, "jump_fall_half.png"), 1, 9, 28) +local sprite_death = Sprite.new("MuleDeath", path.combine(SPRITE_PATH, "death.png"), 10, 45, 55) +local sprite_decoy = Sprite.new("MuleDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 17, 17) + +local sprite_shoot1a = Sprite.new("MuleShoot1_1", path.combine(SPRITE_PATH, "shoot1_1.png"), 6, 21, 34) +local sprite_shoot1b = Sprite.new("MuleShoot1_2", path.combine(SPRITE_PATH, "shoot1_2.png"), 6, 21, 34) +local sprite_shoot1c = Sprite.new("MuleShoot1_3", path.combine(SPRITE_PATH, "shoot1_3.png"), 9, 39, 36) +local sprite_shoot2 = Sprite.new("MuleShoot2", path.combine(SPRITE_PATH, "shoot2.png"), 7, 30, 33) +local sprite_shoot1charge = Sprite.new("MuleShoot1Charge", path.combine(SPRITE_PATH, "shoot1_charge.png"), 6, 17, 38) +local sprite_shoot3 = Sprite.new("MuleShoot3", path.combine(SPRITE_PATH, "shoot3.png"), 15, 48, 33) +local sprite_shoot4 = Sprite.new("MuleShoot4", path.combine(SPRITE_PATH, "shoot4.png"), 15, 23, 27) +local sprite_shoot4boosted = Sprite.new("MuleShoot4Boosted", path.combine(SPRITE_PATH, "shoot4boosted.png"), 15, 23, 27) +local sprite_shoot4drone = Sprite.new("MuleShoot4Drone", path.combine(SPRITE_PATH, "drone.png"), 5, 9, 12) +local sprite_shoot4heal = Sprite.new("MuleShoot4Heal", path.combine(SPRITE_PATH, "droneheal.png"), 5, 16, 14) +local sprite_shoot4droneboosted = Sprite.new("MuleShoot4DroneBoosted", path.combine(SPRITE_PATH, "droneboosted.png"), 5, 14, 14) +local sprite_shoot4healboosted = Sprite.new("MuleShoot4HealBoosted", path.combine(SPRITE_PATH, "dronehealboosted.png"), 5, 17, 14) +local sprite_drone_idle = Sprite.new("MulePlayerDroneIdle", path.combine(SPRITE_PATH, "playerdrone.png"), 5, 15, 13) +local sprite_drone_shoot = Sprite.new("MulePlayerDroneShoot", path.combine(SPRITE_PATH, "playerdroneshoot.png"), 5, 33, 13) +local sprite_trap_debuff = Sprite.new("MuleTrapDebuff", path.combine(SPRITE_PATH, "trap_debuff.png"), 8, 18, 13) +local sprite_snare_debuff = Sprite.new("MuleSnareDebuff", path.combine(SPRITE_PATH, "snare_debuff.png"), 1, 18, 12) +local sprite_sparks1 = Sprite.new("MuleSparks1", path.combine(SPRITE_PATH, "sparks1.png"), 3, 13, 25) +local sprite_sparks2 = Sprite.new("MuleSparks2", path.combine(SPRITE_PATH, "sparks2.png"), 4, 27, 24) +local sprite_sparks3 = Sprite.new("MuleSparks3", path.combine(SPRITE_PATH, "sparks3.png"), 4, 22, 16) + +local sound_select = Sound.new("MuleSelect", path.combine(SOUND_PATH, "select.ogg")) +local sound_shoot1a = Sound.new("MuleShoot1a", path.combine(SOUND_PATH, "skill1a.ogg")) +local sound_shoot1b = Sound.new("MuleShoot1b", path.combine(SOUND_PATH, "skill1b.ogg")) +local sound_shoot1c = Sound.new("MuleShoot1c", path.combine(SOUND_PATH, "skill1c.ogg")) +local sound_shoot2a = Sound.new("MuleShoot2a", path.combine(SOUND_PATH, "skill2a.ogg")) +local sound_shoot2b = Sound.new("MuleShoot2b", path.combine(SOUND_PATH, "skill2b.ogg")) +local sound_shoot3 = Sound.new("MuleShoot3", path.combine(SOUND_PATH, "skill3.ogg")) +local sound_shoot4a = Sound.new("MuleShoot4a", path.combine(SOUND_PATH, "skill4a.ogg")) +local sound_shoot4b = Sound.new("MuleShoot4b", path.combine(SOUND_PATH, "skill4b.ogg")) +local sound_shoot4c = Sound.new("MuleShoot4c", path.combine(SOUND_PATH, "skill4c.ogg")) +local sound_shoot4d = Sound.new("MuleShoot4d", path.combine(SOUND_PATH, "skill4d.ogg")) +local sound_shoot4e = Sound.new("MuleShoot4e", path.combine(SOUND_PATH, "skill4e.ogg")) +local sound_drone_death = Sound.new("MuleDroneDeath", path.combine(SOUND_PATH, "drone_death.ogg")) + +local par_fire4 = Particle.find("Fire4") + +local par_debris = Particle.new("Debris") par_debris:set_sprite(gm.constants.sEfRubble, false, false, true) par_debris:set_color1(Color.WHITE) par_debris:set_alpha2(1, 0) @@ -71,162 +72,178 @@ par_debris:set_direction(45, 135, 0, 0) par_debris:set_gravity(0.13, 270) par_debris:set_life(20, 100) -local mule = Survivor.new(NAMESPACE, "mule") -local mule_id = mule.value +local mule = Survivor.new("mule") mule:set_stats_base({ - maxhp = 115, + health = 115, damage = 11, - regen = 0.012 + regen = 0.012, }) + mule:set_stats_level({ - maxhp = 37, + health = 37, damage = 3, regen = 0.0018, - armor = 3 + armor = 3, }) -mule:set_animations({ - idle = sprite_idle, - walk = sprite_walk, - jump = sprite_jump, - jump_peak = sprite_jump_peak, - fall = sprite_fall, - climb = sprite_climb, - death = sprite_death, - decoy = sprite_decoy, - drone_idle = sprite_drone_idle, - drone_shoot = sprite_drone_shoot, -}) +local mule_log = SurvivorLog.new_from_survivor(mule) +mule_log.portrait_id = sprite_log +mule_log.sprite_id = sprite_walk +mule_log.sprite_icon_id = sprite_portrait -mule:set_cape_offset(0, -14, 0, -18) -mule:set_primary_color(Color.from_rgb(211,176,122)) +mule.primary_color = Color.from_rgb(211, 176, 122) mule.sprite_loadout = sprite_loadout mule.sprite_portrait = sprite_portrait mule.sprite_portrait_small = sprite_portrait_small -mule.sprite_idle = sprite_idle -mule.sprite_title = sprite_walk + +mule.sprite_idle = sprite_idle -- used by skin systen for idle sprite +mule.sprite_title = sprite_walk -- also used by skin system for walk sprite mule.sprite_credits = sprite_credits + +mule.sprite_palette = sprite_palette +mule.sprite_portrait_palette = sprite_palette +mule.sprite_loadout_palette = sprite_palette + mule.select_sound_id = sound_select -mule:set_palettes(sprite_palette, sprite_palette, sprite_palette) +mule.cape_offset = Array.new({0, -14, 0, -18}) +--[[ --skins -mule:add_skin("Yellow Rose", 1, Resources.sprite_load(NAMESPACE, "MuleSelect2", path.combine(SPRITE_PATH, "select2.png"), 15, 56, 0), -Resources.sprite_load(NAMESPACE, "MulePortrait2", path.combine(SPRITE_PATH, "portrait2.png"), 4), -Resources.sprite_load(NAMESPACE, "MulePortraitSmall2", path.combine(SPRITE_PATH, "portraitSmall2.png"))) +mule:add_skin("Yellow Rose", 1, Sprite.new("MuleSelect2", path.combine(SPRITE_PATH, "select2.png"), 15, 56, 0), +Sprite.new("MulePortrait2", path.combine(SPRITE_PATH, "portrait2.png"), 4), +Sprite.new("MulePortraitSmall2", path.combine(SPRITE_PATH, "portraitSmall2.png"))) -mule:add_skin("Steel Soul", 2, Resources.sprite_load(NAMESPACE, "MuleSelect3", path.combine(SPRITE_PATH, "select3.png"), 15, 56, 0), -Resources.sprite_load(NAMESPACE, "MulePortrait3", path.combine(SPRITE_PATH, "portrait3.png"), 4), -Resources.sprite_load(NAMESPACE, "MulePortraitSmall3", path.combine(SPRITE_PATH, "portraitSmall3.png"))) +mule:add_skin("Steel Soul", 2, Sprite.new("MuleSelect3", path.combine(SPRITE_PATH, "select3.png"), 15, 56, 0), +Sprite.new("MulePortrait3", path.combine(SPRITE_PATH, "portrait3.png"), 4), +Sprite.new("MulePortraitSmall3", path.combine(SPRITE_PATH, "portraitSmall3.png"))) -mule:add_skin("Automated Hunter", 3, Resources.sprite_load(NAMESPACE, "MuleSelect4", path.combine(SPRITE_PATH, "select4.png"), 15, 56, 0), -Resources.sprite_load(NAMESPACE, "MulePortrait4", path.combine(SPRITE_PATH, "portrait4.png"), 4), -Resources.sprite_load(NAMESPACE, "MulePortraitSmall4", path.combine(SPRITE_PATH, "portraitSmall4.png"))) +mule:add_skin("Automated Hunter", 3, Sprite.new("MuleSelect4", path.combine(SPRITE_PATH, "select4.png"), 15, 56, 0), +Sprite.new("MulePortrait4", path.combine(SPRITE_PATH, "portrait4.png"), 4), +Sprite.new("MulePortraitSmall4", path.combine(SPRITE_PATH, "portraitSmall4.png"))) -mule:add_skin("Military Grade", 4, Resources.sprite_load(NAMESPACE, "MuleSelect5", path.combine(SPRITE_PATH, "select5.png"), 15, 56, 0), -Resources.sprite_load(NAMESPACE, "MulePortrait5", path.combine(SPRITE_PATH, "portrait5.png"), 4), -Resources.sprite_load(NAMESPACE, "MulePortraitSmall5", path.combine(SPRITE_PATH, "portraitSmall5.png"))) +mule:add_skin("Military Grade", 4, Sprite.new("MuleSelect5", path.combine(SPRITE_PATH, "select5.png"), 15, 56, 0), +Sprite.new("MulePortrait5", path.combine(SPRITE_PATH, "portrait5.png"), 4), +Sprite.new("MulePortraitSmall5", path.combine(SPRITE_PATH, "portraitSmall5.png"))) +]] -mule:clear_callbacks() -mule:onInit(function(actor) +Callback.add(mule.on_init, function(actor) actor.sprite_idle_half = Array.new({sprite_idle, sprite_idle_half, 0}) actor.sprite_walk_half = Array.new({sprite_walk, sprite_walk_half, 0, sprite_walk_back}) actor.sprite_jump_half = Array.new({sprite_jump, sprite_jump_half, 0}) actor.sprite_jump_peak_half = Array.new({sprite_jump_peak, sprite_jump_peak_half, 0}) actor.sprite_fall_half = Array.new({sprite_fall, sprite_fall_half, 0}) + + actor.sprite_idle = sprite_idle + actor.sprite_walk = sprite_walk + actor.sprite_jump = sprite_jump + actor.sprite_jump_peak = sprite_jump_peak + actor.sprite_fall = sprite_fall + actor.sprite_climb = sprite_climb + actor.sprite_death = sprite_death + actor.sprite_decoy = sprite_decoy + actor.sprite_drone_idle = sprite_drone_idle + actor.sprite_drone_shoot = sprite_drone_shoot actor:survivor_util_init_half_sprites() end) -local snare = Buff.new(NAMESPACE, "muleSnare") +local snare = Buff.new("muleSnare") snare.icon_sprite = sprite_snare_debuff snare.show_icon = true snare.is_debuff = true -snare:clear_callbacks() -snare:onPostStep(function(actor, stack) - actor.pHspeed = 0 - if not GM.actor_is_boss(actor) then - actor.activity = 50 - actor:alarm_set(7, 10) - actor:alarm_set(2, 10) - if actor.sprite_climb and GM.actor_state_is_climb_state(actor.actor_state_current_id) then - actor.sprite_index = actor.sprite_climb - actor.image_index = 0 - elseif actor.sprite_idle then - actor.sprite_index = actor.sprite_idle +Callback.add(Callback.ON_STEP, function() + for _, actor in ipairs(snare:get_holding_actors()) do + if Instance.exists(actor) then + actor.pHspeed = 0 + if not GM.actor_is_boss(actor) then + actor.activity = 50 + actor:alarm_set(7, 10) + actor:alarm_set(2, 10) + if actor.sprite_climb and actor:is_climbing() then + actor.sprite_index = actor.sprite_climb + actor.image_index = 0 + elseif actor.sprite_idle then + actor.sprite_index = actor.sprite_idle + end + end end end end) -snare:onRemove(function(actor, stack) +Callback.add(snare.on_remove, function(actor, stack) if not GM.actor_is_boss(actor) then actor:skill_util_reset_activity_state() end end) -local trap = Buff.new(NAMESPACE, "muleTrap") +local trap = Buff.new("muleTrap") trap.show_icon = false trap.is_debuff = true -trap:clear_callbacks() -trap:onApply(function(actor, stack) - actor:get_data().trapspeed = 0 +Callback.add(trap.on_apply, function(actor, stack) + local data = Instance.get_data(actor) + data.trapspeed = 0 end) -trap:onPostDraw(function(actor, stack) - actor:get_data().trapspeed = actor:get_data().trapspeed + 0.25 +trap.effect_display = EffectDisplay.func(function(actor_unwrapped) + local actor = Instance.wrap(actor_unwrapped) + local data = Instance.get_data(actor) - if actor:get_data().trapspeed > 8 then - actor:get_data().trapspeed = 0 + data.trapspeed = data.trapspeed + 0.25 + + if data.trapspeed > 8 then + data.trapspeed = 0 end - gm.draw_sprite(sprite_trap_debuff, actor:get_data().trapspeed, actor.x, actor.y) + GM.draw_sprite(sprite_trap_debuff.value, data.trapspeed, actor.x, actor.y) - for _, victim in ipairs(actor:get_data().trapped_enemies) do + if not data.trapped_enemies then return end + for _, victim in ipairs(data.trapped_enemies) do if Instance.exists(victim) then - local parent = victim.trap_parent + local parent = Instance.get_data(victim).trap_parent if parent and Instance.exists(parent) then - if not (victim:get_data().trap_offset_a and victim:get_data().trap_offset_b) then - victim:get_data().trap_offset_a = math.random(-8, 8) - victim:get_data().trap_offset_b = victim.x + math.random(-16, 16) - + if not (data.trap_offset_a and data.trap_offset_b) then + data.trap_offset_a = math.random(-8, 8) + data.trap_offset_b = victim.x + math.random(-16, 16) + local yy = 0 while yy < 100 and victim:collision_point(victim.x, victim.y + yy, gm.constants.pBlock, true, false) == -4.0 do yy = yy + 2 end - if yy < 100 and not victim:get_data().trap_offset_c then - victim:get_data().trap_offset_c = victim.y + yy + if yy < 100 and not data.trap_offset_c then + data.trap_offset_c = victim.y + yy else - victim:get_data().trap_offset_c = nil + data.trap_offset_c = nil end end - + gm.draw_set_alpha(0.8) gm.draw_set_colour(Color.from_rgb(205, 205, 205)) - gm.draw_line_width(victim.x, victim.y, parent.x, parent.y + victim:get_data().trap_offset_a, 2) + gm.draw_line_width(victim.x, victim.y, parent.x, parent.y + data.trap_offset_a, 2) - if victim:get_data().trap_offset_c then - gm.draw_line_width(victim.x, victim.y, victim:get_data().trap_offset_b, victim:get_data().trap_offset_c, 2) + if data.trap_offset_c then + gm.draw_line_width(victim.x, victim.y, data.trap_offset_b, data.trap_offset_c, 2) end gm.draw_set_alpha(1) end end end -end) +end, EffectDisplay.DrawPriority.BODY_POST) -trap:onRemove(function(actor, stack) - actor:get_data().trapped_enemies:destroy() - actor:get_data().trapspeed = nil +Callback.add(trap.on_remove, function(actor, stack) + local data = Instance.get_data(actor) + data.trapped_enemies = {} + data.trapspeed = nil end) -local objWave = Object.new(NAMESPACE, "MuleShockwave") -objWave.obj_depth = 1 -objWave:clear_callbacks() +local objWave = Object.new("MuleShockwave") +objWave:set_depth(1) -objWave:onCreate(function(self) - local data = self:get_data() +Callback.add(objWave.on_create, function(self) + local data = Instance.get_data(self) self.mask = sprite_wave_mask self.sprite_index = sprite_wave_mask self.image_alpha = 0 @@ -237,8 +254,8 @@ objWave:onCreate(function(self) data.timer = 140 end) -objWave:onStep(function(self) - local data = self:get_data() +Callback.add(objWave.on_step, function(self) + local data = Instance.get_data(self) local parent = self.parent for s = 0, 32 do @@ -269,7 +286,7 @@ objWave:onStep(function(self) data.timer = data.timer - 1 if data.timer % 7 == 0 then - local sparks = gm.instance_create(self.x, self.y - 6, gm.constants.oEfExplosion) + local sparks = Object.find("EfExplosion"):create(self.x, self.y - 6) sparks.depth = -12 sparks.sprite_index = gm.constants.sMinerShoot2Dust2 sparks.image_xscale = 1 - ((self.direction * 2) / 180) @@ -279,7 +296,7 @@ objWave:onStep(function(self) if explode == true then self:screen_shake(4) - local sparks = gm.instance_create(self.x, self.y + 8, gm.constants.oEfExplosion) + local sparks = Object.find("EfExplosion"):create(self.x, self.y + 8) sparks.sprite_index = gm.constants.sBoss1Shoot1Pillar sparks.image_yscale = 1 self:sound_play(gm.constants.wSmite, 0.5, 1.3 + math.random() * 0.2) @@ -293,35 +310,34 @@ objWave:onStep(function(self) end end) -local primary = mule:get_primary() -local secondary = mule:get_secondary() -local utility = mule:get_utility() -local special = mule:get_special() +-- default skills +local primary = mule:get_skills(Skill.Slot.PRIMARY)[1] +local secondary = mule:get_skills(Skill.Slot.SECONDARY)[1] +local utility = mule:get_skills(Skill.Slot.UTILITY)[1] +local special = mule:get_skills(Skill.Slot.SPECIAL)[1] +local specialS = Skill.new("muleVBoosted") -- INTERFERENCE REMOVAL -primary:set_skill_icon(sprite_skills, 0) - +primary.sprite = sprite_skills +primary.subimage = 0 primary.cooldown = 10 primary.damage = 1.6 primary.require_key_press = false primary.is_primary = true -local statePrimaryCharge = State.new(NAMESPACE, "mulePrimaryCharge") -local statePrimaryPunch = State.new(NAMESPACE, "mulePrimaryPunch") -local statePrimarySlam = State.new(NAMESPACE, "mulePrimarySlam") +local statePrimaryCharge = ActorState.new("mulePrimaryCharge") +local statePrimaryPunch = ActorState.new("mulePrimaryPunch") +local statePrimarySlam = ActorState.new("mulePrimarySlam") -primary:clear_callbacks() -primary:onActivate(function(actor) - actor:enter_state(statePrimaryCharge) +Callback.add(primary.on_activate, function(actor, skill, slot) + actor:set_state(statePrimaryCharge) end) -statePrimaryCharge:clear_callbacks() -statePrimaryCharge:onEnter(function(actor, data) +Callback.add(statePrimaryCharge.on_enter, function(actor, data) actor.image_index2 = 0 data.fired = 0 data.strength = 1.6 data.charging_sound = -1 - data.bullshitfixtimer = 0 if not data.attack_side then data.attack_side = 0 @@ -330,11 +346,10 @@ statePrimaryCharge:onEnter(function(actor, data) actor:skill_util_strafe_init() end) -statePrimaryCharge:onStep(function(actor, data) +Callback.add(statePrimaryCharge.on_step, function(actor, data) actor.sprite_index2 = sprite_shoot1charge actor:skill_util_strafe_update(0.06 * actor.attack_speed, 0.5) actor:skill_util_step_strafe_sprites() - data.bullshitfixtimer = data.bullshitfixtimer + 1 if actor.sprite_index == actor.sprite_walk_half[2] then local walk_offset = 0 @@ -348,55 +363,52 @@ statePrimaryCharge:onStep(function(actor, data) end if data.fired < 1 then - if data.charging_sound == -1 and actor.image_index2 >= 0.5 then data.charging_sound = actor:sound_play(sound_shoot1a, 1, (0.9 + math.random() * 0.2)) end - if actor.image_index2 < 5 and actor.sprite_index2 == sprite_shoot1charge then + if actor.image_index2 < 5 and actor.sprite_index2 == sprite_shoot1charge.value then data.strength = actor:skill_get_damage(primary) + 0.6 * math.min(4, math.floor(actor.image_index2)) end - if actor.image_index2 >= gm.sprite_get_number(actor.sprite_index2) - 1 and actor.sprite_index2 == sprite_shoot1charge then + if actor.image_index2 >= GM.sprite_get_number(actor.sprite_index2) - 1 and actor.sprite_index2 == sprite_shoot1charge.value then data.fired = 1 end - local release = not actor:control("skill1", 0) + local release = not Util.bool(actor.z_skill) if not actor:is_authority() then - release = gm.bool(actor.activity_var2) + release = Util.bool(actor.activity_var2) end - - if release and data.fired < 1 and actor.sprite_index2 == sprite_shoot1charge and data.bullshitfixtimer > 1 then - if gm._mod_net_isOnline() then - if gm._mod_net_isHost() then - gm.server_message_send(0, 43, actor:get_object_index_self(), actor.m_id, 1, gm.sign(actor.image_xscale)) + + if release and data.fired < 1 and actor.sprite_index2 == sprite_shoot1charge.value then + if Net.online then + if Net.host then + gm.server_message_send(0, 43, actor:get_object_index_self(), actor.m_id, 1, Math.sign(actor.image_xscale)) else - gm.client_message_send(43, 1, gm.sign(actor.image_xscale)) + gm.client_message_send(43, 1, Math.sign(actor.image_xscale)) end end - if actor:is_authority() then - GM.actor_set_state_networked(actor, statePrimaryPunch) - end + + actor:set_state(statePrimaryPunch) end else - if actor:is_authority() then - GM.actor_set_state_networked(actor, statePrimarySlam) - end + actor:set_state(statePrimarySlam) end end) -statePrimaryCharge:onExit(function(actor, data) +Callback.add(statePrimaryCharge.on_exit, function(actor, data) actor:skill_util_strafe_exit() - actor:get_data().strength = data.strength + + Instance.get_data(actor).strength = data.strength + if gm.audio_is_playing(data.charging_sound) then gm.audio_stop_sound(data.charging_sound) end end) -statePrimaryPunch:clear_callbacks() -statePrimaryPunch:onEnter(function(actor, data) +Callback.add(statePrimaryPunch.on_enter, function(actor, data) data.fired = 0 - data.strength = actor:get_data().strength + data.strength = Instance.get_data(actor).strength actor.image_index = 0 if not data.attack_side then @@ -409,7 +421,7 @@ statePrimaryPunch:onEnter(function(actor, data) end end) -statePrimaryPunch:onStep(function(actor, data) +Callback.add(statePrimaryPunch.on_step, function(actor, data) actor:skill_util_fix_hspeed() if data.fired == 0 and actor.image_index >= 1 then @@ -420,10 +432,10 @@ statePrimaryPunch:onStep(function(actor, data) data.attack_side = (data.attack_side + 1) % 2 if actor:is_authority() then if not GM.skill_util_update_heaven_cracker(actor, 1.25 * data.strength, actor.image_xscale) then - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, actor:buff_stack_count(buff_shadow_clone) do + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do local attack_info = actor:fire_explosion(actor.x + 30 * actor.image_xscale, actor.y, 80, 60, 1.25 * data.strength, nil, sprite_sparks1).attack_info - attack_info.climb = i * 8 + attack_info.climb = i * 8 * 1.35 attack_info.knockback_direction = actor.image_xscale end end @@ -433,18 +445,17 @@ statePrimaryPunch:onStep(function(actor, data) actor:skill_util_exit_state_on_anim_end() end) -statePrimaryPunch:onExit(function(actor, data) - actor:get_data().strength = nil +Callback.add(statePrimaryPunch.on_exit, function(actor, data) + Instance.get_data(actor).strength = nil end) -statePrimarySlam:clear_callbacks() -statePrimarySlam:onEnter(function(actor, data) +Callback.add(statePrimarySlam.on_enter, function(actor, data) data.fired = 0 actor.image_index = 0 actor:actor_animation_set(sprite_shoot1c, 0.2) end) -statePrimarySlam:onStep(function(actor, data) +Callback.add(statePrimarySlam.on_step, function(actor, data) actor:skill_util_fix_hspeed() if data.fired == 0 and actor.image_index >= 3 then @@ -453,17 +464,17 @@ statePrimarySlam:onStep(function(actor, data) actor:skill_util_nudge_forward(10 * actor.image_xscale) actor:sound_play(sound_shoot1c, 1, (0.9 + math.random() * 0.2)) - if not gm.bool(actor.free) then - par_fire4:create(actor.x + 40 * actor.image_xscale, actor.y + 8, 2, Particle.SYSTEM.middle) - par_debris:create(actor.x + 40 * actor.image_xscale, actor.y + 8, 2, Particle.SYSTEM.middle) + if actor:is_grounded() then + par_fire4:create(actor.x + 40 * actor.image_xscale, actor.y + 8, 2, Particle.System.MIDDLE) + par_debris:create(actor.x + 40 * actor.image_xscale, actor.y + 8, 2, Particle.System.MIDDLE) end if actor:is_authority() then if not GM.skill_util_update_heaven_cracker(actor, 1.25 * data.strength, actor.image_xscale) then - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, actor:buff_stack_count(buff_shadow_clone) do + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do local attack_info = actor:fire_explosion(actor.x + 20 * actor.image_xscale, actor.y, 120, 60, 10, nil, sprite_sparks1).attack_info - attack_info.climb = i * 8 + attack_info.climb = i * 8 * 1.35 attack_info.knockback = attack_info.knockback + 9 attack_info.knockback_direction = actor.image_xscale attack_info.knockup = 6 @@ -471,9 +482,9 @@ statePrimarySlam:onStep(function(actor, data) end end - if not gm.bool(actor.free) then - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, actor:buff_stack_count(buff_shadow_clone) do + if actor:is_grounded() then + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do local wave = objWave:create(actor.x + i * 32 * actor.image_xscale, actor.y) wave.direction = actor:skill_util_facing_direction() wave.depth = wave.depth + i @@ -487,35 +498,34 @@ statePrimarySlam:onStep(function(actor, data) end) -- IMMOBILIZE -secondary:set_skill_icon(sprite_skills, 1) +secondary.sprite = sprite_skills +secondary.subimage = 1 secondary.damage = 1.25 secondary.cooldown = 6 * 60 -local stateSecondary = State.new(NAMESPACE, "muleSecondary") +local stateSecondary = ActorState.new("muleSecondary") -secondary:clear_callbacks() -secondary:onActivate(function(actor) - actor:enter_state(stateSecondary) +Callback.add(secondary.on_activate, function(actor, skill, slot) + actor:set_state(stateSecondary) end) -stateSecondary:clear_callbacks() -stateSecondary:onEnter(function(actor, data) +Callback.add(stateSecondary.on_enter, function(actor, data) actor.image_index = 0 data.fired = 0 end) -stateSecondary:onStep(function(actor, data) +Callback.add(stateSecondary.on_step, function(actor, data) actor:actor_animation_set(sprite_shoot2, 0.2) actor:skill_util_fix_hspeed() if data.fired == 0 and actor.image_index >= 1 then data.fired = 1 actor:sound_play(sound_shoot2a, 1, 0.9 + math.random() * 0.2) - if gm._mod_net_isHost() then - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, actor:buff_stack_count(buff_shadow_clone) do + if Net.host then + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do local attack_info = actor:fire_bullet(actor.x, actor.y - 4, 1300, actor:skill_util_facing_direction(), actor:skill_get_damage(secondary), nil, sprite_sparks3).attack_info - attack_info.climb = i * 8 + attack_info.climb = i * 8 * 1.35 attack_info.mule_immobilize = 1 end end @@ -524,29 +534,33 @@ stateSecondary:onStep(function(actor, data) actor:skill_util_exit_state_on_anim_end() end) -local immobilizeSync = Packet.new() -immobilizeSync:onReceived(function(msg) - local actor = msg:read_instance() +local packet = Packet.new("SyncImmobilize") - if not actor:exists() then return end +local serializer = function(buffer, actor) + buffer:write_instance(actor) +end + +local deserializer = function(buffer) + local actor = buffer:read_instance() + + if not Instance.exists(actor) then return end + local data = Instance.get_data(actor) GM.apply_buff(actor, trap, 3 * 60, 1) - - local victims = List.new() + data.trapped_enemies = {} - actor:get_data().trapped_enemies = List.new() - actor:get_data().trapped_enemies:clear() + local victims = List.new() actor:collision_ellipse_list(actor.x - 280, actor.y - 200, actor.x + 280, actor.y + 200, gm.constants.pActor, false, false, victims, true) local count = 0 for _, victim in ipairs(victims) do if victim.team == actor.team then GM.apply_buff(victim, snare, 3 * 60, 1) - victim.trap_parent = actor - victim:get_data().trap_offset_a = nil - victim:get_data().trap_offset_b = nil - victim:get_data().trap_offset_c = nil - actor:get_data().trapped_enemies:add(victim.value) + Instance.get_data(victim).trap_parent = actor + Instance.get_data(victim).trap_offset_a = nil + Instance.get_data(victim).trap_offset_b = nil + Instance.get_data(victim).trap_offset_c = nil + table.insert(data.trapped_enemies, victim.value) count = count + 1 if count > 4 then break @@ -555,45 +569,38 @@ immobilizeSync:onReceived(function(msg) end victims:destroy() -end) - -local function sync_immobilize(actor) - if not gm._mod_net_isHost() then - log.warning("sync_immobilize called on client!") - return - end - - local msg = immobilizeSync:message_begin() - msg:write_instance(actor) - msg:send_to_all() end -Callback.add(Callback.TYPE.onAttackHit, "muleInflictImmobilize", function(hit_info) +packet:set_serializers(serializer, deserializer) + +Callback.add(Callback.ON_ATTACK_HIT, function(hit_info) if hit_info.attack_info.mule_immobilize == 1 then actor = hit_info.target - if gm._mod_net_isOnline() then - sync_immobilize(actor) + + if Net.online then + packet:send_to_all(actor) end + local data = Instance.get_data(actor) + GM.apply_buff(actor, trap, 3 * 60, 1) actor:sound_play(sound_shoot2b, 1, 0.9 + math.random() * 0.2) + + data.trapped_enemies = {} local victims = List.new() - actor:get_data().trapped_enemies = List.new() - actor:get_data().trapped_enemies:clear() - actor:collision_ellipse_list(actor.x - 280, actor.y - 200, actor.x + 280, actor.y + 200, gm.constants.pActor, false, false, victims, true) local count = 0 for _, victim in ipairs(victims) do if victim.team == actor.team then GM.apply_buff(victim, snare, 3 * 60, 1) - victim.trap_parent = actor - victim:get_data().trap_offset_a = nil - victim:get_data().trap_offset_b = nil - victim:get_data().trap_offset_c = nil - actor:get_data().trapped_enemies:add(victim.value) + Instance.get_data(victim).trap_parent = actor + Instance.get_data(victim).trap_offset_a = nil + Instance.get_data(victim).trap_offset_b = nil + Instance.get_data(victim).trap_offset_c = nil + table.insert(data.trapped_enemies, victim.value) count = count + 1 if count > 4 then break @@ -606,30 +613,28 @@ Callback.add(Callback.TYPE.onAttackHit, "muleInflictImmobilize", function(hit_in end) -- TORQUE CALIBRATION -utility:set_skill_icon(sprite_skills, 2) - +utility.sprite = sprite_skills +utility.subimage = 2 utility.cooldown = 4 * 60 utility.damage = 1.0 utility.is_utility = true utility.override_strafe_direction = true utility.ignore_aim_direction = true -local stateUtility = State.new(NAMESPACE, "muleUtility") -stateUtility.activity_flags = State.ACTIVITY_FLAG.allow_rope_cancel +local stateUtility = ActorState.new("muleUtility") +stateUtility.activity_flags = ActorState.ActivityFlag.ALLOW_ROPE_CANCEL -utility:clear_callbacks() -utility:onActivate(function(actor) - actor:enter_state(stateUtility) +Callback.add(utility.on_activate, function(actor, skill, slot) + actor:set_state(stateUtility) end) -stateUtility:clear_callbacks() -stateUtility:onEnter(function(actor, data) +Callback.add(stateUtility.on_enter, function(actor, data) actor.image_index = 0 data.fired = 0 data.sound = 0 end) -stateUtility:onStep(function(actor, data) +Callback.add(stateUtility.on_step, function(actor, data) actor:actor_animation_set(sprite_shoot3, 0.25, false) if actor.image_index < 4 then @@ -649,14 +654,14 @@ stateUtility:onStep(function(actor, data) data.fired = data.fired + 1 if actor:is_authority() then - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i = 0, actor:buff_stack_count(buff_shadow_clone) do + local buff_shadow_clone = Buff.find("shadowClone") + for i = 0, actor:buff_count(buff_shadow_clone) do local attack_info = actor:fire_explosion(actor.x, actor.y, 160, 100, actor:skill_get_damage(utility), nil, sprite_sparks2).attack_info - attack_info.climb = i * 8 + attack_info.climb = i * 8 * 1.35 attack_info.knockback = 5 attack_info.knockback_direction = actor.image_xscale if data.fired == 4 then - attack_info:set_stun(1.5) + attack_info.stun = 1.5 end end end @@ -666,48 +671,38 @@ stateUtility:onStep(function(actor, data) end) -- FAIL-SAFE ASSISTANCE -special:set_skill_icon(sprite_skills, 3) +special.sprite = sprite_skills +special.subimage = 3 special.cooldown = 20 * 60 +special.upgrade_skill = specialS -local scepter = Skill.new(NAMESPACE, "muleVBoosted") -scepter:set_skill_icon(sprite_skills, 4) -scepter.cooldown = 20 * 60 -special:set_skill_upgrade(scepter) +specialS.sprite = sprite_skills +specialS.subimage = 4 +specialS.cooldown = 20 * 60 -local stateSpecial = State.new(NAMESPACE, "muleSpecial") +local stateSpecial = ActorState.new("muleSpecial") -special:clear_callbacks() -special:onActivate(function(actor) - actor:enter_state(stateSpecial) +Callback.add(special.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial) end) -scepter:clear_callbacks() -scepter:onActivate(function(actor) - actor:enter_state(stateSpecial) +Callback.add(specialS.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial) end) --- this sucks but whatever lmao -local function muleSoundHeal(self) - local chance = math.random() - - if chance > 0.75 then - self:sound_play(sound_shoot4b, 1, 0.9 + math.random() * 0.2) - elseif chance > 0.5 then - self:sound_play(sound_shoot4c, 1, 0.9 + math.random() * 0.2) - elseif chance > 0.25 then - self:sound_play(sound_shoot4d, 1, 0.9 + math.random() * 0.2) - else - self:sound_play(sound_shoot4e, 1, 0.9 + math.random() * 0.2) - end -end +local mule_heal_sounds = { + sound_shoot4b, + sound_shoot4c, + sound_shoot4d, + sound_shoot4e, +} -local objDrone = Object.new(NAMESPACE, "muleDrone") -objDrone.obj_sprite = sprite_drone_idle -objDrone.obj_depth = 2 -objDrone:clear_callbacks() +local objDrone = Object.new("muleDrone") +objDrone:set_sprite(sprite_drone_idle) +objDrone:set_depth(2) -objDrone:onCreate(function(self) - local data = self:get_data() +Callback.add(objDrone.on_create, function(self) + local data = Instance.get_data(self) self.image_speed = 0.3 data.timer = 0 @@ -718,16 +713,16 @@ objDrone:onCreate(function(self) data.healing = 0 end) -objDrone:onStep(function(self) - local data = self:get_data() +Callback.add(objDrone.on_step, function(self) + local data = Instance.get_data(self) local parent = data.parent - if parent.dead then + if parent.dead or not Instance.exists(parent) then data.life = 0 end if parent and Instance.exists(parent) then - local float = math.sin(Global._current_frame * 0.1) * 8 + local float = math.sin(data.life * 0.1) * 8 local xx = self.x + ((parent.ghost_x + (24 * (parent.image_xscale * -1))) - self.x) * 0.1 local yy = self.y + (parent.ghost_y - 48 - float - self.y) * 0.1 if data.timer > 44 and data.regen and parent.hp > 0 then @@ -743,28 +738,30 @@ objDrone:onStep(function(self) if data.timer >= 55 then data.timer = 0 if data.regen then - Particle.find("ror", "Spark"):create(parent.ghost_x + 8 * parent.image_xscale, parent.ghost_y - 6, 3, Particle.SYSTEM.middle) - muleSoundHeal(self) + Particle.find("Spark"):create(parent.ghost_x + 8 * parent.image_xscale, parent.ghost_y - 6, 3, Particle.System.MIDDLE) + + self:sound_play(mule_heal_sounds[math.random(4)], 1, 0.9 + math.random() * 0.2) + if parent.maxhp > parent.hp then - if gm._mod_net_isHost() then + if Net.host then parent:heal(data.regen) end parent:sound_play(gm.constants.wHANDShoot2_2, 0.5, 0.9 + math.random() * 0.2) - local flash = GM.instance_create(parent.x, parent.y, gm.constants.oEfFlash) + local flash = Object.find("EfFlash"):create(parent.x, parent.y) flash.parent = parent flash.image_blend = Color.from_rgb(189, 231, 90) flash.rate = 0.05 flash.image_alpha = 0.5 else - if gm._mod_net_isHost() then - parent:add_barrier(data.regen) + if Net.host then + parent:heal_barrier(data.regen) end parent:sound_play(gm.constants.wHANDShoot2_2, 0.5, 0.9 + math.random() * 0.2) - local flash = GM.instance_create(parent.x, parent.y, gm.constants.oEfFlash) + local flash = Object.find("EfFlash"):create(parent.x, parent.y) flash.parent = parent flash.image_blend = Color.from_rgb(255, 255, 155) flash.rate = 0.05 @@ -776,14 +773,14 @@ objDrone:onStep(function(self) end end - if not (data.healing == 1 and self.sprite_index == data.heal_sprite and self.image_index < 4) then + if not (data.healing == 1 and self.sprite_index == data.heal_sprite.value and self.image_index < 4) then self.sprite_index = data.base_sprite end if data.life > 0 then data.life = data.life - 1 else - local sparks = Object.find("ror", "efSparks"):create(self.x, self.y) + local sparks = Object.find("EfSparks"):create(self.x, self.y) sparks.sprite_index = gm.constants.sEfExplosive self:sound_play(sound_drone_death, 1, 0.9 + math.random() * 0.2) self:screen_shake(2) @@ -791,16 +788,15 @@ objDrone:onStep(function(self) end end) -stateSpecial:clear_callbacks() -stateSpecial:onEnter(function(actor, data) +Callback.add(stateSpecial.on_enter, function(actor, data) actor.image_index = 0 data.fired = 0 data.sound = 0 end) -stateSpecial:onStep(function(actor, data) +Callback.add(stateSpecial.on_step, function(actor, data) actor:skill_util_fix_hspeed() - if actor:item_stack_count(Item.find("ror", "ancientScepter")) > 0 then + if actor:item_count(Item.find("ancientScepter")) > 0 then actor:actor_animation_set(sprite_shoot4boosted, 0.27, false) else actor:actor_animation_set(sprite_shoot4, 0.27, false) @@ -815,20 +811,19 @@ stateSpecial:onStep(function(actor, data) data.fired = 1 local drone = objDrone:create(actor.x - (6 * actor.image_xscale * -1), actor.y - 24) - drone:get_data().parent = actor - drone:get_data().life = 300 - if actor:item_stack_count(Item.find("ror", "ancientScepter")) > 0 then - drone:get_data().regen = actor.maxhp * (0.1) - drone:get_data().base_sprite = sprite_shoot4droneboosted - drone:get_data().heal_sprite = sprite_shoot4healboosted + local drone_data = Instance.get_data(drone) + drone_data.parent = actor + drone_data.life = 300 + if actor:item_count(Item.find("ancientScepter")) > 0 then + drone_data.regen = actor.maxhp * (0.1) + drone_data.base_sprite = sprite_shoot4droneboosted + drone_data.heal_sprite = sprite_shoot4healboosted else - drone:get_data().regen = actor.maxhp * (0.07) - drone:get_data().base_sprite = sprite_shoot4drone - drone:get_data().heal_sprite = sprite_shoot4heal + drone_data.regen = actor.maxhp * (0.07) + drone_data.base_sprite = sprite_shoot4drone + drone_data.heal_sprite = sprite_shoot4heal end end actor:skill_util_exit_state_on_anim_end() -end) - -local muleLog = Survivor_Log.new(mule, sprite_log) \ No newline at end of file +end) \ No newline at end of file diff --git a/Survivors/nemesisCommando.lua b/Survivors/nemesisCommando.lua index a9778308..2e58da20 100644 --- a/Survivors/nemesisCommando.lua +++ b/Survivors/nemesisCommando.lua @@ -1,84 +1,92 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Survivors/NemesisCommando") local SOUND_PATH = path.combine(PATH, "Sounds/Survivors/NemesisCommando") -local sprite_select = Resources.sprite_load(NAMESPACE, "NemCommandoSelect", path.combine(SPRITE_PATH, "select.png"), 34, 28, 0) -local sprite_portrait = Resources.sprite_load(NAMESPACE, "NemCommandoPortrait", path.combine(SPRITE_PATH, "portrait.png"), 3) -local sprite_portrait_small = Resources.sprite_load(NAMESPACE, "NemCommandoPortraitSmall", path.combine(SPRITE_PATH, "portraitTiny.png")) -local sprite_credits = Resources.sprite_load(NAMESPACE, "CreditsSurvivorNemCommando", path.combine(SPRITE_PATH, "credits.png"), 1, 7, 11) -local sprite_palette = Resources.sprite_load(NAMESPACE, "NemCommandoPalette", path.combine(SPRITE_PATH, "palette.png")) - -local sprite_idle = Resources.sprite_load(NAMESPACE, "NemCommandoIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 15, 12) -local sprite_idle2 = Resources.sprite_load(NAMESPACE, "NemCommandoIdle2", path.combine(SPRITE_PATH, "idle2.png"), 1, 15, 12) -local sprite_idle_half = Resources.sprite_load(NAMESPACE, "NemCommandoIdleHalf", path.combine(SPRITE_PATH, "idleHalf.png"), 1, 15, 12) +local sprite_select = Sprite.new("NemCommandoSelect", path.combine(SPRITE_PATH, "select.png"), 34, 28, 0) +local sprite_portrait = Sprite.new("NemCommandoPortrait", path.combine(SPRITE_PATH, "portrait.png"), 3) +local sprite_portrait_small = Sprite.new("NemCommandoPortraitSmall", path.combine(SPRITE_PATH, "portraitTiny.png")) +local sprite_credits = Sprite.new("CreditsSurvivorNemCommando", path.combine(SPRITE_PATH, "credits.png"), 1, 7, 11) +local sprite_palette = Sprite.new("NemCommandoPalette", path.combine(SPRITE_PATH, "palette.png")) + +local sprite_idle = Sprite.new("NemCommandoIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 15, 12) +local sprite_idle2 = Sprite.new("NemCommandoIdle2", path.combine(SPRITE_PATH, "idle2.png"), 1, 15, 12) +local sprite_idle_half = Sprite.new("NemCommandoIdleHalf", path.combine(SPRITE_PATH, "idleHalf.png"), 1, 15, 12) +local sprite_walk = Sprite.new("NemCommandoWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 17, 13) +local sprite_walk2 = Sprite.new("NemCommandoWalk2", path.combine(SPRITE_PATH, "walk2.png"), 8, 15, 18) +local sprite_walk_half = Sprite.new("NemCommandoWalkHalf", path.combine(SPRITE_PATH, "walkHalf.png"), 8, 17, 13) +local sprite_walk_back = Sprite.new("NemCommandoWalkBack", path.combine(SPRITE_PATH, "walkBack.png"), 8, 20, 25) +local sprite_walk_back2 = Sprite.new("NemCommandoWalkBack2", path.combine(SPRITE_PATH, "walkBack2.png"), 8, 20, 25) +local sprite_jump = Sprite.new("NemCommandoJump", path.combine(SPRITE_PATH, "jump.png"), 1, 18, 17) +local sprite_jump2 = Sprite.new("NemCommandoJump2", path.combine(SPRITE_PATH, "jump2.png"), 1, 18, 18) +local sprite_jump_half = Sprite.new("NemCommandoJumpHalf", path.combine(SPRITE_PATH, "jumpHalf.png"), 1, 18, 16) +local sprite_jump_peak = Sprite.new("NemCommandoJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 18, 17) +local sprite_jump_peak2 = Sprite.new("NemCommandoJumpPeak2", path.combine(SPRITE_PATH, "jumpPeak2.png"), 1, 18, 18) +local sprite_jump_peak_half = Sprite.new("NemCommandoJumpPeakHalf", path.combine(SPRITE_PATH, "jumpPeakHalf.png"), 1, 18, 16) +local sprite_fall = Sprite.new("NemCommandoFall", path.combine(SPRITE_PATH, "fall.png"), 1, 18, 17) +local sprite_fall2 = Sprite.new("NemCommandoFall2", path.combine(SPRITE_PATH, "fall2.png"), 1, 18, 18) +local sprite_fall_half = Sprite.new("NemCommandoFallHalf", path.combine(SPRITE_PATH, "fallHalf.png"), 1, 18, 16) +local sprite_climb = Sprite.new("NemCommandoClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 20, 18) +local sprite_death = Sprite.new("NemCommandoDeath", path.combine(SPRITE_PATH, "death.png"), 13, 31, 9) + +local sprite_shoot1_1 = Sprite.new("NemCommandoShoot1_1", path.combine(SPRITE_PATH, "shoot1_1.png"), 6, 28, 62) +local sprite_shoot1_2 = Sprite.new("NemCommandoShoot1_2", path.combine(SPRITE_PATH, "shoot1_2.png"), 6, 28, 62) +local sprite_shoot2_half = Sprite.new("NemCommandoShoot2Half", path.combine(SPRITE_PATH, "shoot2Half.png"), 5, 15, 26) +local sprite_shoot2b = Sprite.new("NemCommandoShoot2B", path.combine(SPRITE_PATH, "shoot2b.png"), 10, 36, 39) +local sprite_shoot3 = Sprite.new("NemCommandoShoot3", path.combine(SPRITE_PATH, "shoot3.png"), 6, 14, 13) +local sprite_shoot4_1 = Sprite.new("NemCommandoShoot4_1", path.combine(SPRITE_PATH, "shoot4_1.png"), 10, 23, 30) +local sprite_shoot4_2a = Sprite.new("NemCommandoShoot4_2A", path.combine(SPRITE_PATH, "shoot4_2a.png"), 6, 17, 24) +local sprite_shoot4_2b = Sprite.new("NemCommandoShoot4_2B", path.combine(SPRITE_PATH, "shoot4_2b.png"), 6, 19, 17) +local sprite_shoot4b = Sprite.new("NemCommandoShoot4B", path.combine(SPRITE_PATH, "shoot4b.png"), 9, 47, 33) +local sprite_shoot4b_a = Sprite.new("NemCommandoShoot4B_A", path.combine(SPRITE_PATH, "shoot4b_a.png"), 8, 34, 27) + +local sprite_decoy = Sprite.new("NemCommandoDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 22, 18) +local sprite_drone_idle = Sprite.new("DronePlayerNemCommandoIdle", path.combine(SPRITE_PATH, "drone_idle.png"), 5, 15, 13) +local sprite_drone_shoot = Sprite.new("DronePlayerNemCommandoShoot", path.combine(SPRITE_PATH, "drone_shoot.png"), 5, 33, 13) + +local sprite_skills = Sprite.new("NemCommandoSkills", path.combine(SPRITE_PATH, "skills.png"), 8, 0, 0) +local sprite_gash = Sprite.new("NemCommandoGash", path.combine(SPRITE_PATH, "gash.png"), 4, 25, 25) +local sprite_dust = Sprite.new("NemCommandoDust", path.combine(SPRITE_PATH, "dust.png"), 3, 21, 12) +local sprite_grenade = Sprite.new("NemCommandoGrenade", path.combine(SPRITE_PATH, "grenade.png"), 8, 9, 9) + +gm.sprite_set_bbox_mode(sprite_grenade.value, 2) -- manual +gm.sprite_set_bbox(sprite_grenade.value, 1, 1, 16, 16) -- reduce hitbox by 1px on each side + +local sprite_explosion = Sprite.new("NemCommandoExplosion", path.combine(SPRITE_PATH, "grenade_explosion.png"), 5, 117, 102) +local sprite_rocket = Sprite.new("NemCommandoRocket", path.combine(SPRITE_PATH, "rocket.png"), 3, 33, 10) +local sprite_rocket_mask = Sprite.new("NemCommandoRocketMask", path.combine(SPRITE_PATH, "rocketMask.png"), 1, 0, 2) + +local sprite_portal = Sprite.new("NemCommandoPortal", path.combine(SPRITE_PATH, "portal.png"), 25, 78, 100) +local sprite_portal_inside = Sprite.new("NemCommandoPortalInside", path.combine(SPRITE_PATH, "portal_inside.png"), 25, 78, 100) +local sound_portal = Sound.new("NemCommandoPortal", path.combine(SOUND_PATH, "portal.ogg")) + +local sprite_log = Sprite.new("NemCommandoLog", path.combine(SPRITE_PATH, "log.png")) + -- walk sprites have a sprite speed of 0.8, the slower animation looks better -local sprite_walk = Resources.sprite_load(NAMESPACE, "NemCommandoWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 17, 13, 0.8) -local sprite_walk2 = Resources.sprite_load(NAMESPACE, "NemCommandoWalk2", path.combine(SPRITE_PATH, "walk2.png"), 8, 15, 18, 0.8) -local sprite_walk_half = Resources.sprite_load(NAMESPACE, "NemCommandoWalkHalf", path.combine(SPRITE_PATH, "walkHalf.png"), 8, 17, 13, 0.8) -local sprite_walk_back = Resources.sprite_load(NAMESPACE, "NemCommandoWalkBack", path.combine(SPRITE_PATH, "walkBack.png"), 8, 20, 25, 0.8) -local sprite_walk_back2 = Resources.sprite_load(NAMESPACE, "NemCommandoWalkBack2", path.combine(SPRITE_PATH, "walkBack2.png"), 8, 20, 25, 0.8) -local sprite_jump = Resources.sprite_load(NAMESPACE, "NemCommandoJump", path.combine(SPRITE_PATH, "jump.png"), 1, 18, 17) -local sprite_jump2 = Resources.sprite_load(NAMESPACE, "NemCommandoJump2", path.combine(SPRITE_PATH, "jump2.png"), 1, 18, 18) -local sprite_jump_half = Resources.sprite_load(NAMESPACE, "NemCommandoJumpHalf", path.combine(SPRITE_PATH, "jumpHalf.png"), 1, 18, 16) -local sprite_jump_peak = Resources.sprite_load(NAMESPACE, "NemCommandoJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 18, 17) -local sprite_jump_peak2 = Resources.sprite_load(NAMESPACE, "NemCommandoJumpPeak2", path.combine(SPRITE_PATH, "jumpPeak2.png"), 1, 18, 18) -local sprite_jump_peak_half = Resources.sprite_load(NAMESPACE, "NemCommandoJumpPeakHalf", path.combine(SPRITE_PATH, "jumpPeakHalf.png"), 1, 18, 16) -local sprite_fall = Resources.sprite_load(NAMESPACE, "NemCommandoFall", path.combine(SPRITE_PATH, "fall.png"), 1, 18, 17) -local sprite_fall2 = Resources.sprite_load(NAMESPACE, "NemCommandoFall2", path.combine(SPRITE_PATH, "fall2.png"), 1, 18, 18) -local sprite_fall_half = Resources.sprite_load(NAMESPACE, "NemCommandoFallHalf", path.combine(SPRITE_PATH, "fallHalf.png"), 1, 18, 16) -local sprite_climb = Resources.sprite_load(NAMESPACE, "NemCommandoClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 20, 18) -local sprite_death = Resources.sprite_load(NAMESPACE, "NemCommandoDeath", path.combine(SPRITE_PATH, "death.png"), 13, 31, 9) - -local sprite_shoot1_1 = Resources.sprite_load(NAMESPACE, "NemCommandoShoot1_1", path.combine(SPRITE_PATH, "shoot1_1.png"), 6, 28, 62) -local sprite_shoot1_2 = Resources.sprite_load(NAMESPACE, "NemCommandoShoot1_2", path.combine(SPRITE_PATH, "shoot1_2.png"), 6, 28, 62) ---local sprite_shoot2 = Resources.sprite_load(NAMESPACE, "NemCommandoShoot2", path.combine(SPRITE_PATH, "shoot2.png"), 5, 15, 26) -local sprite_shoot2_half = Resources.sprite_load(NAMESPACE, "NemCommandoShoot2Half", path.combine(SPRITE_PATH, "shoot2Half.png"), 5, 15, 26) -local sprite_shoot2b = Resources.sprite_load(NAMESPACE, "NemCommandoShoot2B", path.combine(SPRITE_PATH, "shoot2b.png"), 10, 36, 39) -local sprite_shoot3 = Resources.sprite_load(NAMESPACE, "NemCommandoShoot3", path.combine(SPRITE_PATH, "shoot3.png"), 6, 14, 13) -local sprite_shoot4_1 = Resources.sprite_load(NAMESPACE, "NemCommandoShoot4_1", path.combine(SPRITE_PATH, "shoot4_1.png"), 10, 23, 30) -local sprite_shoot4_2a = Resources.sprite_load(NAMESPACE, "NemCommandoShoot4_2A", path.combine(SPRITE_PATH, "shoot4_2a.png"), 6, 17, 24) -local sprite_shoot4_2b = Resources.sprite_load(NAMESPACE, "NemCommandoShoot4_2B", path.combine(SPRITE_PATH, "shoot4_2b.png"), 6, 19, 17) -local sprite_shoot4b = Resources.sprite_load(NAMESPACE, "NemCommandoShoot4B", path.combine(SPRITE_PATH, "shoot4b.png"), 9, 47, 33) -local sprite_shoot4b_a = Resources.sprite_load(NAMESPACE, "NemCommandoShoot4B_A", path.combine(SPRITE_PATH, "shoot4b_a.png"), 8, 34, 27) - -local sprite_decoy = Resources.sprite_load(NAMESPACE, "NemCommandoDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 22, 18) -local sprite_drone_idle = Resources.sprite_load(NAMESPACE, "DronePlayerNemCommandoIdle", path.combine(SPRITE_PATH, "drone_idle.png"), 5, 15, 13) -local sprite_drone_shoot = Resources.sprite_load(NAMESPACE, "DronePlayerNemCommandoShoot", path.combine(SPRITE_PATH, "drone_shoot.png"), 5, 33, 13) - -local sprite_skills = Resources.sprite_load(NAMESPACE, "NemCommandoSkills", path.combine(SPRITE_PATH, "skills.png"), 8, 0, 0) -local sprite_gash = Resources.sprite_load(NAMESPACE, "NemCommandoGash", path.combine(SPRITE_PATH, "gash.png"), 4, 25, 25) -local sprite_dust = Resources.sprite_load(NAMESPACE, "NemCommandoDust", path.combine(SPRITE_PATH, "dust.png"), 3, 21, 12) -local sprite_grenade = Resources.sprite_load(NAMESPACE, "NemCommandoGrenade", path.combine(SPRITE_PATH, "grenade.png"), 8, 9, 9) -gm.sprite_set_bbox_mode(sprite_grenade, 2) -- manual -gm.sprite_set_bbox(sprite_grenade, 1, 1, 16, 16) -- reduce hitbox by 1px on each side -local sprite_explosion = Resources.sprite_load(NAMESPACE, "NemCommandoExplosion", path.combine(SPRITE_PATH, "grenade_explosion.png"), 5, 117, 102) -local sprite_rocket = Resources.sprite_load(NAMESPACE, "NemCommandoRocket", path.combine(SPRITE_PATH, "rocket.png"), 3, 33, 10) -local sprite_rocket_mask = Resources.sprite_load(NAMESPACE, "NemCommandoRocketMask", path.combine(SPRITE_PATH, "rocketMask.png"), 1, 0, 2) - -local sprite_log = Resources.sprite_load(NAMESPACE, "NemCommandoLog", path.combine(SPRITE_PATH, "log.png")) - -local sound_select = Resources.sfx_load(NAMESPACE, "UISurvivorsNemCommando", path.combine(SOUND_PATH, "select.ogg")) -local sound_slash = Resources.sfx_load(NAMESPACE, "NemCommandoGash", path.combine(SOUND_PATH, "shoot2b.ogg")) -local sound_grenade_prime = Resources.sfx_load(NAMESPACE, "NemCommandoGrenadePrime", path.combine(SOUND_PATH, "grenade_prime.ogg")) -local sound_grenade_throw = Resources.sfx_load(NAMESPACE, "NemCommandoGrenadeThrow", path.combine(SOUND_PATH, "grenade_throw.ogg")) -local sound_grenade_bounce = Resources.sfx_load(NAMESPACE, "NemCommandoGrenadeBounce", path.combine(SOUND_PATH, "grenade_bounce.ogg")) -local sound_rocket_fire = Resources.sfx_load(NAMESPACE, "NemCommandoRocketFire", path.combine(SOUND_PATH, "rocket_fire.ogg")) +sprite_walk:set_speed(0.8) +sprite_walk2:set_speed(0.8) +sprite_walk_half:set_speed(0.8) +sprite_walk_back:set_speed(0.8) +sprite_walk_back2:set_speed(0.8) + +local sound_select = Sound.new("UISurvivorsNemCommando", path.combine(SOUND_PATH, "select.ogg")) +local sound_slash = Sound.new("NemCommandoGash", path.combine(SOUND_PATH, "shoot2b.ogg")) +local sound_grenade_prime = Sound.new("NemCommandoGrenadePrime", path.combine(SOUND_PATH, "grenade_prime.ogg")) +local sound_grenade_throw = Sound.new("NemCommandoGrenadeThrow", path.combine(SOUND_PATH, "grenade_throw.ogg")) +local sound_grenade_bounce = Sound.new("NemCommandoGrenadeBounce", path.combine(SOUND_PATH, "grenade_bounce.ogg")) +local sound_rocket_fire = Sound.new("NemCommandoRocketFire", path.combine(SOUND_PATH, "rocket_fire.ogg")) -- secondary skill tracer -local particleTracer = Particle.find("ror", "WispGTracer") +local particleTracer = Particle.find("WispGTracer") + -- grenade fx -local particleRubble2 = Particle.find("ror", "Rubble2") -local particleTrail = Particle.find("ror", "PixelDust") --- rocket fx -local particleRocketTrail = Particle.find("ror", "MissileTrailSuper") -local particleRubble1 = Particle.find("ror", "Rubble1") -local particleSpark = Particle.find("ror", "Spark") +local particleRubble2 = Particle.find("Rubble2") +local particleTrail = Particle.find("PixelDust") -local nemCommando = Survivor.new(NAMESPACE, "nemesisCommando") -local nemCommando_id = nemesisCommando +-- rocket fx +local particleRocketTrail = Particle.find("MissileTrailSuper") +local particleRubble1 = Particle.find("Rubble1") +local particleSpark = Particle.find("Spark") -nemCommando.sprite_portrait = sprite_portrait -nemCommando.sprite_portrait_small = sprite_portrait_small -nemCommando.select_sound_id = sound_select +local nemmando = Survivor.new("nemesisCommando") local ATTACK_TAG_APPLY_WOUND = 1 local ATTACK_TAG_EXTEND_WOUND = 2 @@ -98,57 +106,59 @@ local ROCKET_SPEED_START = 0 local ROCKET_SPEED_MAX = 24 local ROCKET_ACCELERATION = 0.35 -nemCommando:set_stats_base({ - maxhp = 115, +nemmando:set_stats_base({ + health = 115, damage = 11, regen = 0.011, }) -nemCommando:set_stats_level({ - maxhp = 36, + +nemmando:set_stats_level({ + health = 36, damage = 3, regen = 0.001, armor = 2, }) -nemCommando:set_animations({ - idle = sprite_idle, - walk = sprite_walk, - jump = sprite_jump, - jump_peak = sprite_jump_peak, - fall = sprite_fall, - climb = sprite_climb, - death = sprite_death, - decoy = sprite_decoy, - - drone_idle = sprite_drone_idle, - drone_shoot = sprite_drone_shoot, -}) +local nemmando_log = SurvivorLog.new_from_survivor(nemmando) +nemmando_log.portrait_id = sprite_log +nemmando_log.sprite_id = sprite_walk +nemmando_log.sprite_icon_id = sprite_portrait + +nemmando.primary_color = Color.from_rgb(250, 40, 40) -nemCommando:set_cape_offset(0, -8, 0, -8) -nemCommando:set_primary_color(Color.from_rgb(250, 40, 40)) +nemmando.sprite_portrait = sprite_portrait +nemmando.sprite_portrait_small = sprite_portrait_small +nemmando.sprite_loadout = sprite_select -nemCommando.sprite_loadout = sprite_select -nemCommando.sprite_idle = sprite_idle -nemCommando.sprite_title = sprite_walk -nemCommando.sprite_credits = sprite_credits -nemCommando:set_palettes(sprite_palette, sprite_palette, sprite_palette) +nemmando.sprite_idle = sprite_idle +nemmando.sprite_title = sprite_walk +nemmando.sprite_credits = sprite_credits +nemmando.sprite_palette = sprite_palette +nemmando.sprite_portrait_palette = sprite_palette +nemmando.sprite_loadout_palette = sprite_palette + +nemmando.select_sound_id = sound_select +nemmando.cape_offset = Array.new({0, -8, 0, -5}) + +--[[ --skins -nemCommando:add_skin("Mk. II", 1, Resources.sprite_load(NAMESPACE, "NemCommandoSelect2", path.combine(SPRITE_PATH, "select2.png"), 34, 28, 0), -Resources.sprite_load(NAMESPACE, "NemCommandoPortrait2", path.combine(SPRITE_PATH, "portrait2.png"), 3), -Resources.sprite_load(NAMESPACE, "NemCommandoPortraitSmall2", path.combine(SPRITE_PATH, "portraitTiny2.png"))) +nemmando:add_skin("Mk. II", 1, Sprite.new("NemCommandoSelect2", path.combine(SPRITE_PATH, "select2.png"), 34, 28, 0), +Sprite.new("NemCommandoPortrait2", path.combine(SPRITE_PATH, "portrait2.png"), 3), +Sprite.new("NemCommandoPortraitSmall2", path.combine(SPRITE_PATH, "portraitTiny2.png"))) -nemCommando:add_skin("Ice Blade", 2, Resources.sprite_load(NAMESPACE, "NemCommandoSelect3", path.combine(SPRITE_PATH, "select3.png"), 34, 28, 0), -Resources.sprite_load(NAMESPACE, "NemCommandoPortrait3", path.combine(SPRITE_PATH, "portrait3.png"), 3), -Resources.sprite_load(NAMESPACE, "NemCommandoPortraitSmall3", path.combine(SPRITE_PATH, "portraitTiny3.png"))) +nemmando:add_skin("Ice Blade", 2, Sprite.new("NemCommandoSelect3", path.combine(SPRITE_PATH, "select3.png"), 34, 28, 0), +Sprite.new("NemCommandoPortrait3", path.combine(SPRITE_PATH, "portrait3.png"), 3), +Sprite.new("NemCommandoPortraitSmall3", path.combine(SPRITE_PATH, "portraitTiny3.png"))) -nemCommando:add_skin("Nature's Gift", 3, Resources.sprite_load(NAMESPACE, "NemCommandoSelect4", path.combine(SPRITE_PATH, "select4.png"), 34, 28, 0), -Resources.sprite_load(NAMESPACE, "NemCommandoPortrait4", path.combine(SPRITE_PATH, "portrait4.png"), 3), -Resources.sprite_load(NAMESPACE, "NemCommandoPortraitSmall4", path.combine(SPRITE_PATH, "portraitTiny4.png"))) +nemmando:add_skin("Nature's Gift", 3, Sprite.new("NemCommandoSelect4", path.combine(SPRITE_PATH, "select4.png"), 34, 28, 0), +Sprite.new("NemCommandoPortrait4", path.combine(SPRITE_PATH, "portrait4.png"), 3), +Sprite.new("NemCommandoPortraitSmall4", path.combine(SPRITE_PATH, "portraitTiny4.png"))) -nemCommando:add_skin("Callback", 4, Resources.sprite_load(NAMESPACE, "NemCommandoSelect5", path.combine(SPRITE_PATH, "select5.png"), 34, 28, 0), -Resources.sprite_load(NAMESPACE, "NemCommandoPortrait5", path.combine(SPRITE_PATH, "portrait5.png"), 3), -Resources.sprite_load(NAMESPACE, "NemCommandoPortraitSmall5", path.combine(SPRITE_PATH, "portraitTiny5.png"))) +nemmando:add_skin("Callback", 4, Sprite.new("NemCommandoSelect5", path.combine(SPRITE_PATH, "select5.png"), 34, 28, 0), +Sprite.new("NemCommandoPortrait5", path.combine(SPRITE_PATH, "portrait5.png"), 3), +Sprite.new("NemCommandoPortraitSmall5", path.combine(SPRITE_PATH, "portraitTiny5.png"))) +]]-- -- utility function for updating his basic sprites depending on if his last skill was the gun local function nemmando_update_sprites(actor, has_gun) @@ -189,42 +199,68 @@ local function nemmando_update_strafe(actor) end end -nemCommando:clear_callbacks() -nemCommando:onInit(function(actor) +Callback.add(nemmando.on_init, function(actor) actor.sprite_idle_half = Array.new({sprite_idle, sprite_idle_half, 0}) actor.sprite_walk_half = Array.new({sprite_walk, sprite_walk_half, 0, sprite_walk_back}) actor.sprite_jump_half = Array.new({sprite_jump, sprite_jump_half, 0}) actor.sprite_jump_peak_half = Array.new({sprite_jump_peak, sprite_jump_peak_half, 0}) actor.sprite_fall_half = Array.new({sprite_fall, sprite_fall_half, 0}) + + actor.sprite_idle = sprite_idle + actor.sprite_walk = sprite_walk + actor.sprite_jump = sprite_jump + actor.sprite_jump_peak = sprite_jump_peak + actor.sprite_fall = sprite_fall + actor.sprite_climb = sprite_climb + actor.sprite_death = sprite_death + actor.sprite_decoy = sprite_decoy + actor.sprite_drone_idle = sprite_drone_idle + actor.sprite_drone_shoot = sprite_drone_shoot + + actor.is_nemesis = true -- toggles the portal spawning animation + actor.sprite_portal = sprite_portal + actor.sprite_portal_inside = sprite_portal_inside + actor.sound_portal = sound_portal actor:survivor_util_init_half_sprites() end) -local nemCommandoPrimary = nemCommando:get_primary() -local nemCommandoSecondary = nemCommando:get_secondary() -local nemCommandoUtility = nemCommando:get_utility() -local nemCommandoSpecial = nemCommando:get_special() +-- default skills +local primary = nemmando:get_skills(Skill.Slot.PRIMARY)[1] +local secondary = nemmando:get_skills(Skill.Slot.SECONDARY)[1] +local utility = nemmando:get_skills(Skill.Slot.UTILITY)[1] +local special = nemmando:get_skills(Skill.Slot.SPECIAL)[1] +local specialS = Skill.new("nemesisCommandoVBoosted") + +-- alt secondary +local secondary2 = Skill.new("nemesisCommandoX2") +nemmando:add_skill(Skill.Slot.SECONDARY, secondary2) + +-- alt special +local special2 = Skill.new("nemesisCommandoV2") +local special2S = Skill.new("nemesisCommandoV2Boosted") +nemmando:add_skill(Skill.Slot.SPECIAL, special2) -- Blade of Cessation -nemCommandoPrimary:set_skill_icon(sprite_skills, 0) -nemCommandoPrimary.cooldown = 10 -nemCommandoPrimary.damage = 1.2 -nemCommandoPrimary.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.any +primary.sprite = sprite_skills +primary.subimage = 0 +primary.cooldown = 10 +primary.damage = 1.2 +primary.required_interrupt_priority = ActorState.InterruptPriority.ANY -local stateNemCommandoPrimary = State.new(NAMESPACE, "nemCommandoPrimary") +local statePrimary = ActorState.new("nemCommandoPrimary") -nemCommandoPrimary:clear_callbacks() -nemCommandoPrimary:onActivate(function(actor) - actor:enter_state(stateNemCommandoPrimary) +Callback.add(primary.on_activate, function(actor, skill, slot) + actor:set_state(statePrimary) end) -stateNemCommandoPrimary:clear_callbacks() -stateNemCommandoPrimary:onEnter(function(actor, data) +Callback.add(statePrimary.on_enter, function(actor, data) actor.image_index = 0 nemmando_update_sprites(actor, false) data.fired = 0 + -- variable used to keep track of which attack anim to use if not data.attack_side then data.attack_side = 0 @@ -235,7 +271,8 @@ stateNemCommandoPrimary:onEnter(function(actor, data) actor.sprite_index = sprite_shoot1_2 end end) -stateNemCommandoPrimary:onStep(function(actor, data) + +Callback.add(statePrimary.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(actor.sprite_index, 0.2) @@ -250,14 +287,14 @@ stateNemCommandoPrimary:onStep(function(actor, data) actor.z_count = actor.z_count + 1 -- this is part of heaven cracker and has to be run shared for reasons -- has to be host-side instead of authority-side because of the wounding thing uughhhhhhhhhhhh - if gm._mod_net_isHost() then - local damage = actor:skill_get_damage(nemCommandoPrimary) + if Net.host then + local damage = actor:skill_get_damage(primary) local direction = actor:skill_util_facing_direction() -- skill_util_update_heaven_cracker runs is_authority internally -- and so we have to manually recreate it to fire the drill host-side, to make it inflict wound, because otherwise it wont work for clients -- ohhhhh the misery - local heaven_cracker_count = actor:item_stack_count(Item.find("ror", "heavenCracker")) + local heaven_cracker_count = actor:item_count(Item.find("heavenCracker")) local cracker_shot = false if heaven_cracker_count > 0 and actor.z_count >= 5 - heaven_cracker_count then @@ -265,10 +302,10 @@ stateNemCommandoPrimary:onStep(function(actor, data) actor.z_count = 0 end - for i=0, actor:buff_stack_count(Buff.find("ror", "shadowClone")) do + for i=0, actor:buff_count(Buff.find("shadowClone")) do local attack_info if cracker_shot then - attack_info = actor:fire_bullet(actor.x, actor.y, 700, direction, damage, 1, gm.constants.sSparks1, Attack_Info.TRACER.drill).attack_info + attack_info = actor:fire_bullet(actor.x, actor.y, 700, direction, damage, 1, gm.constants.sSparks1, Tracer.DRILL).attack_info else attack_info = actor:fire_explosion(actor.x + actor.image_xscale * 30, actor.y, 100, 65, damage, nil, gm.constants.sSparks9r).attack_info end @@ -282,29 +319,28 @@ stateNemCommandoPrimary:onStep(function(actor, data) actor:skill_util_reset_activity_state() end end) -stateNemCommandoPrimary:onGetInterruptPriority(function(actor, data) + +Callback.add(statePrimary.on_get_interrupt_priority, function(actor, data) if actor.image_index >= 4 then - return State.ACTOR_STATE_INTERRUPT_PRIORITY.skill_interrupt_period + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD end end) -local wound = Buff.find("ror", "commandoWound") +local wound = Buff.find("commandoWound") -Callback.add(Callback.TYPE.onAttackHit, "SSNemmandoOnHit", function(hit_info) +Callback.add(Callback.ON_ATTACK_HIT, function(hit_info) local attack_tag = hit_info.attack_info.__ssr_nemmando_wound if attack_tag then local victim = hit_info.target if attack_tag == ATTACK_TAG_APPLY_WOUND then - if victim:buff_stack_count(wound) == 0 then - --victim:buff_apply(wound, 6 * 60) -- doesn't work correctly (????????) + if victim:buff_count(wound) == 0 then GM.apply_buff(victim, wound, 6 * 60, 1) else GM.set_buff_time(victim, wound, WOUND_DEBUFF_DURATION) end elseif attack_tag == ATTACK_TAG_EXTEND_WOUND then - if victim:buff_stack_count(wound) > 0 then - --GM.apply_buff(victim, wound, 6 * 60, 1) + if victim:buff_count(wound) > 0 then GM.set_buff_time(victim, wound, WOUND_DEBUFF_DURATION) end end @@ -312,30 +348,31 @@ Callback.add(Callback.TYPE.onAttackHit, "SSNemmandoOnHit", function(hit_info) end) -- Single Tap -nemCommandoSecondary:set_skill_icon(sprite_skills, 1) -nemCommandoSecondary.cooldown = 2 * 60 -nemCommandoSecondary.damage = 2.0 -nemCommandoSecondary.max_stock = 4 --- is_primary makes the skill's cooldown reduced by attack speed, but also removes cooldown timer on HUD, and causes other unintuitive issues. is it worth? ---nemCommandoSecondary.is_primary = true -nemCommandoSecondary.hold_facing_direction = true - -local tracer_color = Color.from_rgb(252, 118, 98) -local tracer, tracer_info = CustomTracer.new(function(x1, y1, x2, y2) +secondary.sprite = sprite_skills +secondary.subimage = 1 +secondary.cooldown = 2 * 60 +secondary.damage = 2.0 +secondary.max_stock = 4 +secondary.hold_facing_direction = true + +local tracer = Tracer.new("nemCommandoSingleTap") +tracer.sparks_offset_y = -8 + +tracer:set_callback(function(x1, y1, x2, y2, color) y1 = y1 - 8 y2 = y2 - 8 - local dist = gm.point_distance(x1, y1, x2, y2) - local dir = gm.point_direction(x1, y1, x2, y2) + local dist = Math.distance(x1, y1, x2, y2) + local dir = Math.direction(x1, y1, x2, y2) -- tracer - local t = GM.instance_create(x1, y1, gm.constants.oEfProjectileTracer) - t.direction = dir - t.speed = 60 - t.length = 80 - t.blend_1 = tracer_color - t.blend_2 = tracer_color - t:alarm_set(0, math.max(1, dist / t.speed)) + local inst = Object.find("EfProjectileTracer"):create(x1, y1) + inst.direction = dir + inst.speed = 60 + inst.length = 80 + inst.blend_1 = Color.from_rgb(252, 118, 98) + inst.blend_2 = Color.from_rgb(252, 118, 98) + inst:alarm_set(0, math.max(1, dist / inst.speed)) -- particles particleTracer:set_direction(dir, dir, 0, 0) @@ -344,23 +381,20 @@ local tracer, tracer_info = CustomTracer.new(function(x1, y1, x2, y2) local py = y1 local i = 0 while i < dist do - particleTracer:create_colour(px, py, tracer_color, 1) + particleTracer:create_colour(px, py, Color.from_rgb(252, 118, 98), 1) px = px + gm.lengthdir_x(15, dir) py = py + gm.lengthdir_y(15, dir) i = i + 15 end end) -tracer_info.sparks_offset_y = -8 -local stateNemCommandoSecondary = State.new(NAMESPACE, "nemCommandoSecondary") +local stateSecondary = ActorState.new("nemCommandoSecondary") -nemCommandoSecondary:clear_callbacks() -nemCommandoSecondary:onActivate(function(actor) - actor:enter_state(stateNemCommandoSecondary) +Callback.add(secondary.on_activate, function(actor, skill, slot) + actor:set_state(stateSecondary) end) -stateNemCommandoSecondary:clear_callbacks() -stateNemCommandoSecondary:onEnter(function(actor, data) +Callback.add(stateSecondary.on_enter, function(actor, data) actor.image_index2 = 0 data.fired = 0 @@ -369,7 +403,8 @@ stateNemCommandoSecondary:onEnter(function(actor, data) actor:skill_util_strafe_init() --actor:skill_util_strafe_turn_init() -- this and skill_util_strafe_turn_update only works for skills in the Z/primary slot .... end) -stateNemCommandoSecondary:onStep(function(actor, data) + +Callback.add(stateSecondary.on_step, function(actor, data) actor.sprite_index2 = sprite_shoot2_half actor:skill_util_strafe_update(0.25 * actor.attack_speed, 0.5) @@ -382,47 +417,45 @@ stateNemCommandoSecondary:onStep(function(actor, data) actor:sound_play(gm.constants.wBullet2, 1, 1.4 + math.random() * 0.2) if actor:is_authority() then - local damage = actor:skill_get_damage(nemCommandoSecondary) + local damage = actor:skill_get_damage(secondary) local dir = actor:skill_util_facing_direction() - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, actor:buff_stack_count(buff_shadow_clone) do + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do local attack_info = actor:fire_bullet(actor.x, actor.y, 1400, dir, damage, nil, gm.constants.sSparks23r, tracer).attack_info - attack_info.climb = i * 8 + attack_info.climb = i * 8 * 1.35 end end end actor:skill_util_exit_state_on_anim_end() end) -stateNemCommandoSecondary:onExit(function(actor, data) + +Callback.add(stateSecondary.on_exit, function(actor, data) actor:skill_util_strafe_exit() end) -stateNemCommandoSecondary:onGetInterruptPriority(function(actor, data) + +Callback.add(stateSecondary.on_get_interrupt_priority, function(actor, data) if actor.image_index2 + 0.25 * actor.attack_speed >= 3 then - return State.ACTOR_STATE_INTERRUPT_PRIORITY.skill_interrupt_period + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD end end) -- Distant Gash -local objSlash = Object.new(NAMESPACE, "NemmandoSlash") -objSlash.obj_sprite = sprite_gash - -nemCommandoSecondary2 = Skill.new(NAMESPACE, "nemesisCommandoX2") -nemCommando:add_secondary(nemCommandoSecondary2) +local objSlash = Object.new("NemmandoSlash") +objSlash:set_sprite(sprite_gash) -nemCommandoSecondary2:set_skill_icon(sprite_skills, 2) -nemCommandoSecondary2.cooldown = 3 * 60 +secondary2.sprite = sprite_skills +secondary2.subimage = 2 +secondary2.cooldown = 3 * 60 -local stateNemCommandoSecondary2 = State.new(NAMESPACE, "nemCommandoSecondary2") +local stateSecondary2 = ActorState.new("nemCommandoSecondary2") -nemCommandoSecondary2:clear_callbacks() -nemCommandoSecondary2:onActivate(function(actor) - actor:enter_state(stateNemCommandoSecondary2) +Callback.add(secondary2.on_activate, function(actor, skill, slot) + actor:set_state(stateSecondary2) end) -stateNemCommandoSecondary2:clear_callbacks() -stateNemCommandoSecondary2:onEnter(function(actor, data) +Callback.add(stateSecondary2.on_enter, function(actor, data) actor.image_index = 0 data.fired = 0 @@ -430,15 +463,16 @@ stateNemCommandoSecondary2:onEnter(function(actor, data) actor:sound_play(gm.constants.wMercenary_Parry_Deflection, 0.8, 1.1) end) -stateNemCommandoSecondary2:onStep(function(actor, data) + +Callback.add(stateSecondary2.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(sprite_shoot2b, 0.25) if actor.image_index >= 4 and data.fired == 0 then data.fired = 1 - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, actor:buff_stack_count(buff_shadow_clone) do + local buff_shadow_clone = Buff.find("shadowClone") + for i = 0, actor:buff_count(buff_shadow_clone) do local slash = objSlash:create(actor.x - i * 12 * actor.image_xscale, actor.y) slash.parent = actor slash.team = actor.team @@ -459,31 +493,32 @@ stateNemCommandoSecondary2:onStep(function(actor, data) actor:skill_util_exit_state_on_anim_end() end) -stateNemCommandoSecondary2:onGetInterruptPriority(function(actor, data) + +Callback.add(stateSecondary2.on_get_interrupt_priority, function(actor, data) if actor.image_index >= 8 then - return State.ACTOR_STATE_INTERRUPT_PRIORITY.skill_interrupt_period + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD end end) -objSlash:clear_callbacks() -objSlash:onCreate(function(inst) +Callback.add(objSlash.on_create, function(inst) inst.image_speed = 0.25 inst.speed = 10 inst.parent = -4 - local data = inst:get_data() + local data = Instance.get_data(inst) data.hit_list = {} data.lifetime = 80 inst:actor_skin_skinnable_init() end) -objSlash:onStep(function(inst) + +Callback.add(objSlash.on_step, function(inst) if not Instance.exists(inst.parent) then inst:destroy() return end - local data = inst:get_data() + local data = Instance.get_data(inst) data.lifetime = data.lifetime - 1 if data.lifetime < 0 then @@ -492,7 +527,7 @@ objSlash:onStep(function(inst) end if data.lifetime % 8 == 0 then - local trail = GM.instance_create(inst.x, inst.y, gm.constants.oEfTrail) + local trail = Object.find("EfTrail"):create(inst.x, inst.y) trail.sprite_index = inst.sprite_index trail.image_index = inst.image_index trail.image_blend = gm.merge_colour(inst.image_blend, Color.BLACK, 0.5) @@ -508,14 +543,14 @@ objSlash:onStep(function(inst) if math.random() < 0.5 and data.lifetime > 10 then particleTracer:set_direction(inst.direction, inst.direction, 0, 0) - particleTracer:create_colour(inst.x, inst.y + gm.random_range(-20, 20) * scale, tracer_color, 1) + particleTracer:create_colour(inst.x, inst.y + gm.random_range(-20, 20) * scale, Color.from_rgb(252, 118, 98), 1) end local actors = inst:get_collisions(gm.constants.pActorCollisionBase) for _, actor in ipairs(actors) do if inst:attack_collision_canhit(actor) and not data.hit_list[actor.id] then - if gm._mod_net_isHost() then + if Net.host then local attack = inst.parent:fire_direct(actor, 0.9, inst.direction, inst.x, inst.y, gm.constants.sBite3).attack_info attack.__ssr_nemmando_wound = ATTACK_TAG_APPLY_WOUND end @@ -525,55 +560,58 @@ objSlash:onStep(function(inst) end end end) -objSlash:onDraw(function(inst) + +Callback.add(objSlash.on_draw, function(inst) inst:actor_skin_skinnable_draw_self() end) -nemCommandoUtility:set_skill_icon(sprite_skills, 3) -nemCommandoUtility.cooldown = 3 * 60 -nemCommandoUtility.max_stock = 2 -nemCommandoUtility.override_strafe_direction = true -nemCommandoUtility.ignore_aim_direction = true -nemCommandoUtility.is_utility = true +-- Tactical Roll +utility.sprite = sprite_skills +utility.subimage = 3 +utility.cooldown = 3 * 60 +utility.max_stock = 2 +utility.override_strafe_direction = true +utility.ignore_aim_direction = true +utility.is_utility = true -local stateNemCommandoUtility = State.new(NAMESPACE, "nemCommandoUtility") -stateNemCommandoUtility.activity_flags = State.ACTIVITY_FLAG.allow_rope_cancel +local stateUtility = ActorState.new("nemCommandoUtility") +stateUtility.activity_flags = ActorState.ActivityFlag.ALLOW_ROPE_CANCEL -nemCommandoUtility:clear_callbacks() -nemCommandoUtility:onActivate(function(actor) - actor:enter_state(stateNemCommandoUtility) +Callback.add(utility.on_activate, function(actor, skill, slot) + actor:set_state(stateUtility) end) -nemCommandoUtility:onCanActivate(function(actor) - local special_id = State.find(NAMESPACE, "nemCommandoSpecial").value + +Callback.add(utility.on_can_activate, function(actor, skill, slot) + local special_id = ActorState.find("nemCommandoSpecial").value local data = actor.actor_state_current_data_table if actor.actor_state_current_id == special_id and (data.primed == 1 or data.tossed == 1) then return true end end) -stateNemCommandoUtility:clear_callbacks() -stateNemCommandoUtility:onEnter(function(actor, data) +Callback.add(stateUtility.on_enter, function(actor, data) actor.image_index = 0 data.fired = 0 end) -stateNemCommandoUtility:onStep(function(actor, data) + +Callback.add(stateUtility.on_step, function(actor, data) actor:actor_animation_set(sprite_shoot3, 0.25, false) actor.pHspeed = 2.5 * actor.pHmax * actor.image_xscale - actor:set_immune(3) + actor.invincible = math.max(3, actor.invincible) if data.fired == 0 then data.fired = 1 - local secondary = actor:get_active_skill(Skill.SLOT.secondary) + local current_secondary = actor:get_active_skill(Skill.Slot.SECONDARY) - if secondary.skill_id == nemCommandoSecondary.value then - if secondary.stock < secondary.max_stock then + if current_secondary.skill_id == secondary.value then + if current_secondary.stock < secondary.max_stock then actor:sound_play(gm.constants.wSniperReload, 0.8, 1.5) end - GM.actor_skill_add_stock(actor, Skill.SLOT.secondary) - GM.actor_skill_add_stock(actor, Skill.SLOT.secondary) + GM.actor_skill_add_stock(actor, Skill.Slot.SECONDARY) + GM.actor_skill_add_stock(actor, Skill.Slot.SECONDARY) end actor:sound_play(gm.constants.wCommandoRoll, 0.9, 1.2) @@ -589,33 +627,29 @@ end) -- Flush Out -local objGrenade = Object.new(NAMESPACE, "NemmandoGrenade") -objGrenade.obj_sprite = sprite_grenade - -local nemCommandoSpecialBoosted = Skill.new(NAMESPACE, "nemesisCommandoVBoosted") +local objGrenade = Object.new("NemmandoGrenade") +objGrenade:set_sprite(sprite_grenade) -nemCommandoSpecial:set_skill_icon(sprite_skills, 4) -nemCommandoSpecial:set_skill_upgrade(nemCommandoSpecialBoosted) -nemCommandoSpecial.cooldown = 6 * 60 -nemCommandoSpecial.require_key_press = true +special.sprite = sprite_skills +special.subimage = 4 +special.upgrade_skill = specialS +special.cooldown = 6 * 60 -nemCommandoSpecialBoosted:set_skill_icon(sprite_skills, 5) -nemCommandoSpecialBoosted.cooldown = 6 * 60 -nemCommandoSpecialBoosted.require_key_press = true +specialS.sprite = sprite_skills +specialS.subimage = 5 +specialS.cooldown = 6 * 60 -local stateNemCommandoSpecial = State.new(NAMESPACE, "nemCommandoSpecial") +local stateSpecial = ActorState.new("nemCommandoSpecial") -nemCommandoSpecial:clear_callbacks() -nemCommandoSpecial:onActivate(function(actor) - actor:enter_state(stateNemCommandoSpecial) +Callback.add(special.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial) end) -nemCommandoSpecialBoosted:clear_callbacks() -nemCommandoSpecialBoosted:onActivate(function(actor) - actor:enter_state(stateNemCommandoSpecial) + +Callback.add(specialS.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial) end) -stateNemCommandoSpecial:clear_callbacks() -stateNemCommandoSpecial:onEnter(function(actor, data) +Callback.add(stateSpecial.on_enter, function(actor, data) actor:skill_util_strafe_init() actor.image_index2 = 0 @@ -627,7 +661,8 @@ stateNemCommandoSpecial:onEnter(function(actor, data) data.low_toss = 0 data.blip = 0 end) -stateNemCommandoSpecial:onStep(function(actor, data) + +Callback.add(stateSpecial.on_step, function(actor, data) local animation_speed = 0.25 * actor.attack_speed actor:skill_util_strafe_update(animation_speed, 0.75) @@ -639,14 +674,14 @@ stateNemCommandoSpecial:onStep(function(actor, data) if data.fuse_timer % GRENADE_TICK_INTERVAL == 0 then actor:sound_play(gm.constants.wPickupOLD, 0.7, 4) - local flash = GM.instance_create(actor.x, actor.y, gm.constants.oEfFlash) + local flash = Object.find("EfFlash"):create(actor.x, actor.y) flash.parent = actor flash.rate = 0.1 flash.image_alpha = 0.5 end data.fuse_timer = data.fuse_timer - 1 end - + if data.tossed == 0 then if data.primed == 0 and actor.image_index2 >= 2 then data.primed = 1 @@ -663,29 +698,29 @@ stateNemCommandoSpecial:onStep(function(actor, data) actor.image_index2 = 5 data.blip = 0 end - + if actor.image_index2 >= 4 then - local release = not actor:control("skill4", 0) + local release = not Util.bool(actor.v_skill) if not actor:is_authority() then -- for online purposes -- see below on wtf this means - release = gm.bool(actor.activity_var2) + release = Util.bool(actor.activity_var2) end - local low_toss = gm.bool(actor.ropeDown) + local low_toss = Util.bool(actor.ropeDown) local auto_toss = data.fuse_timer <= GRENADE_AUTOTHROW_THRESHOLD if (release or low_toss or auto_toss) and data.tossed == 0 then - if gm._mod_net_isOnline() then + if Net.online then -- magical bullshit to sync grenade releasing -- so the thing is, there's no way to tell if another player is holding down their skill input. -- so to actually sync this, i use the game's own message system to send a vanilla packet -- id 43: "set_activity_var2" -- this packet sets the activity_var2 variable on the actor when received. so this way we can inform the host/other clients that a nemmando released his grenade. -- it also sets the actor's xscale i guess - if gm._mod_net_isHost() then + if Net.host then -- args: [not sure], packet id, object index, net id, value to write to activity_var2, actor xscale - gm.server_message_send(0, 43, actor:get_object_index_self(), actor.m_id, 1, gm.sign(actor.image_xscale)) + gm.server_message_send(0, 43, actor:get_object_index_self(), actor.m_id, 1, Math.sign(actor.image_xscale)) else -- args: packet id, value to write to activity_var2, actor xscale - gm.client_message_send(43, 1, gm.sign(actor.image_xscale)) + gm.client_message_send(43, 1, Math.sign(actor.image_xscale)) end end @@ -713,10 +748,12 @@ stateNemCommandoSpecial:onStep(function(actor, data) nade.vspeed = nade.vspeed + actor.pVspeed * GRENADE_VELOCITY_INHERIT_MULT nade.parent = actor - nade.scepter = actor:item_stack_count(Item.find("ror", "ancientScepter")) - nade.timer = data.fuse_timer - if nade.timer <= GRENADE_SELFSTUN_THRESHOLD then - nade.stun_parent = 1 + + local nade_data = Instance.get_data(nade) + nade_data.scepter = actor:item_count(Item.find("ancientScepter")) + nade_data.timer = data.fuse_timer + if nade_data.timer <= GRENADE_SELFSTUN_THRESHOLD then + nade_data.stun_parent = 1 end end end @@ -726,7 +763,8 @@ stateNemCommandoSpecial:onStep(function(actor, data) end end end) -stateNemCommandoSpecial:onExit(function(actor, data) + +Callback.add(stateSpecial.on_exit, function(actor, data) -- release the grenade if the state was exited for any reason prior to release if data.tossed == 0 then local nade = objGrenade:create(actor.x, actor.y - 5) @@ -738,33 +776,38 @@ stateNemCommandoSpecial:onExit(function(actor, data) nade.vspeed = nade.vspeed + actor.pVspeed * GRENADE_VELOCITY_INHERIT_MULT nade.parent = actor - nade.scepter = actor:item_stack_count(Item.find("ror", "ancientScepter")) - nade.timer = data.fuse_timer - if nade.timer <= GRENADE_SELFSTUN_THRESHOLD then - nade.stun_parent = 1 + Instance.get_data(nade).scepter = actor:item_count(Item.find("ancientScepter")) + Instance.get_data(nade).timer = data.fuse_timer + if Instance.get_data(nade).timer <= GRENADE_SELFSTUN_THRESHOLD then + Instance.get_data(nade).stun_parent = 1 end end actor:skill_util_strafe_exit() end) -objGrenade:clear_callbacks() -objGrenade:onCreate(function(inst) +Callback.add(objGrenade.on_create, function(inst) inst.image_speed = 1 inst.gravity = 0.4 - inst.bounces = 3 - inst.parent = -4 - inst.damage = 7 - inst.timer = GRENADE_FUSE_TIMER - inst.stun_parent = 0 - inst.scepter = 0 + + local data = Instance.get_data(inst) + + data.bounces = 3 + data.damage = 7 + data.stun_parent = 0 + data.timer = GRENADE_FUSE_TIMER + data.scepter = 0 end) -objGrenade:onStep(function(inst) + +Callback.add(objGrenade.on_step, function(inst) if not Instance.exists(inst.parent) then inst:destroy() return end - if inst.bounces > 0 then + + local data = Instance.get_data(inst) + + if data.bounces > 0 then local bounced = false -- extrapolate where the grenade will be next frame for collision detection @@ -780,7 +823,7 @@ objGrenade:onStep(function(inst) end if bounce_v then if inst.vspeed > 0 then - inst.bounces = inst.bounces - 1 + data.bounces = data.bounces - 1 end inst.vspeed = inst.vspeed * -0.5 @@ -798,7 +841,7 @@ objGrenade:onStep(function(inst) end end - if inst.bounces <= 0 then + if data.bounces <= 0 then inst.gravity = 0 inst:move_contact_solid(270, inst.speed) inst.speed = 0 @@ -807,42 +850,45 @@ objGrenade:onStep(function(inst) end if math.random() < 0.5 then - particleTrail:create_color(inst.x, inst.y, 0x454efc, 1, Particle.SYSTEM.below) + particleTrail:create_color(inst.x, inst.y, Color.from_hex(0x454EFC), 1, Particle.System.BELOW) end - if inst.timer % GRENADE_TICK_INTERVAL == 0 then + if data.timer % GRENADE_TICK_INTERVAL == 0 then inst:sound_play(gm.constants.wPickupOLD, 0.7, 4) - local ef = GM.instance_create(0, 0, gm.constants.oEfFlash) + local ef = Object.find("EfFlash"):create(inst.x, inst.y) ef.parent = inst - ef.image_blend = 0x454efc + ef.image_blend = Color.WHITE ef.rate = 0.2 end - inst.timer = inst.timer - 1 + data.timer = data.timer - 1 - if inst.timer <= 0 then + if data.timer <= 0 then inst:destroy() end end) -objGrenade:onDestroy(function(inst) +Callback.add(objGrenade.on_destroy, function(inst) local parent = inst.parent if not Instance.exists(parent) then return end + + local data = Instance.get_data(inst) particleRubble2:create(inst.x, inst.y, 15) - local ef = gm.instance_create(inst.x, inst.y, gm.constants.oEfExplosion) + + local ef = Object.find("EfExplosion"):create(inst.x, inst.y) ef.sprite_index = sprite_explosion inst:sound_play(gm.constants.wBanditShoot2Explo, 1, 1 + math.random() * 0.2) inst:screen_shake(4) - local boosted = inst.scepter > 0 + local boosted = data.scepter > 0 if parent:is_authority() then - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, parent:buff_stack_count(buff_shadow_clone) do - local attack = parent:fire_explosion(inst.x, inst.y, 192, 160, inst.damage).attack_info + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, parent:buff_count(buff_shadow_clone) do + local attack = parent:fire_explosion(inst.x, inst.y, 192, 160, data.damage).attack_info attack.climb = i * 8 * 1.35 if boosted then @@ -851,18 +897,21 @@ objGrenade:onDestroy(function(inst) end end - if inst.stun_parent == 1 then + if data.stun_parent == 1 then -- this occurs host-side - GM.actor_knockback_inflict(parent, 1, -parent.image_xscale, 60) + if Net.host then + parent:apply_knockback(-parent.image_xscale, 60) + end end if boosted then -- this doesn't sync, but it doesn't matter because the client that threw them is the one that fires the attacks, and it'll feel fine for them - for i=-1, 1 do + for i = -1, 1 do local nade = objGrenade:create(inst.x, inst.y) - nade.timer = GRENADE_FUSE_TIMER * (0.4 + math.random() * 0.1 - i * 0.05) + Instance.get_data(nade).timer = GRENADE_FUSE_TIMER * (0.4 + math.random() * 0.1 - i * 0.05) + Instance.get_data(nade).damage = 1.5 + nade.parent = parent - nade.damage = 1.5 nade.direction = 90 + i * 45 nade.speed = 5 + math.random(2) @@ -873,54 +922,49 @@ objGrenade:onDestroy(function(inst) end) -- Devastator -local objRocket = Object.new(NAMESPACE, "NemmandoRocket") -objRocket.obj_sprite = sprite_rocket - -local nemCommandoSpecial2 = Skill.new(NAMESPACE, "nemesisCommandoV2") -local nemCommandoSpecial2Boosted = Skill.new(NAMESPACE, "nemesisCommandoV2Boosted") - -nemCommando:add_special(nemCommandoSpecial2) - -nemCommandoSpecial2:set_skill_icon(sprite_skills, 6) -nemCommandoSpecial2:set_skill_upgrade(nemCommandoSpecial2Boosted) -nemCommandoSpecial2.cooldown = 6 * 60 -nemCommandoSpecial2.disable_aim_stall = true -nemCommandoSpecial2Boosted:set_skill_icon(sprite_skills, 7) -nemCommandoSpecial2Boosted.cooldown = 6 * 60 -nemCommandoSpecial2Boosted.max_stock = 2 -nemCommandoSpecial2Boosted.disable_aim_stall = true - -local stateNemCommandoSpecial2 = State.new(NAMESPACE, "nemCommandoSpecial2") - -nemCommandoSpecial2:clear_callbacks() -nemCommandoSpecial2:onActivate(function(actor) - actor:enter_state(stateNemCommandoSpecial2) +local objRocket = Object.new("NemmandoRocket") +objRocket:set_sprite(sprite_rocket) + +special2.sprite = sprite_skills +special2.subimage = 6 +special2.upgrade_skill = special2S +special2.cooldown = 6 * 60 +special2.disable_aim_stall = true +special2S.sprite = sprite_skills +special2S.subimage = 7 +special2S.cooldown = 6 * 60 +special2S.max_stock = 2 +special2S.disable_aim_stall = true + +local stateSpecial2 = ActorState.new("nemCommandoSpecial2") + +Callback.add(special2.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial2) end) -nemCommandoSpecial2Boosted:clear_callbacks() -nemCommandoSpecial2Boosted:onActivate(function(actor) - actor:enter_state(stateNemCommandoSpecial2) + +Callback.add(special2S.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial2) end) -stateNemCommandoSpecial2:clear_callbacks() -stateNemCommandoSpecial2:onEnter(function(actor, data) +Callback.add(stateSpecial2.on_enter, function(actor, data) actor.image_index = 0 data.fired = 0 - data.airborne = actor.free + data.airborne = not actor:is_grounded() -- airborne is determined only on state entry so you can do the tech where you jump after pressing the button but before firing - if gm.bool(data.airborne) then + if data.airborne then actor.sprite_index = sprite_shoot4b_a else actor.sprite_index = sprite_shoot4b end actor:sound_play(sound_rocket_fire, 1, 1) end) -stateNemCommandoSpecial2:onStep(function(actor, data) + +Callback.add(stateSpecial2.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(actor.sprite_index, 0.2) - local airborne = gm.bool(data.airborne) - local should_fire = actor.image_index >= 3 or (actor.image_index >= 2 and airborne) + local should_fire = actor.image_index >= 3 or (actor.image_index >= 2 and data.airborne) if should_fire and data.fired == 0 then data.fired = 1 @@ -931,10 +975,10 @@ stateNemCommandoSpecial2:onStep(function(actor, data) if actor:is_authority() then -- fire backblast. there's zero reason for this to exist, but it's funny! - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, actor:buff_stack_count(buff_shadow_clone) do + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do local backblast = actor:fire_explosion(actor.x - 50 * actor.image_xscale, actor.y-12, 100, 60, 1.0) - if airborne then + if data.airborne then -- rotate explosion attack ... cursed, but funny backblast.image_angle = actor.image_xscale * -45 backblast.y = backblast.y - 50 @@ -949,10 +993,10 @@ stateNemCommandoSpecial2:onStep(function(actor, data) rocket.parent = actor rocket.direction = actor:skill_util_facing_direction() rocket.image_xscale = actor.image_xscale - rocket.scepter = actor:item_stack_count(Item.find("ror", "ancientScepter")) + rocket.scepter = actor:item_count(Item.find("ancientScepter")) actor.pHspeed = actor.pHspeed + actor.pHmax * -2 * actor.image_xscale - if airborne then + if data.airborne then rocket.direction = 270 + actor.image_xscale * 45 actor.pVspeed = actor.pVmax * -1.2 @@ -968,8 +1012,7 @@ stateNemCommandoSpecial2:onStep(function(actor, data) actor:skill_util_exit_state_on_anim_end() end) -objRocket:clear_callbacks() -objRocket:onCreate(function(inst) +Callback.add(objRocket.on_create, function(inst) inst.speed = ROCKET_SPEED_START inst.mask_index = sprite_rocket_mask @@ -981,12 +1024,13 @@ objRocket:onCreate(function(inst) inst.lifetime = 3 * 60 end) -objRocket:onStep(function(inst) +Callback.add(objRocket.on_step, function(inst) local dir = inst.direction local xoff = gm.lengthdir_x(16, dir) local yoff = gm.lengthdir_y(16, dir) + particleRocketTrail:set_orientation(dir, dir, 0, 0, 0) - particleRocketTrail:create(inst.x - xoff, inst.y - yoff, 1, Particle.SYSTEM.above) + particleRocketTrail:create(inst.x - xoff, inst.y - yoff, 1) inst.speed = math.min(ROCKET_SPEED_MAX, inst.speed + ROCKET_ACCELERATION) @@ -1013,7 +1057,8 @@ objRocket:onStep(function(inst) inst:destroy() end end) -objRocket:onDestroy(function(inst) + +Callback.add(objRocket.on_destroy, function(inst) local ef = gm.instance_create(inst.x, inst.y, gm.constants.oEfExplosion) ef.sprite_index = gm.constants.sEfSuperMissileExplosion @@ -1028,8 +1073,8 @@ objRocket:onDestroy(function(inst) if Instance.exists(inst.parent) and inst.parent:is_authority() then local boosted = inst.scepter > 0 - local buff_shadow_clone = Buff.find("ror", "shadowClone") - for i=0, inst.parent:buff_stack_count(buff_shadow_clone) do + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, inst.parent:buff_count(buff_shadow_clone) do -- direct hit if inst.victim ~= -4 then local direct = inst.parent:fire_direct(inst.victim, 10, inst.direction, inst.x, inst.y).attack_info @@ -1053,4 +1098,3 @@ objRocket:onDestroy(function(inst) end end) -local nemCommandoLog = Survivor_Log.new(nemCommando, sprite_log) diff --git a/Survivors/nemesisMercenary.lua b/Survivors/nemesisMercenary.lua index 06b8056e..bd9330bc 100644 --- a/Survivors/nemesisMercenary.lua +++ b/Survivors/nemesisMercenary.lua @@ -1,468 +1,1081 @@ --- local SPRITE_PATH = path.combine(PATH, "Sprites/Survivors/NemesisMercenary") --- local SOUND_PATH = path.combine(PATH, "Sounds/Survivors/NemesisMercenary") - --- local sprite_loadout = Resources.sprite_load(NAMESPACE, "NemesisMercenarySelect", path.combine(SPRITE_PATH, "select.png"), 19, 28, 0) --- local sprite_portrait = Resources.sprite_load(NAMESPACE, "NemesisMercenaryPortrait", path.combine(SPRITE_PATH, "portrait.png"), 3) --- local sprite_portrait_small = Resources.sprite_load(NAMESPACE, "NemesisMercenaryPortraitSmall", path.combine(SPRITE_PATH, "portraitSmall.png")) --- local sprite_skills = Resources.sprite_load(NAMESPACE, "NemesisMercenarySkills", path.combine(SPRITE_PATH, "skills.png"), 5) --- local sprite_credits = Resources.sprite_load(NAMESPACE, "CreditsSurvivorNemesisMercenary", path.combine(SPRITE_PATH, "credits.png"), 1, 8, 10) - --- local sprite_idle = Resources.sprite_load(NAMESPACE, "NemesisMercenaryIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 11, 15) --- local sprite_idle2 = Resources.sprite_load(NAMESPACE, "NemesisMercenaryIdle2", path.combine(SPRITE_PATH, "idle2.png"), 1, 8, 10) --- local sprite_walk = Resources.sprite_load(NAMESPACE, "NemesisMercenaryWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 12, 17, 0.75) --- local sprite_walk_back = Resources.sprite_load(NAMESPACE, "NemesisMercenaryWalkBack", path.combine(SPRITE_PATH, "walkBack.png"), 8, 12, 17, 0.75) --- local sprite_jump = Resources.sprite_load(NAMESPACE, "NemesisMercenaryJump", path.combine(SPRITE_PATH, "jump.png"), 1, 8, 10) --- local sprite_jump_peak = Resources.sprite_load(NAMESPACE, "NemesisMercenaryJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 8, 10) --- local sprite_fall = Resources.sprite_load(NAMESPACE, "NemesisMercenaryFall", path.combine(SPRITE_PATH, "fall.png"), 1, 8, 10) --- local sprite_climb = Resources.sprite_load(NAMESPACE, "NemesisMercenaryClimb", path.combine(SPRITE_PATH, "climb.png"), 2, 8, 14) --- local sprite_death = Resources.sprite_load(NAMESPACE, "NemesisMercenaryDeath", path.combine(SPRITE_PATH, "death.png"), 10, 18, 16) --- local sprite_decoy = Resources.sprite_load(NAMESPACE, "NemesisMercenaryDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 16, 18) - --- local sprite_shoot1a = Resources.sprite_load(NAMESPACE, "NemesisMercenaryShoot1a", path.combine(SPRITE_PATH, "shoot1a.png"), 9, 14, 20) --- local sprite_shoot1b = Resources.sprite_load(NAMESPACE, "NemesisMercenaryShoot1b", path.combine(SPRITE_PATH, "shoot1b.png"), 9, 18, 20) --- local sprite_shoot2a = Resources.sprite_load(NAMESPACE, "NemesisMercenaryShoot2a", path.combine(SPRITE_PATH, "shoot2a.png"), 5, 12, 28) --- local sprite_shoot2b = Resources.sprite_load(NAMESPACE, "NemesisMercenaryShoot2b", path.combine(SPRITE_PATH, "shoot2b.png"), 5, 14, 28) --- local sprite_shoot3 = Resources.sprite_load(NAMESPACE, "NemesisMercenaryShoot3", path.combine(SPRITE_PATH, "shoot3.png"), 8, 18, 10) --- local sprite_shoot4 = Resources.sprite_load(NAMESPACE, "NemesisMercenaryShoot4", path.combine(SPRITE_PATH, "shoot4.png"), 18, 60, 32) --- local sprite_shoot5 = Resources.sprite_load(NAMESPACE, "NemesisMercenaryShoot5", path.combine(SPRITE_PATH, "shoot5.png"), 18, 60, 32) --- local sprite_sparks = Resources.sprite_load(NAMESPACE, "NemesisMercenarySparks", path.combine(SPRITE_PATH, "slash.png"), 5, 52, 50) --- local sprite_sparks2 = Resources.sprite_load(NAMESPACE, "NemesisMercenarySparks2", path.combine(SPRITE_PATH, "slash2.png"), 5, 52, 50) - --- local sprite_log = Resources.sprite_load(NAMESPACE, "NemesisMercenaryLog", path.combine(SPRITE_PATH, "log.png")) - --- local sound_shoot2a = Resources.sfx_load(NAMESPACE, "ExecutionerShoot2a", path.combine(SOUND_PATH, "shoot2a.ogg")) --- local sound_shoot2b = Resources.sfx_load(NAMESPACE, "ExecutionerShoot2b", path.combine(SOUND_PATH, "shoot2b.ogg")) --- local sound_shoot4 = Resources.sfx_load(NAMESPACE, "ExecutionerShoot4", path.combine(SOUND_PATH, "shoot4.ogg")) - --- local particleWispGTracer = Particle.find("ror", "WispGTracer") --- local particleBlood = Particle.find("ror-Blood1") - --- local nemmerc = Survivor.new(NAMESPACE, "nemesisMercenary") --- local nemmerc_id = nemmerc.value - --- nemmerc:set_stats_base({ - -- maxhp = 115, - -- damage = 11, - -- regen = 0.04, --- }) --- nemmerc:set_stats_level({ - -- maxhp = 38, - -- damage = 3, - -- regen = 0.002, - -- armor = 3, --- }) - --- nemmerc:set_animations({ - -- idle = sprite_idle, - -- walk = sprite_walk, - -- jump = sprite_jump, - -- jump_peak = sprite_jump_peak, - -- fall = sprite_fall, - -- climb = sprite_climb, - -- death = sprite_death, - -- decoy = sprite_decoy, - -- --drone_idle = sprite_drone_idle, - -- --drone_shoot = sprite_drone_shoot, --- }) - --- nemmerc:set_cape_offset(-3, -8, 0, -5) --- nemmerc:set_primary_color(Color.from_hex(0xFC4E45)) - --- nemmerc.sprite_loadout = sprite_loadout --- nemmerc.sprite_portrait = sprite_portrait --- nemmerc.sprite_portrait_small = sprite_portrait_small --- nemmerc.sprite_idle = sprite_idle -- used by skin systen for idle sprite --- nemmerc.sprite_title = sprite_walk -- also used by skin system for walk sprite --- nemmerc.sprite_credits = sprite_credits --- nemmerc:clear_callbacks() - --- nemmerc:onInit(function(actor) - -- actor:get_data().slide = 0 - -- actor:get_data().slideDir = 0 - -- actor:get_data().slidePrep = 0 --- end) - --- nemmerc:onStep(function(actor) - -- if actor:get_data().slide > 0 then - -- actor:get_data().slide = actor:get_data().slide - 1 - -- actor.pHspeed = actor:get_data().slide / 50 * 2 * actor.pHmax * actor:get_data().slideDir + actor.pHmax * actor:get_data().slideDir - -- actor.moveLeft = 0 - -- actor.moveRight = 0 - -- actor.sprite_idle = sprite_idle2 - -- actor.sprite_jump = sprite_idle2 - -- actor.sprite_jump_peak = sprite_idle2 - -- actor.sprite_fall = sprite_idle2 - -- else - -- actor.sprite_idle = sprite_idle - -- actor.sprite_jump = sprite_jump - -- actor.sprite_jump_peak = sprite_jump_peak - -- actor.sprite_fall = sprite_fall - -- end --- end) - --- local stab = nemmerc:get_primary() --- local trigger = nemmerc:get_secondary() --- local slide = nemmerc:get_utility() --- local devit = nemmerc:get_special() --- local absDevit = Skill.new(NAMESPACE, "nemesisMercenaryVBoosted") - --- -- stab --- stab:set_skill_icon(sprite_skills, 0) --- stab.cooldown = 0 --- stab.damage = 1.3 --- stab.is_primary = true --- stab.require_key_press = false --- stab.hold_facing_direction = true --- stab.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.any --- stab:clear_callbacks() - --- local stateStab = State.new(NAMESPACE, "attachmentStab") --- stateStab:clear_callbacks() - --- stab:onActivate(function(actor, skill) - -- actor:enter_state(stateStab) --- end) - --- stateStab:onEnter(function(actor, data) - -- actor.image_index = 0 - -- data.fired = 0 --- end) - --- stateStab:onStep(function(actor, data) - -- if actor:get_data().slide > 0 then - -- actor:actor_animation_set(sprite_shoot1b, 0.2) - -- else - -- actor:skill_util_fix_hspeed() - -- actor:actor_animation_set(sprite_shoot1a, 0.2) - -- end - - -- if actor.image_index >= 3 and data.fired == 0 then - -- data.fired = 1 - -- actor:sound_play(gm.constants.wMercenaryShoot1_2, 1, 0.9 + math.random() * 0.2) - -- actor:skill_util_nudge_forward(4 * actor.image_xscale) +local SPRITE_PATH = path.combine(PATH, "Sprites/Survivors/NemesisMercenary") +local SOUND_PATH = path.combine(PATH, "Sounds/Survivors/NemesisMercenary") + +local sprite_loadout = Sprite.new("NemesisMercenarySelect", path.combine(SPRITE_PATH, "select.png"), 4, 28, 0) +local sprite_portrait = Sprite.new("NemesisMercenaryPortrait", path.combine(SPRITE_PATH, "portrait.png"), 3) +local sprite_portrait_small = Sprite.new("NemesisMercenaryPortraitSmall", path.combine(SPRITE_PATH, "portraitSmall.png")) +local sprite_skills = Sprite.new("NemesisMercenarySkills", path.combine(SPRITE_PATH, "skills.png"), 5) +local sprite_credits = Sprite.new("CreditsSurvivorNemesisMercenary", path.combine(SPRITE_PATH, "credits.png"), 1, 8, 10) + +local sprite_idle = Sprite.new("NemesisMercenaryIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 11, 15) +local sprite_idle2 = Sprite.new("NemesisMercenaryIdle2", path.combine(SPRITE_PATH, "idle2.png"), 3, 19, 14) +local sprite_idle2l = Sprite.new("NemesisMercenaryIdle2l", path.combine(SPRITE_PATH, "idle2l.png"), 3, 19, 7) +local sprite_walk = Sprite.new("NemesisMercenaryWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 12, 17) +local sprite_walk_back = Sprite.new("NemesisMercenaryWalkBack", path.combine(SPRITE_PATH, "walkBack.png"), 8, 12, 17) +local sprite_jump = Sprite.new("NemesisMercenaryJump", path.combine(SPRITE_PATH, "jump.png"), 1, 10, 16) +local sprite_jump_peak = Sprite.new("NemesisMercenaryJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 13, 15) +local sprite_fall = Sprite.new("NemesisMercenaryFall", path.combine(SPRITE_PATH, "fall.png"), 1, 15, 14) +local sprite_climb = Sprite.new("NemesisMercenaryClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 13, 23) +local sprite_death = Sprite.new("NemesisMercenaryDeath", path.combine(SPRITE_PATH, "death.png"), 15, 50, 17) +local sprite_decoy = Sprite.new("NemesisMercenaryDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 22, 18) +local sprite_drone_idle = Sprite.new("DronePlayerNemesisMercenaryIdle", path.combine(SPRITE_PATH, "drone_idle.png"), 5, 22, 13) +local sprite_drone_shoot = Sprite.new("DronePlayerNemesisMercenaryShoot", path.combine(SPRITE_PATH, "drone_shoot.png"), 5, 30, 12) +local sprite_palette = Sprite.new("NemesisMercenaryPalette", path.combine(SPRITE_PATH, "palette.png")) + +local sprite_shoot1_1a = Sprite.new("NemesisMercenaryShoot1_1a", path.combine(SPRITE_PATH, "shoot1_1a.png"), 5, 17, 37) +local sprite_shoot1_2a = Sprite.new("NemesisMercenaryShoot1_2a", path.combine(SPRITE_PATH, "shoot1_2a.png"), 5, 21, 16) +local sprite_shoot1_3a = Sprite.new("NemesisMercenaryShoot1_3a", path.combine(SPRITE_PATH, "shoot1_3a.png"), 5, 19, 42) +local sprite_shoot1_4a = Sprite.new("NemesisMercenaryShoot1_4a", path.combine(SPRITE_PATH, "shoot1_4a.png"), 5, 31, 25) +local sprite_shoot1b = Sprite.new("NemesisMercenaryShoot1b", path.combine(SPRITE_PATH, "shoot1b.png"), 5, 20, 31) +local sprite_shoot1lb = Sprite.new("NemesisMercenaryShoot1lb", path.combine(SPRITE_PATH, "shoot1lb.png"), 5, 64, 13) + +local sprite_shoot2a = Sprite.new("NemesisMercenaryShoot2a", path.combine(SPRITE_PATH, "shoot2a.png"), 5, 17, 60) +local sprite_shoot2b = Sprite.new("NemesisMercenaryShoot2b", path.combine(SPRITE_PATH, "shoot2b.png"), 5, 21, 53) +local sprite_shoot2lb = Sprite.new("NemesisMercenaryShoot2lb", path.combine(SPRITE_PATH, "shoot2lb.png"), 5, 84, 60) + +local sprite_shoot3 = Sprite.new("NemesisMercenaryShoot3", path.combine(SPRITE_PATH, "shoot3.png"), 9, 21, 16) + +local sprite_shoot4_1 = Sprite.new("NemesisMercenaryShoot4_1", path.combine(SPRITE_PATH, "shoot4_1.png"), 8, 28, 29) +local sprite_shoot4_2 = Sprite.new("NemesisMercenaryShoot4_2a", path.combine(SPRITE_PATH, "shoot4_2a.png"), 11, 47, 130) +local sprite_shoot4_2b = Sprite.new("NemesisMercenaryShoot4_2b", path.combine(SPRITE_PATH, "shoot4_2b.png"), 11, 77, 77) +local sprite_shoot4_2c = Sprite.new("NemesisMercenaryShoot4_2c", path.combine(SPRITE_PATH, "shoot4_2c.png"), 11, 82, 85) +local sprite_shoot4_3 = Sprite.new("NemesisMercenaryShoot4_3", path.combine(SPRITE_PATH, "shoot4_3.png"), 5, 24, 15) + +local sprite_sparks = Sprite.new("NemesisMercenarySparks", path.combine(SPRITE_PATH, "sparks.png"), 5, 13, 17) +local sprite_sparks2 = Sprite.new("NemesisMercenarySparks2", path.combine(SPRITE_PATH, "sparks2.png"), 5, 56, 40) +local sprite_boost = Sprite.new("NemesisMercenaryBoost", path.combine(SPRITE_PATH, "boost.png"), 5, 59, 20) + +local sprite_portal = Sprite.new("NemesisMercenaryPortal", path.combine(SPRITE_PATH, "portal.png"), 27, 108, 170) +local sprite_portal_inside = Sprite.new("NemesisMercenaryPortalInside", path.combine(SPRITE_PATH, "portal_inside.png"), 27, 108, 170) +local sound_portal = Sound.new("NemesisMercenaryPortal", path.combine(SOUND_PATH, "portal.ogg")) + +local sprite_log = Sprite.new("NemesisMercenaryLog", path.combine(SPRITE_PATH, "log.png")) + +sprite_walk:set_speed(0.75) +sprite_walk_back:set_speed(0.75) + +local sound_shoot2a = Sound.new("NemesisMercenaryShoot2a", path.combine(SOUND_PATH, "shoot2a.ogg")) +local sound_shoot2b = Sound.new("NemesisMercenaryShoot2b", path.combine(SOUND_PATH, "shoot2b.ogg")) +local sound_shoot3a = Sound.new("NemesisMercenaryShoot3a", path.combine(SOUND_PATH, "shoot3a.ogg")) +local sound_shoot3b = Sound.new("NemesisMercenaryShoot3b", path.combine(SOUND_PATH, "shoot3b.ogg")) +local sound_disappear = Sound.new("NemesisMercenaryDisappear", path.combine(SOUND_PATH, "disappear.ogg")) +local sound_teleport = Sound.new("NemesisMercenaryTeleport", path.combine(SOUND_PATH, "teleport.ogg")) +local sound_shoot4 = Sound.new("NemesisMercenaryShoot4", path.combine(SOUND_PATH, "shoot4.ogg")) +local sound_slide = Sound.new("NemesisMercenarySlide", path.combine(SOUND_PATH, "slide.ogg")) +--local sound_select = Sound.new("UISurvivorsNemesisMercenary", path.combine(SOUND_PATH, "select.ogg")) + +local particleWispGTracer = Particle.find("WispGTracer") +local particleBlood = Particle.find("Blood1") + +local particleSpecialTrail = Particle.new("NemmercSpecialTrail") +particleSpecialTrail:set_shape(Particle.Shape.DISK) +particleSpecialTrail:set_scale(1, 1) +particleSpecialTrail:set_size(0.3, 0.3, -0.01, 0) +particleSpecialTrail:set_life(60, 60) +particleSpecialTrail:set_color2(Color.WHITE, Color.from_rgb(233, 142, 67)) + +local nemmerc = Survivor.new("nemesisMercenary") + +nemmerc:set_stats_base({ + health = 118, + damage = 11, + regen = 0.02, +}) + +nemmerc:set_stats_level({ + health = 36, + damage = 3.5, + regen = 0.0025, + armor = 3, +}) +--[[ +local nemmerc_log = SurvivorLog.new_from_survivor(nemmerc) +nemmerc_log.portrait_id = sprite_log +nemmerc_log.sprite_id = sprite_walk +nemmerc_log.sprite_icon_id = sprite_portrait +]] +nemmerc.primary_color = Color.from_hex(0xFC4E45) + +nemmerc.sprite_loadout = sprite_loadout +nemmerc.sprite_portrait = sprite_portrait +nemmerc.sprite_portrait_small = sprite_portrait_small + +nemmerc.sprite_idle = sprite_idle -- used by skin systen for idle sprite +nemmerc.sprite_title = sprite_walk -- also used by skin system for walk sprite +nemmerc.sprite_credits = sprite_credits + +nemmerc.sprite_palette = sprite_palette +nemmerc.sprite_portrait_palette = sprite_palette +nemmerc.sprite_loadout_palette = sprite_palette + +--nemmerc.select_sound_id = sound_select +nemmerc.cape_offset = Array.new({-3, -8, 0, -5}) + +Callback.add(nemmerc.on_init, function(actor) + actor.sprite_idle = sprite_idle + actor.sprite_walk = sprite_walk + actor.sprite_walk_half = Array.new({sprite_walk, nil, 0, sprite_walk_back}) + actor.sprite_jump = sprite_jump + actor.sprite_jump_peak = sprite_jump_peak + actor.sprite_fall = sprite_fall + actor.sprite_climb = sprite_climb + actor.sprite_death = sprite_death + actor.sprite_decoy = sprite_decoy + actor.sprite_drone_idle = sprite_drone_idle + actor.sprite_drone_shoot = sprite_drone_shoot + + -- slide sprites + actor.sprite_idle_slide = sprite_idle2 + actor.sprite_shoot1_slide = sprite_shoot1b + actor.sprite_shoot2_slide = sprite_shoot2b + + actor.sprite_idle_slide_left = sprite_idle2l + actor.sprite_shoot1_slide_left = sprite_shoot1lb + actor.sprite_shoot2_slide_left = sprite_shoot2lb + + actor.sprite_idle_normal = sprite_idle + actor.sprite_walk_normal = sprite_walk + actor.sprite_walk_back_normal = sprite_walk_back + actor.sprite_jump_normal = sprite_jump + actor.sprite_jump_peak_normal = sprite_jump_peak + actor.sprite_fall_normal = sprite_fall + + local data = Instance.get_data(actor) + + data.nemmerc_slide = 0 + data.nemmerc_slide_dir = 0 + data.nemmerc_slide_turned = 0 + data.nemmerc_slide_prep = 0 + data.nemmerc_primary_combo_timer = 0 + data.nemmerc_primary_combo_count = 0 + + actor.is_nemesis = true -- toggles the portal spawning animation + + actor.sprite_portal = sprite_portal + actor.sprite_portal_inside = sprite_portal_inside + actor.sound_portal = sound_portal +end) + +local slide_packet = Packet.new("SyncNemmercSlide") + +local slide_serializer = function(buffer, actor, dir, hold_dir, br_left, br_right) + buffer:write_instance(actor) + buffer:write_short(dir) + buffer:write_short(hold_dir) + buffer:write_bool(br_left) + buffer:write_bool(br_right) +end + +local slide_deserializer = function(buffer) + local actor = buffer:read_instance() + local dir = buffer:read_short() + local hold_dir = buffer:read_short() + local br_left = buffer:read_bool() + local br_right = buffer:read_bool() + + local data = Instance.get_data(actor) + + if br_left or br_right then + data.nemmerc_slide = math.max(data.nemmerc_slide - 1, 0) + end + + actor.pHspeed = data.nemmerc_slide / 50 * 2 * actor.pHmax * dir + actor.pHmax * dir + actor.image_xscale = dir + actor.moveLeft = 0 + actor.moveRight = 0 +end + +slide_packet:set_serializers(slide_serializer, slide_deserializer) + +local aim_packet = Packet.new("SyncNemmercSlideAim") + +local aim_serializer = function(buffer, actor, aim, dir) + buffer:write_instance(actor) + buffer:write_int(aim) + buffer:write_byte(dir + 1) +end + +local aim_deserializer = function(buffer) + local actor = buffer:read_instance() + local aim = buffer:read_int() + local dir = buffer:read_byte() - 1 + + local data = Instance.get_data(actor) + + data.nemmerc_slide_dir = dir + + if aim == 0 then + actor.sprite_idle = actor.sprite_idle_slide_left + actor.sprite_walk = actor.sprite_idle_slide_left + actor.sprite_walk_half = Array.new({actor.sprite_idle_slide_left, nil, 0, actor.sprite_idle_slide_left}) + actor.sprite_jump = actor.sprite_idle_slide_left + actor.sprite_jump_peak = actor.sprite_idle_slide_left + actor.sprite_fall = actor.sprite_idle_slide_left + actor.sprite_shoot1 = actor.sprite_shoot1_slide_left + actor.sprite_shoot2 = actor.sprite_shoot2_slide_left + elseif aim == 1 then + actor.sprite_idle = actor.sprite_idle_slide + actor.sprite_walk = actor.sprite_idle_slide + actor.sprite_walk_half = Array.new({actor.sprite_idle_slide, nil, 0, actor.sprite_idle_slide}) + actor.sprite_jump = actor.sprite_idle_slide + actor.sprite_jump_peak = actor.sprite_idle_slide + actor.sprite_fall = actor.sprite_idle_slide + actor.sprite_shoot1 = actor.sprite_shoot1_slide + actor.sprite_shoot2 = actor.sprite_shoot2_slide + end +end + +aim_packet:set_serializers(aim_serializer, aim_deserializer) + +Callback.add(nemmerc.on_step, function(actor) + local data = Instance.get_data(actor) + + if data.nemmerc_primary_combo_timer > 0 then + data.nemmerc_primary_combo_timer = data.nemmerc_primary_combo_timer - 1 + else + data.nemmerc_primary_combo_count = 0 + end + + if data.nemmerc_slide > 0 then + data.nemmerc_slide = data.nemmerc_slide - 1 - -- if actor:is_authority() then - -- local damage = actor:skill_get_damage(stab) + actor.image_xscale = data.nemmerc_slide_dir + if actor:is_authority() then + local braking_left = (Util.bool(actor.moveLeft) and not Util.bool(actor.moveRight) and data.nemmerc_slide_dir > 0) + local braking_right = (Util.bool(actor.moveRight) and not Util.bool(actor.moveLeft) and data.nemmerc_slide_dir < 0) + + if braking_left or braking_right then + data.nemmerc_slide = math.max(data.nemmerc_slide - 1, 0) + end + + actor.pHspeed = data.nemmerc_slide / 50 * 2 * actor.pHmax * data.nemmerc_slide_dir + actor.pHmax * data.nemmerc_slide_dir + actor.moveLeft = 0 + actor.moveRight = 0 + + if actor.hold_facing_direction_xscale ~= data.nemmerc_slide_dir and data.nemmerc_slide_turned ~= actor.hold_facing_direction_xscale then + actor.sprite_idle = actor.sprite_idle_slide_left + actor.sprite_walk = actor.sprite_idle_slide_left + actor.sprite_walk_half = Array.new({actor.sprite_idle_slide_left, nil, 0, actor.sprite_idle_slide_left}) + actor.sprite_jump = actor.sprite_idle_slide_left + actor.sprite_jump_peak = actor.sprite_idle_slide_left + actor.sprite_fall = actor.sprite_idle_slide_left + actor.sprite_shoot1 = actor.sprite_shoot1_slide_left + actor.sprite_shoot2 = actor.sprite_shoot2_slide_left + + data.nemmerc_slide_turned = actor.hold_facing_direction_xscale + + if Net.online then + aim_packet:send_to_all(actor, 0, data.nemmerc_slide_dir) + end + elseif actor.hold_facing_direction_xscale == data.nemmerc_slide_dir and data.nemmerc_slide_turned ~= actor.hold_facing_direction_xscale then + actor.sprite_idle = actor.sprite_idle_slide + actor.sprite_walk = actor.sprite_idle_slide + actor.sprite_walk_half = Array.new({actor.sprite_idle_slide, nil, 0, actor.sprite_idle_slide}) + actor.sprite_jump = actor.sprite_idle_slide + actor.sprite_jump_peak = actor.sprite_idle_slide + actor.sprite_fall = actor.sprite_idle_slide + actor.sprite_shoot1 = actor.sprite_shoot1_slide + actor.sprite_shoot2 = actor.sprite_shoot2_slide + + data.nemmerc_slide_turned = actor.hold_facing_direction_xscale + + if Net.online then + aim_packet:send_to_all(actor, 1, data.nemmerc_slide_dir) + end + end - -- if not GM.skill_util_update_heaven_cracker(actor, damage, actor.image_xscale) then - -- local buff_shadow_clone = Buff.find("ror", "shadowClone") - -- for i=0, actor:buff_stack_count(buff_shadow_clone) do - -- local attack = actor:fire_explosion(actor.x + actor.image_xscale * 30, actor.y, 100, 65, damage, nil, gm.constants.sSparks9) - -- attack.attack_info.climb = i * 8 - -- attack.max_hit_number = 3 - -- end - -- end - -- end - -- end - - -- actor:skill_util_exit_state_on_anim_end() --- end) - --- stateStab:onGetInterruptPriority(function(actor, data) - -- if actor.image_index >= 6 then - -- return State.ACTOR_STATE_INTERRUPT_PRIORITY.any - -- end --- end) - --- -- quick trigger --- trigger:set_skill_icon(sprite_skills, 1) --- trigger.cooldown = 6 * 60 --- trigger.damage = 6 --- trigger.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.any --- trigger:clear_callbacks() - --- local stateTrigger = State.new(NAMESPACE, "quickTrigger") --- stateTrigger:clear_callbacks() - --- trigger:onActivate(function(actor, skill) - -- actor:enter_state(stateTrigger) --- end) - --- stateTrigger:onEnter(function(actor, data) - -- actor.image_index = 0 - -- data.fired = 0 - -- data.reloaded = 0 --- end) - --- stateTrigger:onStep(function(actor, data) - -- if actor:get_data().slide > 0 then - -- actor:actor_animation_set(sprite_shoot2b, 0.2) - -- else - -- actor:skill_util_fix_hspeed() - -- actor:actor_animation_set(sprite_shoot2a, 0.2) - -- end - - -- if data.fired == 0 then - -- data.fired = 1 - -- actor:sound_play(sound_shoot2a, 1, 0.9 + math.random() * 0.2) - -- actor:screen_shake(3) + if Net.online and Global._current_frame % 10 == 0 then + slide_packet:send_to_all(actor, data.nemmerc_slide_dir, actor.hold_facing_direction_xscale, braking_left, braking_right) + end + end + else + actor.sprite_idle = actor.sprite_idle_normal + actor.sprite_walk = actor.sprite_walk_normal + actor.sprite_walk_half = Array.new({sprite_walk, nil, 0, actor.sprite_walk_back_normal}) + actor.sprite_jump = actor.sprite_jump_normal + actor.sprite_jump_peak = actor.sprite_jump_peak_normal + actor.sprite_fall = actor.sprite_fall_normal + end +end) + +local primary = nemmerc:get_skills(Skill.Slot.PRIMARY)[1] +local secondary = nemmerc:get_skills(Skill.Slot.SECONDARY)[1] +local utility = nemmerc:get_skills(Skill.Slot.UTILITY)[1] +local special = nemmerc:get_skills(Skill.Slot.SPECIAL)[1] +local specialS = Skill.new("nemesisMercenaryVBoosted") + +-- Lascerate +primary.sprite = sprite_skills +primary.subimage = 0 +primary.damage = 0.8 +primary.cooldown = 0 +primary.is_primary = true +primary.required_interrupt_priority = ActorState.InterruptPriority.ANY + +local statePrimaryA = ActorState.new("nemMercenaryAttachmentStabA") +local statePrimaryB = ActorState.new("nemMercenaryAttachmentStabB") +local statePrimaryC = ActorState.new("nemMercenaryAttachmentStabC") +local statePrimaryD = ActorState.new("nemMercenaryAttachmentStabD") +local statePrimaryE = ActorState.new("nemMercenaryAttachmentStabE") + +Callback.add(primary.on_activate, function(actor, skill, slot) + local data = Instance.get_data(actor) + + if data.nemmerc_slide <= 0 then + if data.nemmerc_primary_combo_count == 0 then + actor:set_state(statePrimaryA) + elseif data.nemmerc_primary_combo_count == 1 then + actor:set_state(statePrimaryB) + elseif data.nemmerc_primary_combo_count == 2 then + actor:set_state(statePrimaryC) + elseif data.nemmerc_primary_combo_count == 3 then + actor:set_state(statePrimaryD) + end + else + actor:set_state(statePrimaryE) + end +end) + +local function nemmerc_primary_code(actor, data, sprite_a, sound, combo) + local get_data = Instance.get_data(actor) + + local speed = 0.2 + if combo == 3 then + speed = 0.17 + end + + actor:skill_util_fix_hspeed() + actor:actor_animation_set(sprite_a, speed) + + local condition = (actor.image_index >= 1 and data.fired == 0) + if combo == 1 then + condition = ((actor.image_index >= 0 and data.fired == 0) or (actor.image_index >= 2 and data.fired == 1)) + end + + if condition then + data.fired = data.fired + 1 + get_data.nemmerc_primary_combo_timer = 45 - -- if actor:is_authority() then - -- local damage = actor:skill_get_damage(trigger) + if combo < 3 then + get_data.nemmerc_primary_combo_count = combo + 1 + else + get_data.nemmerc_primary_combo_count = 0 + end + + if data.fired == 2 then + sound = gm.constants.wMercenaryShoot1_3 + end + + actor:sound_play(sound, 1, 0.9 + math.random() * 0.2) + actor:skill_util_nudge_forward(2 * actor.image_xscale) + + if actor:is_authority() then + local damage = actor:skill_get_damage(primary) + local offset = 40 + local range = 100 - -- local buff_shadow_clone = Buff.find("ror", "shadowClone") - -- for i=0, actor:buff_stack_count(buff_shadow_clone) do - -- local attack = actor:fire_bullet(actor.x, actor.y, 200, actor:skill_util_facing_direction(), damage, 1, gm.constants.sSparks4, Attack_Info.TRACER.enforcer1) - -- attack.attack_info:set_stun(2) - -- attack.attack_info.climb = i * 8 - -- end - -- end - -- end - - -- if data.reloaded == 0 and actor.image_index >= 3 then - -- data.reloaded = 1 - -- actor:sound_play(sound_shoot2b, 0.6, 0.9 + math.random() * 0.2) - -- end - - -- actor:skill_util_exit_state_on_anim_end() --- end) - --- stateTrigger:onGetInterruptPriority(function(actor, data) - -- if actor.image_index >= 3 then - -- return State.ACTOR_STATE_INTERRUPT_PRIORITY.any - -- end --- end) - --- -- blinding slide --- slide:set_skill_icon(sprite_skills, 2) --- slide.cooldown = 3 * 60 --- slide.is_utility = true --- slide.override_strafe_direction = true --- slide.ignore_aim_direction = true --- slide:clear_callbacks() - --- local stateSlide = State.new(NAMESPACE, "blindingSlide") --- stateSlide:clear_callbacks() - --- slide:onActivate(function(actor, skill) - -- actor:enter_state(stateSlide) --- end) - --- stateSlide:onEnter(function(actor, data) - -- actor.image_index = 0 - -- actor:get_data().slidePrep = 1 - -- data.fired = 0 - -- data.counter = 0 - -- actor:sound_play(gm.constants.wMercenary_Parry_Release, 1, 0.9 + math.random() * 0.2) --- end) - --- stateSlide:onStep(function(actor, data) - -- actor:actor_animation_set(sprite_shoot3, 0.3, false) - -- if actor:get_data().slide < 25 then - -- actor:skill_util_fix_hspeed() - -- actor:skill_util_nudge_forward(1 * -actor.image_xscale) - -- end - - -- if data.fired == 0 then - -- if actor.invincible < 10 then - -- actor.invincible = 10 - -- end - -- end - - -- if actor.image_index >= 6 and data.fired == 0 then - -- data.counter = 11 - -- data.fired = 1 - -- actor:sound_play(gm.constants.wCommandoSlide, 1, 0.9 + math.random() * 0.2) - -- actor:get_data().slide = 50 - -- actor:get_data().slideDir = actor.image_xscale - -- end - - -- if data.counter > 0 and data.fired == 1 then - -- data.counter = data.counter - 1 - -- elseif data.counter == 1 and data.fired == 1 then - -- actor:get_data().slidePrep = 0 - -- end - - -- actor:skill_util_exit_state_on_anim_end() --- end) - --- stateSlide:onExit(function(actor, data) - -- actor:get_data().slidePrep = 0 --- end) - --- Callback.add(Callback.TYPE.onDamageBlocked, "SSRNemmercSlideReloadShotgun", function(actor, attacker, hit_info) - -- if actor.object_index == gm.constants.oP and actor.class == nemmerc_id then - -- if actor:get_data().slidePrep == 1 then - -- actor:refresh_skill(Skill.SLOT.secondary) - -- actor:get_data().slidePrep = 0 - -- actor:sound_play(sound_shoot2b, 0.6, 0.9 + math.random() * 0.2) - -- end - -- end --- end) - --- -- devitalize --- devit:set_skill_icon(sprite_skills, 3) --- devit.cooldown = 8 * 60 --- devit.damage = 8.5 --- devit.require_key_press = false --- devit.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.any --- devit:set_skill_upgrade(absDevit) --- devit:clear_callbacks() - --- local stateDevit = State.new(NAMESPACE, "devitalize") --- stateDevit:clear_callbacks() - --- devit:onActivate(function(actor, skill) - -- actor:enter_state(stateDevit) --- end) - --- stateDevit:onEnter(function(actor, data) - -- actor.image_index = 0 - -- data.fired = 0 - -- data.reloaded = 0 --- end) - --- stateDevit:onStep(function(actor, data) - -- actor:skill_util_fix_hspeed() - -- actor:actor_animation_set(sprite_shoot4, 0.3) - - -- if actor.invincible < 5 then - -- actor.invincible = 5 - -- end - - -- if actor.image_index >= 5 then - -- local target = nil - -- local enemies = List.new() - -- actor:collision_rectangle_list(actor.x - 25 * actor.image_xscale, actor.y - 500, actor.x + 500 * actor.image_xscale, actor.y + 500, gm.constants.pActor, false, true, enemies, false) - -- for _, enemy in ipairs(enemies) do - -- if enemy.team ~= actor.team and gm.point_distance(actor.x, actor.y, enemy.x, enemy.y) < 500 and enemy.stunned then - -- if target == nil then - -- target = enemy - -- else - -- if enemy.hp < target.hp then - -- target = enemy - -- end - -- end - -- end - -- end - -- enemies:destroy() + if combo == 3 then + damage = damage * 1.5 + offset = 60 + range = 140 + end + + if not GM.skill_util_update_heaven_cracker(actor, damage, actor.image_xscale) then + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do + local attack = actor:fire_explosion(actor.x + offset * actor.image_xscale, actor.y, range, 80, damage, nil, sprite_sparks).attack_info + attack.climb = i * 8 * 1.35 + + if combo == 3 then + attack.knockback = attack.knockback + 3 + attack.knockback_direction = actor.image_xscale + end + end + end + end + end +end + +Callback.add(statePrimaryA.on_enter, function(actor, data) + actor.image_index = 0 + data.fired = 0 +end) + +Callback.add(statePrimaryA.on_step, function(actor, data) + nemmerc_primary_code(actor, data, sprite_shoot1_1a, gm.constants.wMercenaryShoot1_2, 0) + actor:skill_util_exit_state_on_anim_end() +end) + +Callback.add(statePrimaryA.on_get_interrupt_priority, function(actor, data) + if actor.image_index >= 2 then + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD + else + return ActorState.InterruptPriority.PRIORITY_SKILL + end +end) + +Callback.add(statePrimaryB.on_enter, function(actor, data) + actor.image_index = 0 + data.fired = 0 +end) + +Callback.add(statePrimaryB.on_step, function(actor, data) + nemmerc_primary_code(actor, data, sprite_shoot1_2a, gm.constants.wMercenaryShoot1_1, 1) + actor:skill_util_exit_state_on_anim_end() +end) + +Callback.add(statePrimaryB.on_get_interrupt_priority, function(actor, data) + if actor.image_index >= 2 then + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD + else + return ActorState.InterruptPriority.PRIORITY_SKILL + end +end) + +Callback.add(statePrimaryC.on_enter, function(actor, data) + actor.image_index = 0 + data.fired = 0 +end) + +Callback.add(statePrimaryC.on_step, function(actor, data) + nemmerc_primary_code(actor, data, sprite_shoot1_3a, gm.constants.wMercenaryShoot1_2, 2) + actor:skill_util_exit_state_on_anim_end() +end) + +Callback.add(statePrimaryC.on_get_interrupt_priority, function(actor, data) + if actor.image_index >= 2 then + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD + else + return ActorState.InterruptPriority.PRIORITY_SKILL + end +end) + +Callback.add(statePrimaryD.on_enter, function(actor, data) + actor.image_index = 0 + data.fired = 0 +end) + +Callback.add(statePrimaryD.on_step, function(actor, data) + nemmerc_primary_code(actor, data, sprite_shoot1_4a, gm.constants.wMercenary_Parry_StandardSlash, 3) + actor:skill_util_exit_state_on_anim_end() +end) + +Callback.add(statePrimaryD.on_get_interrupt_priority, function(actor, data) + return ActorState.InterruptPriority.PRIORITY_SKILL +end) + +Callback.add(statePrimaryE.on_enter, function(actor, data) + actor.image_index = 0 + data.fired = 0 + data.dir = actor.hold_facing_direction_xscale +end) + +Callback.add(statePrimaryE.on_step, function(actor, data) + local get_data = Instance.get_data(actor) + actor:actor_animation_set(actor.sprite_shoot1, 0.2) + + if actor.image_index >= 1 and data.fired == 0 then + data.fired = 1 - -- if target == nil then - -- local enemies = List.new() - -- actor:collision_rectangle_list(actor.x - 25 * actor.image_xscale, actor.y - 250, actor.x + 250 * actor.image_xscale, actor.y + 250, gm.constants.pActor, false, true, enemies, true) - -- for _, enemy in ipairs(enemies) do - -- if enemy.team ~= actor.team and gm.point_distance(actor.x, actor.y, enemy.x, enemy.y) < 250 then - -- if target == nil then - -- target = enemy - -- else - -- if enemy.hp < target.hp then - -- target = enemy - -- end - -- end - -- end - -- end - -- enemies:destroy() - -- end + actor:sound_play(gm.constants.wMercenaryShoot1_2, 1, 0.9 + math.random() * 0.2) - -- if target == nil then - -- local enemies = List.new() - -- actor:collision_rectangle_list(actor.x - 25 * actor.image_xscale, actor.y - 250, actor.x + 250 * actor.image_xscale, actor.y + 250, gm.constants.pActorCollisionBase, false, true, enemies, true) - -- for _, enemy in ipairs(enemies) do - -- if enemy.team ~= actor.team and gm.point_distance(actor.x, actor.y, enemy.x, enemy.y) < 250 then - -- if target == nil then - -- target = enemy - -- end - -- end - -- end - -- enemies:destroy() - -- end + if actor:is_authority() then + local damage = actor:skill_get_damage(primary) + local offset = 40 + local range = 100 + + if not GM.skill_util_update_heaven_cracker(actor, damage, actor.image_xscale) then + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do + local attack = actor:fire_explosion(actor.x + offset * data.dir, actor.y, range, 80, damage, nil, sprite_sparks).attack_info + attack.climb = i * 8 * 1.35 + end + end + end + end + + actor:skill_util_exit_state_on_anim_end() +end) + +Callback.add(statePrimaryE.on_get_interrupt_priority, function(actor, data) + return ActorState.InterruptPriority.PRIORITY_SKILL +end) + +-- Quick Trigger +secondary.sprite = sprite_skills +secondary.subimage = 1 +secondary.cooldown = 6 * 60 +secondary.damage = 4 +secondary.required_interrupt_priority = ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD +secondary.require_key_press = true + +local stateSecondary = ActorState.new("nemMercenaryQuickTrigger") + +Callback.add(secondary.on_activate, function(actor, skill, slot) + actor:set_state(stateSecondary) +end) + +Callback.add(stateSecondary.on_enter, function(actor, data) + local get_data = Instance.get_data(actor) + + actor.image_index = 0 + data.fired = 0 + data.reloaded = 0 + data.dir = 90 - 90 * actor.hold_facing_direction_xscale + + data.sprite = nil + if get_data.nemmerc_slide > 0 then + data.sprite = actor.sprite_shoot2 + else + data.sprite = sprite_shoot2a.value + end +end) + +Callback.add(stateSecondary.on_step, function(actor, data) + local get_data = Instance.get_data(actor) + + actor:actor_animation_set(data.sprite, 0.2) + + if get_data.nemmerc_slide <= 0 then + actor:skill_util_fix_hspeed() + end + + if data.fired == 0 then + data.fired = 1 + actor:sound_play(sound_shoot2a.value, 1.5, 0.9 + math.random() * 0.2) + actor:screen_shake(3) - -- if target ~= nil and data.fired == 0 then - -- data.fired = 1 - -- actor:sound_play(sound_shoot4, 1, 0.9 + math.random() * 0.2) - -- actor:screen_shake(3) - -- if gm._mod_net_isHost() then - -- local damage = actor:skill_get_damage(devit) - -- local sparks = sprite_sparks - -- if actor:item_stack_count(Item.find("ror", "ancientScepter")) > 0 then - -- damage = actor:skill_get_damage(absDevit) - -- sparks = sprite_sparks2 - -- end + if actor:is_authority() then + local damage = actor:skill_get_damage(secondary) + + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do + local attack = actor:fire_bullet(actor.x, actor.y - 4, 200, data.dir, damage, 1, sprite_sparks2, Tracer.ENFORCER1) + attack.attack_info.stun = 2 + attack.attack_info.climb = i * 8 * 1.35 + end + end + end + + if data.reloaded == 0 and actor.image_index >= 3 then + data.reloaded = 1 + actor:sound_play(sound_shoot2b.value, 0.6, 0.9 + math.random() * 0.2) + end + + actor:skill_util_exit_state_on_anim_end() +end) + +Callback.add(stateSecondary.on_get_interrupt_priority, function(actor, data) + if actor.image_index >= 2 then + return ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD + else + return ActorState.InterruptPriority.PRIORITY_SKILL + end +end) + +-- Blinding Slide +utility.sprite = sprite_skills +utility.subimage = 2 +utility.cooldown = 3 * 60 +utility.is_utility = true +utility.override_strafe_direction = true +utility.ignore_aim_direction = true +utility.required_interrupt_priority = ActorState.InterruptPriority.SKILL + +local stateUtility = ActorState.new("nemMercenaryBlindingSlide") +stateUtility.activity_flags = ActorState.ActivityFlag.ALLOW_ROPE_CANCEL + +Callback.add(utility.on_activate, function(actor, skill, slot) + actor:set_state(stateUtility) +end) + +Callback.add(stateUtility.on_enter, function(actor, data) + local get_data = Instance.get_data(actor) + get_data.nemmerc_slide_prep = 1 + get_data.nemmerc_slide = 0 + + actor.image_index = 0 + data.fired = 0 + data.sound = 0 + data.counter = 0 +end) + +Callback.add(stateUtility.on_step, function(actor, data) + actor:actor_animation_set(sprite_shoot3, 0.3, false) + + local get_data = Instance.get_data(actor) + + local release = not Util.bool(actor.c_skill) + + if not actor:is_authority() then + release = Util.bool(actor.activity_var2) + end + + if release then + if Net.online then + if Net.host then + gm.server_message_send(0, 43, actor:get_object_index_self(), actor.m_id, 1, Math.sign(actor.image_xscale)) + else + gm.client_message_send(43, 1, Math.sign(actor.image_xscale)) + end + end + + if actor.image_index < 6 then + actor.image_index = 6 + end + end + + if actor.image_index >= 1 and data.sound == 0 then + data.sound = 1 + actor:sound_play(sound_shoot3a.value, 1, 0.9 + math.random() * 0.2) + end + + if get_data.nemmerc_slide < 25 then + actor:skill_util_fix_hspeed() + end + + if data.fired == 0 then + actor.invincible = math.max(actor.invincible, 5) + end + + if actor.image_index >= 6 and data.fired == 0 then + + data.counter = 11 + data.fired = 1 + + local sparks = Object.find("EfSparks"):create(actor.x, actor.y - 2) + sparks.sprite_index = sprite_boost + sparks.image_xscale = actor.image_xscale + sparks.image_yscale = 1 + + actor:sound_play(sound_slide.value, 1, 0.9 + math.random() * 0.2) + + get_data.nemmerc_slide = 50 + get_data.nemmerc_slide_dir = actor.image_xscale + get_data.nemmerc_slide_turned = 0 + end + + if data.counter > 0 and data.fired == 1 then + data.counter = data.counter - 1 + elseif data.counter == 1 and data.fired == 1 then + get_data.nemmerc_slide_prep = 0 + end + + actor:skill_util_exit_state_on_anim_end() +end) + +Callback.add(stateUtility.on_exit, function(actor, data) + local get_data = Instance.get_data(actor) + get_data.nemmerc_slide_prep = 0 +end) + +Callback.add(stateUtility.on_get_interrupt_priority, function(actor, data) + if actor.image_index >= 7 then + return ActorState.InterruptPriority.ANY + else + return ActorState.InterruptPriority.PRIORITY_SKILL + end +end) + +DamageDodge.add(function(api, current_dodge) + if not Instance.exists(api.hit) then return end + + local data = Instance.get_data(api.hit) + + if data.nemmerc_slide_prep == 1 then + api.hit:get_default_skill(Skill.Slot.SECONDARY):reset_cooldown() + data.nemmerc_slide_prep = 0 + api.hit:sound_play(sound_shoot3b.value, 0.7, 0.9 + math.random() * 0.2) + api.hit:sound_play(sound_shoot2b.value, 0.6, 0.9 + math.random() * 0.2) + + local flash = Object.find("EfFlash"):create(api.hit.x, api.hit.y) + flash.parent = api.hit + flash.rate = 0.1 + flash.image_alpha = 1 + + api.hit.invincible = math.max(api.hit.invincible, 50) + return DamageDodge.DEFLECTED + end +end) + +-- Devitalize +special.sprite = sprite_skills +special.subimage = 3 +special.cooldown = 8 * 60 +special.damage = 7 +special.required_interrupt_priority = ActorState.InterruptPriority.SKILL +special.upgrade_skill = specialS + +specialS.sprite = sprite_skills +specialS.subimage = 4 +specialS.cooldown = 8 * 60 +specialS.damage = 7 +specialS.required_interrupt_priority = ActorState.InterruptPriority.SKILL + +local stateSpecialPre = ActorState.new("nemMercenaryDevitalizePre") +local stateSpecial = ActorState.new("nemMercenaryDevitalize") +local stateSpecialEnd = ActorState.new("nemMercenaryDevitalizePost") + +local function nemmerc_get_target(actor) + local target = nil + + for _, enemy in ipairs(actor:get_collisions_rectangle(gm.constants.pActorCollisionBase, actor.x - 500 * actor.image_xscale, actor.y - 500, actor.x + 500 * actor.image_xscale, actor.y + 500)) do + if actor:attack_collision_canhit(enemy) and Math.distance(actor.x, actor.y, enemy.x, enemy.y) < 500 and GM.attack_collision_resolve(enemy).stunned and ssr_instance_line_of_sight(actor, enemy) then + if target == nil then + target = enemy + else + if GM.attack_collision_resolve(enemy).hp < GM.attack_collision_resolve(target).hp then + target = enemy + elseif GM.attack_collision_resolve(enemy).hp == GM.attack_collision_resolve(target).hp and Math.distance(actor.x, actor.y, enemy.x, enemy.y) <= Math.distance(actor.x, actor.y, target.x, target.y) then + target = enemy + end + end + end + end + + if target == nil then + for _, enemy in ipairs(actor:get_collisions_rectangle(gm.constants.pActorCollisionBase, actor.x - 500 * actor.image_xscale, actor.y - 500, actor.x + 500 * actor.image_xscale, actor.y + 500)) do + if actor:attack_collision_canhit(enemy) and Math.distance(actor.x, actor.y, enemy.x, enemy.y) < 500 and ssr_instance_line_of_sight(actor, enemy) then + if target == nil then + target = enemy + else + if GM.attack_collision_resolve(enemy).hp < GM.attack_collision_resolve(target).hp then + target = enemy + elseif GM.attack_collision_resolve(enemy).hp == GM.attack_collision_resolve(target).hp and Math.distance(actor.x, actor.y, enemy.x, enemy.y) <= Math.distance(actor.x, actor.y, target.x, target.y) then + target = enemy + end + end + end + end + end + + return target +end + +Callback.add(special.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecialPre) +end) + +Callback.add(specialS.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecialPre) +end) + +local devitalize_packet = Packet.new("SyncNemmercDevialize") + +local devitalize_serializer = function(buffer, actor, num) + buffer:write_instance(actor) + buffer:write_byte(num) +end + +local devitalize_deserializer = function(buffer) + local actor = buffer:read_instance() + local num = buffer:read_byte() + + actor.actor_state_current_data_table.slash = num +end + +devitalize_packet:set_serializers(devitalize_serializer, devitalize_deserializer) + +Callback.add(stateSpecialPre.on_enter, function(actor, data) + Instance.get_data(actor).nemmerc_slide = 0 + actor.image_index = 0 + + actor:sound_play(sound_disappear.value, 1, 1) + + local target = nil + target = nemmerc_get_target(actor) + + if target then + data.target = target.value + data.target_x = target.x + data.target_y = target.y + + if target.x >= actor.x then + actor.image_xscale = 1 + else + actor.image_xscale = -1 + end + else + data.target = nil + end + + if not (data.killed and data.killed == 1) then + data.begin_x = actor.x + data.begin_y = actor.y + end + + if actor:is_authority() then + data.slash = math.random(3) -- select the slash anim, we do this here so that theres a bit of time for the packet to arrive + if Net.online then + devitalize_packet:send_to_all(actor, data.slash) + end + end + + Instance.get_data(actor).nemmerc_special_state = 1 +end) + +Callback.add(stateSpecialPre.on_step, function(actor, data) + actor:skill_util_fix_hspeed() + actor:actor_animation_set(sprite_shoot4_1, 0.3) + + actor:get_active_skill(Skill.Slot.SPECIAL):freeze_cooldown() + actor.invincible = math.max(actor.invincible, 5) + + if (actor.image_index + actor.image_speed >= actor.image_number) or (data.killed and data.killed == 1) then + if data.target then + actor:set_state(stateSpecial) + else + actor:set_state(stateSpecialEnd) + end + end +end) + +Callback.add(stateSpecialPre.on_get_interrupt_priority, function(actor, data) + return ActorState.InterruptPriority.PRIORITY_SKILL +end) + +Callback.add(stateSpecial.on_enter, function(actor, data) + actor.image_index = 0 + + data.fired = 0 + data.killed = 0 + data.v_held = 1 + data.life = 0 +end) + +local slashes = { + sprite_shoot4_2, + sprite_shoot4_2b, + sprite_shoot4_2c, +} + +local efFollow = Object.new("NemmercSpecialFollow") +efFollow:set_sprite(sprite_shoot4_2) +efFollow:set_depth(-200) + +Callback.add(efFollow.on_create, function(self) + self.image_speed = 0.25 +end) + +Callback.add(efFollow.on_step, function(self) + if self.image_index < 4 and self.target and Instance.exists(self.target) then + self.x = self.target.x + self.y = self.target.y + end + + if self.image_index >= 10 then + self:destroy() + end +end) + +Callback.add(stateSpecial.on_step, function(actor, data) + actor:get_default_skill(Skill.Slot.SPECIAL):freeze_cooldown() + actor.invincible = math.max(actor.invincible, 5) + actor.activity_type = 8 + actor.visible = false + actor.pVspeed = actor.pVspeed * 0.7 + actor.pHspeed = 0 + data.life = data.life + 1 * math.min(1.5, math.max(1, 1 + (actor.attack_speed - 1) / 2)) + + local release = not Util.bool(actor.v_skill) + + if not actor:is_authority() then + release = Util.bool(actor.activity_var2) + end + + if release then + if Net.online then + if Net.host then + gm.server_message_send(0, 43, actor:get_object_index_self(), actor.m_id, 1, Math.sign(actor.image_xscale)) + else + gm.client_message_send(43, 1, Math.sign(actor.image_xscale)) + end + end + + data.v_held = 0 + end + + local target = Instance.wrap(data.target) + local true_target = Instance.wrap(GM.attack_collision_resolve(data.target)) + local xx = target.x or data.target_x + local yy = target.y or data.target_y + + if not (xx and yy) then + actor:set_state(stateSpecialEnd) + end + + local x_offset = 0 + local y_offset = 0 + local x2_offset = 0 + local y2_offset = 0 + + if data.slash == 1 then + x_offset = 0 + y_offset = -130 + x2_offset = 0 + y2_offset = 90 + elseif data.slash == 2 then + x_offset = -45 * actor.image_xscale + y_offset = 70 + x2_offset = 60 * actor.image_xscale + y2_offset = -50 + else + x_offset = -70 * actor.image_xscale + y_offset = -70 + x2_offset = 50 * actor.image_xscale + y2_offset = 65 + end + + if data.fired == 0 then + local spark = efFollow:create(xx, yy) + spark.sprite_index = slashes[data.slash] + spark.target = target + spark.image_xscale = actor.image_xscale + spark.image_speed = 0.25 * math.min(1.5, math.max(1, 1 + (actor.attack_speed - 1) / 2)) + + if ssr_is_near_ground(actor, xx, yy, 64) then + GM.teleport_nearby(actor, xx, yy) + else + actor:move_contact_solid(Math.direction(actor.x, actor.y, xx, yy), Math.distance(actor.x, actor.y, xx, yy)) + actor:move_contact_solid(Math.direction(actor.x, actor.y, xx + x2_offset, yy + y2_offset), Math.distance(actor.x, actor.y, xx + x2_offset, yy + y2_offset)) + end + + local amount = gm.round(Math.distance(data.begin_x, data.begin_y, xx + x_offset, yy + y_offset) / 16) + for i = 1, amount do + particleSpecialTrail:set_size(0.45 * (1 - (i / amount)), 0.45 * (1 - (i / amount)), -0.015, 0) -- set the size of the particle, bigger near the enemy + particleSpecialTrail:create(Math.lerp(data.begin_x, xx + x_offset, 1 - (i / amount)), Math.lerp(data.begin_y, yy + y_offset, 1 - (i / amount)), 1) -- create the particle, each time approaching the enemy more and more + end + + data.begin_x = xx + x_offset + data.begin_y = yy + y_offset + + actor:sound_play(sound_teleport.value, 0.7, 0.9 + math.random() * 0.2) + data.fired = 1 + elseif data.life >= 14 and data.fired == 1 then + data.fired = 2 + + data.begin_x = xx + x2_offset + data.begin_y = yy + y2_offset + + actor:sound_play(sound_shoot4.value, 1, 0.9 + math.random() * 0.2) + actor:screen_shake(3) + + if target and Instance.exists(target) then + if actor:is_authority() then + local damage = actor:skill_get_damage(special) - -- local buff_shadow_clone = Buff.find("ror", "shadowClone") - -- for i=0, actor:buff_stack_count(buff_shadow_clone) do - -- local attack = actor:fire_direct(target, damage, actor:skill_util_facing_direction(), target.x, target.y, sparks) - -- attack.attack_info.__ssr_nemmerc_devitalize = 1 - -- end - -- end - -- elseif target == nil and data.fired == 0 then - -- data.fired = 1 - -- actor:sound_play(gm.constants.wMercenary_EviscerateWhiff, 1, 0.9 + math.random() * 0.2) - -- end - -- end - - -- actor:skill_util_exit_state_on_anim_end() --- end) - --- stateDevit:onGetInterruptPriority(function(actor, data) - -- if actor.image_index >= 12 then - -- return State.ACTOR_STATE_INTERRUPT_PRIORITY.any - -- end --- end) - --- local devitalizeSync = Packet.new() --- devitalizeSync:onReceived(function(msg) - -- local x = msg:read_ushort() - -- local y = msg:read_ushort() - -- local parent = msg:read_instance() - -- local target = msg:read_instance() - -- local damage = msg:read_float() - - -- if not parent:exists() then return end - - -- if target.stunned == true and Instance.exists(parent) then - -- particleBlood:set_direction(0, 360, 0, 0) - -- particleBlood:create(x, y, 25, Particle.SYSTEM.middle) - -- parent:screen_shake(2) - -- end - -- if target.hp - damage <= 0 and Instance.exists(parent) then - -- parent:refresh_skill(Skill.SLOT.special) - -- end --- end) - --- local function sync_devit(x, y, parent, target, damage) - -- if not gm._mod_net_isHost() then - -- log.warning("sync_devit called on client!") - -- return - -- end - - -- local msg = devitalizeSync:message_begin() - -- msg:write_ushort(x) - -- msg:write_ushort(y) - -- msg:write_instance(parent) - -- msg:write_instance(target) - -- msg:write_float(damage) - -- msg:send_to_all() --- end - --- Callback.add(Callback.TYPE.onAttackHit, "SSRNemmercDevitalize", function(hit_info) - -- if hit_info.attack_info.__ssr_nemmerc_devitalize == 1 then - -- if gm._mod_net_isOnline() then - -- sync_devit(hit_info.x, hit_info.y, hit_info.parent, hit_info.target, hit_info.damage) - -- end - -- if hit_info.target.stunned == true and Instance.exists(hit_info.parent) then - -- particleBlood:set_direction(0, 360, 0, 0) - -- particleBlood:create(hit_info.x, hit_info.y, 25, Particle.SYSTEM.middle) - -- gm.draw_damage_networked(hit_info.x, hit_info.y - 16, hit_info.damage * 0.5, hit_info.critical, Color.from_hex(0xFC4E45), hit_info.parent.team, 0) - -- hit_info.parent:screen_shake(2) - -- hit_info.damage = hit_info.damage * 1.5 - -- end - -- if hit_info.target.hp - hit_info.damage <= 0 and Instance.exists(hit_info.parent) then - -- hit_info.parent:refresh_skill(Skill.SLOT.special) - -- end - -- end --- end) - --- -- absolute devitalization --- absDevit:set_skill_icon(sprite_skills, 4) --- absDevit.cooldown = 6 * 60 --- absDevit.damage = 11 --- absDevit.require_key_press = false --- absDevit.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.any --- absDevit:clear_callbacks() - --- absDevit:onActivate(function(actor, skill) - -- actor:enter_state(stateDevit) --- end) - --- local nemmercLog = Survivor_Log.new(nemmerc, sprite_log) \ No newline at end of file + if actor:item_count(Item.find("ancientScepter")) > 0 then + damage = actor:skill_get_damage(specialS) + end + + local buff_shadow_clone = Buff.find("shadowClone") + for i=0, actor:buff_count(buff_shadow_clone) do + local attack = actor:fire_direct(target, damage, actor:skill_util_facing_direction(), target.x, target.y).attack_info + attack.climb = i * 8 * 1.35 + attack.damage_color = 5350773 -- well use this specific color to signify that this is nemmerc's special attack + end + end + else + data.killed = 1 + end + + if actor:is_authority() then + data.slash = math.random(3) -- select the slash anim, we do this here so that theres a bit of time for the packet to arrive + if Net.online then + devitalize_packet:send_to_all(actor, data.slash) + end + end + end + + if data.life >= 35 then -- after 35 frames, end + if not Instance.exists(target) or (Instance.exists(target) and not GM.actor_is_alive(true_target)) or data.killed == 1 then + data.killed = 1 + else + data.killed = 0 + end + + if data.killed == 1 then + if data.v_held == 1 then + actor:set_state(stateSpecialPre) + else + actor:set_state(stateSpecialEnd) + end + else + actor:set_state(stateSpecialEnd) + end + end +end) + +Callback.add(stateSpecial.on_get_interrupt_priority, function(actor, data) + return ActorState.InterruptPriority.PRIORITY_SKILL +end) + +Callback.add(stateSpecialEnd.on_enter, function(actor, data) + actor.image_index = 0 + actor.free = 1 + actor.visible = true + + if not data.life then + data.life = 0 + end + + if ssr_is_near_ground(actor, actor.x, actor.y, 128) and data.life > 0 then + GM.teleport_nearby(actor, actor.x, actor.y) + end + + actor:sound_play(gm.constants.wMercenary_EviscerateWhiff, 1, 1) + + if data.killed == 1 then + actor:get_active_skill(Skill.Slot.SPECIAL):reset_cooldown() + end + + data.killed = 0 + data.life = 0 + + Instance.get_data(actor).nemmerc_special_state = 0 +end) + +Callback.add(stateSpecialEnd.on_step, function(actor, data) + actor:skill_util_fix_hspeed() + actor:actor_animation_set(sprite_shoot4_3, 0.25, false) + actor.invincible = math.max(actor.invincible, 5) + + actor:skill_util_exit_state_on_anim_end() +end) + +Callback.add(stateSpecialEnd.on_get_interrupt_priority, function(actor, data) + if actor.image_index >= 2 then + return ActorState.InterruptPriority.ANY + else + return ActorState.InterruptPriority.PRIORITY_SKILL + end +end) + +DamageCalculate.add(function(api) + -- this is incredibly jank but it works so im not gonna complain + if not api.damage_col then return end + if api.damage_col ~= 5350773 then return end + if not Instance.exists(api.parent) then return end + if not api.parent.class == nemmerc.value then return end + + api.damage_col = Color.WHITE + + if api.hit.stunned == true then + particleBlood:set_direction(0, 360, 0, 0) + particleBlood:create(api.hit_x, api.hit_y, 25, Particle.System.MIDDLE) + api.hit:screen_shake(2) + + if api.critical then + api.damage_mult(2, true) + else + api:set_critical(true) + end + end + + if api.parent:item_count(Item.find("ancientScepter")) > 0 then + if GM.actor_get_hp_percent(api.hit) <= 0.25 then + api.damage_col = Color.from_hex(0xf77c8e) + api.hit.hp = -10000 + end + end +end) \ No newline at end of file diff --git a/Survivors/technician.lua b/Survivors/technician.lua index 93a92ede..eb0f2aa7 100644 --- a/Survivors/technician.lua +++ b/Survivors/technician.lua @@ -1,108 +1,108 @@ local SPRITE_PATH = path.combine(PATH, "Sprites/Survivors/Technician") local SOUND_PATH = path.combine(PATH, "Sounds/Survivors/Technician") -local buff_mirror = Buff.find("ror", "shadowClone") --Shattered Mirror buff -local item_scepter = Item.find("ror", "ancientScepter") -local object_sparks = Object.find("ror", "EfSparks") --Standard hit sparks object -local object_flash = Object.find("ror", "EfFlash") -local object_missile = Object.find("ror", "EfMissile") -local particle_spark = Particle.find("ror", "Spark") - --- Define all the assets -local sprite_loadout = Resources.sprite_load(NAMESPACE, "TechnicianSelect", path.combine(SPRITE_PATH, "select.png"), 18, 28, 0) -local sprite_portrait = Resources.sprite_load(NAMESPACE, "TechnicianPortrait", path.combine(SPRITE_PATH, "portrait.png"), 3) --CSS screen/General UI survivor icons -local sprite_portrait_small = Resources.sprite_load(NAMESPACE, "TechnicianPortraitSmall", path.combine(SPRITE_PATH, "portraitSmall.png")) --Ditto -local sprite_skills = Resources.sprite_load(NAMESPACE, "TechnicianSkills", path.combine(SPRITE_PATH, "skills.png"), 7) -local sprite_credits = Resources.sprite_load(NAMESPACE, "TechnicianCredits", path.combine(SPRITE_PATH, "credits.png"), 1, 7, 12) --The sprite used at the end of the credits; 2x scale RoR1 idle sprite if it exists -local sprite_palette = Resources.sprite_load(NAMESPACE, "TechnicianPalette", path.combine(SPRITE_PATH, "palette.png")) --The color palette used to map skins -local sprite_log = Resources.sprite_load(NAMESPACE, "TechnicianLog", path.combine(SPRITE_PATH, "log.png")) --The logbook portrait - -local sprite_idle = Resources.sprite_load(NAMESPACE, "TechnicianIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 9, 13) -local sprite_idle_half = Resources.sprite_load(NAMESPACE, "TechnicianIdleHalf", path.combine(SPRITE_PATH, "idleHalf.png"), 1, 9, 13) -local sprite_walk = Resources.sprite_load(NAMESPACE, "TechnicianWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 11, 15) -local sprite_walk_half = Resources.sprite_load(NAMESPACE, "TechnicianWalkHalf", path.combine(SPRITE_PATH, "walkHalf.png"), 8, 10, 15) -local sprite_walk_back = Resources.sprite_load(NAMESPACE, "TechnicianWalkBack", path.combine(SPRITE_PATH, "walkBack.png"), 8, 13, 15) -local sprite_jump = Resources.sprite_load(NAMESPACE, "TechnicianJump", path.combine(SPRITE_PATH, "jump.png"), 1, 14, 14) -local sprite_jump_half = Resources.sprite_load(NAMESPACE, "TechnicianJumpHalf", path.combine(SPRITE_PATH, "jumpHalf.png"), 1, 14, 14) -local sprite_jump_peak = Resources.sprite_load(NAMESPACE, "TechnicianJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 14, 14) -local sprite_jump_peak_half = Resources.sprite_load(NAMESPACE, "TechnicianJumpPeakHalf", path.combine(SPRITE_PATH, "jumpPeakHalf.png"), 1, 14, 12) -local sprite_fall = Resources.sprite_load(NAMESPACE, "TechnicianFall", path.combine(SPRITE_PATH, "fall.png"), 1, 14, 12) -local sprite_climb = Resources.sprite_load(NAMESPACE, "TechnicianClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 10, 15) -local sprite_fall_half = Resources.sprite_load(NAMESPACE, "TechnicianFallHalf", path.combine(SPRITE_PATH, "fallHalf.png"), 1, 14, 12) -local sprite_death = Resources.sprite_load(NAMESPACE, "TechnicianDeath", path.combine(SPRITE_PATH, "death.png"), 8, 13, 12) -local sprite_decoy = Resources.sprite_load(NAMESPACE, "TechnicianDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 17, 16) -local sprite_drone_idle = Resources.sprite_load(NAMESPACE, "DronePlayerTechnicianIdle", path.combine(SPRITE_PATH, "drone_idle.png"), 5, 11, 15) -local sprite_drone_shoot = Resources.sprite_load(NAMESPACE, "DronePlayerTechnicianShoot", path.combine(SPRITE_PATH, "drone_shoot.png"), 5, 25, 15) - -local sprite_shoot1_1 = Resources.sprite_load(NAMESPACE, "TechnicianShoot1_1", path.combine(SPRITE_PATH, "shoot1_1.png"), 7, 31, 28) -local sprite_shoot1_2 = Resources.sprite_load(NAMESPACE, "TechnicianShoot1_2", path.combine(SPRITE_PATH, "shoot1_2.png"), 7, 31, 28) -local sprite_shoot1T_1 = Resources.sprite_load(NAMESPACE, "TechnicianShoot1T_1", path.combine(SPRITE_PATH, "shoot1T_1.png"), 7, 23, 21) -local sprite_shoot1T_2 = Resources.sprite_load(NAMESPACE, "TechnicianShoot1T_2", path.combine(SPRITE_PATH, "shoot1T_2.png"), 7, 12, 17) -local sprite_shoot2 = Resources.sprite_load(NAMESPACE, "TechnicianShoot2", path.combine(SPRITE_PATH, "shoot2.png"), 7, 17, 13) -local sprite_shoot4 = Resources.sprite_load(NAMESPACE, "TechnicianShoot4", path.combine(SPRITE_PATH, "shoot4.png"), 8, 16, 23) -local sprite_shoot5 = Resources.sprite_load(NAMESPACE, "TechnicianShoot4S", path.combine(SPRITE_PATH, "shoot5.png"), 8, 16, 25) - -local sprite_sparks1 = Resources.sprite_load(NAMESPACE, "TechnicianSparks1", path.combine(SPRITE_PATH, "sparks1.png"), 4, 12, 16) -local sprite_sparks2 = Resources.sprite_load(NAMESPACE, "TechnicianSparks2", path.combine(SPRITE_PATH, "sparks2.png"), 4, 17, 22) -local sprite_sparks3 = Resources.sprite_load(NAMESPACE, "TechnicianSparks3", path.combine(SPRITE_PATH, "sparks3.png"), 4, 16, 22) -local sprite_sparks4 = Resources.sprite_load(NAMESPACE, "TechnicianSparks4", path.combine(SPRITE_PATH, "sparks4.png"), 4, 9, 12) -local sprite_sparks5 = Resources.sprite_load(NAMESPACE, "TechnicianSparks5", path.combine(SPRITE_PATH, "sparks5.png"), 4, 26, 4) - -local sprite_drink_technician_1 = Resources.sprite_load(NAMESPACE, "TechnicianDrink", path.combine(SPRITE_PATH, "Drink/technician_drink_1.png"), 12, 9, 13) -local sprite_drink_technician_2 = Resources.sprite_load(NAMESPACE, "TechnicianDrinkUp", path.combine(SPRITE_PATH, "Drink/technician_drink_2.png"), 12, 9, 13) - -local sprite_turretaI = Resources.sprite_load(NAMESPACE, "TechnicianTurretaIdle", path.combine(SPRITE_PATH, "turreta.png"), 4, 12, 14) -local sprite_turretashoot = Resources.sprite_load(NAMESPACE, "TechnicianTurretaShoot", path.combine(SPRITE_PATH, "turretashoot.png"), 4, 17, 14) -local sprite_turretbI = Resources.sprite_load(NAMESPACE, "TechnicianTurretbIdle", path.combine(SPRITE_PATH, "turretb.png"), 4, 20, 17) -local sprite_turretbshoot = Resources.sprite_load(NAMESPACE, "TechnicianTurretbshoot", path.combine(SPRITE_PATH, "turretbshoot.png"), 4, 26, 17) -local sprite_turretcI = Resources.sprite_load(NAMESPACE, "TechnicianTurretcIdle", path.combine(SPRITE_PATH, "turretc.png"), 4, 21, 17) -local sprite_turretcshoot = Resources.sprite_load(NAMESPACE, "TechnicianTurretcshoot", path.combine(SPRITE_PATH, "turretcshoot.png"), 4, 30, 16) -local sprite_turretc_mis1 = Resources.sprite_load(NAMESPACE, "TechnicianTurretcMissile1", path.combine(SPRITE_PATH, "turretc_mis1.png"), 4, 21, 17) -local sprite_turretc_mis2 = Resources.sprite_load(NAMESPACE, "TechnicianTurretcMissile2", path.combine(SPRITE_PATH, "turretc_mis2.png"), 5, 21, 19) -local sprite_turretc_mis3 = Resources.sprite_load(NAMESPACE, "TechnicianTurretcMissile3", path.combine(SPRITE_PATH, "turretc_mis3.png"), 5, 29, 32) - -local sprite_vending1 = Resources.sprite_load(NAMESPACE, "TechnicianVendingMachine", path.combine(SPRITE_PATH, "vendinga.png"), 10, 21, 34) -local sprite_vending2 = Resources.sprite_load(NAMESPACE, "TechnicianVendingMachine2", path.combine(SPRITE_PATH, "vendingb.png"), 10, 19, 37) -local buff_vending_sprite = Resources.sprite_load(NAMESPACE, "BuffHydrated", path.combine(PATH, "Sprites/Buffs/hydrated.png"), 1, 8, 12) -local buff_vending_2_sprite = Resources.sprite_load(NAMESPACE, "BuffReallyHydrated", path.combine(PATH, "Sprites/Buffs/reallyHydrated.png"), 1, 8, 12) - -local sprite_mine1 = Resources.sprite_load(NAMESPACE, "TechnicianMine1", path.combine(SPRITE_PATH, "minea.png"), 6, 7, 32) -local sprite_mine2 = Resources.sprite_load(NAMESPACE, "TechnicianMine2", path.combine(SPRITE_PATH, "mineb.png"), 6, 13, 36) - -local sprite_amplifier1 = Resources.sprite_load(NAMESPACE, "TechnicianAmplifier1", path.combine(SPRITE_PATH, "amplifiera.png"), 1, 10, 38) -local sprite_amplifier2 = Resources.sprite_load(NAMESPACE, "TechnicianAmplifier2", path.combine(SPRITE_PATH, "amplifierb.png"), 1, 10, 38) -local buff_exposed_sprite = Resources.sprite_load(NAMESPACE, "BuffExposed", path.combine(PATH, "Sprites/Buffs/exposed.png"), 1, 10, 10) -local buff_exposed_2_sprite = Resources.sprite_load(NAMESPACE, "BuffExposed2", path.combine(PATH, "Sprites/Buffs/exposed2.png"), 1, 10, 10) - -local sprite_wrench = Resources.sprite_load(NAMESPACE, "TechnicianWrench", path.combine(SPRITE_PATH, "wrench.png"), 1, 9, 9) - ---Object hitbox sprites -local mine_mask = Resources.sprite_load(NAMESPACE, "TechnicianMineMask", path.combine(SPRITE_PATH, "minemask.png"), 1, 7, 18) -local turret_mask = Resources.sprite_load(NAMESPACE, "TechnicianTurretMask", path.combine(SPRITE_PATH, "turretmask.png"), 1, 11, 8) -local wrench_mask = Resources.sprite_load(NAMESPACE, "TechnicianWrenchMask", path.combine(SPRITE_PATH, "wrenchmask.png"), 1, 11, 8) -local amplifier_mask = Resources.sprite_load(NAMESPACE, "TechnicianAmplifierMask", path.combine(SPRITE_PATH, "amplifiermask.png"), 1, 8, 24) - -local mine_explosion = Resources.sprite_load(NAMESPACE, "TechnicianMineExplosion", path.combine(SPRITE_PATH, "mineExplosion.png"), 7, 63, 92) - -local sound_select = Resources.sfx_load(NAMESPACE, "TechnicianSelect", path.combine(SOUND_PATH, "select.ogg")) -local sound_shoot1 = Resources.sfx_load(NAMESPACE, "TechnicianShoot1", path.combine(SOUND_PATH, "shoot1.ogg")) -local sound_shoot1T = Resources.sfx_load(NAMESPACE, "TechnicianShoot1T", path.combine(SOUND_PATH, "shoot1T.ogg")) -local sound_shoot2 = Resources.sfx_load(NAMESPACE, "TechnicianShoot2", path.combine(SOUND_PATH, "shoot2.ogg")) -local sound_shoot4 = Resources.sfx_load(NAMESPACE, "TechnicianShoot4", path.combine(SOUND_PATH, "shoot4.ogg")) -local sound_mineExplode1 = Resources.sfx_load(NAMESPACE, "TechnicianMineExplode1", path.combine(SOUND_PATH, "mineExplode1.ogg")) -local sound_mineExplode2 = Resources.sfx_load(NAMESPACE, "TechnicianMineExplode1", path.combine(SOUND_PATH, "mineExplode2.ogg")) -local sound_mineUpgrade = Resources.sfx_load(NAMESPACE, "TechnicianMineUpgrade", path.combine(SOUND_PATH, "mineUpgrade.ogg")) -local sound_vendingDispense = Resources.sfx_load(NAMESPACE, "TechnicianVendingDispense", path.combine(SOUND_PATH, "vendingDispense.ogg")) -local sound_vendingDrink = Resources.sfx_load(NAMESPACE, "TechnicianVendingDrink", path.combine(SOUND_PATH, "vendingDrink.ogg")) -local sound_vendingUpgrade = Resources.sfx_load(NAMESPACE, "TechnicianVendingUpgrade", path.combine(SOUND_PATH, "vendingUpgrade.ogg")) -local sound_turretShoot1 = Resources.sfx_load(NAMESPACE, "TechnicianTurretShoot1", path.combine(SOUND_PATH, "turretShoot1.ogg")) -local sound_turretShoot2 = Resources.sfx_load(NAMESPACE, "TechnicianTurretShoot2", path.combine(SOUND_PATH, "turretShoot2.ogg")) -local sound_turretDeath = Resources.sfx_load(NAMESPACE, "TechnicianTurretDeath", path.combine(SOUND_PATH, "turretDeath.ogg")) -local sound_turretUpgrade = Resources.sfx_load(NAMESPACE, "TechnicianTurretUpgrade", path.combine(SOUND_PATH, "turretUpgrade.ogg")) -local sound_wrenchHit = Resources.sfx_load(NAMESPACE, "TechnicianWrenchHit", path.combine(SOUND_PATH, "wrenchHit.ogg")) -local sound_upgrade = Resources.sfx_load(NAMESPACE, "TechnicianUpgrade", path.combine(SOUND_PATH, "upgrade.ogg")) -local sound_downgrade = Resources.sfx_load(NAMESPACE, "TechnicianDowngrade", path.combine(SOUND_PATH, "downgrade.ogg")) -local sound_downgradeBeep = Resources.sfx_load(NAMESPACE, "TechnicianDowngradeBeep", path.combine(SOUND_PATH, "downgradeBeep.ogg")) +local buff_mirror = Buff.find("shadowClone") -- shattered mirror buff +local item_scepter = Item.find("ancientScepter") +local object_sparks = Object.find("EfSparks") -- standard hit sparks object +local object_flash = Object.find("EfFlash") +local object_missile = Object.find("EfMissile") +local particle_spark = Particle.find("Spark") + +-- define all the assets +local sprite_loadout = Sprite.new("TechnicianSelect", path.combine(SPRITE_PATH, "select.png"), 18, 28, 0) +local sprite_portrait = Sprite.new("TechnicianPortrait", path.combine(SPRITE_PATH, "portrait.png"), 3) -- CSS screen/general UI survivor icons +local sprite_portrait_small = Sprite.new("TechnicianPortraitSmall", path.combine(SPRITE_PATH, "portraitSmall.png")) -- ditto +local sprite_skills = Sprite.new("TechnicianSkills", path.combine(SPRITE_PATH, "skills.png"), 7) +local sprite_credits = Sprite.new("TechnicianCredits", path.combine(SPRITE_PATH, "credits.png"), 1, 7, 12) -- the sprite used at the end of the credits; 2x scale ror1 idle sprite if it exists +local sprite_palette = Sprite.new("TechnicianPalette", path.combine(SPRITE_PATH, "palette.png")) -- the color palette used to map skins +local sprite_log = Sprite.new("TechnicianLog", path.combine(SPRITE_PATH, "log.png")) -- the logbook portrait + +local sprite_idle = Sprite.new("TechnicianIdle", path.combine(SPRITE_PATH, "idle.png"), 1, 9, 13) +local sprite_idle_half = Sprite.new("TechnicianIdleHalf", path.combine(SPRITE_PATH, "idleHalf.png"), 1, 9, 13) +local sprite_walk = Sprite.new("TechnicianWalk", path.combine(SPRITE_PATH, "walk.png"), 8, 11, 15) +local sprite_walk_half = Sprite.new("TechnicianWalkHalf", path.combine(SPRITE_PATH, "walkHalf.png"), 8, 10, 15) +local sprite_walk_back = Sprite.new("TechnicianWalkBack", path.combine(SPRITE_PATH, "walkBack.png"), 8, 13, 15) +local sprite_jump = Sprite.new("TechnicianJump", path.combine(SPRITE_PATH, "jump.png"), 1, 14, 14) +local sprite_jump_half = Sprite.new("TechnicianJumpHalf", path.combine(SPRITE_PATH, "jumpHalf.png"), 1, 14, 14) +local sprite_jump_peak = Sprite.new("TechnicianJumpPeak", path.combine(SPRITE_PATH, "jumpPeak.png"), 1, 14, 14) +local sprite_jump_peak_half = Sprite.new("TechnicianJumpPeakHalf", path.combine(SPRITE_PATH, "jumpPeakHalf.png"), 1, 14, 12) +local sprite_fall = Sprite.new("TechnicianFall", path.combine(SPRITE_PATH, "fall.png"), 1, 14, 12) +local sprite_climb = Sprite.new("TechnicianClimb", path.combine(SPRITE_PATH, "climb.png"), 6, 10, 15) +local sprite_fall_half = Sprite.new("TechnicianFallHalf", path.combine(SPRITE_PATH, "fallHalf.png"), 1, 14, 12) +local sprite_death = Sprite.new("TechnicianDeath", path.combine(SPRITE_PATH, "death.png"), 8, 13, 12) +local sprite_decoy = Sprite.new("TechnicianDecoy", path.combine(SPRITE_PATH, "decoy.png"), 1, 17, 16) +local sprite_drone_idle = Sprite.new("DronePlayerTechnicianIdle", path.combine(SPRITE_PATH, "drone_idle.png"), 5, 11, 15) +local sprite_drone_shoot = Sprite.new("DronePlayerTechnicianShoot", path.combine(SPRITE_PATH, "drone_shoot.png"), 5, 25, 15) + +local sprite_shoot1_1 = Sprite.new("TechnicianShoot1_1", path.combine(SPRITE_PATH, "shoot1_1.png"), 7, 31, 28) +local sprite_shoot1_2 = Sprite.new("TechnicianShoot1_2", path.combine(SPRITE_PATH, "shoot1_2.png"), 7, 31, 28) +local sprite_shoot1T_1 = Sprite.new("TechnicianShoot1T_1", path.combine(SPRITE_PATH, "shoot1T_1.png"), 7, 23, 21) +local sprite_shoot1T_2 = Sprite.new("TechnicianShoot1T_2", path.combine(SPRITE_PATH, "shoot1T_2.png"), 7, 12, 17) +local sprite_shoot2 = Sprite.new("TechnicianShoot2", path.combine(SPRITE_PATH, "shoot2.png"), 7, 17, 13) +local sprite_shoot4 = Sprite.new("TechnicianShoot4", path.combine(SPRITE_PATH, "shoot4.png"), 8, 16, 23) +local sprite_shoot5 = Sprite.new("TechnicianShoot4S", path.combine(SPRITE_PATH, "shoot5.png"), 8, 16, 25) + +local sprite_sparks1 = Sprite.new("TechnicianSparks1", path.combine(SPRITE_PATH, "sparks1.png"), 4, 12, 16) +local sprite_sparks2 = Sprite.new("TechnicianSparks2", path.combine(SPRITE_PATH, "sparks2.png"), 4, 17, 22) +local sprite_sparks3 = Sprite.new("TechnicianSparks3", path.combine(SPRITE_PATH, "sparks3.png"), 4, 16, 22) +local sprite_sparks4 = Sprite.new("TechnicianSparks4", path.combine(SPRITE_PATH, "sparks4.png"), 4, 9, 12) +local sprite_sparks5 = Sprite.new("TechnicianSparks5", path.combine(SPRITE_PATH, "sparks5.png"), 4, 26, 4) + +local sprite_drink_technician_1 = Sprite.new("TechnicianDrink", path.combine(SPRITE_PATH, "Drink/technician_drink_1.png"), 12, 9, 13) +local sprite_drink_technician_2 = Sprite.new("TechnicianDrinkUp", path.combine(SPRITE_PATH, "Drink/technician_drink_2.png"), 12, 9, 13) + +local sprite_turretaI = Sprite.new("TechnicianTurretaIdle", path.combine(SPRITE_PATH, "turreta.png"), 4, 12, 14) +local sprite_turretashoot = Sprite.new("TechnicianTurretaShoot", path.combine(SPRITE_PATH, "turretashoot.png"), 4, 17, 14) +local sprite_turretbI = Sprite.new("TechnicianTurretbIdle", path.combine(SPRITE_PATH, "turretb.png"), 4, 20, 17) +local sprite_turretbshoot = Sprite.new("TechnicianTurretbshoot", path.combine(SPRITE_PATH, "turretbshoot.png"), 4, 26, 17) +local sprite_turretcI = Sprite.new("TechnicianTurretcIdle", path.combine(SPRITE_PATH, "turretc.png"), 4, 21, 17) +local sprite_turretcshoot = Sprite.new("TechnicianTurretcshoot", path.combine(SPRITE_PATH, "turretcshoot.png"), 4, 30, 16) +local sprite_turretc_mis1 = Sprite.new("TechnicianTurretcMissile1", path.combine(SPRITE_PATH, "turretc_mis1.png"), 4, 21, 17) +local sprite_turretc_mis2 = Sprite.new("TechnicianTurretcMissile2", path.combine(SPRITE_PATH, "turretc_mis2.png"), 5, 21, 19) +local sprite_turretc_mis3 = Sprite.new("TechnicianTurretcMissile3", path.combine(SPRITE_PATH, "turretc_mis3.png"), 5, 29, 32) + +local sprite_vending1 = Sprite.new("TechnicianVendingMachine", path.combine(SPRITE_PATH, "vendinga.png"), 10, 21, 34) +local sprite_vending2 = Sprite.new("TechnicianVendingMachine2", path.combine(SPRITE_PATH, "vendingb.png"), 10, 19, 37) +local buff_vending_sprite = Sprite.new("BuffHydrated", path.combine(PATH, "Sprites/Buffs/hydrated.png"), 1, 8, 12) +local buff_vending_2_sprite = Sprite.new("BuffReallyHydrated", path.combine(PATH, "Sprites/Buffs/reallyHydrated.png"), 1, 8, 12) + +local sprite_mine1 = Sprite.new("TechnicianMine1", path.combine(SPRITE_PATH, "minea.png"), 6, 7, 32) +local sprite_mine2 = Sprite.new("TechnicianMine2", path.combine(SPRITE_PATH, "mineb.png"), 6, 13, 36) + +local sprite_amplifier1 = Sprite.new("TechnicianAmplifier1", path.combine(SPRITE_PATH, "amplifiera.png"), 1, 10, 38) +local sprite_amplifier2 = Sprite.new("TechnicianAmplifier2", path.combine(SPRITE_PATH, "amplifierb.png"), 1, 10, 38) +local buff_exposed_sprite = Sprite.new("BuffExposed", path.combine(PATH, "Sprites/Buffs/exposed.png"), 1, 10, 10) +local buff_exposed_2_sprite = Sprite.new("BuffExposed2", path.combine(PATH, "Sprites/Buffs/exposed2.png"), 1, 10, 10) + +local sprite_wrench = Sprite.new("TechnicianWrench", path.combine(SPRITE_PATH, "wrench.png"), 1, 9, 9) + +-- object hitbox sprites +local mine_mask = Sprite.new("TechnicianMineMask", path.combine(SPRITE_PATH, "minemask.png"), 1, 7, 18) +local turret_mask = Sprite.new("TechnicianTurretMask", path.combine(SPRITE_PATH, "turretmask.png"), 1, 11, 8) +local wrench_mask = Sprite.new("TechnicianWrenchMask", path.combine(SPRITE_PATH, "wrenchmask.png"), 1, 11, 8) +local amplifier_mask = Sprite.new("TechnicianAmplifierMask", path.combine(SPRITE_PATH, "amplifiermask.png"), 1, 8, 24) + +local mine_explosion = Sprite.new("TechnicianMineExplosion", path.combine(SPRITE_PATH, "mineExplosion.png"), 7, 63, 92) + +local sound_select = Sound.new("TechnicianSelect", path.combine(SOUND_PATH, "select.ogg")) +local sound_shoot1 = Sound.new("TechnicianShoot1", path.combine(SOUND_PATH, "shoot1.ogg")) +local sound_shoot1T = Sound.new("TechnicianShoot1T", path.combine(SOUND_PATH, "shoot1T.ogg")) +local sound_shoot2 = Sound.new("TechnicianShoot2", path.combine(SOUND_PATH, "shoot2.ogg")) +local sound_shoot4 = Sound.new("TechnicianShoot4", path.combine(SOUND_PATH, "shoot4.ogg")) +local sound_mineExplode1 = Sound.new("TechnicianMineExplode1", path.combine(SOUND_PATH, "mineExplode1.ogg")) +local sound_mineExplode2 = Sound.new("TechnicianMineExplode1", path.combine(SOUND_PATH, "mineExplode2.ogg")) +local sound_mineUpgrade = Sound.new("TechnicianMineUpgrade", path.combine(SOUND_PATH, "mineUpgrade.ogg")) +local sound_vendingDispense = Sound.new("TechnicianVendingDispense", path.combine(SOUND_PATH, "vendingDispense.ogg")) +local sound_vendingDrink = Sound.new("TechnicianVendingDrink", path.combine(SOUND_PATH, "vendingDrink.ogg")) +local sound_vendingUpgrade = Sound.new("TechnicianVendingUpgrade", path.combine(SOUND_PATH, "vendingUpgrade.ogg")) +local sound_turretShoot1 = Sound.new("TechnicianTurretShoot1", path.combine(SOUND_PATH, "turretShoot1.ogg")) +local sound_turretShoot2 = Sound.new("TechnicianTurretShoot2", path.combine(SOUND_PATH, "turretShoot2.ogg")) +local sound_turretDeath = Sound.new("TechnicianTurretDeath", path.combine(SOUND_PATH, "turretDeath.ogg")) +local sound_turretUpgrade = Sound.new("TechnicianTurretUpgrade", path.combine(SOUND_PATH, "turretUpgrade.ogg")) +local sound_wrenchHit = Sound.new("TechnicianWrenchHit", path.combine(SOUND_PATH, "wrenchHit.ogg")) +local sound_upgrade = Sound.new("TechnicianUpgrade", path.combine(SOUND_PATH, "upgrade.ogg")) +local sound_downgrade = Sound.new("TechnicianDowngrade", path.combine(SOUND_PATH, "downgrade.ogg")) +local sound_downgradeBeep = Sound.new("TechnicianDowngradeBeep", path.combine(SOUND_PATH, "downgradeBeep.ogg")) local color_tech_red = Color.from_hex(0xFF4843) local color_tech_blue = Color.from_hex(0x96FFFF) @@ -112,11 +112,11 @@ local explosion2 = gm.constants.sMinerExplosion local explosion3 = gm.constants.sDroneDeath local soundImpact = gm.constants.wTurtleExplosion ---Constants for various things -local WRENCH_BLAST_OFFSET_X = get_tiles(0.8) -local WRENCH_BLAST_OFFSET_Y = -get_tiles(0.1) -local WRENCH_BLAST_W = get_tiles(2.2) -local WRENCH_BLAST_H = get_tiles(1.2) +-- constants for various things +local WRENCH_BLAST_OFFSET_X = ssr_get_tiles(0.8) +local WRENCH_BLAST_OFFSET_Y = -ssr_get_tiles(0.1) +local WRENCH_BLAST_W = ssr_get_tiles(2.2) +local WRENCH_BLAST_H = ssr_get_tiles(1.2) local WRENCH_DOWNGRADE_TIME = 60 * 20 local WRENCH_THROW_DOWNGRADE_TIME = 60 * 20 @@ -126,26 +126,26 @@ local MACHINE_VENDING_MOVESPEED = 0.56 local MACHINE_VENDING_ATTACKSPEED = 0.2 local MACHINE_VENDING_ATTACKSPEED2 = 0.4 local MACHINE_VENDING_CRIT = 20 -local MACHINE_VENDING_BLAST_W = get_tiles(4) -local MACHINE_VENDING_BLAST_H = get_tiles(2) +local MACHINE_VENDING_BLAST_W = ssr_get_tiles(4) +local MACHINE_VENDING_BLAST_H = ssr_get_tiles(2) local MACHINE_MINE_GRAV = 0.2 -local MACHINE_MINE_PULL_RADIUS = get_tiles(4) +local MACHINE_MINE_PULL_RADIUS = ssr_get_tiles(4) local MACHINE_MINE_PULL_INTERVAL = 75 --80 local MACHINE_MINE_PULL_LIFE = 90 local MACHINE_AMPLIFIER_RADIUS = 140 --- His only friends are machines -local technician = Survivor.new(NAMESPACE, "technician") -local technician_id = technician.value +-- his only friends are machines +local technician = Survivor.new("technician") ---Set base and level stats +-- set base and level stats technician:set_stats_base({ maxhp = 102, damage = 11, regen = 0.011, }) + technician:set_stats_level({ maxhp = 29, damage = 3, @@ -153,105 +153,99 @@ technician:set_stats_level({ armor = 2, }) -technician:set_animations({ - idle = sprite_idle, - walk = sprite_walk, - jump = sprite_jump, - jump_peak = sprite_jump_peak, - fall = sprite_fall, - climb = sprite_climb, - death = sprite_death, - decoy = sprite_decoy, - drone_idle = sprite_drone_idle, - drone_shoot = sprite_drone_shoot, -}) +-- create the survivor log +-- all text is defined in the language file +local technician_log = SurvivorLog.new_from_survivor(technician) +technician_log.portrait_id = sprite_log +technician_log.sprite_id = sprite_walk +technician_log.sprite_icon_id = sprite_portrait -technician:set_cape_offset(-4, -7, -3, -9) -technician:set_primary_color(Color.from_rgb(104, 191, 208)) +technician.primary_color = Color.from_rgb(104, 191, 208) technician.sprite_loadout = sprite_loadout technician.sprite_portrait = sprite_portrait technician.sprite_portrait_small = sprite_portrait_small + technician.sprite_idle = sprite_idle technician.sprite_title = sprite_walk technician.sprite_credits = sprite_credits -technician.select_sound_id = sound_select - ---Set palettes to be used for skins -technician:set_palettes(sprite_palette, sprite_palette, sprite_palette) - ---Add a few skins ----1st arg is internal name, 2nd is the column in the palette sprite where the skin is located, 3rd-5th are recolored sprites in the CSS -technician:add_skin("TechnicianRose", 1, Resources.sprite_load(NAMESPACE, "TechnicianSelect_PAL2", path.combine(SPRITE_PATH, "selectS1.png"), 18, 28, 0), -Resources.sprite_load(NAMESPACE, "TechnicianPortrait_PAL2", path.combine(SPRITE_PATH, "portraitS1.png"), 3), -Resources.sprite_load(NAMESPACE, "TechnicianPortraitSmall_PAL2", path.combine(SPRITE_PATH, "portraitSmallS1.png"))) - -technician:add_skin("TechnicianBlack", 2, Resources.sprite_load(NAMESPACE, "TechnicianSelect_PAL4", path.combine(SPRITE_PATH, "selectS2.png"), 18, 28, 0), -Resources.sprite_load(NAMESPACE, "TechnicianPortrait_PAL4", path.combine(SPRITE_PATH, "portraitS2.png"), 3), -Resources.sprite_load(NAMESPACE, "TechnicianPortraitSmall_PAL4", path.combine(SPRITE_PATH, "portraitSmallS2.png"))) - -technician:add_skin("TechnicianBlue", 3, Resources.sprite_load(NAMESPACE, "TechnicianSelect_PAL5", path.combine(SPRITE_PATH, "selectS3.png"), 18, 28, 0), -Resources.sprite_load(NAMESPACE, "TechnicianPortrait_PAL5", path.combine(SPRITE_PATH, "portraitS3.png"), 3), -Resources.sprite_load(NAMESPACE, "TechnicianPortraitSmall_PAL5", path.combine(SPRITE_PATH, "portraitSmallS3.png"))) +-- set palettes to be used for skins +technician.sprite_palette = sprite_palette +technician.sprite_portrait_palette = sprite_palette +technician.sprite_loadout_palette = sprite_palette ---[[technician:add_skin("TechnicianOperator", 4, Resources.sprite_load(NAMESPACE, "TechnicianSelect_PAL1", path.combine(SPRITE_PATH, "selectS4.png"), 18, 28, 0), -Resources.sprite_load(NAMESPACE, "TechnicianPortrait_PAL1", path.combine(SPRITE_PATH, "portraitS4.png"), 3), -Resources.sprite_load(NAMESPACE, "TechnicianPortraitSmall_PAL1", path.combine(SPRITE_PATH, "portraitSmallS4.png")))]] - ---[[technician:add_skin("TechnicianEngineer", 5, Resources.sprite_load(NAMESPACE, "TechnicianSelect_PAL3", path.combine(SPRITE_PATH, "selectS1.png"), 18, 28, 0), -Resources.sprite_load(NAMESPACE, "TechnicianPortrait_PAL3", path.combine(SPRITE_PATH, "portraitS1.png"), 3), -Resources.sprite_load(NAMESPACE, "TechnicianPortraitSmall_PAL3", path.combine(SPRITE_PATH, "portraitSmallS1.png")))]] - ---[[technician:add_skin("TechnicianProvidence", 5, Resources.sprite_load(NAMESPACE, "TechnicianSelect_PROV", path.combine(SPRITE_PATH, "selectPROV.png"), 18, 28, 0), -Resources.sprite_load(NAMESPACE, "TechnicianPortrait_PROV", path.combine(SPRITE_PATH, "portraitPROV.png"), 3), -Resources.sprite_load(NAMESPACE, "TechnicianPortraitSmall_PROV", path.combine(SPRITE_PATH, "portraitSmallPROV.png")))]] - ---Create the survivor log ----All text is defined in the language file -local technicianLog = Survivor_Log.new(technician, sprite_log) - ---Retrieve the skills automatically created with custom survivors, and manually create skills for alts and Red Button -local technicianPrimary = technician:get_primary() -local technicianSecondary = technician:get_secondary() -local technicianUtility = technician:get_utility() -local technicianSpecial = technician:get_special() -local technicianSecondary_Det = Skill.new(NAMESPACE, "technicianXD") - -local technicianPrimaryAlt = Skill.new(NAMESPACE, "technicianZ2") -local technicianUtilityAlt = Skill.new(NAMESPACE, "technicianC2") - -technician:clear_callbacks() -technician:onInit(function(actor) +technician.select_sound_id = sound_select +technician.cape_offset = Array.new({-4, -7, -3, -9}) + +--[[ +-- add a few skins +-- 1st arg is internal name, 2nd is the column in the palette sprite where the skin is located, 3rd-5th are recolored sprites in the CSS +technician:add_skin("TechnicianRose", 1, Sprite.new("TechnicianSelect_PAL2", path.combine(SPRITE_PATH, "selectS1.png"), 18, 28, 0), +Sprite.new("TechnicianPortrait_PAL2", path.combine(SPRITE_PATH, "portraitS1.png"), 3), +Sprite.new("TechnicianPortraitSmall_PAL2", path.combine(SPRITE_PATH, "portraitSmallS1.png"))) + +technician:add_skin("TechnicianBlack", 2, Sprite.new("TechnicianSelect_PAL4", path.combine(SPRITE_PATH, "selectS2.png"), 18, 28, 0), +Sprite.new("TechnicianPortrait_PAL4", path.combine(SPRITE_PATH, "portraitS2.png"), 3), +Sprite.new("TechnicianPortraitSmall_PAL4", path.combine(SPRITE_PATH, "portraitSmallS2.png"))) + +technician:add_skin("TechnicianBlue", 3, Sprite.new("TechnicianSelect_PAL5", path.combine(SPRITE_PATH, "selectS3.png"), 18, 28, 0), +Sprite.new("TechnicianPortrait_PAL5", path.combine(SPRITE_PATH, "portraitS3.png"), 3), +Sprite.new("TechnicianPortraitSmall_PAL5", path.combine(SPRITE_PATH, "portraitSmallS3.png"))) +]]-- + +--[[technician:add_skin("TechnicianOperator", 4, Sprite.new("TechnicianSelect_PAL1", path.combine(SPRITE_PATH, "selectS4.png"), 18, 28, 0), +Sprite.new("TechnicianPortrait_PAL1", path.combine(SPRITE_PATH, "portraitS4.png"), 3), +Sprite.new("TechnicianPortraitSmall_PAL1", path.combine(SPRITE_PATH, "portraitSmallS4.png")))]] + +--[[technician:add_skin("TechnicianEngineer", 5, Sprite.new("TechnicianSelect_PAL3", path.combine(SPRITE_PATH, "selectS1.png"), 18, 28, 0), +Sprite.new("TechnicianPortrait_PAL3", path.combine(SPRITE_PATH, "portraitS1.png"), 3), +Sprite.new("TechnicianPortraitSmall_PAL3", path.combine(SPRITE_PATH, "portraitSmallS1.png")))]] + +--[[technician:add_skin("TechnicianProvidence", 5, Sprite.new("TechnicianSelect_PROV", path.combine(SPRITE_PATH, "selectPROV.png"), 18, 28, 0), +Sprite.new("TechnicianPortrait_PROV", path.combine(SPRITE_PATH, "portraitPROV.png"), 3), +Sprite.new("TechnicianPortraitSmall_PROV", path.combine(SPRITE_PATH, "portraitSmallPROV.png")))]] + +-- default skills +local primary = technician:get_skills(Skill.Slot.PRIMARY)[1] +local secondary = technician:get_skills(Skill.Slot.SECONDARY)[1] +local secondary_det = Skill.new("technicianXD") +local utility = technician:get_skills(Skill.Slot.UTILITY)[1] +local special = technician:get_skills(Skill.Slot.SPECIAL)[1] +local specialS = Skill.new("technicianVBoosted") + +-- alt primary +local primary2 = Skill.new("technicianZ2") +technician:add_skill(Skill.Slot.PRIMARY, primary2) + +-- alt utility +local utility2 = Skill.new("technicianC2") +--technician:add_skill(Skill.Slot.UTILITY, utility2) + +Callback.add(technician.on_init, function(actor) -- setup half-sprite nonsense - local idle_half = Array.new() - local walk_half = Array.new() - local jump_half = Array.new() - local jump_peak_half = Array.new() - local fall_half = Array.new() - idle_half:push(sprite_idle, sprite_idle_half, 0) - walk_half:push(sprite_walk, sprite_walk_half, 0, sprite_walk_back) - jump_half:push(sprite_jump, sprite_jump_half, 0) - jump_peak_half:push(sprite_jump_peak, sprite_jump_peak_half, 0) - fall_half:push(sprite_fall, sprite_fall_half, 0) - - actor.sprite_idle_half = idle_half - actor.sprite_walk_half = walk_half - actor.sprite_jump_half = jump_half - actor.sprite_jump_peak_half = jump_peak_half - actor.sprite_fall_half = fall_half + actor.sprite_idle_half = Array.new({sprite_idle, sprite_idle_half, 0}) + actor.sprite_walk_half = Array.new({sprite_walk, sprite_walk_half, 0, sprite_walk_back}) + actor.sprite_jump_half = Array.new({sprite_jump, sprite_jump_half, 0}) + actor.sprite_jump_peak_half = Array.new({sprite_jump_peak, sprite_jump_peak_half, 0}) + actor.sprite_fall_half = Array.new({sprite_fall, sprite_fall_half, 0}) + actor.sprite_idle = sprite_idle + actor.sprite_walk = sprite_walk + actor.sprite_jump = sprite_jump + actor.sprite_jump_peak = sprite_jump_peak + actor.sprite_fall = sprite_fall + actor.sprite_climb = sprite_climb + actor.sprite_death = sprite_death + actor.sprite_decoy = sprite_decoy + actor.sprite_drone_idle = sprite_drone_idle + actor.sprite_drone_shoot = sprite_drone_shoot + actor:survivor_util_init_half_sprites() - - --Skill overrides aren't removed when transitioning between stages so this does that - --Otherwise the skill stocks of it get all messed up and secondary does nothing because there's no mine to detonate - actor:add_callback("onStageStart", "SSOnStageStartTech", function(actor) - actor:remove_skill_override(Skill.SLOT.secondary, technicianSecondary_Det, 1) - end) end) --- Adjusts your vertical offset to keep in line with the legs animation while strafing ---- Offsets and frames may vary per survivor +-- adjusts your vertical offset to keep in line with the legs animation while strafing +-- offsets and frames may vary per survivor local handle_strafing_yoffset = function(actor) if actor.sprite_index == actor.sprite_walk_half[2] then local walk_offset = 0 @@ -265,58 +259,80 @@ local handle_strafing_yoffset = function(actor) end end ----Create some buffs -local buff_vending = Buff.new(NAMESPACE, "hydrated") -local buff_vending_2 = Buff.new(NAMESPACE, "really_hydrated") -local buff_exposed = Buff.new(NAMESPACE, "exposed") -local buff_exposed_2 = Buff.new(NAMESPACE, "exposed2") +-- create some buffs +local buff_vending = Buff.new("hydrated") +local buff_vending_2 = Buff.new("really_hydrated") +local buff_exposed = Buff.new("exposed") +local buff_exposed_2 = Buff.new("exposed2") -local fake_mocha = Item.new(NAMESPACE, "fakeMocha", true) +-- if a mocha is added to a turret the game crashes. create fake mochas to replace real ones when they get added to the turret +local fake_mocha = Item.new("fakeMocha") fake_mocha.is_hidden = true -fake_mocha:clear_callbacks() -fake_mocha:onStatRecalc(function(actor, stack) - actor.attack_speed = actor.attack_speed + 0.15 * stack - actor.pHmax = actor.pHmax + 0.06 * stack + +RecalculateStats.add(function(actor, api) + local stack = actor:item_count(fake_mocha) + if stack <= 0 then return end + + api.attack_speed_add(0.15 * stack) + api.pHmax_add(0.06 * stack) end) -- All the machines.........! ---Create an EfFlash (Fading solid color overlay of the object) +-- create an EfFlash (fading solid color overlay of the object) local machineFlash = function(inst, color) local flash = object_flash:create(inst.x, inst.y) flash.parent = inst.id flash.image_blend = color or Color.WHITE end ---MULTIPLAYER: Create a new packet, when it is retrieved by a client the cooresponding machine flashes and plays a sound -local machine_temp_visual_packet = Packet.new() -machine_temp_visual_packet:onReceived(function(message, player) - local inst = message:read_instance() - local isFinal = message:read_byte() --Plays a different sound when downgrading finishes +-- MULTIPLAYER: create a new packet, when it is retrieved by a client the cooresponding machine flashes and plays a sound +local machine_temp_visual_packet = Packet.new("SyncTechnicianMachineVisuals") + +local machine_temp_visual_packet_deserializer = function(buffer) + local inst = buffer:read_instance() + local isFinal = buffer:read_byte() -- plays a different sound when downgrading finishes + machineFlash(inst, color_tech_red) - gm.sound_play_at(gm.bool(isFinal) and sound_downgrade or sound_downgradeBeep, 1, 1, inst.x, inst.y) -end) + + local sound = sound_downgradeBeep.value + if Util.bool(isFinal) then + sound = sound_downgrade.value + end + + gm.sound_play_at(sound, 1, 1, inst.x, inst.y) +end + +local machine_temp_visual_packet_serializer = function(buffer, inst) + buffer:write_instance(inst) -- add the machine as an argument to the packet + + local final = 0 + if inst.upgrade_progress_temp_timer <= 0 then + final = 1 + end + + buffer:write_byte(final) -- tells the client whether or not to play the full downgrade sound (1) or beep sound (0) +end ---Update machine temporary upgrade logic +machine_temp_visual_packet:set_serializers(machine_temp_visual_packet_serializer, machine_temp_visual_packet_deserializer) + +-- update machine temporary upgrade logic local machine_update_temp = function(inst) if inst.upgrade_progress_temp > 0 then if inst.upgrade_progress_temp_timer < 300 and inst.upgrade_progress_temp_timer % 120 == 0 then - if gm._mod_net_isHost() then --Only ran by the host in a Multiplayer game, must be done to have properly synced visuals (through packets) - machineFlash(inst, color_tech_red) --Create a red flash on the instance + if Net.host then -- only ran by the host in a multiplayer game, must be done to have properly synced visuals (through packets) + machineFlash(inst, color_tech_red) -- create a red flash on the instance if inst.upgrade_progress_temp_timer <= 0 then - inst.upgrade_progress = inst.upgrade_progress - inst.upgrade_progress_temp --Reduce the upgrade_progress of the instance; The machine itself handles the reprecussions + inst.upgrade_progress = inst.upgrade_progress - inst.upgrade_progress_temp -- reduce the upgrade_progress of the instance; The machine itself handles the reprecussions inst.upgrade_progress_temp = 0 - gm.sound_play_at(sound_downgrade, 1, 1, inst.x, inst.y) --Play a non-moving sound at the instance's location + gm.sound_play_at(sound_downgrade.value, 1, 1, inst.x, inst.y) -- play a non-moving sound at the instance's location else - gm.sound_play_at(sound_downgradeBeep, 1, 1, inst.x, inst.y) --Ditto + gm.sound_play_at(sound_downgradeBeep.value, 1, 1, inst.x, inst.y) -- ditto end - --MULTIPLAYER: Create and send the temp packet visual which runs code similar to above on all clients - if not Net.is_single() then - local buffer = machine_temp_visual_packet:message_begin() - buffer:write_instance(inst) --Add the machine as an argument to the packet - buffer:write_byte(inst.upgrade_progress_temp_timer <= 0 and 1 or 0) --Tells the client whether or not to play the full downgrade sound (1) or beep sound (0) - buffer:send_to_all() + -- MULTIPLAYER: create and send the temp packet visual which runs code similar to above on all clients + if Net.online then + machine_temp_visual_packet:send_to_all(inst) end end end @@ -324,61 +340,113 @@ local machine_update_temp = function(inst) end end ---Increments the upgrade_progress variable on a machine +local create_particles_packet = Packet.new("SyncTechnicianUpgradeParticles") + +local create_particles_packet_deserializer = function(buffer) + local inst = buffer:read_instance() + + particle_spark:create(inst.x, inst.y, math.random(2, 4), Particle.System.MIDDLE) -- creates 2-4 spark particles at the machine's location + gm.sound_play_at(sound_wrenchHit.value, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) -- plays a non-moving sound at the machine's location +end + +local create_particles_packet_serializer = function(buffer, inst) + buffer:write_instance(inst) +end + +create_particles_packet:set_serializers(create_particles_packet_serializer, create_particles_packet_deserializer) + +-- increments the upgrade_progress variable on a machine local upgrade_machine = function(inst, amount, tempTimer) local shouldTempReset = (inst.upgrade_progress_temp_timer <= 240 and inst.upgrade_progress_temp_timer > 0) if inst.upgrade_progress < inst.upgrade_progress_max or shouldTempReset then - if gm._mod_net_isHost() then --Host handles all upgrade logic + if Net.host then -- host handles all upgrade logic if tempTimer or shouldTempReset then if inst.upgrade_progress < inst.upgrade_progress_max then - inst.upgrade_progress_temp = math.min(inst.upgrade_progress_temp + amount, inst.upgrade_progress_max) --Stores the amount of progress to be reverted when the machine is downgraded + inst.upgrade_progress_temp = math.min(inst.upgrade_progress_temp + amount, inst.upgrade_progress_max) -- stores the amount of progress to be reverted when the machine is downgraded end - inst.upgrade_progress_temp_timer = math.max(inst.upgrade_progress_temp_timer, tempTimer or 0) --Reset the machine's temporary upgrade timer + inst.upgrade_progress_temp_timer = math.max(inst.upgrade_progress_temp_timer, tempTimer or 0) -- reset the machine's temporary upgrade timer end - inst.upgrade_progress = math.min(inst.upgrade_progress + amount, inst.upgrade_progress_max) --Increments the machine's upgrade_progress variable; The machine itself handles the reprecussions + + inst.upgrade_progress = math.min(inst.upgrade_progress + amount, inst.upgrade_progress_max) -- increments the machine's upgrade_progress variable; The machine itself handles the reprecussions + particle_spark:create(inst.x, inst.y, math.random(2, 4), Particle.System.MIDDLE) -- creates 2-4 spark particles at the machine's location + gm.sound_play_at(sound_wrenchHit.value, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) -- plays a non-moving sound at the machine's location + create_particles_packet:send_to_all(inst) end - particle_spark:create(inst.x, inst.y, math.random(2, 4), Particle.SYSTEM.middle) --Creates 2-4 spark particles at the machine's location - gm.sound_play_at(sound_wrenchHit, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) --Plays a non-moving sound at the machine's location end end --- Turret +-- turret ---MULTIPLAYER: Define a few new packets for the Turret machine ----Each one is for purely visual and auditory purposes +-- MULTIPLAYER: define a few new packets for the Turret machine +-- each one is for purely visual and auditory purposes ---Plays the fire animation and sound when recieved by clients -local turret_shoot_packet = Packet.new() -turret_shoot_packet:onReceived(function(message, player) - local inst = message:read_instance() +-- plays the fire animation and sound when recieved by clients +local turret_shoot_packet = Packet.new("SyncTechnicianTurretAttack") + +local turret_shoot_packet_deserializer = function(buffer) + local inst = buffer:read_instance() + inst.playanim = 1 inst.image_index = 0 - gm.sound_play_at(inst.upgradeState >= 1 and sound_turretShoot2 or sound_turretShoot1, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) -end) + + local sound = sound_turretShoot1.value + if inst.upgradeState >= 1 then + sound = sound_turretShoot2.value + end + + gm.sound_play_at(sound, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) +end + +local turret_shoot_packet_serializer = function(buffer, inst) + buffer:write_instance(inst) +end + +turret_shoot_packet:set_serializers(turret_shoot_packet_serializer, turret_shoot_packet_deserializer) ---Plays the fire animation and sound when recieved by the host, then sends the above packet to all clients except the one who sent this packet -local turret_shoot_packet_host = Packet.new() -turret_shoot_packet_host:onReceived(function(message, player) - local inst = message:read_instance() - local owner = message:read_instance() +-- plays the fire animation and sound when recieved by the host, then sends the above packet to all clients except the one who sent this packet +local turret_shoot_packet_host = Packet.new("SyncTechnicianTurretAttackHost") + +local turret_shoot_packet_host_deserializer = function(buffer) + local inst = buffer:read_instance() + local owner = buffer:read_instance() + inst.playanim = 1 inst.image_index = 0 - gm.sound_play_at(inst.upgradeState >= 1 and sound_turretShoot2 or sound_turretShoot1, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) - local buffer = turret_shoot_packet:message_begin() + local sound = sound_turretShoot1.value + if inst.upgradeState >= 1 then + sound = sound_turretShoot2.value + end + + gm.sound_play_at(sound, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) + + turret_shoot_packet:send_exclude(owner, inst) +end + +local turret_shoot_packet_host_serializer = function(buffer, inst, owner) buffer:write_instance(inst) - buffer:send_exclude(owner) -end) + buffer:write_instance(owner) +end ---Changes the display state of the missile silo on Scepter turret when recieved by clients -local turret_missile_state_packet = Packet.new() -turret_missile_state_packet:onReceived(function(message, player) - local inst = message:read_instance() - local missileState = message:read_byte() - 1 +turret_shoot_packet_host:set_serializers(turret_shoot_packet_host_serializer, turret_shoot_packet_host_deserializer) + +-- changes the display state of the missile silo on Scepter turret when recieved by clients +local turret_missile_state_packet = Packet.new("SyncTechnicianTurretMissileState") + +local turret_missile_state_packet_deserializer = function(buffer) + local inst = buffer:read_instance() + local missileState = buffer:read_byte() - 1 + inst.switchMissileState = missileState -end) +end + +local turret_missile_state_packet_serializer = function(buffer, inst) + buffer:write_instance(inst) + buffer:write_byte(inst.switchMissileState + 1) +end + +turret_missile_state_packet:set_serializers(turret_missile_state_packet_serializer, turret_missile_state_packet_deserializer) --- local shoot_missiledis_offset_map = { {-8, 2}, {-4, 1}, @@ -386,10 +454,10 @@ local shoot_missiledis_offset_map = { {0, 0}, } -local obj_turret = Object.new(NAMESPACE, "turret", Object.PARENT.actor) +local obj_turret = Object.new("TechnicianTurret", Object.Parent.ACTOR) obj_turret:set_sprite(sprite_turretaI) -obj_turret:clear_callbacks() -obj_turret:onCreate(function(inst) + +Callback.add(obj_turret.on_create, function(inst) inst:init_actor_default() inst.mask_index = turret_mask @@ -399,7 +467,7 @@ obj_turret:onCreate(function(inst) inst.intangible = true inst.init = nil - inst.team = gm.constants.TEAM_PLAYER + inst.team = 1 inst.upgrade_progress = 0 inst.upgrade_progress_max = 3 @@ -416,7 +484,7 @@ obj_turret:onCreate(function(inst) inst.basesecondarystocks = 4 inst.damage = 1 - inst.co_damage = technicianSpecial.damage + inst.co_damage = special.damage inst.ff = 0 inst.image_speed = 0.2 @@ -426,7 +494,8 @@ obj_turret:onCreate(function(inst) inst.dirty = 1 inst:instance_sync() end) -obj_turret:onStep(function(inst) + +Callback.add(obj_turret.on_step, function(inst) inst:step_actor() if inst.parent and Instance.exists(inst.parent) then @@ -434,9 +503,9 @@ obj_turret:onStep(function(inst) inst.ff = inst.ff + 1 inst.y = inst.oy - math.sin(inst.ff / 20) * 2 - 2 - inst.scepter = inst.parent:item_stack_count(item_scepter) + inst.scepter = inst.parent:item_count(item_scepter) if not inst.init then - local xx, _ = move_point_contact_solid(inst.x, inst.y, 90 - 90 * inst.image_xscale, 1000, inst) + local xx, _ = ssr_move_point_contact_solid(inst.x, inst.y, 90 - 90 * inst.image_xscale, 1000, inst) inst.range = math.abs(xx - inst.x) inst.init = 1 @@ -460,7 +529,7 @@ obj_turret:onStep(function(inst) inst.shoot = sprite_turretashoot inst.sparks = sprite_sparks1 inst.basecooldown = 50 - inst.co_damage = technicianSpecial.damage + inst.co_damage = special.damage inst.upgradeState = 0 inst.cooldown = math.min(inst.cooldown, inst.basecooldown) inst.playanim = nil @@ -480,7 +549,7 @@ obj_turret:onStep(function(inst) inst.skin_layer.visible = true if (inst.prevUpgradeState or inst.upgradeState) < inst.upgradeState then machineFlash(inst) - gm.sound_play_at(sound_turretUpgrade, 1, 1, inst.x, inst.y) + gm.sound_play_at(sound_turretUpgrade.value, 1, 1, inst.x, inst.y) end if inst.missileDis then inst.missileDis:destroy() end doResync = true @@ -501,21 +570,21 @@ obj_turret:onStep(function(inst) inst.playanim = nil machineFlash(inst) machineFlash(inst.missileDis) - gm.sound_play_at(sound_turretUpgrade, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) + gm.sound_play_at(sound_turretUpgrade.value, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) doResync = true end if inst.playanim then inst.image_speed = 0.2 * inst.attack_speed inst.sprite_index = inst.shoot - if inst.image_index >= gm.sprite_get_number(inst.shoot) - 1 then + if inst.image_index >= GM.sprite_get_number(inst.shoot) - 1 then inst.playanim = nil end else inst.sprite_index = inst.idle inst.image_speed = 0.2 end - if gm._mod_net_isHost() and doResync then + if Net.host and doResync then inst:instance_resync() end @@ -528,8 +597,8 @@ obj_turret:onStep(function(inst) if inst.switchMissileState then if inst.switchMissileState == -1 then if inst.missileDis.sprite_index == sprite_turretc_mis3 then - inst.missileDis.image_index = math.min(inst.missileDis.image_index + 0.3, gm.sprite_get_number(sprite_turretc_mis3) - 1) - if inst.missileDis.image_index >= gm.sprite_get_number(inst.missileDis.sprite_index) - 1 then + inst.missileDis.image_index = math.min(inst.missileDis.image_index + 0.3, GM.sprite_get_number(sprite_turretc_mis3) - 1) + if inst.missileDis.image_index >= GM.sprite_get_number(inst.missileDis.sprite_index) - 1 then inst.missileDis.sprite_index = sprite_turretc_mis2 end else @@ -541,15 +610,15 @@ obj_turret:onStep(function(inst) end else inst.missileDis.sprite_index = sprite_turretc_mis2 - inst.missileDis.image_index = math.min(inst.missileDis.image_index + 0.3, gm.sprite_get_number(inst.missileDis.sprite_index) - 1) - if inst.missileDis.image_index >= gm.sprite_get_number(inst.missileDis.sprite_index) - 1 then + inst.missileDis.image_index = math.min(inst.missileDis.image_index + 0.3, GM.sprite_get_number(inst.missileDis.sprite_index) - 1) + if inst.missileDis.image_index >= GM.sprite_get_number(inst.missileDis.sprite_index) - 1 then inst.missileState = 2 inst.switchMissileState = nil end end elseif inst.missileState == 2 then inst.missileDis.sprite_index = sprite_turretc_mis3 - inst.missileDis.image_index = math.min(inst.missileDis.image_index + 0.2, gm.sprite_get_number(sprite_turretc_mis3) - 1) + inst.missileDis.image_index = math.min(inst.missileDis.image_index + 0.2, GM.sprite_get_number(inst.missileDis.sprite_index) - 1) else inst.missileDis.sprite_index = sprite_turretc_mis1 inst.missileDis.image_index = inst.image_index @@ -558,7 +627,7 @@ obj_turret:onStep(function(inst) inst.missileDis.y = inst.y + yo end - if inst.parent:is_authority() or (inst.upgradeState == 2 and gm._mod_net_isHost()) then + if inst.parent:is_authority() or (inst.upgradeState == 2 and Net.host) then local wantattack = false local victims = List.new() inst:collision_line_list(inst.x, inst.y + (inst.upgradeState >= 1 and 8 or 10), inst.x + inst.range * inst.image_xscale, inst.y, gm.constants.pActorCollisionBase, false, true, victims, false) @@ -569,6 +638,8 @@ obj_turret:onStep(function(inst) end end + victims:destroy() + inst.cooldown = inst.cooldown - 1 inst.secondarycooldown = inst.secondarycooldown - 1 if wantattack then @@ -577,25 +648,29 @@ obj_turret:onStep(function(inst) inst.image_index = 0 inst.cooldown = inst.basecooldown / inst.attack_speed inst.damage = inst.parent.damage - for i = 0, inst.parent:buff_stack_count(buff_mirror) do - local attack_info = inst:fire_bullet(inst.x, inst.y + (inst.upgradeState >= 1 and 8 or 10), 1000, 90 - 90 * inst.image_xscale, inst.co_damage, nil, inst.sparks, Attack_Info.TRACER.commando1).attack_info + + local turret_tracer = Tracer.COMMANDO1 + if inst.upgradeState == 1 then + turret_tracer = Tracer.PLAYER_DRONE + elseif inst.upgradeState == 2 then + turret_tracer = Tracer.SNIPER1 + end + + for i = 0, inst.parent:buff_count(buff_mirror) do + local attack_info = inst:fire_bullet(inst.x, inst.y + (inst.upgradeState >= 1 and 8 or 10), 1000, 90 - 90 * inst.image_xscale, inst.co_damage, nil, inst.sparks, turret_tracer).attack_info attack_info.climb = i * 8 * 1.35 end - gm.sound_play_at(inst.upgradeState >= 1 and sound_turretShoot2 or sound_turretShoot1, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) - if not Net.is_single() then - if gm._mod_net_isHost() then - local buffer = turret_shoot_packet:message_begin() - buffer:write_instance(inst) - buffer:send_to_all() + + gm.sound_play_at(inst.upgradeState >= 1 and sound_turretShoot2.value or sound_turretShoot1.value, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) + if Net.online then + if Net.host then + turret_shoot_packet:send_to_all(inst) else - local buffer = turret_shoot_packet_host:message_begin() - buffer:write_instance(inst) - buffer:write_instance(inst.parent) - buffer:send_to_host() + turret_shoot_packet_host:send_to_host(inst, inst.parent) end end end - if inst.upgradeState == 2 and gm._mod_net_isHost() then + if inst.upgradeState == 2 and Net.host then if inst.missileState == 2 then if inst.secondarycooldown <= 0 then local missile = object_missile:create(inst.x - 16 * inst.image_xscale, inst.y - 20) @@ -611,23 +686,17 @@ obj_turret:onStep(function(inst) inst.secondarystocks = inst.basesecondarystocks inst.switchMissileState = -1 - if not Net.is_single() then - local buffer = turret_missile_state_packet:message_begin() - buffer:write_instance(inst) - buffer:write_byte(inst.switchMissileState + 1) - buffer:send_to_all() + if Net.online then + turret_missile_state_packet:send_to_all(inst) end end end - elseif inst.secondarycooldown <= 4 * gm.sprite_get_number(sprite_turretc_mis2) and not inst.switchMissileState then + elseif inst.secondarycooldown <= GM.sprite_get_number(sprite_turretc_mis2) and not inst.switchMissileState then inst.missileDis.image_index = 0 inst.switchMissileState = 1 - if not Net.is_single() then - local buffer = turret_missile_state_packet:message_begin() - buffer:write_instance(inst) - buffer:write_byte(inst.switchMissileState + 1) - buffer:send_to_all() + if Net.online then + turret_missile_state_packet:send_to_all(inst) end end end @@ -635,11 +704,8 @@ obj_turret:onStep(function(inst) if inst.switchMissileState ~= -1 then inst.switchMissileState = -1 - if not Net.is_single() then - local buffer = turret_missile_state_packet:message_begin() - buffer:write_instance(inst) - buffer:write_byte(inst.switchMissileState + 1) - buffer:send_to_all() + if Net.online then + turret_missile_state_packet:send_to_all(inst) end end if inst.secondarycooldown <= 0 then @@ -661,62 +727,73 @@ obj_turret:onStep(function(inst) inst:destroy() end end) -obj_turret:onDestroy(function(inst) + +Callback.add(obj_turret.on_destroy, function(inst) local ef_sparks = object_sparks:create(inst.x, inst.y) ef_sparks.sprite_index = explosion3 ef_sparks.image_speed = 0.3 ef_sparks.image_yscale = 1 - gm.sound_play_at(sound_turretDeath, 1, 1, inst.x, inst.y) + gm.sound_play_at(sound_turretDeath.value, 1, 1, inst.x, inst.y) inst:screen_shake(2) - if gm._mod_net_isHost() then + if Net.host then inst:instance_destroy_sync() end end) -obj_turret:onSerialize(function(self, buffer) + +local obj_turret_serializer = function(self, buffer) buffer:write_instance(self.parent) buffer:write_byte(self.team) - buffer:write_byte(self.image_xscale + 1) --We have to add +1 otherwise the byte will underflow if -1 + buffer:write_byte(self.image_xscale + 1) -- we have to add +1 otherwise the byte will underflow if -1 buffer:write_byte(self.upgrade_progress) -end) -obj_turret:onDeserialize(function(self, buffer) +end + +local obj_turret_deserializer = function(self, buffer) self.parent = buffer:read_instance() self.team = buffer:read_byte() - self.image_xscale = buffer:read_byte() - 1 --Revert the +1 after being recieved + self.image_xscale = buffer:read_byte() - 1 -- revert the +1 after being recieved self.upgrade_progress = buffer:read_byte() -end) +end --- Vending Machine +Object.add_serializers(obj_turret, obj_turret_serializer, obj_turret_deserializer) + +-- vending Machine buff_vending.icon_sprite = buff_vending_sprite buff_vending_2.icon_sprite = buff_vending_2_sprite -buff_vending:clear_callbacks() -buff_vending_2:clear_callbacks() -buff_vending:onStatRecalc(function(actor) - actor.attack_speed = actor.attack_speed + MACHINE_VENDING_ATTACKSPEED - actor.pHmax = actor.pHmax + MACHINE_VENDING_MOVESPEED + +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buff_vending) + if stack <= 0 then return end + + api.attack_speed_add(MACHINE_VENDING_ATTACKSPEED) + api.pHmax_add(MACHINE_VENDING_MOVESPEED) end) -buff_vending_2:onStatRecalc(function(actor) - actor.attack_speed = actor.attack_speed + MACHINE_VENDING_ATTACKSPEED2 - actor.pHmax = actor.pHmax + MACHINE_VENDING_MOVESPEED - actor.critical_chance = actor.critical_chance + MACHINE_VENDING_CRIT + +RecalculateStats.add(function(actor, api) + local stack = actor:buff_count(buff_vending_2) + if stack <= 0 then return end + + api.attack_speed_add(MACHINE_VENDING_ATTACKSPEED2) + api.pHmax_add(MACHINE_VENDING_MOVESPEED) + api.critical_chance_add(MACHINE_VENDING_CRIT) end) local drinkSprites = { - [technician_id] = {sprite_drink_technician_1, sprite_drink_technician_2} + [technician.value] = {sprite_drink_technician_1, sprite_drink_technician_2} } -local stateDrink = State.new(NAMESPACE, "technicianDrink") +local stateDrink = ActorState.new("technicianDrink") -stateDrink:clear_callbacks() -stateDrink:onEnter(function(actor, data) +Callback.add(stateDrink.on_enter, function(actor, data) actor.image_index2 = 0 - data.sprite = actor.__ssr_current_drink_sprite or drinkSprites[technician_id][1] + data.sprite = actor.__ssr_current_drink_sprite or drinkSprites[technician.value][1] actor:skill_util_strafe_init() actor:skill_util_strafe_turn_init() end) -stateDrink:onStep(function(actor, data) + +Callback.add(stateDrink.on_step, function(actor, data) actor.sprite_index2 = data.sprite actor:skill_util_strafe_update(0.2, 1) @@ -725,34 +802,46 @@ stateDrink:onStep(function(actor, data) handle_strafing_yoffset(actor) - if actor.image_index2 >= gm.sprite_get_number(data.sprite) then + if actor.image_index2 >= GM.sprite_get_number(data.sprite) then actor:skill_util_reset_activity_state() end end) -stateDrink:onExit(function(actor, data) + +Callback.add(stateDrink.on_exit, function(actor, data) actor:skill_util_strafe_exit() end) -stateDrink:onGetInterruptPriority(function(actor, data) - return State.ACTOR_STATE_INTERRUPT_PRIORITY.any + +Callback.add(stateDrink.on_get_interrupt_priority, function(actor, data) + return ActorState.InterruptPriority.ANY end) -local vending_shoot_packet = Packet.new() -vending_shoot_packet:onReceived(function(message, player) - local inst = message:read_instance() - local recipient = message:read_instance() +local vending_shoot_packet = Packet.new("SyncVendingDispense") + +local vending_shoot_packet_deserializer = function(buffer, self) + local inst = buffer:read_instance() + local recipient = buffer:read_instance() + inst.playanim = 1 + if recipient.actor_state_current_id == -1 and drinkSprites[recipient.class] then - recipient:sound_play(sound_vendingDrink, 1, 1) + recipient:sound_play(sound_vendingDrink.value, 1, 1) recipient.__ssr_current_drink_sprite = drinkSprites[recipient.class][inst:get_data().upgraded and 2 or 1] - recipient:enter_state(stateDrink) + recipient:set_state(stateDrink) end -end) +end -local obj_vending = Object.new(NAMESPACE, "vending") +local vending_shoot_packet_serializer = function(buffer, self, inst, recipient) + buffer:write_instance(inst) + buffer:write_instance(recipient) +end + +vending_shoot_packet:set_serializers(vending_shoot_packet_serializer, vending_shoot_packet_deserializer) + +local obj_vending = Object.new("TechnicianVendingMachine") obj_vending:set_sprite(sprite_vending1) -obj_vending.obj_depth = 20 -obj_vending:clear_callbacks() -obj_vending:onCreate(function(inst) +obj_vending:set_depth(20) + +Callback.add(obj_vending.on_create, function(inst) inst.gravity = MACHINE_VENDING_GRAV inst.init = nil inst.upgrade_progress = 0 @@ -765,16 +854,17 @@ obj_vending:onCreate(function(inst) inst:actor_skin_skinnable_init() inst:instance_sync() end) -obj_vending:onStep(function(inst) + +Callback.add(obj_vending.on_step, function(inst) if inst.parent and Instance.exists(inst.parent) then local height = inst.bbox_bottom - inst.bbox_top for i = 1, (math.floor((inst.vspeed + inst.gravity) / height) + 1) do - if is_colliding_stage(inst, inst.x, inst.y + inst.vspeed + inst.gravity - height * (i - 1)) then - move_contact_solid(inst, 90, 32) + if ssr_is_colliding_stage(inst, inst.x, inst.y + inst.vspeed + inst.gravity - height * (i - 1)) then + ssr_move_contact_solid(inst, 90, 32) if inst.vspeed > MACHINE_VENDING_DAMAGE_THRESHOLD then if inst.parent:is_authority() then - for i = 0, inst.parent:buff_stack_count(buff_mirror) do - local attack_info = inst.parent:fire_explosion(inst.x, inst.y - MACHINE_VENDING_BLAST_H / 2, MACHINE_VENDING_BLAST_W, MACHINE_VENDING_BLAST_H, inst.parent:skill_get_damage(technicianUtility) * (inst.vspeed / MACHINE_VENDING_DAMAGE_THRESHOLD)^1.5).attack_info + for i = 0, inst.parent:buff_count(buff_mirror) do + local attack_info = inst.parent:fire_explosion(inst.x, inst.y - MACHINE_VENDING_BLAST_H / 2, MACHINE_VENDING_BLAST_W, MACHINE_VENDING_BLAST_H, inst.parent:skill_get_damage(utility) * (inst.vspeed / MACHINE_VENDING_DAMAGE_THRESHOLD)^1.5).attack_info attack_info.climb = i * 8 * 1.35 end end @@ -797,7 +887,7 @@ obj_vending:onStep(function(inst) end if inst.vspeed > MACHINE_VENDING_DAMAGE_THRESHOLD and Global._current_frame % math.max(4 - math.floor(inst.vspeed / MACHINE_VENDING_DAMAGE_THRESHOLD), 1) == 0 then - local trail = GM.instance_create(inst.x, inst.y - height * (i - 1), gm.constants.oEfTrail) + local trail = Object.find("EfTrail"):create(inst.x, inst.y - height * (i - 1)) trail.sprite_index = inst.sprite_index trail.image_index = inst.image_index trail.image_blend = gm.merge_colour(inst.image_blend, Color.BLACK, 0.25) @@ -805,7 +895,7 @@ obj_vending:onStep(function(inst) trail.image_yscale = inst.image_yscale trail.image_alpha = trail.image_alpha * (0.2 * (inst.vspeed / MACHINE_VENDING_DAMAGE_THRESHOLD)) trail.depth = inst.depth + 1 - trail:actor_skin_skinnable_set_skin(inst.parent) + --trail:actor_skin_skinnable_set_skin(inst.parent) BUG end end @@ -830,36 +920,37 @@ obj_vending:onStep(function(inst) inst.upgraded = 1 inst.buff = buff_vending_2 machineFlash(inst) - gm.sound_play_at(sound_vendingUpgrade, 1, 1, inst.x, inst.y) + gm.sound_play_at(sound_vendingUpgrade.value, 1, 1, inst.x, inst.y) doResync = true end if inst.playanim then - if inst.image_index >= gm.sprite_get_number(inst.sprite_index) - 1 then + if inst.image_index >= GM.sprite_get_number(inst.sprite_index) - 1 then inst.playanim = nil end else inst.image_index = 0 end - if gm._mod_net_isHost() then + if Net.host then if doResync then inst:instance_resync() end + for _, player in ipairs(inst:get_collisions(gm.constants.oP)) do - if player.team == inst.team and player:buff_stack_count(inst.buff) <= 0 then - gm.sound_play_at(sound_vendingDispense, 1, 1, inst.x, inst.y) + if player.team == inst.team and player:buff_count(inst.buff) <= 0 then + gm.sound_play_at(sound_vendingDispense.value, 1, 1, inst.x, inst.y) player:buff_remove(buff_vending) player:buff_apply(inst.buff, 5 * 60) + if player.actor_state_current_id == -1 and drinkSprites[player.class] then - player:sound_play(sound_vendingDrink, 1, 1) + player:sound_play(sound_vendingDrink.value, 1, 1) player.__ssr_current_drink_sprite = drinkSprites[player.class][inst.upgraded and 2 or 1] - player:enter_state(stateDrink) + player:set_state(stateDrink) end + inst.playanim = 1 - if not Net.is_single() then - local buffer = vending_shoot_packet:message_begin() - buffer:write_instance(inst) - buffer:write_instance(player) - buffer:send_to_all() + + if Net.online then + vending_shoot_packet:send_to_all(inst, player) end break end @@ -869,56 +960,62 @@ obj_vending:onStep(function(inst) inst:destroy() end end) -obj_vending:onDraw(function(inst) + +Callback.add(obj_vending.on_draw, function(inst) inst:actor_skin_skinnable_draw_self() end) -obj_vending:onDestroy(function(inst) - if gm._mod_net_isHost() then + +Callback.add(obj_vending.on_destroy, function(inst) + if Net.host then inst:instance_destroy_sync() end end) -obj_vending:onSerialize(function(self, buffer) + +local obj_vending_serializer = function(self, buffer) buffer:write_instance(self.parent) buffer:write_byte(self.team) - buffer:write_byte(self.image_xscale + 1) --We have to add +1 otherwise the byte will underflow if -1 + buffer:write_byte(self.image_xscale + 1) -- we have to add +1 otherwise the byte will underflow if -1 buffer:write_byte(self.upgrade_progress) -end) -obj_vending:onDeserialize(function(self, buffer) +end + +local obj_vending_deserializer = function(self, buffer) self.parent = buffer:read_instance() self.team = buffer:read_byte() - self.image_xscale = buffer:read_byte() - 1 --Revert the +1 after being recieved + self.image_xscale = buffer:read_byte() - 1 -- revert the +1 after being recieved self.upgrade_progress = buffer:read_byte() self:actor_skin_skinnable_set_skin(self.parent) -end) +end + +Object.add_serializers(obj_vending, obj_vending_serializer, obj_vending_deserializer) -- and they called it a mine -local obj_mine_pull = Object.new(NAMESPACE, "mine_pull") -obj_mine_pull:clear_callbacks() -obj_mine_pull:onCreate(function(inst) +local obj_mine_pull = Object.new("TechnicianMinePull") + +Callback.add(obj_mine_pull.on_create, function(inst) inst.life = MACHINE_MINE_PULL_LIFE inst.ff = 0 end) -obj_mine_pull:onStep(function(inst) + +Callback.add(obj_mine_pull.on_step, function(inst) inst.ff = inst.ff + 1 - local targets = List.new() - inst:collision_circle_list(inst.x, inst.y, MACHINE_MINE_PULL_RADIUS, gm.constants.pActor, false, true, targets, false) - for _, target in ipairs(targets) do - --Check if we should pull the target - --Pulling is weird with ropes so climbing enemies are excluded - --And intangible enemies are usually doing special behavior so best not to interrupt - if target.team ~= inst.team and not target.intangible and not GM.actor_state_is_climb_state(target.actor_state_current_id) then + + for _, target in ipairs(inst:get_collisions_circle(gm.constants.pActor, MACHINE_MINE_PULL_RADIUS, inst.x, inst.y)) do + -- check if we should pull the target + -- fulling is weird with ropes so climbing enemies are excluded + -- and intangible enemies are usually doing special behavior so best not to interrupt + if target.team ~= inst.team and not target.intangible and not target:is_climbing() then local lastx = target.x local lasty = target.y local strength = math.max(1, math.ceil((0.5 + 2.5 * (1 - inst.life / MACHINE_MINE_PULL_LIFE) + math.max(-1.5 + 3 * (1 - inst.life / MACHINE_MINE_PULL_LIFE), 0)) * (0.2 + 0.8 * (1 - gm.point_distance(inst.x, inst.y, target.x, target.y) / MACHINE_MINE_PULL_RADIUS)))) - if GM.actor_is_classic(target) then --Classic enemies (Eg. NOT Jellyfish or Archer Bugs) are pulled horizontally to the center of the pull - target:move_contact_solid(180 + gm.point_direction(inst.x, target.y, target.x, target.y), strength) - elseif not GM.actor_is_boss(target) then --Non-boss, non-classic enemies are pulled directly to the center - target.x = target.x - math.cos(math.rad(gm.point_direction(inst.x, inst.y, target.x, target.y))) * strength - target.y = target.y + math.sin(math.rad(gm.point_direction(inst.x, inst.y, target.x, target.y))) * strength + if GM.actor_is_classic(target) then -- classic enemies (Eg. NOT Jellyfish or Archer Bugs) are pulled horizontally to the center of the pull + target:move_contact_solid(180 + Math.direction(inst.x, target.y, target.x, target.y), strength) + elseif not GM.actor_is_boss(target) then -- non-boss, non-classic enemies are pulled directly to the center + target.x = target.x - math.cos(math.rad(Math.direction(inst.x, inst.y, target.x, target.y))) * strength + target.y = target.y + math.sin(math.rad(Math.direction(inst.x, inst.y, target.x, target.y))) * strength end - --Prevent overshooting on both axes + -- prevent overshooting on both axes if lastx < inst.x and target.x >= inst.x then target.x = math.min(target.x, inst.x) elseif lastx > inst.x and target.x <= inst.x then @@ -932,28 +1029,33 @@ obj_mine_pull:onStep(function(inst) end end end - targets:destroy() + inst.life = inst.life - 1 + if inst.life <= 0 then inst:destroy() end end) + local int1 = 60 local int2 = 120 / int1 -obj_mine_pull:onDraw(function(inst) + +Callback.add(obj_mine_pull.on_draw, function(inst) gm.draw_set_alpha(((0.2 * math.sin(inst.ff / 10)) + 0.4) * math.min(inst.ff / (int1 / 2), 1)) gm.draw_set_colour(color_tech_blue) + local a = inst.ff > int1 and (int1 * int2 - (inst.ff - int1) * int1 / (MACHINE_MINE_PULL_LIFE - int1) * int2) or (inst.ff * int2) local t = math.min(inst.ff > int1 and (1 - (inst.ff - int1) / (MACHINE_MINE_PULL_LIFE - int1)) or (inst.ff / int1), 1) + gm.draw_circle(math.floor(inst.x + 0.5), math.floor(inst.y + 0.5), a + (MACHINE_MINE_PULL_RADIUS - a) * t, true) gm.draw_set_alpha(1) end) -local obj_mine = Object.new(NAMESPACE, "mine") +local obj_mine = Object.new("TechnicianMine") obj_mine:set_sprite(sprite_mine1) ---obj_mine.obj_depth = 218 -- GRAAAAAAH THIS WONT GO BACK --- Note from the future: Now it does, thanks kris :) ----Never mind I don't like it -obj_mine:clear_callbacks() -obj_mine:onCreate(function(inst) +-- obj_mine:set_depth(218) -- GRAAAAAAH THIS WONT GO BACK -- note from the future: now it does, thanks kris :) -- never mind I don't like it + +Callback.add(obj_mine.on_create, function(inst) inst.gravity = MACHINE_MINE_GRAV inst.mask_index = mine_mask inst.upgrade_progress = 0 @@ -967,20 +1069,25 @@ obj_mine:onCreate(function(inst) inst:actor_skin_skinnable_init() inst:instance_sync() end) -obj_mine:onStep(function(inst) + +Callback.add(obj_mine.on_step, function(inst) if inst.parent and Instance.exists(inst.parent) then inst.ff = inst.ff + 1 inst.hspeed = inst.hspeed * 0.9 - if is_colliding_stage(inst, inst.x + inst.hspeed, inst.y) then + + if ssr_is_colliding_stage(inst, inst.x + inst.hspeed, inst.y) then inst.hspeed = 0 end - if is_colliding_stage(inst, inst.x, inst.y + inst.vspeed + inst.gravity) then - move_contact_solid(inst, 90) + + if ssr_is_colliding_stage(inst, inst.x, inst.y + inst.vspeed + inst.gravity) then + ssr_move_contact_solid(inst, 90) inst.vspeed = 0 inst.y = inst.y - inst.gravity end + local doResync = false machine_update_temp(inst) + if inst.upgrade_progress < 3 and inst.upgraded then inst.sprite_index = sprite_mine1 for _, pull in ipairs(Instance.find_all(obj_mine_pull)) do @@ -991,17 +1098,20 @@ obj_mine:onStep(function(inst) inst.upgraded = nil doResync = true end + if inst.upgrade_progress >= 3 and not inst.upgraded then inst.sprite_index = sprite_mine2 inst.pull_timer = 0 inst.upgraded = 1 machineFlash(inst) - gm.sound_play_at(sound_mineUpgrade, 1, 1, inst.x, inst.y) + gm.sound_play_at(sound_mineUpgrade.value, 1, 1, inst.x, inst.y) doResync = true end - if gm._mod_net_isHost() and doResync then + + if Net.online and doResync then inst:instance_resync() end + if inst.upgraded == 1 then inst.pull_timer = inst.pull_timer - 1 if inst.pull_timer <= 0 then @@ -1015,8 +1125,10 @@ obj_mine:onStep(function(inst) inst:destroy() end end) -obj_mine:onDraw(function(inst) + +Callback.add(obj_mine.on_draw, function(inst) inst:actor_skin_skinnable_draw_self() + if not inst.upgraded then gm.draw_set_alpha((0.3 * math.sin(inst.ff / 10)) + 0.4) gm.draw_set_colour(color_tech_red) @@ -1024,58 +1136,62 @@ obj_mine:onDraw(function(inst) gm.draw_set_alpha(1) end end) -obj_mine:onDestroy(function(inst) + +Callback.add(obj_mine.on_destroy, function(inst) for _, pull in ipairs(Instance.find_all(obj_mine_pull)) do if pull.parent == inst.id then pull:destroy() end end + local ef_sparks = object_sparks:create(inst.x, inst.y) ef_sparks.sprite_index = mine_explosion ef_sparks.image_speed = 0.2 ef_sparks.image_yscale = 1 - gm.sound_play_at(sound_mineExplode1, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) + gm.sound_play_at(sound_mineExplode1.value, 1, 0.9 + math.random() * 0.2, inst.x, inst.y) inst:screen_shake(5) - if gm._mod_net_isHost() then - for i = 0, inst.parent:buff_stack_count(buff_mirror) do - local attack_info = inst.parent:fire_explosion(inst.x, inst.y, get_tiles(6), get_tiles(6), inst.parent:skill_get_damage(technicianSecondary)).attack_info + if Net.host then + for i = 0, inst.parent:buff_count(buff_mirror) do + local attack_info = inst.parent:fire_explosion(inst.x, inst.y, ssr_get_tiles(6), ssr_get_tiles(6), inst.parent:skill_get_damage(secondary)).attack_info attack_info.knockback = 6 attack_info.climb = i * 8 * 1.35 if inst.upgraded then - attack_info:set_stun(1.2) + attack_info.stun = 1.2 end end inst:instance_destroy_sync() end end) -obj_mine:onSerialize(function(self, buffer) + +local obj_mine_serializer = function(self, buffer) buffer:write_instance(self.parent) buffer:write_byte(self.team) buffer:write_int(self.hspeed) buffer:write_byte(self.upgrade_progress) -end) -obj_mine:onDeserialize(function(self, buffer) +end + +local obj_mine_deserializer = function(self, buffer) self.parent = buffer:read_instance() self.team = buffer:read_byte() self.hspeed = buffer:read_int() self.upgrade_progress = buffer:read_byte() self:actor_skin_skinnable_set_skin(self.parent) -end) +end + +Object.add_serializers(obj_mine, obj_mine_serializer, obj_mine_deserializer) -- Stick of Death buff_exposed.icon_sprite = buff_exposed_sprite buff_exposed_2.icon_sprite = buff_exposed_2_sprite -buff_exposed:clear_callbacks() -buff_exposed_2:clear_callbacks() -local obj_amplifier = Object.new(NAMESPACE, "amplifier") +local obj_amplifier = Object.new("amplifier") obj_amplifier:set_sprite(sprite_amplifier1) -obj_amplifier:clear_callbacks() -obj_amplifier:onCreate(function(inst) + +Callback.add(obj_amplifier.on_create, function(inst) inst.gravity = MACHINE_VENDING_GRAV inst.mask_index = amplifier_mask inst.idle = sprite_turretaI @@ -1091,13 +1207,14 @@ obj_amplifier:onCreate(function(inst) inst.buff = buff_exposed inst:instance_sync() end) -obj_amplifier:onStep(function(inst) + +Callback.add(obj_amplifier.on_step, function(inst) if inst.parent and Instance.exists(inst.parent) then inst.ff = inst.ff + 1 - if is_colliding_stage(inst, inst.x, inst.y + inst.vspeed + inst.gravity) then - move_contact_solid(inst, 90, 32) + if ssr_is_colliding_stage(inst, inst.x, inst.y + inst.vspeed + inst.gravity) then + ssr_move_contact_solid(inst, 90, 32) if inst.vspeed > MACHINE_VENDING_DAMAGE_THRESHOLD and inst.parent:is_authority() then - inst.parent:fire_explosion(inst.x, inst.y - MACHINE_VENDING_BLAST_H / 2, MACHINE_VENDING_BLAST_W, MACHINE_VENDING_BLAST_H, inst.parent:skill_get_damage(technicianUtilityAlt) * (inst.vspeed / MACHINE_VENDING_DAMAGE_THRESHOLD) * 2 - inst.parent:skill_get_damage(technicianUtilityAlt)) + inst.parent:fire_explosion(inst.x, inst.y - MACHINE_VENDING_BLAST_H / 2, MACHINE_VENDING_BLAST_W, MACHINE_VENDING_BLAST_H, inst.parent:skill_get_damage(utility2) * (inst.vspeed / MACHINE_VENDING_DAMAGE_THRESHOLD) * 2 - inst.parent:skill_get_damage(utility2)) inst:screen_shake(math.floor(inst.vspeed / MACHINE_VENDING_DAMAGE_THRESHOLD) + 2) local ef_sparks = object_sparks:create(inst.x, inst.y - 9) @@ -1109,16 +1226,18 @@ obj_amplifier:onStep(function(inst) inst.gravity = 0 end - inst.visual_radius = gm.lerp(inst.visual_radius, inst.radius, 0.2) + inst.visual_radius = Math.lerp(inst.visual_radius, inst.radius, 0.2) local doResync = false machine_update_temp(inst) + if inst.upgrade_progress < 3 and inst.upgraded then inst.sprite_index = sprite_amplifier1 inst.buff = buff_exposed inst.upgraded = nil doResync = true end + if inst.upgrade_progress >= 3 and not inst.upgraded then inst.sprite_index = sprite_amplifier2 inst.buff = buff_exposed_2 @@ -1127,7 +1246,7 @@ obj_amplifier:onStep(function(inst) doResync = true end - if gm._mod_net_isHost() then + if Net.host then if doResync then inst:instance_resync() end @@ -1144,38 +1263,47 @@ obj_amplifier:onStep(function(inst) inst:destroy() end end) -obj_amplifier:onDraw(function(inst) + +Callback.add(obj_amplifier.on_draw, function(inst) gm.draw_set_alpha((0.4 * math.sin(inst.ff * 0.07)) + 0.4) gm.draw_set_colour(inst.upgraded and color_tech_orange or color_tech_red) gm.draw_circle(math.floor(inst.x + 0.5), math.floor(inst.y + 0.5), inst.visual_radius, true) gm.draw_set_alpha(1) end) -obj_amplifier:onDestroy(function(inst) - if gm._mod_net_isHost() then + +Callback.add(obj_amplifier.on_destroy, function(inst) + if Net.host then inst:instance_destroy_sync() end end) -obj_amplifier:onSerialize(function(self, buffer) + +local obj_amplifier_serializer = function(self, buffer) buffer:write_instance(self.parent) buffer:write_byte(self.team) buffer:write_byte(self.upgrade_progress) -end) -obj_amplifier:onDeserialize(function(self, buffer) +end + +local obj_amplifier_deserializer = function(self, buffer) self.parent = buffer:read_instance() self.team = buffer:read_byte() self.upgrade_progress = buffer:read_byte() -end) +end + +Object.add_serializers(obj_amplifier, obj_amplifier_serializer, obj_amplifier_deserializer) -- Misc local machines = {obj_turret, obj_vending, obj_mine, obj_amplifier} + local healable_survivors = { - [Survivor.find("ror-chef").value] = true, - [Survivor.find("ror-hand").value] = true, + [Survivor.find("chef").value] = true, + [Survivor.find("hand").value] = true, + [Survivor.find("mule").value] = true, } -local healable_objects = { Object.find("ror", "EngiTurret"), Object.find("ror", "EngiTurretB") } -Callback.add(Callback.TYPE.onAttackHit, "SSOnHitTechnician", function(hit_info) +local healable_objects = {Object.find("EngiTurret"), Object.find("EngiTurretB")} + +Callback.add(Callback.ON_ATTACK_HIT, function(hit_info) --[[local expose = 0 if hit_info.target:buff_stack_count(buff_exposed) >= 1 then expose = 1 end if hit_info.target:buff_stack_count(buff_exposed_2) >= 1 then expose = 2 end @@ -1188,50 +1316,44 @@ Callback.add(Callback.TYPE.onAttackHit, "SSOnHitTechnician", function(hit_info) if hit_info.attack_info.__wrench_hit then if hit_info.attack_info.__wrench_hit == 1 then - gm.sound_play_at(sound_wrenchHit, 1, 0.8 + math.random() * 0.3, hit_info.target.x, hit_info.target.y) + gm.sound_play_at(sound_wrenchHit.value, 1, 0.8 + math.random() * 0.3, hit_info.target.x, hit_info.target.y) hit_info.attack_info.__wrench_hit = 2 end local sparks = object_sparks:create(hit_info.target.x, hit_info.target.y) sparks.sprite_index = (hit_info.attack_info.__wrench_hit == 3 and sprite_sparks5 or sprite_sparks4) sparks.image_speed = 0.33 - sparks.image_xscale = gm.sign(hit_info.target.x - hit_info.inflictor.x) + sparks.image_xscale = Math.sign(hit_info.target.x - hit_info.inflictor.x) sparks.depth = hit_info.target.depth - 1 end end) ---Hook from Needles -gm.pre_script_hook(gm.constants.damager_calculate_damage, function(self, other, result, args) - local _hit_info = args[1] - local _damage = args[4] - local _critical = args[5] - if _hit_info and _hit_info.value and (_hit_info.value.attack_info.__ssr_technician_is_expose or 0) >= 2 and not gm.bool(_critical.value) then - _critical.value = true - _damage.value = _damage.value * 2 - end +DamageCalculate.add(function(api) + if not Instance.exists(api.parent) then return end + + if not (api.hit_info and (api.hit_info.attack_info.__ssr_technician_is_expose or 0) >= 2 and not api.critical) then return end + + api:set_critical(true) end) --- Wrench Whack -technicianPrimary.sprite = sprite_skills -technicianPrimary.subimage = 0 - -technicianPrimary.cooldown = 5 -technicianPrimary.damage = 1.8 -technicianPrimary.require_key_press = false -technicianPrimary.is_primary = true -technicianPrimary.does_change_activity_state = true -technicianPrimary.hold_facing_direction = true -technicianPrimary.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.any +-- wrench whack +primary.sprite = sprite_skills +primary.subimage = 0 +primary.cooldown = 5 +primary.damage = 1.8 +primary.require_key_press = false +primary.is_primary = true +primary.does_change_activity_state = true +primary.hold_facing_direction = true +primary.required_interrupt_priority = ActorState.InterruptPriority.ANY -local stateTechnicianPrimary = State.new(NAMESPACE, "technicianPrimary") +local statePrimary = ActorState.new("technicianPrimary") -technicianPrimary:clear_callbacks() -technicianPrimary:onActivate(function(actor) - actor:enter_state(stateTechnicianPrimary) +Callback.add(primary.on_activate, function(actor, skill, slot) + actor:set_state(statePrimary) end) -stateTechnicianPrimary:clear_callbacks() -stateTechnicianPrimary:onEnter(function(actor, data) +Callback.add(statePrimary.on_enter, function(actor, data) actor.image_index2 = 0 data.fired = 0 -- gamemaker bools are a pain to deal with in lua, so just use numbers instead data.currentAnim = ((data.currentAnim or 1) + 1) % 2 @@ -1239,7 +1361,7 @@ stateTechnicianPrimary:onEnter(function(actor, data) actor:skill_util_strafe_turn_init() end) -stateTechnicianPrimary:onStep(function(actor, data) +Callback.add(statePrimary.on_step, function(actor, data) actor.sprite_index2 = (data.currentAnim == 1 and sprite_shoot1_2 or sprite_shoot1_1) actor:skill_util_strafe_update(0.18 * actor.attack_speed, gm.constants.STRAFE_SPEED_NORMAL) @@ -1251,15 +1373,15 @@ stateTechnicianPrimary:onStep(function(actor, data) if data.fired == 0 and actor.image_index2 >= 2 then data.fired = 1 - actor:sound_play(sound_shoot1, 0.3, 0.9 + math.random() * 0.2) + actor:sound_play(sound_shoot1.value, 0.3, 0.9 + math.random() * 0.2) - local damage = actor:skill_get_damage(technicianPrimary) + local damage = actor:skill_get_damage(primary) local dir = actor:skill_util_facing_direction() if not GM.skill_util_update_heaven_cracker(actor, damage, actor.image_xscale) then - for i = 0, actor:buff_stack_count(buff_mirror) do + for i = 0, actor:buff_count(buff_mirror) do if actor:is_authority() then - local attack_info = actor:fire_explosion(actor.x + WRENCH_BLAST_OFFSET_X * actor.image_xscale, actor.y + WRENCH_BLAST_OFFSET_Y, WRENCH_BLAST_W, WRENCH_BLAST_H, actor:skill_get_damage(technicianPrimary)).attack_info + local attack_info = actor:fire_explosion(actor.x + WRENCH_BLAST_OFFSET_X * actor.image_xscale, actor.y + WRENCH_BLAST_OFFSET_Y, WRENCH_BLAST_W, WRENCH_BLAST_H, actor:skill_get_damage(primary)).attack_info attack_info.climb = i * 8 * 1.35 attack_info.knockback_direction = actor.image_xscale @@ -1267,10 +1389,13 @@ stateTechnicianPrimary:onStep(function(actor, data) attack_info.__wrench_hit = 1 end + local machinesHit = List.new() local x, y, x2, y2 = (actor.x + (WRENCH_BLAST_OFFSET_X - WRENCH_BLAST_W / 2) * actor.image_xscale), (actor.y - WRENCH_BLAST_H / 2 + WRENCH_BLAST_OFFSET_Y), (actor.x + (WRENCH_BLAST_OFFSET_X + WRENCH_BLAST_W / 2) * actor.image_xscale), (actor.y + WRENCH_BLAST_H / 2 + WRENCH_BLAST_OFFSET_Y) + actor:collision_rectangle_list(x, y, x2, y2, gm.constants.oCustomObject, false, true, machinesHit, false) actor:collision_rectangle_list(x, y, x2, y2, gm.constants.oCustomObject_pNPC, false, true, machinesHit, false) + -- ^ finding all machines in machines table that collides with the explosion to upgrade for _, machineObj in ipairs(machines) do for _, instance in ipairs(machinesHit) do @@ -1281,24 +1406,34 @@ stateTechnicianPrimary:onStep(function(actor, data) end end end + + machinesHit:destroy() + local healablesHit = List.new() + actor:collision_rectangle_list(x, y, x2, y2, gm.constants.oP, false, true, healablesHit, false) + for _, object in ipairs(healable_objects) do actor:collision_rectangle_list(x, y, x2, y2, object, false, true, healablesHit, false) end + for _, instance in ipairs(healablesHit) do if instance.object_index ~= gm.constants.oP or healable_survivors[instance.class] then instance:heal(10) - gm.sound_play_at(sound_wrenchHit, 1, 0.9 + math.random() * 0.2, instance.x, instance.y) - particle_spark:create(instance.x, instance.y, math.random(2, 4), Particle.SYSTEM.middle) + gm.sound_play_at(sound_wrenchHit.value, 1, 0.9 + math.random() * 0.2, instance.x, instance.y) + particle_spark:create(instance.x, instance.y, math.random(2, 4), Particle.System.MIDDLE) end end + + healablesHit:destroy() end else local machinesHit = List.new() - local endX, _ = move_point_contact_solid(actor.x, actor.y, 90 - 90 * actor.image_xscale, 700, actor) + local endX, _ = ssr_move_point_contact_solid(actor.x, actor.y, 90 - 90 * actor.image_xscale, 700, actor) + actor:collision_line_list(actor.x, actor.y, endX, actor.y, gm.constants.oCustomObject, false, true, machinesHit, false) actor:collision_line_list(actor.x, actor.y, endX, actor.y, gm.constants.oCustomObject_pNPC, false, true, machinesHit, false) + for _, machineObj in ipairs(machines) do for _, instance in ipairs(machinesHit) do if machineObj.value == instance.__object_index then @@ -1308,86 +1443,102 @@ stateTechnicianPrimary:onStep(function(actor, data) end end end + + machinesHit:destroy() + local healablesHit = List.new() + actor:collision_line_list(actor.x, actor.y, endX, actor.y, gm.constants.oP, false, true, healablesHit, false) + for _, object in ipairs(healable_objects) do actor:collision_line_list(actor.x, actor.y, endX, actor.y, object, false, true, healablesHit, false) end + for _, instance in ipairs(healablesHit) do if instance.object_index ~= gm.constants.oP or healable_survivors[instance.class] then instance:heal(10) - gm.sound_play_at(sound_wrenchHit, 1, 0.9 + math.random() * 0.2, instance.x, instance.y) - particle_spark:create(instance.x, instance.y, math.random(2, 4), Particle.SYSTEM.middle) + gm.sound_play_at(sound_wrenchHit.value, 1, 0.9 + math.random() * 0.2, instance.x, instance.y) + particle_spark:create(instance.x, instance.y, math.random(2, 4), Particle.System.MIDDLE) end end + + healablesHit:destroy() end end - if actor.image_index2 >= gm.sprite_get_number(actor.sprite_index2) then + if actor.image_index2 >= GM.sprite_get_number(actor.sprite_index2) then actor:skill_util_reset_activity_state() end end) -stateTechnicianPrimary:onExit(function(actor, data) + +Callback.add(statePrimary.on_exit, function(actor, data) actor:skill_util_strafe_exit() end) -stateTechnicianPrimary:onGetInterruptPriority(function(actor, data) - return actor.image_index2 >= 6 and State.ACTOR_STATE_INTERRUPT_PRIORITY.any or State.ACTOR_STATE_INTERRUPT_PRIORITY.skill + +Callback.add(statePrimary.on_get_interrupt_priority, function(actor, data) + return actor.image_index2 >= 6 and ActorState.InterruptPriority.ANY or ActorState.InterruptPriority.SKILL end) -- Wrench Throw -local obj_wrench = Object.new(NAMESPACE, "wrench") +local obj_wrench = Object.new("wrench") obj_wrench:set_sprite(sprite_wrench) -obj_wrench:clear_callbacks() -obj_wrench:onCreate(function(inst) - local data = inst:get_data() + +Callback.add(obj_wrench.on_create, function(inst) + local data = Instance.get_data(inst) data.hit = {} inst.mask_index = wrench_mask inst.image_speed = 0.2 - inst.damage = 1 + data.damage = 1 inst.speed = 5 - inst.climb = 0 - inst.life = 170 - inst.hits = 3 + data.climb = 0 + data.life = 170 + data.hits = 3 inst.team = 1 inst.parent = -4 inst:actor_skin_skinnable_init() end) -obj_wrench:onStep(function(inst) - local data = inst:get_data() - if inst.life <= 0 or not (inst.parent and Instance.exists(inst.parent)) then + +Callback.add(obj_wrench.on_step, function(inst) + local data = Instance.get_data(inst) + + if data.life <= 0 or not (inst.parent and Instance.exists(inst.parent)) then inst.image_alpha = inst.image_alpha - 0.04 inst.image_angle = inst.image_angle - inst.speed * 3 + if inst.image_alpha <= 0 then inst:destroy() end - elseif inst.hits <= 0 then + elseif data.hits <= 0 then inst:destroy() elseif inst:is_colliding(gm.constants.pBlock) then - particle_spark:create(inst.x, inst.y, math.random(2, 3), Particle.SYSTEM.middle) - gm.sound_play_at(sound_wrenchHit, 1, 0.8 + math.random() * 0.3, inst.x, inst.y) + particle_spark:create(inst.x, inst.y, math.random(2, 3), Particle.System.MIDDLE) + gm.sound_play_at(sound_wrenchHit.value, 1, 0.8 + math.random() * 0.3, inst.x, inst.y) inst:destroy() else - inst.life = inst.life - 1 + data.life = data.life - 1 inst.image_angle = inst.image_angle - inst.speed * 3 for _, actor in ipairs(inst:get_collisions(gm.constants.pActorCollisionBase)) do if inst:attack_collision_canhit(actor) and not data.hit[actor.id] then if inst.parent:is_authority() then - for i = 0, inst.parent:buff_stack_count(buff_mirror) do - local attack_info = inst.parent:fire_direct(actor, inst.damage, inst.direction, inst.x, inst.y).attack_info + for i = 0, inst.parent:buff_count(buff_mirror) do + local attack_info = inst.parent:fire_direct(actor, data.damage, inst.direction, inst.x, inst.y).attack_info attack_info.climb = i * 8 * 1.35 attack_info.__wrench_hit = 3 end end - inst.hits = inst.hits - 1 + data.hits = data.hits - 1 data.hit[actor.id] = true - gm.sound_play_at(sound_wrenchHit, 1, 0.8 + math.random() * 0.3, inst.x, inst.y) + gm.sound_play_at(sound_wrenchHit.value, 1, 0.8 + math.random() * 0.3, inst.x, inst.y) break end end + local machinesHit = List.new() + inst.parent:collision_rectangle_list(inst.x - 9, inst.y - 9, inst.x + 9, inst.y + 9, gm.constants.oCustomObject, false, true, machinesHit, false) inst.parent:collision_rectangle_list(inst.x - 9, inst.y - 9, inst.x + 9, inst.y + 9, gm.constants.oCustomObject_pNPC, false, true, machinesHit, false) + for _, machineObj in ipairs(machines) do for _, instance in ipairs(machinesHit) do if machineObj.value == instance.__object_index then @@ -1399,46 +1550,50 @@ obj_wrench:onStep(function(inst) end end end + + machinesHit:destroy() + local healablesHit = List.new() inst.parent:collision_rectangle_list(inst.x - 9, inst.y - 9, inst.x + 9, inst.y + 9, gm.constants.oP, false, true, healablesHit, false) + for _, object in ipairs(healable_objects) do inst.parent:collision_rectangle_list(inst.x - 9, inst.y - 9, inst.x + 9, inst.y + 9, object, false, true, healablesHit, false) end + for _, instance in ipairs(healablesHit) do if (instance.object_index ~= gm.constants.oP or healable_survivors[instance.class]) and not data.hit[instance.id] then instance:heal(10) - particle_spark:create(instance.x, instance.y, math.random(2, 4), Particle.SYSTEM.middle) - gm.sound_play_at(sound_wrenchHit, 1, 0.9 + math.random() * 0.2, instance.x, instance.y) + particle_spark:create(instance.x, instance.y, math.random(2, 4), Particle.System.MIDDLE) + gm.sound_play_at(sound_wrenchHit.value, 1, 0.9 + math.random() * 0.2, instance.x, instance.y) data.hit[instance.id] = true end end + + healablesHit:destroy() end end) -obj_wrench:onDraw(function(inst) + +Callback.add(obj_wrench.on_draw, function(inst) inst:actor_skin_skinnable_draw_self() end) -technicianPrimaryAlt.sprite = sprite_skills -technicianPrimaryAlt.subimage = 6 -technician:add_primary(technicianPrimaryAlt) - -technicianPrimaryAlt.cooldown = 5 -technicianPrimaryAlt.damage = 1.6 -technicianPrimaryAlt.require_key_press = false -technicianPrimaryAlt.is_primary = true -technicianPrimaryAlt.does_change_activity_state = true -technicianPrimaryAlt.hold_facing_direction = true -technicianPrimaryAlt.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.any +primary2.sprite = sprite_skills +primary2.subimage = 6 +primary2.cooldown = 5 +primary2.damage = 1.6 +primary2.require_key_press = false +primary2.is_primary = true +primary2.does_change_activity_state = true +primary2.hold_facing_direction = true +primary2.required_interrupt_priority = ActorState.InterruptPriority.ANY -local stateTechnicianPrimaryAlt = State.new(NAMESPACE, "technicianPrimaryAlt") +local statePrimary2 = ActorState.new("technicianPrimaryAlt") -technicianPrimaryAlt:clear_callbacks() -technicianPrimaryAlt:onActivate(function(actor) - actor:enter_state(stateTechnicianPrimaryAlt) +Callback.add(primary2.on_activate, function(actor, skill, slot) + actor:set_state(statePrimary2) end) -stateTechnicianPrimaryAlt:clear_callbacks() -stateTechnicianPrimaryAlt:onEnter(function(actor, data) +Callback.add(statePrimary2.on_enter, function(actor, data) actor.image_index2 = 0 data.currentAnim = math.random(0, 1) @@ -1447,7 +1602,8 @@ stateTechnicianPrimaryAlt:onEnter(function(actor, data) actor:skill_util_strafe_init() actor:skill_util_strafe_turn_init() end) -stateTechnicianPrimaryAlt:onStep(function(actor, data) + +Callback.add(statePrimary2.on_step, function(actor, data) actor.sprite_index2 = data.currentAnim == 1 and sprite_shoot1T_2 or sprite_shoot1T_1 actor:skill_util_strafe_update(0.2 * actor.attack_speed, gm.constants.STRAFE_SPEED_NORMAL) @@ -1459,9 +1615,9 @@ stateTechnicianPrimaryAlt:onStep(function(actor, data) if data.fired == 0 and actor.image_index2 >= 3 then data.fired = 1 - actor:sound_play(sound_shoot1T, 1, 0.9 + math.random() * 0.2) + actor:sound_play(sound_shoot1T.value, 1, 0.9 + math.random() * 0.2) - local damage = actor:skill_get_damage(technicianPrimaryAlt) + local damage = actor:skill_get_damage(primary2) local dir = actor:skill_util_facing_direction() if not GM.skill_util_update_heaven_cracker(actor, damage, actor.image_xscale) then @@ -1473,9 +1629,11 @@ stateTechnicianPrimaryAlt:onStep(function(actor, data) wrench:actor_skin_skinnable_set_skin(actor) else local machinesHit = List.new() - local endX, _ = move_point_contact_solid(actor.x, actor.y, 90 - 90 * actor.image_xscale, 700, actor) + local endX, _ = ssr_move_point_contact_solid(actor.x, actor.y, 90 - 90 * actor.image_xscale, 700, actor) + actor:collision_line_list(actor.x, actor.y, endX, actor.y, gm.constants.oCustomObject, false, true, machinesHit, false) actor:collision_line_list(actor.x, actor.y, endX, actor.y, gm.constants.oCustomObject_pNPC, false, true, machinesHit, false) + for _, machineObj in ipairs(machines) do for _, instance in ipairs(machinesHit) do if machineObj.value == instance.__object_index then @@ -1485,98 +1643,107 @@ stateTechnicianPrimaryAlt:onStep(function(actor, data) end end end + + machinesHit:destroy() + local healablesHit = List.new() actor:collision_line_list(actor.x, actor.y, endX, actor.y, gm.constants.oP, false, true, healablesHit, false) + for _, object in ipairs(healable_objects) do actor:collision_line_list(actor.x, actor.y, endX, actor.y, object, false, true, healablesHit, false) end + for _, instance in ipairs(healablesHit) do if instance.object_index ~= gm.constants.oP or healable_survivors[instance.class] then instance:heal(10) - gm.sound_play_at(sound_wrenchHit, 1, 0.9 + math.random() * 0.2, instance.x, instance.y) + gm.sound_play_at(sound_wrenchHit.value, 1, 0.9 + math.random() * 0.2, instance.x, instance.y) particle_spark:create(instance.x, instance.y, math.random(2, 4), Particle.SYSTEM.middle) end end + + healablesHit:destroy() end end - if actor.image_index2 >= gm.sprite_get_number(actor.sprite_index2) then + if actor.image_index2 >= GM.sprite_get_number(actor.sprite_index2) then actor:skill_util_reset_activity_state() end end) -stateTechnicianPrimaryAlt:onExit(function(actor, data) + +Callback.add(statePrimary2.on_exit, function(actor, data) actor:skill_util_strafe_exit() end) -- Forced Shutdown -technicianSecondary.sprite = sprite_skills -technicianSecondary.subimage = 1 -technicianSecondary.cooldown = 3 * 60 -technicianSecondary.damage = 5 -technicianSecondary.require_key_press = true -technicianSecondary.does_change_activity_state = true -technicianSecondary.use_delay = 10 -technicianSecondary.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill_interrupt_period - --- big red button -technicianSecondary_Det.sprite = sprite_skills -technicianSecondary_Det.subimage = 2 - -technicianSecondary_Det.cooldown = 0.5 * 60 -technicianSecondary_Det.damage = 1.0 --This damage isn't used for anything, only the original skill is -technicianSecondary_Det.require_key_press = true -technicianSecondary_Det.does_change_activity_state = true -technicianSecondary_Det.hold_facing_direction = true -technicianSecondary_Det.auto_restock = false --Makes stocks not regenerate by themselves -technicianSecondary_Det.use_delay = 10 -technicianSecondary_Det.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill_interrupt_period - -local state_technician_secondary = State.new(NAMESPACE, "technician_secondary") - -technicianSecondary:clear_callbacks() -technicianSecondary:onActivate(function(actor) - actor:enter_state(state_technician_secondary) -end) -state_technician_secondary:clear_callbacks() -state_technician_secondary:onEnter(function(actor, data) +secondary.sprite = sprite_skills +secondary.subimage = 1 +secondary.cooldown = 3 * 60 +secondary.damage = 5 +secondary.require_key_press = true +secondary.does_change_activity_state = true +secondary.use_delay = 10 +secondary.required_interrupt_priority = ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD + +-- Big Red Button +secondary_det.sprite = sprite_skills +secondary_det.subimage = 2 +secondary_det.cooldown = 0.5 * 60 +secondary_det.damage = 1.0 -- this damage isn't used for anything, only the original skill is +secondary_det.require_key_press = true +secondary_det.does_change_activity_state = true +secondary_det.hold_facing_direction = true +secondary_det.auto_restock = false -- makes stocks not regenerate by themselves +secondary_det.use_delay = 10 +secondary_det.required_interrupt_priority = ActorState.InterruptPriority.SKILL_INTERRUPT_PERIOD + +local stateSecondary = ActorState.new("technician_secondary") + +Callback.add(secondary.on_activate, function(actor, skill, slot) + actor:set_state(stateSecondary) +end) + +Callback.add(stateSecondary.on_enter, function(actor, data) actor.image_index = 0 data.used = nil end) -state_technician_secondary:onStep(function(actor, data) + +Callback.add(stateSecondary.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(sprite_shoot2, 0.2, true) if actor.image_index >= 3 and not data.used then - actor.tech_saved_stock = actor:get_default_skill(Skill.SLOT.secondary).stock - actor:add_skill_override(Skill.SLOT.secondary, technicianSecondary_Det, 1) --Replaces the secondary with a new secondary (Remote Detonator) - actor:get_active_skill(Skill.SLOT.secondary).stock = math.max(actor.tech_saved_stock, 1) + actor.tech_saved_stock = actor:get_active_skill(Skill.Slot.SECONDARY).stock + actor:add_skill_override(Skill.Slot.SECONDARY, secondary_det, 1) -- replaces the secondary with a new secondary (Remote Detonator) + actor:get_active_skill(Skill.Slot.SECONDARY).stock = math.max(actor.tech_saved_stock, 1) - gm.sound_play_at(sound_shoot2, 1, 1, actor.x, actor.y) + gm.sound_play_at(sound_shoot2.value, 1, 1, actor.x, actor.y) - if gm._mod_net_isHost() then + if Net.host then local mine_inst = obj_mine:create(actor.x + 12 * actor.image_xscale, actor.y) mine_inst.team = actor.team mine_inst.parent = actor mine_inst.hspeed = 8 * actor.image_xscale mine_inst:actor_skin_skinnable_set_skin(actor) end + data.used = 1 end + if not data.used then - actor:freeze_default_skill(Skill.SLOT.secondary) + actor:get_active_skill(Skill.Slot.SECONDARY):freeze_cooldown() end + actor:skill_util_exit_state_on_anim_end() end) -- blows up mine with mind... -technicianSecondary_Det:clear_callbacks() -technicianSecondary_Det:onActivate(function(actor) - actor:remove_skill_override(Skill.SLOT.secondary, technicianSecondary_Det, 1) - actor:get_default_skill(Skill.SLOT.secondary).stock = actor.tech_saved_stock - actor:override_default_skill_cooldown(Skill.SLOT.secondary, actor:get_default_skill(Skill.SLOT.secondary).cooldown_base * (1 - actor.cdr)) +Callback.add(secondary_det.on_activate, function(actor, skill, slot) + actor:remove_skill_override(Skill.Slot.SECONDARY, secondary_det) + actor:get_active_skill(Skill.Slot.SECONDARY).stock = actor.tech_saved_stock + actor:get_active_skill(Skill.Slot.SECONDARY):override_cooldown(actor:get_active_skill(Skill.Slot.SECONDARY).cooldown_base * (1 - actor.cdr)) - if gm._mod_net_isHost() then + if Net.host then local mines, _ = Instance.find_all(obj_mine) for _, mine in ipairs(mines) do if mine.parent.id == actor.id then @@ -1585,37 +1752,51 @@ technicianSecondary_Det:onActivate(function(actor) end end end) -technicianSecondary_Det:onStep(function(actor) - actor:freeze_default_skill(Skill.SLOT.secondary) + +Callback.add(secondary_det.on_step, function(actor, data) + actor:get_active_skill(Skill.Slot.SECONDARY):freeze_cooldown() +end) + +-- skill overrides aren't removed when transitioning between stages so this does that +-- otherwise the skill stocks of it get all messed up and secondary does nothing because there's no mine to detonate +Callback.add(Callback.ON_STAGE_START, function() + for _, actor in ipairs(Instance.find_all(gm.constants.oP)) do + if actor.class == technician.value then + actor:remove_skill_override(Skill.Slot.SECONDARY, secondary_det) + end + end end) -- Vending Machine -technicianUtility.sprite = sprite_skills -technicianUtility.damage = 2 -technicianUtility.subimage = 3 -technicianUtility.cooldown = 7 * 60 -technicianUtility.is_utility = true -technicianUtility.require_key_press = true -technicianUtility.override_strafe_direction = true -technicianUtility.ignore_aim_direction = true - -technicianUtility:clear_callbacks() -technicianUtility:onActivate(function(actor) - if gm._mod_net_isHost() then + +utility.sprite = sprite_skills +utility.damage = 2 +utility.subimage = 3 +utility.cooldown = 7 * 60 +utility.is_utility = true +utility.require_key_press = true +utility.override_strafe_direction = true +utility.ignore_aim_direction = true + +Callback.add(utility.on_activate, function(actor, skill, slot) + if Net.host then local vendings, _ = Instance.find_all(obj_vending) - if #vendings >= actor:get_default_skill(Skill.SLOT.utility).max_stock then + if #vendings >= actor:get_active_skill(Skill.Slot.UTILITY).max_stock then local oldestID = math.huge local oldestInst = nil + for _, vending in ipairs(vendings) do if vending.parent.id == actor.id and vending.id < oldestID then oldestID = vending.id oldestInst = vending end end + if oldestInst then oldestInst:destroy() end end + local vending_inst = obj_vending:create(actor.x, actor.y + 4) vending_inst.parent = actor vending_inst.team = actor.team @@ -1625,119 +1806,125 @@ technicianUtility:onActivate(function(actor) end) -- Radial Amplifier -technicianUtilityAlt.sprite = sprite_skills -technicianUtilityAlt.damage = 0.5 -technicianUtilityAlt.subimage = 0 -technicianUtilityAlt.cooldown = 12 * 60 -technicianUtilityAlt.is_utility = true -technicianUtilityAlt.override_strafe_direction = true -technicianUtilityAlt.ignore_aim_direction = true ---technician:add_utility(technicianUtilityAlt) - -local stateTechnicianUtilityAlt = State.new(NAMESPACE, "technicianUtilityAlt") -stateTechnicianUtilityAlt.activity_flags = State.ACTIVITY_FLAG.allow_rope_cancel - -technicianUtilityAlt:clear_callbacks() -technicianUtilityAlt:onActivate(function(actor) - actor:enter_state(stateTechnicianUtilityAlt) +utility2.sprite = sprite_skills +utility2.damage = 0.5 +utility2.subimage = 0 +utility2.cooldown = 12 * 60 +utility2.is_utility = true +utility2.override_strafe_direction = true +utility2.ignore_aim_direction = true + +local stateUtility2 = ActorState.new("technicianUtilityAlt") +stateUtility2.activity_flags = ActorState.ActivityFlag.ALLOW_ROPE_CANCEL + +Callback.add(utility2.on_activate, function(actor, skill, slot) + actor:set_state(stateUtility2) end) -stateTechnicianUtilityAlt:clear_callbacks() -stateTechnicianUtilityAlt:onEnter(function(actor, data) + +Callback.add(stateUtility2.on_enter, function(actor, data) actor.image_index = 0 data.created = nil end) -stateTechnicianUtilityAlt:onStep(function(actor, data) + +Callback.add(stateUtility2.on_step, function(actor, data) actor:skill_util_fix_hspeed() local animation = sprite_shoot4 - if not data.created and actor.image_index >= 4 and gm._mod_net_isHost() then + + if not data.created and actor.image_index >= 4 and Net.host then local amplifiers, _ = Instance.find_all(obj_amplifier) + for _, amplifier in ipairs(amplifiers) do if amplifier.parent.id == actor.id then amplifier:destroy() end end + local amplifier_inst = obj_amplifier:create(actor.x, actor.y + 12) amplifier_inst.parent = actor amplifier_inst.team = actor.team data.created = 1 end + actor:actor_animation_set(animation, 0.25, true) actor:skill_util_exit_state_on_anim_end() end) -- Backup Firewall -technicianSpecial.sprite = sprite_skills -technicianSpecial.subimage = 4 -technicianSpecial.cooldown = 9 * 60 -technicianSpecial.damage = 1.8 -technicianSpecial.require_key_press = true -technicianSpecial.does_change_activity_state = true -technicianSpecial.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill +special.sprite = sprite_skills +special.subimage = 4 +special.cooldown = 9 * 60 +special.damage = 1.8 +special.require_key_press = true +special.does_change_activity_state = true +special.required_interrupt_priority = ActorState.InterruptPriority.SKILL +special.upgrade_skill = specialS -- Backup Firewall 2.0 -local technicianSpecialScepter = Skill.new(NAMESPACE, "technicianVBoosted") -technicianSpecial:set_skill_upgrade(technicianSpecialScepter) - -technicianSpecialScepter.sprite = sprite_skills -technicianSpecialScepter.subimage = 5 -technicianSpecialScepter.cooldown = 9 * 60 -technicianSpecialScepter.damage = 1.8 -technicianSpecialScepter.require_key_press = true -technicianSpecialScepter.does_change_activity_state = true -technicianSpecialScepter.required_interrupt_priority = State.ACTOR_STATE_INTERRUPT_PRIORITY.skill - -local stateTechnicianSpecial = State.new(NAMESPACE, "technicianSpecial") - -technicianSpecial:clear_callbacks() -technicianSpecial:onActivate(function(actor) - actor:enter_state(stateTechnicianSpecial) +specialS.sprite = sprite_skills +specialS.subimage = 5 +specialS.cooldown = 9 * 60 +specialS.damage = 1.8 +specialS.require_key_press = true +specialS.does_change_activity_state = true +specialS.required_interrupt_priority = ActorState.InterruptPriority.SKILL + +local stateSpecial = ActorState.new("technicianSpecial") + +Callback.add(special.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial) end) -technicianSpecialScepter:clear_callbacks() -technicianSpecialScepter:onActivate(function(actor) - actor:enter_state(stateTechnicianSpecial) + +Callback.add(specialS.on_activate, function(actor, skill, slot) + actor:set_state(stateSpecial) end) -stateTechnicianSpecial:clear_callbacks() -stateTechnicianSpecial:onEnter(function(actor, data) +Callback.add(stateSpecial.on_enter, function(actor, data) actor.image_index = 0 - actor:sound_play(sound_shoot4, 1, 0.9 + math.random() * 0.2) + actor:sound_play(sound_shoot4.value, 1, 0.9 + math.random() * 0.2) data.created = nil - data.scepter = actor:item_stack_count(item_scepter) + data.scepter = actor:item_count(item_scepter) end) -stateTechnicianSpecial:onStep(function(actor, data) + +Callback.add(stateSpecial.on_step, function(actor, data) actor:skill_util_fix_hspeed() actor:actor_animation_set(data.scepter > 0 and sprite_shoot5 or sprite_shoot4, 0.2, true) - if not data.created and actor.image_index >= 6 and gm._mod_net_isHost() then + + if not data.created and actor.image_index >= 6 and Net.host then local turrets, _ = Instance.find_all(obj_turret) - if #turrets >= actor:get_default_skill(Skill.SLOT.special).max_stock then + if #turrets >= actor:get_active_skill(Skill.Slot.SPECIAL).max_stock then local oldestID = math.huge local oldestInst = nil + for _, turret in ipairs(turrets) do if turret.parent.id == actor.id and turret.id < oldestID then oldestID = turret.id oldestInst = turret end end + if oldestInst then oldestInst:destroy() end end + local turret_inst = obj_turret:create(actor.x + 18 * actor.image_xscale, actor.y - 8) turret_inst.parent = actor turret_inst.team = actor.team turret_inst.image_xscale = actor.image_xscale GM.actor_queue_dirty(turret_inst) - GM.inventory_items_copy(actor, turret_inst, Item.LOOT_TAG.item_blacklist_engi_turrets) + GM.inventory_items_copy(actor, turret_inst, Item.LootTag.ITEM_BLACKLIST_ENGI_TURRETS) - turret_inst:item_give(fake_mocha, turret_inst:item_stack_count(Item.find("ror-mocha"))) -- this is stupid but has to be done because for whatever reason mochas crash the game when certain actors get it (like drones) - turret_inst:item_remove(Item.find("ror-mocha"), turret_inst:item_stack_count(Item.find("ror-mocha")), Item.STACK_KIND.any) + turret_inst:item_give(fake_mocha, turret_inst:item_count(Item.find("mocha"))) -- this is stupid but has to be done because for whatever reason mochas crash the game when certain actors get it (like drones) + turret_inst:item_take(Item.find("mocha"), turret_inst:item_count(Item.find("mocha")), Item.StackKind.ANY) if data.scepter > 0 then turret_inst.upgrade_progress = 3 end + data.created = 1 end + actor:skill_util_exit_state_on_anim_end() end) \ No newline at end of file diff --git a/main.lua b/main.lua index bbbef8e5..237bec2f 100644 --- a/main.lua +++ b/main.lua @@ -1,22 +1,49 @@ -- starstorm returns -- ssr team -mods["RoRRModdingToolkit-RoRR_Modding_Toolkit"].auto(true) +mods["ReturnsAPI-ReturnsAPI"].auto{mp = true, namespace = "ssr"} +--- GLOBALS (Should be in ALL-CAPS for constants, Uppercase Initial for variables) PATH = _ENV["!plugins_mod_folder_path"] -NAMESPACE = "ssr" +-- mod options stuff + +Options = ModOptions.new("ssr") +-- Settings with defaults +-- (useful for setting the default settings when the toml file hasn't been generated +-- or config options are missing due to a new update adding more) + +Settings = { + title_replacement = true, + chirrsmas = 0, +} + +SettingsFile = TOML.new() + +ssr_chirrsmas_active = false -- gets set in the library file local init = function() + --- Initialize settings here before requiring anything, useful if some options require a restart to apply. + if SettingsFile:read() == nil then + SettingsFile:write(Settings) + else + Settings = SettingsFile:read() + end + local folders = { "Misc", -- contains utility functions that other code depends on, so load first + "Language", "Actors", "Elites", - "Gameplay", "Survivors", "Items", "Equipments", - "Artifacts" + "Interactables", + "Artifacts", + "Stages", + "Gameplay" } + Stage.remove_all_rooms() -- reload stages + for _, folder in ipairs(folders) do -- NOTE: this includes filepaths within subdirectories of the above folders local filepaths = path.get_files(path.combine(PATH, folder)) @@ -27,13 +54,13 @@ local init = function() end end end - require("stageLoader") --temporaryoh -- once we have loaded everything, enable hot/live reloading. -- this variable may be used by content code to make sure it behaves correctly when hotloading HOTLOADING = true end -Initialize(init) + +Initialize.add(init) if HOTLOADING then init() diff --git a/manifest.json b/manifest.json index 1db10b1e..3c691045 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,11 @@ { "name": "StarstormReturns", - "version_number": "0.1.6", + "version_number": "0.1.7", "website_url": "https://discord.gg/fGShMVEayr", - "description": "A remaster of Starstorm 1, adding new survivors, items, artifacts, stages, enemies and much more to Risk of Rain Returns.", + "description": "A reimagining of Starstorm 1, adding new survivors, items, artifacts, stages, enemies and much more to Risk of Rain Returns.", "dependencies": [ "ReturnOfModding-ReturnOfModding-1.1.3", - "RoRRModdingToolkit-RoRR_Modding_Toolkit-1.2.25" + "LuaENVY-ENVY-1.2.0", + "ReturnsAPI-ReturnsAPI-1.0.0" ] } diff --git a/stageLoader.lua b/stageLoader.lua deleted file mode 100644 index 9278ebe9..00000000 --- a/stageLoader.lua +++ /dev/null @@ -1,144 +0,0 @@ ---local EnvPlaceholder = Resources.sprite_load(NAMESPACE, "EnvPlaceholder", path.combine(PATH, "EnvironmentPlaceholder.png")) - ---- WHISTLING BASIN --- - ---Stage Resources -Resources.sprite_load(NAMESPACE, "tile16basin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "tile16basin.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "BackTilesModded2", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "BackTilesModded2.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "LandCloudWhistlingBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "LandCloudWhistlingBasin.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "SkyBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "SkyBasin.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "MoonBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "MoonBasin.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "MountainsBasinNew", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "MountainsBasinNew.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "MountainsBasinNew2", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "MountainsBasinNew2.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "LandCloud4WhistlingBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "LandCloud4WhistlingBasin.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "LandCloud5WhistlingBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "LandCloud5WhistlingBasin.png"), 1, 0, 0) - ---Menu Resources -local EnvironmentWhistlingBasin = Resources.sprite_load(NAMESPACE, "EnvironmentWhistlingBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "EnvironmentWhistlingBasin.png")) -local GroundStripWhistlingBasin = Resources.sprite_load(NAMESPACE, "GroundStripWhistlingBasin", path.combine(PATH.."/Sprites/Stages/WhistlingBasin", "GroundStripWhistlingBasin.png")) - ---Stage -local basin_stage = Stage.new(NAMESPACE, "whistlingBasin") -basin_stage.music_id = gm.sound_add_w(NAMESPACE, "musicWhistlingBasin", path.combine(PATH.."/Sounds/Music", "musicWhistlingBasin.ogg")) -basin_stage.token_name = Language.translate_token("stage.whistlingBasin.name") -basin_stage.token_subname = Language.translate_token("stage.whistlingBasin.subname") -basin_stage.teleporter_index = 0 -basin_stage:set_index(2) - ---Rooms -basin_stage:clear_rooms() -basin_stage:add_room(path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin1.rorlvl")) -basin_stage:add_room(path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin2.rorlvl")) -basin_stage:add_room(path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin3.rorlvl")) -basin_stage:add_room(path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin4.rorlvl")) -basin_stage:add_room(path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin5.rorlvl")) -basin_stage:add_room(path.combine(PATH.."/Stages/WhistlingBasin", "whistlingBasin6.rorlvl")) - ---Spawn list -basin_stage:add_monster({ - "lemurian", - "wisp", - "imp", - "crab", - "greaterWisp", - "ancientWisp", - "archaicWisp", - "bramble", - "scavenger" -}) -basin_stage:add_monster_loop({ - "impOverlord", - "lemrider", - Monster_Card.find(NAMESPACE, "admonitor") -}) - -basin_stage:add_interactable({ - "barrel1", - "barrelEquipment", - "chest1", - "chest2", - "chest3", - "drone3", - "drone4", - "shrine2", - "chestHealing1", - "equipmentActivator", - "droneRecycler" -}) -basin_stage:add_interactable_loop({ - "chestHealing2", - "chest4", - "shrine3S", - "barrel2", - "chest5" -}) - ---Environment Log -basin_stage:set_log_icon(EnvironmentWhistlingBasin) - ---Main Menu -basin_stage:set_title_screen_properties(GroundStripWhistlingBasin) - ---- TORRID OUTLANDS --- -Resources.sprite_load(NAMESPACE, "Tile16Outlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Tile16Outlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "BackTilesOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "BackTilesOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "MoonOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "MoonOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "Arch2TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Arch2TorridOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "Arch1TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Arch1TorridOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "Arch3TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Arch3TorridOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "CanyonsBack2TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "CanyonsBack2TorridOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "Clouds1TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "Clouds1TorridOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "CanyonsBack1TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "CanyonsBack1TorridOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "CanyonsBack3TorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "CanyonsBack3TorridOutlands.png"), 1, 0, 0) -Resources.sprite_load(NAMESPACE, "EelBone", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "skeelton.png"), 1, 0, 0) - ---Menu Resources -local EnvironmentTorridOutlands = Resources.sprite_load(NAMESPACE, "EnvironmentTorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "EnvironmentTorridOutlands.png")) -local GroundStripTorridOutlands = Resources.sprite_load(NAMESPACE, "GroundStripTorridOutlands", path.combine(PATH.."/Sprites/Stages/TorridOutlands", "GroundStripTorridOutlands.png")) - -local outlands_stage = Stage.new(NAMESPACE, "torridOutlands") -outlands_stage.music_id = gm.sound_add_w(NAMESPACE, "musicTorridOutlands", path.combine(PATH.."/Sounds/Music", "musicTorridOutlands.ogg")) -outlands_stage.token_name = Language.translate_token("stage.torridOutlands.name") -outlands_stage.token_subname = Language.translate_token("stage.torridOutlands.subname") -outlands_stage.teleporter_index = 0 -outlands_stage.interactable_spawn_points = 900 -outlands_stage:set_index(3) - -outlands_stage:clear_rooms() -outlands_stage:add_room(path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands1.rorlvl")) -outlands_stage:add_room(path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands2.rorlvl")) -outlands_stage:add_room(path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands3.rorlvl")) -outlands_stage:add_room(path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands4.rorlvl")) -outlands_stage:add_room(path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands5.rorlvl")) -outlands_stage:add_room(path.combine(PATH.."/Stages/TorridOutlands", "torridOutlands6.rorlvl")) - -outlands_stage:add_monster({ - "wisp", - "imp", - "bison", - "spitter", - "colossus", - "clayMan", - "toxicBeast", - "scavenger", - Monster_Card.find(NAMESPACE, "admonitor") -}) - -outlands_stage:add_interactable({ - "barrel1", - "barrelEquipment", - "chest1", - "chest4", - "chest3", - "drone6", - "drone4", - "shrine2", - "equipmentActivator", - "droneRecycler" -}) - ---Environment Log -outlands_stage:set_log_icon(EnvironmentTorridOutlands) - ---Main Menu -outlands_stage:set_title_screen_properties(GroundStripTorridOutlands) \ No newline at end of file