Skip to content

Commit

Permalink
Add Flag Touch Switch Walls
Browse files Browse the repository at this point in the history
Originally implemented in Chronia Helper based on Helping Hand code, UnderDragon provided the modified code for it to be merged back into Helping Hand
  • Loading branch information
maddie480 committed Sep 13, 2024
1 parent d774b0a commit a5681cc
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 26 deletions.
44 changes: 44 additions & 0 deletions Ahorn/entities/maxHelpingHandFlagTouchSwitchWall.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module MaxHelpingHandFlagTouchSwitchWall

using ..Ahorn, Maple

@mapdef Entity "MaxHelpingHand/FlagTouchSwitchWall" FlagTouchSwitchWall(x::Integer, y::Integer, width::Integer=16, height::Integer=16,
flag::String="flag_touch_switch", icon::String="vanilla", persistent::Bool=false, hideIfFlag::String="",
inactiveColor::String="5FCDE4", activeColor::String="FFFFFF", finishColor::String="F141DF", smoke::Bool=true, animationLength::Integer=6,
inverted::Bool=false, allowDisable::Bool=false, playerCanActivate::Bool=true, hitSound::String="event:/game/general/touchswitch_any",
completeSoundFromSwitch::String="event:/game/general/touchswitch_last_cutoff", completeSoundFromScene::String="event:/game/general/touchswitch_last_oneshot")

const bundledIcons = String["vanilla", "tall", "triangle", "circle", "diamond", "double", "heart", "square", "wide", "winged", "cross", "drop", "hourglass", "split", "star", "triple"]

const placements = Ahorn.PlacementDict(
"Flag Touch Switch Wall (Maddie's Helping Hand)" => Ahorn.EntityPlacement(
FlagTouchSwitchWall
)
)

Ahorn.editingOrder(entity::FlagTouchSwitchWall) = String["x", "y", "width", "height", "inactiveColor", "activeColor", "finishColor", "hitSound", "completeSoundFromSwitch", "completeSoundFromScene"]

Ahorn.editingOptions(entity::FlagTouchSwitchWall) = Dict{String,Any}(
"icon" => bundledIcons
)

Ahorn.minimumSize(entity::FlagTouchSwitchWall) = 8, 8
Ahorn.resizable(entity::FlagTouchSwitchWall) = true, true
Ahorn.selection(entity::FlagTouchSwitchWall) = Ahorn.getEntityRectangle(entity)

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::FlagTouchSwitchWall, room::Maple.Room)
icon = get(entity.data, "icon", "vanilla")

iconPath = "objects/touchswitch/icon00.png"
if icon != "vanilla"
iconPath = "objects/MaxHelpingHand/flagTouchSwitch/$(icon)/icon00.png"
end

width = get(entity.data, "width", 8)
height = get(entity.data, "height", 8)

Ahorn.drawRectangle(ctx, 0, 0, width, height, (0.0, 0.0, 0.0, 0.3), (1.0, 1.0, 1.0, 0.5))
Ahorn.drawSprite(ctx, iconPath, width / 2, height / 2)
end

end
19 changes: 19 additions & 0 deletions Ahorn/lang/en_gb.lang
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ placements.entities.MaxHelpingHand/FlagTouchSwitch.names.completeSoundFromScene=
placements.entities.MaxHelpingHand/FlagTouchSwitch.tooltips.hideIfFlag=If a session flag is specified here, turning it on will make the touch switch disappear, and will make other touch switches in the room ignore it for the group's completion.
placements.entities.MaxHelpingHand/FlagTouchSwitch.tooltips.animationLength=The length of the provided spinning animation, in frames.

# Flag Touch Switch Wall
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.flag=The session flag this touch switch sets. Give the same to multiple touch switches and switch gates to group them.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.icon=The name of the icon for the touch switch (relative to objects/MaxHelpingHand/FlagTouchSwitchWall), or "vanilla" for the default one.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.persistent=If enabled, the touch switch will stay active when the player dies or changes rooms.\nThis touch switch will also set a flag when it is enabled: [flagName]_switch[entityID]
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.inactiveColor=The switch color when not triggered yet.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.activeColor=The switch color when triggered, but the group is not complete yet.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.finishColor=The switch color when the group is complete.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.smoke=Whether the touch switch emits smoke when the group is complete.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.inverted=Whether the touch switch should turn off the flag instead of turning it on. The option should match on all touch switches of the group.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.allowDisable=Whether the touch switch can be disabled once the group is complete, if the flag change is reverted. Does not work with non-persistent flag switch gates.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.playerCanActivate=Whether the player can activate the touch switch. If not, only throwables can activate it.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.hitSound=The sound played when the switch is hit by the player.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.completeSoundFromSwitch=The sound emitted by the latest switch collected when the group is complete.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.completeSoundFromScene=A global sound emitted when the group is complete. As opposed to "Complete Sound (Switch)", this sound isn't emitted by a particular switch.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.names.completeSoundFromSwitch=Complete Sound (Switch)
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.names.completeSoundFromScene=Complete Sound (Global)
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.hideIfFlag=If a session flag is specified here, turning it on will make the touch switch disappear, and will make other touch switches in the room ignore it for the group's completion.
placements.entities.MaxHelpingHand/FlagTouchSwitchWall.tooltips.animationLength=The length of the provided spinning animation, in frames.

