Skip to content

Commit f8a71b1

Browse files
authored
Merge pull request #10 from CommunalHelper/bigkahuna443/BoomBoxZip
Add Boom Box Zips from SSC23
2 parents d2a0002 + 90d6643 commit f8a71b1

File tree

6 files changed

+397
-10
lines changed

6 files changed

+397
-10
lines changed

Ahorn/entities/boomBoxZip.jl

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module FactoryHelperBoomBoxZip
2+
using ..Ahorn, Maple
3+
4+
@mapdef Entity "FactoryHelper/BoomBoxZip" BoomBoxZip(x::Integer, y::Integer, activationId::String="", initialDelay::Number=0.0, startActive::Bool=false)
5+
6+
const placements = Ahorn.PlacementDict(
7+
"Boom Box Zip (Factory Helper)" => Ahorn.EntityPlacement(
8+
BoomBoxZip,
9+
"point",
10+
Dict{String, Any}(
11+
"startActive" => true
12+
),
13+
function(entity)
14+
entity.data["nodes"] = [(Int(entity.data["x"]) + 32, Int(entity.data["y"]))]
15+
end
16+
),
17+
)
18+
activeSprite = "objects/FactoryHelper/boomBox/active00"
19+
20+
Ahorn.nodeLimits(entity::BoomBoxZip) = 1,1
21+
22+
ropeColor = (77, 60, 34) ./ 255
23+
function Ahorn.renderAbs(ctx::Ahorn.Cairo.CairoContext, entity::BoomBoxZip, room::Maple.Room)
24+
x, y = Ahorn.position(entity)
25+
26+
Ahorn.drawSprite(ctx, activeSprite, x + 12, y + 12)
27+
28+
nx, ny = Int.(entity.data["nodes"][1])
29+
30+
cx, cy = x + 12, y + 12
31+
cnx,cny = nx + 12, ny + 12
32+
length = sqrt((x - nx)^2 + (y - ny)^2)
33+
theta = atan(cny - cy, cnx - cx)
34+
Ahorn.Cairo.save(ctx)
35+
36+
Ahorn.translate(ctx, cx, cy)
37+
Ahorn.rotate(ctx, theta)
38+
39+
Ahorn.setSourceColor(ctx, ropeColor)
40+
Ahorn.set_antialias(ctx, 1)
41+
Ahorn.set_line_width(ctx, 1);
42+
43+
# Offset for rounding errors
44+
Ahorn.move_to(ctx, 0, 4 + (theta <= 0))
45+
Ahorn.line_to(ctx, length, 4 + (theta <= 0))
46+
47+
Ahorn.move_to(ctx, 0, -4 - (theta > 0))
48+
Ahorn.line_to(ctx, length, -4 - (theta > 0))
49+
50+
Ahorn.stroke(ctx)
51+
52+
Ahorn.Cairo.restore(ctx)
53+
54+
Ahorn.drawSprite(ctx, "objects/zipmover/cog", cnx, cny)
55+
56+
end
57+
58+
function Ahorn.selection(entity::BoomBoxZip)
59+
x, y = Ahorn.position(entity)
60+
nx, ny = Int.(entity.data["nodes"][1])
61+
62+
width = Int(get(entity.data, "width", 8))
63+
height = Int(get(entity.data, "height", 8))
64+
65+
return [Ahorn.Rectangle(x, y, 24, 24), Ahorn.Rectangle(nx + floor(Int, width / 2) - 5, ny + floor(Int, height / 2) - 5, 10, 10)]
66+
end
67+
68+
69+
end

Ahorn/lang/en_gb.lang

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ placements.entities.FactoryHelper/BoomBox.tooltips.activationId=String value of
77
placements.entities.FactoryHelper/BoomBox.tooltips.initialDelay=The time it takes before the box starts turning on in seconds after activated.
88
placements.entities.FactoryHelper/BoomBox.tooltips.startActive=If `true` the boom box will initially be active.
99

10+
# Boom Box Zip
11+
placements.entities.FactoryHelper/BoomBoxZip.tooltips.activationId=String value of the entity's activator ID. When a factory activator with this ID is turned on, the entity will toggle state.
12+
placements.entities.FactoryHelper/BoomBoxZip.tooltips.initialDelay=The time it takes before the box starts turning on in seconds after activated.
13+
placements.entities.FactoryHelper/BoomBoxZip.tooltips.startActive=If `true` the boom box will initially be active.
14+
1015
# Conveyor Belt
1116
placements.entities.FactoryHelper/Conveyor.tooltips.activationId=String value of the entity's activator ID. When a factory activator with this ID is turned on, the entity will toggle state.
1217
placements.entities.FactoryHelper/Conveyor.tooltips.startLeft=If `true` the conveyor will initially be moving left.

