Skip to content

Commit

Permalink
Add Respawning Bounce Jellyfish
Browse files Browse the repository at this point in the history
  • Loading branch information
maddie480 committed Dec 17, 2023
1 parent 3a86cf3 commit e490ec6
Show file tree
Hide file tree
Showing 15 changed files with 400 additions and 142 deletions.
32 changes: 32 additions & 0 deletions Ahorn/entities/maxHelpingHandRespawningBounceJellyfish.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module MaxHelpingHandRespawningBounceJellyfish

using ..Ahorn, Maple

@mapdef Entity "MaxHelpingHand/RespawningBounceJellyfish" RespawningBounceJellyfish(x::Integer, y::Integer, platform::Bool=true, soulBound::Bool=true, baseDashCount::Integer=1,
respawnTime::Number=2.0, spriteDirectory::String="objects/MaxHelpingHand/glider")

const placements = Ahorn.PlacementDict(
"Respawning Bounce Jellyfish (Bounce Helper + Maddie's Helping Hand)" => Ahorn.EntityPlacement(
RespawningBounceJellyfish
)
)

function Ahorn.selection(entity::RespawningBounceJellyfish)
x, y = Ahorn.position(entity)
sprite = get(entity, "spriteDirectory", "objects/MaxHelpingHand/glider") * "/idle0"

return Ahorn.getSpriteRectangle(sprite, x, y - 4)
end

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::RespawningBounceJellyfish, room::Maple.Room)
sprite = get(entity, "spriteDirectory", "objects/MaxHelpingHand/glider") * "/idle0"
Ahorn.drawSprite(ctx, sprite, 0, -4)

if get(entity, "platform", false)
curve = Ahorn.SimpleCurve((-7, -1), (7, -1), (0, -6))
Ahorn.drawSimpleCurve(ctx, curve, (1.0, 1.0, 1.0, 1.0), thickness=1)
end
end


end
7 changes: 7 additions & 0 deletions Ahorn/lang/en_gb.lang
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ placements.entities.MaxHelpingHand/RespawningJellyfish.tooltips.tutorial=Whether
placements.entities.MaxHelpingHand/RespawningJellyfish.tooltips.respawnTime=The jellyfish will respawn at its starting location after this amount of time (in seconds) after being destroyed.
placements.entities.MaxHelpingHand/RespawningJellyfish.tooltips.spriteDirectory=The directory containing all the sprites for the respawning jellyfish.\nTo make your own, copy Graphics/Atlases/Gameplay/objects/MaxHelpingHand/glider from the mod zip to Mods/yourmod/Graphics/Atlases/Gameplay/MyMap/myglider and type MyMap/myglider in this field.

# Respawning Bounce Jellyfish
placements.entities.MaxHelpingHand/RespawningBounceJellyfish.tooltips.platform=Whether the jelly starts floating.
placements.entities.MaxHelpingHand/RespawningBounceJellyfish.tooltips.soulBound=Whether the jelly kills the player on death and can't be left behind.
placements.entities.MaxHelpingHand/RespawningBounceJellyfish.tooltips.baseDashCount=How many dashes the jelly will start with and refill to.
placements.entities.MaxHelpingHand/RespawningBounceJellyfish.tooltips.respawnTime=The jellyfish will respawn at its starting location after this amount of time (in seconds) after being destroyed.
placements.entities.MaxHelpingHand/RespawningBounceJellyfish.tooltips.spriteDirectory=The directory containing all the sprites for the respawning jellyfish.\nTo make your own, copy Graphics/Atlases/Gameplay/objects/MaxHelpingHand/glider from the mod zip to Mods/yourmod/Graphics/Atlases/Gameplay/MyMap/myglider and type MyMap/myglider in this field.

# Grouped Trigger Spikes
placements.entities.MaxHelpingHand/GroupedTriggerSpikesUp.tooltips.type=Changes the visual appearance of the spikes.
placements.entities.MaxHelpingHand/GroupedTriggerSpikesDown.tooltips.type=Changes the visual appearance of the spikes.
Expand Down
57 changes: 57 additions & 0 deletions Entities/RespawningBounceJellyfish.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Celeste.Mod.BounceHelper;
using Celeste.Mod.Entities;
using Microsoft.Xna.Framework;
using System.Collections;
using Monocle;
using System.Collections.Generic;
using System;

namespace Celeste.Mod.MaxHelpingHand.Entities {
[CustomEntity("MaxHelpingHand/RespawningJellyfish")]
public class RespawningBounceJellyfish : BounceJellyfish {
public static void LoadBounceHelper() {
RespawningJellyfishGeneric<RespawningBounceJellyfish, BounceJellyfish>.Load();
Everest.Events.Level.OnLoadEntity += onLoadEntity;
On.Monocle.Tracker.Initialize += onTrackerInitialize;
}

public static void UnloadBounceHelper() {
RespawningJellyfishGeneric<RespawningBounceJellyfish, BounceJellyfish>.Unload();
Everest.Events.Level.OnLoadEntity -= onLoadEntity;
On.Monocle.Tracker.Initialize -= onTrackerInitialize;
}

private static bool onLoadEntity(Level level, LevelData levelData, Vector2 offset, EntityData entityData) {
if (entityData.Name == "MaxHelpingHand/RespawningBounceJellyfish") {
level.Add(new RespawningBounceJellyfish(entityData, offset));
return true;
}

return false;
}

private static void onTrackerInitialize(On.Monocle.Tracker.orig_Initialize orig) {
orig();
Tracker.TrackedEntityTypes[typeof(RespawningBounceJellyfish)] = new List<Type> { typeof(BounceJellyfish) };
}


private RespawningJellyfishGeneric<RespawningBounceJellyfish, BounceJellyfish> manager;

public RespawningBounceJellyfish(EntityData data, Vector2 offset) : base(data, offset) {
manager = new RespawningJellyfishGeneric<RespawningBounceJellyfish, BounceJellyfish>(this, data, () => Speed, speed => Speed = speed);
}

public override void Update() {
manager.Update(base.Update);
}

protected override void OnSquish(CollisionData data) {
manager.OnSquish(base.OnSquish, data);
}

private IEnumerator destroyThenRespawnRoutine() {
return manager.destroyThenRespawnRoutine();
}
}
}
140 changes: 5 additions & 135 deletions Entities/RespawningJellyfish.cs
Original file line number Diff line number Diff line change
@@ -1,156 +1,26 @@
using Celeste.Mod.Entities;
using Microsoft.Xna.Framework;
using Mono.Cecil.Cil;
using Monocle;
using MonoMod.Cil;
using MonoMod.Utils;
using System;
using System.Collections;

