Skip to content

Commit 2e13c12

Browse files
committed
Added strawberries that pop when you try to collect them
1 parent d2eecff commit 2e13c12

File tree

3 files changed

+336
-1
lines changed

3 files changed

+336
-1
lines changed

Ahorn/entities/trollStrawberry.jl

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
module SpringCollab2020TrollStrawberry
2+
3+
using ..Ahorn, Maple
4+
5+
@mapdef Entity "SpringCollab2020/trollStrawberry" TrollStrawberry(x::Integer, y::Integer, winged::Bool=false)
6+
7+
const placements = Ahorn.PlacementDict(
8+
"Troll Strawberry (Spring Collab 2020)" => Ahorn.EntityPlacement(
9+
TrollStrawberry
10+
),
11+
12+
"Troll Strawberry (Winged) (Spring Collab 2020)" => Ahorn.EntityPlacement(
13+
TrollStrawberry,
14+
"point",
15+
Dict{String, Any}(
16+
"winged" => true
17+
)
18+
),
19+
)
20+
21+
# name, winged
22+
sprites = Dict{Tuple{String, Bool}, String}(
23+
("SpringCollab2020/trollStrawberry", false) => "collectables/strawberry/normal00",
24+
("SpringCollab2020/trollStrawberry", true) => "collectables/strawberry/wings01",
25+
)
26+
27+
fallback = "collectables/strawberry/normal00"
28+
29+
Ahorn.nodeLimits(entity::TrollStrawberry) = 0, -1
30+
31+
function Ahorn.selection(entity::TrollStrawberry)
32+
x, y = Ahorn.position(entity)
33+
34+
winged = get(entity.data, "winged", false)
35+
36+
sprite = sprites[(entity.name, winged)]
37+
38+
res = Ahorn.Rectangle[Ahorn.getSpriteRectangle(sprite, x, y)]
39+
40+
return res
41+
end
42+
43+
function Ahorn.renderSelectedAbs(ctx::Ahorn.Cairo.CairoContext, entity::TrollStrawberry)
44+
45+
end
46+
47+
function Ahorn.renderAbs(ctx::Ahorn.Cairo.CairoContext, entity::TrollStrawberry, room::Maple.Room)
48+
x, y = Ahorn.position(entity)
49+
50+
winged = get(entity.data, "winged", false)
51+
52+
sprite = sprites[(entity.name, winged)]
53+
54+
Ahorn.drawSprite(ctx, sprite, x, y)
55+
end
56+
57+
end

Ahorn/lang/en_gb.lang

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,7 @@ placements.entities.SpringCollab2020/CustomBirdTutorial.tooltips.controls=The co
126126

127127
# Custom Bird Tutorial Trigger
128128
placements.triggers.SpringCollab2020/CustomBirdTutorialTrigger.tooltips.birdId=The ID of the bird this trigger is controlling.
129-
placements.triggers.SpringCollab2020/CustomBirdTutorialTrigger.tooltips.showTutorial=If checked, the trigger will make the bird show the tutorial bubble. If unchecked, the trigger will make the bird fly away.
129+
placements.triggers.SpringCollab2020/CustomBirdTutorialTrigger.tooltips.showTutorial=If checked, the trigger will make the bird show the tutorial bubble. If unchecked, the trigger will make the bird fly away.
130+
131+
# Troll Strawberry
132+
placements.entities.SpringCollab2020/trollStrawberry.tooltips.winged=The strawberry attempts to vertically rise offscreen when the player dashes.