Code/Entities/BoomBox.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ public class BoomBox : Solid {
2222
SpeedMax = 24f
2323
};
2424

25+
protected Circle _boomCollider;
26+
2527
private readonly float _initialDelay;
2628
private readonly Sprite _sprite;
2729
private readonly Sprite _boomSprite;
28-
private readonly BoomCollider _boomCollider;
2930
private readonly SoundSource _sfx;
3031
private readonly float _startupTime = 1.5f;
3132
private float _angryResetTimer = 0f;
@@ -63,7 +64,7 @@ public BoomBox(Vector2 position, string activationId, float initialDelay, bool s
6364
_boomSprite.CenterOrigin();
6465
_boomSprite.Position = new Vector2(Width / 2, Height / 2);
6566

66-
_boomCollider = new BoomCollider(position + new Vector2(Width / 2, Height / 2));
67+
_boomCollider = new Circle(40f, X + Width / 2, Y + Height / 2);
6768
Add(_sfx = new SoundSource());
6869
_sfx.Position = new Vector2(Width / 2, Height / 2);
6970
Add(new LightOcclude(0.2f));
@@ -145,7 +146,6 @@ private IEnumerator WindDownSequence() {
145146

146147
public override void Added(Scene scene) {
147148
base.Added(scene);
148-
scene.Add(_boomCollider);
149149
Activator.HandleStartup(scene);
150150
}
151151

@@ -172,6 +172,13 @@ public override void Update() {
172172
}
173173
}
174174

175+
public override void DebugRender(Camera camera) {
176+
base.DebugRender(camera);
177+
if (Activator.IsOn) {
178+
_boomCollider.Render(camera, Color.HotPink);
179+
}
180+
}
181+
175182
private void HandleAngryMode() {
176183
CheckForAngryMode();
177184
if (_angryMode) {
@@ -216,7 +223,7 @@ private void Explode() {
216223
(Scene as Level).Displacement.AddBurst(Center, 0.35f, 4f, 64f, 0.5f);
217224
Player player = Scene.Tracker.GetEntity<Player>();
218225
Collidable = false;
219-
if (player != null && player.CollideCheck(_boomCollider) && !Scene.CollideCheck<Solid>(player.Center, Center)) {
226+
if (player != null && _boomCollider.Collide(player) && !Scene.CollideCheck<Solid>(player.Center, Center)) {
220227
if (player.Bottom < Top && player.Top > Bottom) {
221228
player.ExplodeLaunch(Center, false, true);
222229
} else {
@@ -226,11 +233,5 @@ private void Explode() {
226233

227234
Collidable = true;
228235
}
229-
230-
private class BoomCollider : Entity {
231-
public BoomCollider(Vector2 position) : base(position) {
232-
Collider = new Circle(40f, 0, 0);
233-
}
234-
}
235236
}
236237
}

Code/Entities/BoomBoxZip.cs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
using Celeste;
2+
using Celeste.Mod.Entities;
3+
using Microsoft.Xna.Framework;
4+
using Monocle;
5+
using System;
6+
using System.Collections;
7+
8+
namespace FactoryHelper.Entities {
9+
[CustomEntity("FactoryHelper/BoomBoxZip")]
10+
public class BoomBoxZip : BoomBox {
11+
public float percent;
12+
public Vector2 start, target;
13+
private ZipMoverPathRenderer pathRenderer;
14+
15+
public BoomBoxZip(EntityData data, Vector2 offset)
16+
: this(data.Position + offset, data.Attr("activationId", ""), data.Float("initialDelay", 0f), data.Bool("startActive", false), data.Nodes[0] + offset) {
17+
}
18+
19+
public BoomBoxZip(Vector2 position, string activationId, float initialDelay, bool startActive, Vector2 target)
20+
: base(position, activationId, initialDelay, startActive) {
21+
Add(new Coroutine(ZipMoverSequence()));
22+
this.start = this.Position;
23+
this.target = target;
24+
}
25+
26+
public override void Added(Scene scene) {
27+
base.Added(scene);
28+
scene.Add(pathRenderer = new ZipMoverPathRenderer(this));
29+
}
30+
31+
private IEnumerator ZipMoverSequence() {
32+
start = Position;
33+
while (true) {
34+
if (!HasPlayerRider()) {
35+
yield return null;
36+
continue;
37+
}
38+
Input.Rumble(RumbleStrength.Medium, RumbleLength.Short);
39+
StartShaking(0.1f);
40+
yield return 0.1f;
41+
StopPlayerRunIntoAnimation = false;
42+
float at2 = 0f;
43+
while (at2 < 1f) {
44+
yield return null;
45+
at2 = Calc.Approach(at2, 1f, 2f * Engine.DeltaTime);
46+
percent = Ease.SineIn(at2);
47+
Vector2 vector = Vector2.Lerp(start, target, percent);
48+
ScrapeParticlesCheck(vector);
49+
if (Scene.OnInterval(0.1f)) {
50+
pathRenderer.CreateSparks();
51+
}
52+
53+
MoveTo(vector);
54+
_boomCollider.Position = vector + new Vector2(Width / 2, Height / 2);
55+
}
56+
57+
StartShaking(0.2f);
58+
Input.Rumble(RumbleStrength.Strong, RumbleLength.Medium);
59+
SceneAs<Level>().Shake();
60+
StopPlayerRunIntoAnimation = true;
61+
yield return 0.5f;
62+
StopPlayerRunIntoAnimation = false;
63+
at2 = 0f;
64+
while (at2 < 1f) {
65+
yield return null;
66+
at2 = Calc.Approach(at2, 1f, 0.5f * Engine.DeltaTime);
67+
percent = 1f - Ease.SineIn(at2);
68+
Vector2 position = Vector2.Lerp(target, start, Ease.SineIn(at2));
69+
MoveTo(position);
70+
_boomCollider.Position = position + new Vector2(Width / 2, Height / 2);
71+
}
72+
73+
StopPlayerRunIntoAnimation = true;
74+
StartShaking(0.2f);
75+
yield return 0.5f;
76+
}
77+
}
78+
79+
private void ScrapeParticlesCheck(Vector2 to) {
80+
if (!base.Scene.OnInterval(0.03f)) {
81+
return;
82+
}
83+
84+
bool flag = to.Y != base.ExactPosition.Y;
85+
bool flag2 = to.X != base.ExactPosition.X;
86+
if (flag && !flag2) {
87+
int num = Math.Sign(to.Y - base.ExactPosition.Y);
88+
Vector2 value = (num != 1) ? base.TopLeft : base.BottomLeft;
89+
int num2 = 4;
90+
if (num == 1) {
91+
num2 = Math.Min((int)base.Height - 12, 20);
92+
}
93+
94+
int num3 = (int)base.Height;
95+
if (num == -1) {
96+
num3 = Math.Max(16, (int)base.Height - 16);
97+
}
98+
99+
if (base.Scene.CollideCheck<Solid>(value + new Vector2(-2f, num * -2))) {
100+
for (int i = num2; i < num3; i += 8) {
101+
SceneAs<Level>().ParticlesFG.Emit(ZipMover.P_Scrape, base.TopLeft + new Vector2(0f, (float)i + (float)num * 2f), (num == 1) ? (-(float)Math.PI / 4f) : ((float)Math.PI / 4f));
102+
}
103+
}
104+
105+
if (base.Scene.CollideCheck<Solid>(value + new Vector2(base.Width + 2f, num * -2))) {
106+
for (int j = num2; j < num3; j += 8) {
107+
SceneAs<Level>().ParticlesFG.Emit(ZipMover.P_Scrape, base.TopRight + new Vector2(-1f, (float)j + (float)num * 2f), (num == 1) ? ((float)Math.PI * -3f / 4f) : ((float)Math.PI * 3f / 4f));
108+
}
109+
}
110+
} else {
111+
if (!flag2 || flag) {
112+
return;
113+
}
114+
115+
int num4 = Math.Sign(to.X - base.ExactPosition.X);
116+
Vector2 value2 = (num4 != 1) ? base.TopLeft : base.TopRight;
117+
int num5 = 4;
118+
if (num4 == 1) {
119+
num5 = Math.Min((int)base.Width - 12, 20);
120+
}
121+
122+
int num6 = (int)base.Width;
123+
if (num4 == -1) {
124+
num6 = Math.Max(16, (int)base.Width - 16);
125+
}
126+
127+
if (base.Scene.CollideCheck<Solid>(value2 + new Vector2(num4 * -2, -2f))) {
128+
for (int k = num5; k < num6; k += 8) {
129+
SceneAs<Level>().ParticlesFG.Emit(ZipMover.P_Scrape, base.TopLeft + new Vector2((float)k + (float)num4 * 2f, -1f), (num4 == 1) ? ((float)Math.PI * 3f / 4f) : ((float)Math.PI / 4f));
130+
}
131+
}
132+
133+
if (base.Scene.CollideCheck<Solid>(value2 + new Vector2(num4 * -2, base.Height + 2f))) {
134+
for (int l = num5; l < num6; l += 8) {
135+
SceneAs<Level>().ParticlesFG.Emit(ZipMover.P_Scrape, base.BottomLeft + new Vector2((float)l + (float)num4 * 2f, 0f), (num4 == 1) ? ((float)Math.PI * -3f / 4f) : (-(float)Math.PI / 4f));
136+
}
137+
}
138+
}
139+
}
140+
141+
private class ZipMoverPathRenderer : Entity {
142+
private static readonly Color ropeColor = Calc.HexToColor("4d3c22");
143+
private static readonly Color ropeLightColor = Calc.HexToColor("766c49");
144+
145+
public BoomBoxZip zipMover;
146+
147+
private MTexture cog;
148+
private Vector2 from;
149+
private Vector2 to;
150+
private Vector2 sparkAdd;
151+
private float sparkDirFromA;
152+
private float sparkDirFromB;
153+
private float sparkDirToA;
154+
private float sparkDirToB;
155+
156+
public ZipMoverPathRenderer(BoomBoxZip zipMover) {
157+
base.Depth = 5000;
158+
this.zipMover = zipMover;
159+
from = this.zipMover.start + new Vector2(this.zipMover.Width / 2f, this.zipMover.Height / 2f);
160+
to = this.zipMover.target + new Vector2(this.zipMover.Width / 2f, this.zipMover.Height / 2f);
161+
sparkAdd = (from - to).SafeNormalize(5f).Perpendicular();
162+
float num = (from - to).Angle();
163+
sparkDirFromA = num + (float)Math.PI / 8f;
164+
sparkDirFromB = num - (float)Math.PI / 8f;
165+
sparkDirToA = num + (float)Math.PI - (float)Math.PI / 8f;
166+
sparkDirToB = num + (float)Math.PI + (float)Math.PI / 8f;
167+
cog = GFX.Game["objects/zipmover/cog"];
168+
}
169+
170+
public void CreateSparks() {
171+
SceneAs<Level>().ParticlesBG.Emit(ZipMover.P_Sparks, from + sparkAdd + Calc.Random.Range(-Vector2.One, Vector2.One), sparkDirFromA);
172+
SceneAs<Level>().ParticlesBG.Emit(ZipMover.P_Sparks, from - sparkAdd + Calc.Random.Range(-Vector2.One, Vector2.One), sparkDirFromB);
173+
SceneAs<Level>().ParticlesBG.Emit(ZipMover.P_Sparks, to + sparkAdd + Calc.Random.Range(-Vector2.One, Vector2.One), sparkDirToA);
174+
SceneAs<Level>().ParticlesBG.Emit(ZipMover.P_Sparks, to - sparkAdd + Calc.Random.Range(-Vector2.One, Vector2.One), sparkDirToB);
175+
}
176+
177+
public override void Render() {
178+
DrawCogs(Vector2.UnitY, Color.Black);
179+
DrawCogs(Vector2.Zero);
180+
}
181+
182+
private void DrawCogs(Vector2 offset, Color? colorOverride = null) {
183+
Vector2 vector = (to - from).SafeNormalize();
184+
Vector2 value = vector.Perpendicular() * 3f;
185+
Vector2 value2 = -vector.Perpendicular() * 4f;
186+
float rotation = zipMover.percent * (float)Math.PI * 2f;
187+
Draw.Line(from + value + offset, to + value + offset, colorOverride.HasValue ? colorOverride.Value : ropeColor);
188+
Draw.Line(from + value2 + offset, to + value2 + offset, colorOverride.HasValue ? colorOverride.Value : ropeColor);
189+
for (float num = 4f - zipMover.percent * (float)Math.PI * 8f % 4f; num < (to - from).Length(); num += 4f) {
190+
Vector2 value3 = from + value + vector.Perpendicular() + vector * num;
191+
Vector2 value4 = to + value2 - vector * num;
192+
Draw.Line(value3 + offset, value3 + vector * 2f + offset, colorOverride.HasValue ? colorOverride.Value : ropeLightColor);
193+
Draw.Line(value4 + offset, value4 - vector * 2f + offset, colorOverride.HasValue ? colorOverride.Value : ropeLightColor);
194+
}
195+
196+
cog.DrawCentered(from + offset, colorOverride.HasValue ? colorOverride.Value : Color.White, 1f, rotation);
197+
cog.DrawCentered(to + offset, colorOverride.HasValue ? colorOverride.Value : Color.White, 1f, rotation);
198+
}
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)