diff --git a/Ahorn/entities/nonBadelineMovingBlock.jl b/Ahorn/entities/nonBadelineMovingBlock.jl new file mode 100644 index 0000000..144f98e --- /dev/null +++ b/Ahorn/entities/nonBadelineMovingBlock.jl @@ -0,0 +1,61 @@ +module SpringCollab2020NonBadelineMovingBlock + +using ..Ahorn, Maple + +@pardef NonBadelineMovingBlock(x1::Integer, y1::Integer, x2::Integer=x1+16, y2::Integer=y1, width::Integer=Maple.defaultBlockWidth, height::Integer=Maple.defaultBlockHeight, tiletype::String="g", highlightTiletype::String="G") = Entity("SpringCollab2020/nonBadelineMovingBlock", x=x1, y=y1, nodes=Tuple{Int, Int}[(x2, y2)], width=width, height=height, tiletype=tiletype, highlightTiletype=highlightTiletype) + +const placements = Ahorn.PlacementDict( + "Non-Badeline Boss Moving Block (Spring Collab 2020)" => Ahorn.EntityPlacement( + NonBadelineMovingBlock, + "rectangle", + Dict{String, Any}(), + function(entity) + entity.data["nodes"] = [(Int(entity.data["x"]) + Int(entity.data["width"]) + 8, Int(entity.data["y"]))] + end + ) +) + +Ahorn.editingOptions(entity::NonBadelineMovingBlock) = Dict{String, Any}( + "tiletype" => Ahorn.tiletypeEditingOptions(), + "highlightTiletype" => Ahorn.tiletypeEditingOptions() +) + +Ahorn.nodeLimits(entity::NonBadelineMovingBlock) = 1, 1 +Ahorn.minimumSize(entity::NonBadelineMovingBlock) = 8, 8 +Ahorn.resizable(entity::NonBadelineMovingBlock) = true, true + +function Ahorn.selection(entity::NonBadelineMovingBlock) + if entity.name == "SpringCollab2020/nonBadelineMovingBlock" + x, y = Ahorn.position(entity) + nx, ny = Int.(entity.data["nodes"][1]) + + width = Int(get(entity.data, "width", 8)) + height = Int(get(entity.data, "height", 8)) + + return [Ahorn.Rectangle(x, y, width, height), Ahorn.Rectangle(nx, ny, width, height)] + end +end + +function Ahorn.renderAbs(ctx::Ahorn.Cairo.CairoContext, entity::NonBadelineMovingBlock, room::Maple.Room) + Ahorn.drawTileEntity(ctx, room, entity, material=get(entity.data, "tiletype", "g")[1], blendIn=false) +end + +function Ahorn.renderSelectedAbs(ctx::Ahorn.Cairo.CairoContext, entity::NonBadelineMovingBlock, room::Maple.Room) + x, y = Ahorn.position(entity) + nodes = get(entity.data, "nodes", ()) + + width = Int(get(entity.data, "width", 8)) + height = Int(get(entity.data, "height", 8)) + + if !isempty(nodes) + nx, ny = Int.(nodes[1]) + cox, coy = floor(Int, width / 2), floor(Int, height / 2) + + # Use 'G' instead of 'g', as that is the highlight color of the block (the active color) + fakeTiles = Ahorn.createFakeTiles(room, nx, ny, width, height, get(entity.data, "highlightTiletype", "G")[1], blendIn=false) + Ahorn.drawFakeTiles(ctx, room, fakeTiles, room.objTiles, true, nx, ny, clipEdges=true) + Ahorn.drawArrow(ctx, x + cox, y + coy, nx + cox, ny + coy, Ahorn.colors.selection_selected_fc, headLength=6) + end +end + +end \ No newline at end of file diff --git a/Ahorn/lang/en_gb.lang b/Ahorn/lang/en_gb.lang index f85771b..6a18dd6 100644 --- a/Ahorn/lang/en_gb.lang +++ b/Ahorn/lang/en_gb.lang @@ -55,6 +55,10 @@ placements.entities.SpringCollab2020/UpsideDownJumpThru.tooltips.texture=Changes placements.entities.SpringCollab2020/SidewaysJumpThru.tooltips.texture=Changes the appearance of the platform. placements.entities.SpringCollab2020/SidewaysJumpThru.tooltips.left=Whether the solid side of the jumpthru is the left side. +# Non-Badeline Moving Blocks +placements.entities.SpringCollab2020/nonBadelineMovingBlock.tooltips.tiletype=Changes the visual appearance of the block before it starts moving. +placements.entities.SpringCollab2020/nonBadelineMovingBlock.tooltips.highlightTiletype=Changes the visual appearance of the block once it starts moving. + # Custom Sandwich Lava placements.entities.SpringCollab2020/CustomSandwichLava.tooltips.direction=Changes the sandwich lava direction. CoreModeBased is the same behaviour as vanilla, depending on core mode. placements.entities.SpringCollab2020/CustomSandwichLava.tooltips.speed=Changes the speed the lava is moving at, in pixels per second. Vanilla value is 20. diff --git a/Entities/NonBadelineMovingBlock.cs b/Entities/NonBadelineMovingBlock.cs new file mode 100644 index 0000000..2377df5 --- /dev/null +++ b/Entities/NonBadelineMovingBlock.cs @@ -0,0 +1,217 @@ +using Celeste.Mod.Entities; +using Microsoft.Xna.Framework; +using Monocle; +using System; +using System.Collections; + +namespace Celeste.Mod.SpringCollab2020.Entities { + [CustomEntity("SpringCollab2020/nonBadelineMovingBlock")] + [Tracked(false)] + public class NonBadelineMovingBlock : Solid { + + private float startDelay; + + private int nodeIndex; + + private Vector2[] nodes; + + private TileGrid sprite; + + private TileGrid highlight; + + private Coroutine moveCoroutine; + + private bool isHighlighted; + + public NonBadelineMovingBlock(Vector2[] nodes, float width, float height, char tiletype, char highlightTiletype) + : base(nodes[0], width, height, safe: false) { + this.nodes = nodes; + int newSeed = Calc.Random.Next(); + Calc.PushRandom(newSeed); + sprite = GFX.FGAutotiler.GenerateBox(tiletype, (int) base.Width / 8, (int) base.Height / 8).TileGrid; + Add(sprite); + Calc.PopRandom(); + Calc.PushRandom(newSeed); + highlight = GFX.FGAutotiler.GenerateBox(highlightTiletype, (int) (base.Width / 8f), (int) base.Height / 8).TileGrid; + highlight.Alpha = 0f; + Add(highlight); + Calc.PopRandom(); + Add(new TileInterceptor(sprite, highPriority: false)); + Add(new LightOcclude()); + } + + public NonBadelineMovingBlock(EntityData data, Vector2 offset) + : this(data.NodesWithPosition(offset), data.Width, data.Height, data.Char("tiletype", 'g'), data.Char("highlightTiletype", 'G')) { + } + + public override void Added(Scene scene) { + base.Added(scene); + StartMoving(0); + } + + public override void OnShake(Vector2 amount) { + base.OnShake(amount); + sprite.Position = amount; + } + + public void StartMoving(float delay) { + startDelay = delay; + Add(moveCoroutine = new Coroutine(MoveSequence())); + } + + private IEnumerator MoveSequence() { + while (true) { + StartShaking(0.2f + startDelay); + if (!isHighlighted) { + for (float p = 0f; p < 1f; p += Engine.DeltaTime / (0.2f + startDelay + 0.2f)) { + highlight.Alpha = Ease.CubeIn(p); + sprite.Alpha = 1f - highlight.Alpha; + yield return null; + } + highlight.Alpha = 1f; + sprite.Alpha = 0f; + isHighlighted = true; + } else { + yield return 0.2f + startDelay + 0.2f; + } + startDelay = 0f; + nodeIndex++; + nodeIndex %= nodes.Length; + Vector2 from = Position; + Vector2 to = nodes[nodeIndex]; + Tween tween = Tween.Create(Tween.TweenMode.Oneshot, Ease.CubeIn, 0.8f, start: true); + tween.OnUpdate = delegate (Tween t) { + MoveTo(Vector2.Lerp(from, to, t.Eased)); + }; + tween.OnComplete = delegate { + if (CollideCheck(Position + (to - from).SafeNormalize() * 2f)) { + Audio.Play("event:/game/06_reflection/fallblock_boss_impact", Center); + ImpactParticles(to - from); + } else { + StopParticles(to - from); + } + }; + Add(tween); + yield return 0.8f; + } + } + + private void StopParticles(Vector2 moved) { + Level level = SceneAs(); + float direction = moved.Angle(); + if (moved.X > 0f) { + Vector2 value = new Vector2(Right - 1f, Top); + for (int i = 0; i < Height; i += 4) { + level.Particles.Emit(FinalBossMovingBlock.P_Stop, value + Vector2.UnitY * (2 + i + Calc.Random.Range(-1, 1)), direction); + } + } else if (moved.X < 0f) { + Vector2 value2 = new Vector2(Left, Top); + for (int j = 0; j < Height; j += 4) { + level.Particles.Emit(FinalBossMovingBlock.P_Stop, value2 + Vector2.UnitY * (2 + j + Calc.Random.Range(-1, 1)), direction); + } + } + if (moved.Y > 0f) { + Vector2 value3 = new Vector2(Left, Bottom - 1f); + for (int k = 0; k < Width; k += 4) { + level.Particles.Emit(FinalBossMovingBlock.P_Stop, value3 + Vector2.UnitX * (2 + k + Calc.Random.Range(-1, 1)), direction); + } + } else if (moved.Y < 0f) { + Vector2 value4 = new Vector2(Left, Top); + for (int l = 0; l < Width; l += 4) { + level.Particles.Emit(FinalBossMovingBlock.P_Stop, value4 + Vector2.UnitX * (2 + l + Calc.Random.Range(-1, 1)), direction); + } + } + } + + private void BreakParticles() { + for (int i = 0; i < Width; i += 4) { + for (int j = 0; j < Height; j += 4) { + SceneAs().Particles.Emit(FinalBossMovingBlock.P_Break, 1, Position + new Vector2(2 + i, 2 + j), Vector2.One * 2f, (Position + new Vector2(2 + i, 2 + j) - Center).Angle()); + } + } + } + + private void ImpactParticles(Vector2 moved) { + if (moved.X < 0f) { + Vector2 offset = new Vector2(0f, 2f); + for (int i = 0; i < Height / 8f; i++) { + Vector2 collideCheckPos = new Vector2(Left - 1f, Top + 4f + (i * 8)); + if (!Scene.CollideCheck(collideCheckPos) && Scene.CollideCheck(collideCheckPos)) { + SceneAs().ParticlesFG.Emit(CrushBlock.P_Impact, collideCheckPos + offset, 0f); + SceneAs().ParticlesFG.Emit(CrushBlock.P_Impact, collideCheckPos - offset, 0f); + } + } + } else if (moved.X > 0f) { + Vector2 offset = new Vector2(0f, 2f); + for (int j = 0; j < Height / 8f; j++) { + Vector2 collideCheckPos = new Vector2(Right + 1f, Top + 4f + (j * 8)); + if (!Scene.CollideCheck(collideCheckPos) && Scene.CollideCheck(collideCheckPos)) { + SceneAs().ParticlesFG.Emit(CrushBlock.P_Impact, collideCheckPos + offset, (float) Math.PI); + SceneAs().ParticlesFG.Emit(CrushBlock.P_Impact, collideCheckPos - offset, (float) Math.PI); + } + } + } + if (moved.Y < 0f) { + Vector2 offset = new Vector2(2f, 0f); + for (int k = 0; k < Width / 8f; k++) { + Vector2 collideCheckPos = new Vector2(Left + 4f + (k * 8), Top - 1f); + if (!Scene.CollideCheck(collideCheckPos) && Scene.CollideCheck(collideCheckPos)) { + SceneAs().ParticlesFG.Emit(CrushBlock.P_Impact, collideCheckPos + offset, (float) Math.PI / 2f); + SceneAs().ParticlesFG.Emit(CrushBlock.P_Impact, collideCheckPos - offset, (float) Math.PI / 2f); + } + } + } else { + if (!(moved.Y > 0f)) { + return; + } + Vector2 offset = new Vector2(2f, 0f); + for (int l = 0; l < Width / 8f; l++) { + Vector2 collideCheckPos = new Vector2(Left + 4f + (l * 8), Bottom + 1f); + if (!Scene.CollideCheck(collideCheckPos) && Scene.CollideCheck(collideCheckPos)) { + SceneAs().ParticlesFG.Emit(CrushBlock.P_Impact, collideCheckPos + offset, -(float) Math.PI / 2f); + SceneAs().ParticlesFG.Emit(CrushBlock.P_Impact, collideCheckPos - offset, -(float) Math.PI / 2f); + } + } + } + } + + public override void Render() { + Vector2 position = Position; + Position += Shake; + base.Render(); + if (highlight.Alpha > 0f && highlight.Alpha < 1f) { + int num = (int) ((1f - highlight.Alpha) * 16f); + Rectangle rect = new Rectangle((int) X, (int) Y, (int) Width, (int) Height); + rect.Inflate(num, num); + Draw.HollowRect(rect, Color.Lerp(Color.Purple, Color.Pink, 0.7f)); + } + Position = position; + } + + private void Finish() { + Vector2 from = CenterRight + Vector2.UnitX * 10f; + for (int i = 0; i < Width / 8f; i++) { + for (int j = 0; j < Height / 8f; j++) { + Scene.Add(Engine.Pooler.Create().Init(Position + new Vector2(4 + i * 8, 4 + j * 8), 'f', playSound: true).BlastFrom(from)); + } + } + BreakParticles(); + DestroyStaticMovers(); + RemoveSelf(); + } + + public void Destroy(float delay) { + if (Scene != null) { + if (moveCoroutine != null) { + Remove(moveCoroutine); + } + if (delay <= 0f) { + Finish(); + return; + } + StartShaking(delay); + Alarm.Set(this, delay, Finish); + } + } + } +}