Entities/TrollStrawberry.cs

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
using Celeste.Mod.Entities;
2+
using Microsoft.Xna.Framework;
3+
using Monocle;
4+
using System;
5+
using System.Collections;
6+
7+
namespace Celeste.Mod.SpringCollab2020.Entities {
8+
[CustomEntity("SpringCollab2020/trollStrawberry")]
9+
public class TrollStrawberry : Entity {
10+
11+
public EntityID ID;
12+
13+
private Sprite sprite;
14+
15+
public Follower Follower;
16+
17+
private Wiggler wiggler;
18+
19+
private Wiggler rotateWiggler;
20+
21+
private BloomPoint bloom;
22+
23+
private VertexLight light;
24+
25+
private Tween lightTween;
26+
27+
private float wobble;
28+
29+
private Vector2 start;
30+
31+
private float collectTimer;
32+
33+
private bool collected;
34+
35+
private bool flyingAway;
36+
37+
private float flapSpeed;
38+
39+
public bool ReturnHomeWhenLost = true;
40+
41+
public bool Winged {
42+
get;
43+
private set;
44+
}
45+
46+
private bool IsFirstStrawberry {
47+
get {
48+
for (int i = Follower.FollowIndex - 1; i >= 0; i--) {
49+
if (Follower.Leader.Followers[i].Entity is Strawberry strawberry && !strawberry.Golden) {
50+
return false;
51+
}
52+
}
53+
return true;
54+
}
55+
}
56+
57+
public TrollStrawberry(EntityData data, Vector2 offset, EntityID gid) {
58+
ID = gid;
59+
Position = (start = data.Position + offset);
60+
Winged = data.Bool("winged", false);
61+
Depth = -100;
62+
Collider = new Hitbox(14f, 14f, -7f, -7f);
63+
Add(new PlayerCollider(new Action<Player>(OnPlayer), null, null));
64+
Add(new MirrorReflection());
65+
Add(Follower = new Follower(ID, null, new Action(OnLoseLeader)));
66+
Follower.FollowDelay = 0.3f;
67+
if (Winged) {
68+
Add(new DashListener {
69+
OnDash = new Action<Vector2>(OnDash)
70+
});
71+
}
72+
}
73+
74+
public override void Added(Scene scene) {
75+
base.Added(scene);
76+
Add(sprite = GFX.SpriteBank.Create("strawberry"));
77+
if (Winged) {
78+
sprite.Play("flap", false, false);
79+
}
80+
sprite.OnFrameChange = new Action<string>(OnAnimate);
81+
Add(wiggler = Wiggler.Create(0.4f, 4f, delegate (float v) {
82+
sprite.Scale = Vector2.One * (1f + v * 0.35f);
83+
}, false, false));
84+
Add(rotateWiggler = Wiggler.Create(0.5f, 4f, delegate (float v) {
85+
sprite.Rotation = v * 30f * 0.0174532924f;
86+
}, false, false));
87+
Add(bloom = new BloomPoint(1f, 12f));
88+
Add(light = new VertexLight(Color.White, 1f, 16, 24));
89+
Add(lightTween = light.CreatePulseTween());
90+
if ((scene as Level).Session.BloomBaseAdd > 0.1f) {
91+
bloom.Alpha *= 0.5f;
92+
}
93+
}
94+
95+
public override void Update() {
96+
if (!collected) {
97+
if (!Winged) {
98+
wobble += Engine.DeltaTime * 4f;
99+
sprite.Y = (bloom.Y = (light.Y = (float) Math.Sin(wobble) * 2f));
100+
}
101+
int followIndex = Follower.FollowIndex;
102+
if (Follower.Leader != null && Follower.DelayTimer <= 0f && IsFirstStrawberry) {
103+
bool collectable = false;
104+
if (Follower.Leader.Entity is Player player && player.Scene != null && !player.StrawberriesBlocked) {
105+
collectable = player.OnSafeGround && player.StateMachine.State != 13;
106+
}
107+
if (collectable) {
108+
collectTimer += Engine.DeltaTime;
109+
if (collectTimer > 0.15f) {
110+
OnCollect();
111+
}
112+
} else {
113+
collectTimer = Math.Min(collectTimer, 0f);
114+
}
115+
} else {
116+
if (followIndex > 0) {
117+
collectTimer = -0.15f;
118+
}
119+
if (Winged) {
120+
Y += flapSpeed * Engine.DeltaTime;
121+
if (flyingAway) {
122+
if (Y < (SceneAs<Level>().Bounds.Top - 16)) {
123+
RemoveSelf();
124+
}
125+
} else {
126+
flapSpeed = Calc.Approach(flapSpeed, 20f, 170f * Engine.DeltaTime);
127+
if (Y < start.Y - 5f) {
128+
Y = start.Y - 5f;
129+
} else if (Y > start.Y + 5f) {
130+
Y = start.Y + 5f;
131+
}
132+
}
133+
}
134+
}
135+
}
136+
base.Update();
137+
if (Follower.Leader != null && Scene.OnInterval(0.08f)) {
138+
SceneAs<Level>().ParticlesFG.Emit(Strawberry.P_Glow, Position + Calc.Random.Range(-Vector2.One * 6f, Vector2.One * 6f));
139+
}
140+
}
141+
142+
private void OnDash(Vector2 dir) {
143+
if (!flyingAway && Winged) {
144+
Depth = -1000000;
145+
Add(new Coroutine(FlyAwayRoutine(), true));
146+
flyingAway = true;
147+
}
148+
}
149+
150+
private void OnAnimate(string id) {
151+
if (!flyingAway && id == "flap" && sprite.CurrentAnimationFrame % 9 == 4) {
152+
Audio.Play("event:/game/general/strawberry_wingflap", Position);
153+
flapSpeed = -50f;
154+
}
155+
int pulseFrame;
156+
if (id == "flap") {
157+
pulseFrame = 25;
158+
} else {
159+
pulseFrame = 35;
160+
}
161+
if (sprite.CurrentAnimationFrame == pulseFrame) {
162+
lightTween.Start();
163+
if (!collected && (CollideCheck<FakeWall>() || CollideCheck<Solid>())) {
164+
Audio.Play("event:/game/general/strawberry_pulse", Position);
165+
SceneAs<Level>().Displacement.AddBurst(Position, 0.6f, 4f, 28f, 0.1f, null, null);
166+
return;
167+
}
168+
Audio.Play("event:/game/general/strawberry_pulse", Position);
169+
SceneAs<Level>().Displacement.AddBurst(Position, 0.6f, 4f, 28f, 0.2f, null, null);
170+
}
171+
}
172+
173+
public void OnPlayer(Player player) {
174+
if (Follower.Leader == null && !collected) {
175+
ReturnHomeWhenLost = true;
176+
if (Winged) {
177+
Level level = SceneAs<Level>();
178+
Winged = false;
179+
sprite.Rate = 0f;
180+
Alarm.Set(this, Follower.FollowDelay, delegate {
181+
sprite.Rate = 1f;
182+
sprite.Play("idle", false, false);
183+
level.Particles.Emit(Strawberry.P_WingsBurst, 8, Position + new Vector2(8f, 0f), new Vector2(4f, 2f));
184+
level.Particles.Emit(Strawberry.P_WingsBurst, 8, Position - new Vector2(8f, 0f), new Vector2(4f, 2f));
185+
}, Alarm.AlarmMode.Oneshot);
186+
}
187+
Audio.Play("event:/game/general/strawberry_touch", Position);
188+
player.Leader.GainFollower(Follower);
189+
wiggler.Start();
190+
Depth = -1000000;
191+
}
192+
}
193+
194+
public void OnCollect() {
195+
if (collected) {
196+
return;
197+
}
198+
int collectIndex = 0;
199+
collected = true;
200+
if (Follower.Leader != null) {
201+
Player p = Follower.Leader.Entity as Player;
202+
collectIndex = p.StrawberryCollectIndex;
203+
p.StrawberryCollectIndex++;
204+
p.StrawberryCollectResetTimer = 2.5f;
205+
Follower.Leader.LoseFollower(Follower);
206+
}
207+
Session session = (Scene as Level).Session;
208+
session.DoNotLoad.Add(ID);
209+
session.UpdateLevelStartDashes();
210+
Add(new Coroutine(CollectRoutine(collectIndex), true));
211+
}
212+
213+
private IEnumerator FlyAwayRoutine() {
214+
rotateWiggler.Start();
215+
flapSpeed = -200f;
216+
Tween tween = Tween.Create(Tween.TweenMode.Oneshot, Ease.CubeOut, 0.25f, start: true);
217+
tween.OnUpdate = delegate (Tween t) {
218+
flapSpeed = MathHelper.Lerp(-200f, 0f, t.Eased);
219+
};
220+
Add(tween);
221+
yield return 0.1f;
222+
Audio.Play("event:/game/general/strawberry_laugh", Position);
223+
yield return 0.2f;
224+
if (!Follower.HasLeader) {
225+
Audio.Play("event:/game/general/strawberry_flyaway", Position);
226+
}
227+
tween = Tween.Create(Tween.TweenMode.Oneshot, null, 0.5f, start: true);
228+
tween.OnUpdate = delegate (Tween t) {
229+
flapSpeed = MathHelper.Lerp(0f, -200f, t.Eased);
230+
};
231+
Add(tween);
232+
}
233+
234+
private IEnumerator CollectRoutine(int collectIndex) {
235+
Tag = Tags.TransitionUpdate;
236+
Depth = -2000010;
237+
Audio.Play("event:/game/general/seed_poof", Position);
238+
Collidable = false;
239+
sprite.Scale = Vector2.One * 2f;
240+
yield return 0.05f;
241+
Input.Rumble(RumbleStrength.Medium, RumbleLength.Medium);
242+
for (int i = 0; i < 6; i++) {
243+
float num = Calc.Random.NextFloat((float) Math.PI * 2f);
244+
SceneAs<Level>().ParticlesFG.Emit(StrawberrySeed.P_Burst, 1, Position + Calc.AngleToVector(num, 4f), Vector2.Zero, num);
245+
}
246+
Visible = false;
247+
sprite.Play("collect");
248+
while (sprite.Animating) {
249+
yield return null;
250+
}
251+
//Scene.Add(new StrawberryPoints(Position, isGhostBerry, collectIndex, Moon));
252+
RemoveSelf();
253+
}
254+
255+
private void OnLoseLeader() {
256+
if (!collected && ReturnHomeWhenLost) {
257+
Alarm.Set(this, 0.15f, delegate {
258+
Vector2 displacement = (start - Position).SafeNormalize();
259+
float dist = Vector2.Distance(Position, start);
260+
float scaleFactor = Calc.ClampedMap(dist, 16f, 120f, 16f, 96f);
261+
Vector2 control = start + displacement * 16f + displacement.Perpendicular() * scaleFactor * Calc.Random.Choose(1, -1);
262+
SimpleCurve curve = new SimpleCurve(Position, start, control);
263+
Tween tween = Tween.Create(Tween.TweenMode.Oneshot, Ease.SineOut, MathHelper.Max(dist / 100f, 0.4f), start: true);
264+
tween.OnUpdate = delegate (Tween f) {
265+
Position = curve.GetPoint(f.Eased);
266+
};
267+
tween.OnComplete = delegate {
268+
Depth = 0;
269+
};
270+
Add(tween);
271+
});
272+
}
273+
}
274+
}
275+
}

0 commit comments

Comments
 (0)