# Moving Flag Touch Switch
placements.entities.MaxHelpingHand/MovingFlagTouchSwitch.tooltips.flag=The session flag this touch switch sets. Give the same to multiple touch switches and switch gates to group them.
placements.entities.MaxHelpingHand/MovingFlagTouchSwitch.tooltips.icon=The name of the icon for the touch switch (relative to objects/MaxHelpingHand/flagTouchSwitch), or "vanilla" for the default one.
Expand Down
66 changes: 42 additions & 24 deletions Entities/FlagTouchSwitch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Celeste.Mod.MaxHelpingHand.Entities {
/// - inactiveColor / activeColor / finishColor: custom colors for the touch switch.
/// </summary>
[CustomEntity("MaxHelpingHand/FlagTouchSwitch")]
[Tracked]
[Tracked(inherited: true)]
public class FlagTouchSwitch : Entity {
private static FieldInfo seekerPushRadius = typeof(Seeker).GetField("pushRadius", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo seekerPhysicsHitbox = typeof(Seeker).GetField("physicsHitbox", BindingFlags.NonPublic | BindingFlags.Instance);
Expand Down Expand Up @@ -90,7 +90,7 @@ private static void turnOnTouchSwitchesCollidingWith(Entity self) {

private MTexture border = GFX.Game["objects/touchswitch/container"];

private Sprite icon;
protected Sprite icon;

private int[] frames;
private bool persistent;
Expand Down Expand Up @@ -119,6 +119,8 @@ private static void turnOnTouchSwitchesCollidingWith(Entity self) {

private Level level => (Level) Scene;

protected virtual Vector2 IconPosition => Vector2.Zero;

public FlagTouchSwitch(EntityData data, Vector2 offset)
: base(data.Position + offset) {

Expand Down Expand Up @@ -152,13 +154,7 @@ public FlagTouchSwitch(EntityData data, Vector2 offset)
Color = finishColor
};

// set up collision
Collider = new Hitbox(16f, 16f, -8f, -8f);
if (data.Bool("playerCanActivate", defaultValue: true)) {
Add(new PlayerCollider(onPlayer, null, new Hitbox(30f, 30f, -15f, -15f)));
}
Add(new HoldableCollider(onHoldable, new Hitbox(20f, 20f, -10f, -10f)));
Add(new SeekerCollider(onSeeker, new Hitbox(24f, 24f, -12f, -12f)));
setUpCollision(data);

// set up the icon
string iconAttribute = data.Attr("icon", "vanilla");
Expand All @@ -175,16 +171,27 @@ public FlagTouchSwitch(EntityData data, Vector2 offset)
icon.Play("spin");
icon.Color = inactiveColor;
icon.CenterOrigin();
icon.Position = IconPosition;

Add(bloom = new BloomPoint(0f, 16f));
bloom.Alpha = 0f;
bloom.Position = IconPosition;

Add(wiggler = Wiggler.Create(0.5f, 4f, v => {
pulse = Vector2.One * (1f + v * 0.25f);
}));

Add(new VertexLight(Color.White, 0.8f, 16, 32));
Add(touchSfx = new SoundSource());
Add(new VertexLight(Color.White, 0.8f, 16, 32) { Position = IconPosition });
Add(touchSfx = new SoundSource { Position = IconPosition });
}

protected virtual void setUpCollision(EntityData data) {
Collider = new Hitbox(16f, 16f, -8f, -8f);
if (data.Bool("playerCanActivate", defaultValue: true)) {
Add(new PlayerCollider(onPlayer, null, new Hitbox(30f, 30f, -15f, -15f)));
}
Add(new HoldableCollider(onHoldable, new Hitbox(20f, 20f, -10f, -10f)));
Add(new SeekerCollider(onSeeker, new Hitbox(24f, 24f, -12f, -12f)));
}

public override void Added(Scene scene) {
Expand Down Expand Up @@ -224,15 +231,15 @@ public override void Awake(Scene scene) {
new DynData<TouchSwitch>(touchSwitch).Get<string>("flag") == flag).ToList();
}

private void onPlayer(Player player) {
protected void onPlayer(Player player) {
TurnOn();
}

private void onHoldable(Holdable h) {
protected void onHoldable(Holdable h) {
TurnOn();
}

private void onSeeker(Seeker seeker) {
protected void onSeeker(Seeker seeker) {
if (SceneAs<Level>().InsideCamera(Position, 10f)) {
TurnOn();
}
Expand All @@ -249,31 +256,38 @@ public void TurnOn() {
wiggler.Start();
for (int i = 0; i < 32; i++) {
float num = Calc.Random.NextFloat((float) Math.PI * 2f);
level.Particles.Emit(TouchSwitch.P_FireWhite, Position + Calc.AngleToVector(num, 6f), num);
level.Particles.Emit(TouchSwitch.P_FireWhite, Position + IconPosition + Calc.AngleToVector(num, 6f), num);
}
});
icon.Rate = 4f;

HandleCollectedFlagTouchSwitch(flag, inverted, persistent, level, id, allTouchSwitchesInRoom, allMovingFlagTouchSwitchesInRoom, () => doEffect(() => {
SoundEmitter.Play(completeSoundFromScene);
Add(new SoundSource(completeSoundFromSwitch));
Add(new SoundSource(completeSoundFromSwitch) { Position = IconPosition });
}));
}
}

// returns true if the entire group was completed.
internal static bool HandleCollectedFlagTouchSwitch(string flag, bool inverted, bool persistent, Level level, int id,
List<FlagTouchSwitch> allTouchSwitchesInRoom, List<TouchSwitch> allMovingFlagTouchSwitchesInRoom, Action onFinished) {
internal static bool HandleCollectedFlagTouchSwitch(
string flag,
bool inverted,
bool persistent,
Level level,
int id,
List<FlagTouchSwitch> allTouchSwitchesInRoom,
List<TouchSwitch> allMovingFlagTouchSwitchesInRoom,
Action onFinished) {

if (persistent) {
// this switch is persistent. save its activation in the session.
level.Session.SetFlag(flag + "_switch" + id, true);
}

if (MaxHelpingHandMapDataProcessor.FlagTouchSwitches[level.Session.Area.SID][(int) level.Session.Area.Mode][new KeyValuePair<string, bool>(flag, inverted)]
.All(touchSwitchID => touchSwitchID.Level == level.Session.Level || level.Session.GetFlag(flag + "_switch" + touchSwitchID.ID))
&& allTouchSwitchesInRoom.All(touchSwitch => touchSwitch.Activated || touchSwitch.isHidden())
&& allMovingFlagTouchSwitchesInRoom.All(touchSwitch => touchSwitch.Switch.Activated || MovingFlagTouchSwitch.IsHidden(touchSwitch))) {
.All(touchSwitchID => touchSwitchID.Level == level.Session.Level || level.Session.GetFlag(flag + "_switch" + touchSwitchID.ID))
&& allTouchSwitchesInRoom.All(touchSwitch => touchSwitch.Activated || touchSwitch.isHidden())
&& allMovingFlagTouchSwitchesInRoom.All(touchSwitch => touchSwitch.Switch.Activated || MovingFlagTouchSwitch.IsHidden(touchSwitch))) {

// all switches in the room are enabled or hidden, and all session flags for switches outside the room are enabled.
// so, the group is complete.
Expand Down Expand Up @@ -366,10 +380,10 @@ public override void Update() {
icon.Rate = 0.1f;
wiggler.Start();
icon.Play("idle");
level.Displacement.AddBurst(Position, 0.6f, 4f, 28f, 0.2f);
level.Displacement.AddBurst(Position + IconPosition, 0.6f, 4f, 28f, 0.2f);
}
} else if (Scene.OnInterval(0.03f) && smoke) {
Vector2 position = Position + new Vector2(0f, 1f) + Calc.AngleToVector(Calc.Random.NextAngle(), 5f);
Vector2 position = Position + IconPosition + new Vector2(0f, 1f) + Calc.AngleToVector(Calc.Random.NextAngle(), 5f);
level.ParticlesBG.Emit(P_RecoloredFire, position);
}

Expand All @@ -395,9 +409,13 @@ public override void Update() {
}

public override void Render() {
renderBorder();
base.Render();
}

protected virtual void renderBorder() {
border.DrawCentered(Position + new Vector2(0f, -1f), Color.Black);
border.DrawCentered(Position, icon.Color, pulse);
base.Render();
}

private void doEffect(Action effect) {
Expand Down
29 changes: 29 additions & 0 deletions Entities/FlagTouchSwitchWall.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Celeste.Mod.Entities;
using Microsoft.Xna.Framework;
using Monocle;

namespace Celeste.Mod.MaxHelpingHand.Entities {
[CustomEntity("MaxHelpingHand/FlagTouchSwitchWall")]
public class FlagTouchSwitchWall : FlagTouchSwitch {
protected override Vector2 IconPosition => new Vector2(Width / 2, Height / 2);

public FlagTouchSwitchWall(EntityData data, Vector2 offset) : base(data, offset) {
}

protected override void setUpCollision(EntityData data) {
Collider = new Hitbox(data.Width, data.Height);

if (data.Bool("playerCanActivate", defaultValue: true)) {
Add(new PlayerCollider(onPlayer, null, new Hitbox(data.Width, data.Height)));
}

Add(new HoldableCollider(onHoldable, new Hitbox(data.Width, data.Height)));
Add(new SeekerCollider(onSeeker, new Hitbox(data.Width, data.Height)));
}

protected override void renderBorder() {
Draw.HollowRect(X - 1, Y - 1, Width + 2, Height + 2, new Color(icon.Color.R, icon.Color.G, icon.Color.B, (int) (0.7f * 255)));
Draw.Rect(X + 1, Y + 1, Width - 2, Height - 2, Color.Lerp(icon.Color, Calc.HexToColor("0a0a0a"), 0.5f) * 0.3f);
}
}
}
1 change: 0 additions & 1 deletion Loenn/entities/flagTouchSwitch.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
local drawableSprite = require("structs.drawable_sprite")
local utils = require("utils")

local touchSwitch = {}

Expand Down
77 changes: 77 additions & 0 deletions Loenn/entities/flagTouchSwitchWall.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
local drawableSprite = require("structs.drawable_sprite")
local drawableRectangle = require("structs.drawable_rectangle")

local touchSwitch = {}

touchSwitch.name = "MaxHelpingHand/FlagTouchSwitchWall"
touchSwitch.depth = 2000
touchSwitch.minimumSize = {8, 8}
touchSwitch.placements = {
{
name = "touch_switch",
data = {
width = 16,
height = 16,
flag = "flag_touch_switch",
icon = "vanilla",
animationLength = 6,
persistent = false,
inactiveColor = "5FCDE4",
activeColor = "FFFFFF",
finishColor = "F141DF",
smoke = true,
inverted = false,
allowDisable = false,
playerCanActivate = true,
hitSound = "event:/game/general/touchswitch_any",
completeSoundFromSwitch = "event:/game/general/touchswitch_last_cutoff",
completeSoundFromScene = "event:/game/general/touchswitch_last_oneshot",
hideIfFlag = ""
}
}
}

function touchSwitch.fieldOrder(entity)
local fieldOrder = {"x", "y", "width", "height", "inactiveColor", "activeColor", "finishColor", "hitSound", "completeSoundFromSwitch", "completeSoundFromScene"}

-- only include animationLength to fieldOrder if the field exists, otherwise it will appear as nil in the entity properties window
if entity.animationLength ~= nil then
table.insert(fieldOrder, "animationLength")
end

return fieldOrder
end

touchSwitch.fieldInformation = {
inactiveColor = {
fieldType = "color"
},
activeColor = {
fieldType = "color"
},
finishColor = {
fieldType = "color"
},
icon = {
options = { "vanilla", "tall", "triangle", "circle", "diamond", "double", "heart", "square", "wide", "winged", "cross", "drop", "hourglass", "split", "star", "triple" }
},
animationLength = {
fieldType = "integer"
}
}

function touchSwitch.sprite(room, entity)
local containerSprite = drawableRectangle.fromRectangle('bordered', entity.x, entity.y, entity.width, entity.height, {0.0, 0.0, 0.0, 0.3}, {1.0, 1.0, 1.0, 0.5})

local iconResource = "objects/touchswitch/icon00"
if entity.icon ~= "vanilla" then
iconResource = "objects/MaxHelpingHand/flagTouchSwitch/" .. entity.icon .."/icon00"
end

local iconSprite = drawableSprite.fromTexture(iconResource, entity)
iconSprite:setPosition(entity.x + entity.width / 2, entity.y + entity.height / 2)

return {containerSprite, iconSprite}
end

return touchSwitch
Loading

0 comments on commit a5681cc

Please sign in to comment.