namespace Celeste.Mod.MaxHelpingHand.Entities {
[CustomEntity("MaxHelpingHand/RespawningJellyfish")]
public class RespawningJellyfish : Glider {
public static void Load() {
IL.Celeste.Glider.Update += modGliderUpdate;
}

public static void Unload() {
IL.Celeste.Glider.Update -= modGliderUpdate;
}

private static ParticleType P_NotGlow;

private DynData<Glider> self;

private float respawnTime;
private bool bubble;

private Sprite sprite;

private Vector2 initialPosition;
private bool respawning;

private bool shouldRespawn = true;
private RespawningJellyfishGeneric<RespawningJellyfish, Glider> manager;

public RespawningJellyfish(EntityData data, Vector2 offset) : base(data, offset) {
if (P_NotGlow == null) {
// P_NotGlow is a transparent particle.
P_NotGlow = new ParticleType(P_Glow) {
Color = Color.Transparent,
Color2 = Color.Transparent
};
}

respawnTime = data.Float("respawnTime");
bubble = data.Bool("bubble");
initialPosition = Position;
respawning = false;

// get the sprite, and replace it depending on the path in entity properties.
self = new DynData<Glider>(this);
sprite = self.Get<Sprite>("sprite");
new DynData<Sprite>(sprite)["atlas"] = GFX.Game;
sprite.Path = data.Attr("spriteDirectory", defaultValue: "objects/MaxHelpingHand/glider") + "/";
sprite.Stop();
sprite.ClearAnimations();
sprite.AddLoop("idle", "idle", 0.1f);
sprite.AddLoop("held", "held", 0.1f);
sprite.Add("fall", "fall", 0.06f, "fallLoop");
sprite.AddLoop("fallLoop", "fallLoop", 0.06f);
sprite.Add("death", "death", 0.06f);
sprite.Add("respawn", "respawn", 0.03f, "idle");
sprite.Play("idle");

// make the jelly go invisible when the death animation is done.
sprite.OnFinish += anim => {
if (anim == "death") {
Visible = false;
}
};

// listen for transitions: if the jelly is carried to another screen, it should not respawn anymore.
Add(new TransitionListener() {
OnOutBegin = () => shouldRespawn = false
});
manager = new RespawningJellyfishGeneric<RespawningJellyfish, Glider>(this, data, () => Speed, speed => Speed = speed);
}

public override void Update() {
if (shouldRespawn && !respawning && Top + Speed.Y * Engine.DeltaTime > (SceneAs<Level>().Bounds.Bottom + 16)) {
// the jellyfish glided off-screen.
removeAndRespawn();
}

// if the jelly is invisible, "disable" the particles (actually make them invisible).
ParticleType vanillaGlow = P_Glow;
if (!Visible) P_Glow = P_NotGlow;

base.Update();

P_Glow = vanillaGlow;
}

private static void modGliderUpdate(ILContext il) {
ILCursor cursor = new ILCursor(il);

while (cursor.TryGotoNext(MoveType.After, instr => instr.MatchNewobj<Coroutine>())) {
Logger.Log("MaxHelpingHand/RespawningJellyfish", $"Replacing coroutine to make jellyfish respawn at {cursor.Index} in IL for Glider.Update");

cursor.Emit(OpCodes.Ldarg_0);
cursor.EmitDelegate<Func<Coroutine, Glider, Coroutine>>((orig, self) => {
if (self is RespawningJellyfish jelly) {
return new Coroutine(jelly.destroyThenRespawnRoutine());
}
return orig;
});
}
manager.Update(base.Update);
}

protected override void OnSquish(CollisionData data) {
if (shouldRespawn) {
if (!TrySquishWiggle(data)) {
// the jellyfish was squished.
removeAndRespawn();
}
} else {
// vanilla behavior
base.OnSquish(data);
}
}

private void removeAndRespawn() {
Collidable = false;
Visible = false;
self["destroyed"] = true;
Add(new Coroutine(respawnRoutine()));
manager.OnSquish(base.OnSquish, data);
}

private IEnumerator destroyThenRespawnRoutine() {
// do like vanilla, but instead of removing the jelly, wait then have it respawn.
Audio.Play("event:/new_content/game/10_farewell/glider_emancipate", Position);
sprite.Play("death");

return respawnRoutine();
}

private IEnumerator respawnRoutine() {
respawning = true;

// wait for the respawn time
yield return respawnTime;

// then respawn at the initial position
Visible = true;
Position = initialPosition;
Speed = Vector2.Zero;
sprite.Play("respawn");

yield return 0.24f;

respawning = false;
self["destroyed"] = false;
self["bubble"] = bubble;
Collidable = true;
return manager.destroyThenRespawnRoutine();
}
}
}
Loading

0 comments on commit e490ec6

Please sign in to comment.