Turn the touches back into reward designs styled like any other

The Touch setting is gone. Its unique mechanics become reward designs that use
the entry's particle: Burst (on jumping, landing or sneaking, as a puff or a
ring), Footprints, Team link, Shimmer and Heartbeat. Each is unlocked by any
of the rewards that had it, and Burst's triggers are locked separately. The
wing beat becomes the Beat on jump wing motion, unlocked by WarGear Season
2022. December snow and the glyphs are dropped: the first is a plain particle
with a condition, the second only works with enchanting glyphs.
This commit is contained in:
2026-10-26 15:08:08 +01:00
parent 36e6329f2b
commit db939e27d9
18 changed files with 231 additions and 356 deletions
@@ -116,7 +116,6 @@ PARTICLE_SLOT_COST = §7Particles per tick§8: §e{0}§8/§7{1}
PARTICLE_ENTRY_EDITOR = §0{0}
PARTICLE_ENTRY_DESIGN = §7Design§8: {0}
PARTICLE_ENTRY_PARTICLE = §7Particle§8: {0}
PARTICLE_ENTRY_TOUCH = §7Touch§8: {0}
PARTICLE_ENTRY_LOCKED = §cDesign locked, not shown
PARTICLE_ENTRY_COST = §7Particles per tick§8: §e{0}
PARTICLE_ENTRY_COST_SHARE = §7This entry§8: §e{0}
@@ -136,9 +135,23 @@ PARTICLE_DESIGN_SIMPLE = §fSimple
PARTICLE_DESIGN_CIRCLE = §fCircle
PARTICLE_DESIGN_WING = §fWings
PARTICLE_DESIGN_CLOUD = §fCloud
PARTICLE_DESIGN_BURST = §fBurst
PARTICLE_DESIGN_FOOTPRINTS = §fFootprints
PARTICLE_DESIGN_TEAM_LINK = §fTeam link
PARTICLE_DESIGN_PULSE_SHIMMER = §fShimmer
PARTICLE_DESIGN_HEART_BEAT = §fHeartbeat
PARTICLE_PARAM_PARTICLE = §fParticle
PARTICLE_PARAM_TOUCH = §fTouch
PARTICLE_PARAM_BURST_TRIGGER = §fWhen
PARTICLE_PARAM_BURST_SHAPE = §fShape
PARTICLE_PARAM_BURST_RADIUS = §fRing radius
PARTICLE_PARAM_BURST_FALL = §fMinimum fall height
PARTICLE_PARAM_FOOTPRINT_LIFETIME = §fPrint lifetime (ticks)
PARTICLE_PARAM_FOOTPRINT_STEP = §fDistance between prints
PARTICLE_PARAM_LINK_SPACING = §fDistance between particles
PARTICLE_PARAM_PULSE_SPEED = §fPulse duration (ticks)
PARTICLE_PARAM_PULSE_ROTATION = §fRotation
PARTICLE_PARAM_HEART_BEAT_SPEED = §fBeat duration (ticks)
PARTICLE_PARAM_COUNT = §fAmount
PARTICLE_PARAM_SPREAD_X = §fSpread X
PARTICLE_PARAM_SPREAD_Y = §fSpread Y
@@ -204,28 +217,17 @@ PARTICLE_CHOICE_IMAGE_MWGL = §fMiniWarGear League
PARTICLE_CHOICE_IMAGE_SW = §fSteamWar
PARTICLE_CHOICE_IMAGE_WGS = §fWarGear Season
PARTICLE_CHOICE_IMAGE_SWORD_CROSSED = §fCrossed Swords
PARTICLE_CHOICE_TOUCH_NONE = §7None
PARTICLE_CHOICE_TOUCH_JUMP_CLOUD = §fCloud puff on jumping
PARTICLE_CHOICE_TOUCH_JUMP_WINGS = §5Wing beat on jumping
PARTICLE_CHOICE_TOUCH_LANDING_SPLASH = §bSplash on landing
PARTICLE_CHOICE_TOUCH_CRASH_LANDING = §cCrash landing
PARTICLE_CHOICE_TOUCH_LAVA_LANDING = §6Lava on landing
PARTICLE_CHOICE_TOUCH_OBSIDIAN_LANDING = §5Obsidian tears on landing
PARTICLE_CHOICE_TOUCH_GLYPH_BURST = §dGlyph burst on sneaking
PARTICLE_CHOICE_TOUCH_WET_FOOTPRINTS = §bWet footprints
PARTICLE_CHOICE_TOUCH_CLOUD_FOOTPRINTS = §fCloud footprints
PARTICLE_CHOICE_TOUCH_BLOSSOM_FOOTPRINTS = §aBlossom footprints
PARTICLE_CHOICE_TOUCH_WATER_LINK = §bWater link to others with it
PARTICLE_CHOICE_TOUCH_BUBBLE_LINK = §3Bubble link to others with it
PARTICLE_CHOICE_TOUCH_GLYPH_PULL = §dGlyphs while standing still
PARTICLE_CHOICE_TOUCH_STILL_SHIMMER = §3Shimmer while standing still
PARTICLE_CHOICE_TOUCH_HEARTBEAT = §cHeartbeat while standing still
PARTICLE_CHOICE_TOUCH_DECEMBER_SNOW = §fSnow in December
PARTICLE_CHOICE_COLORING_OVER_TIME = §fChange over time
PARTICLE_CHOICE_COLORING_ALONG_WING = §fFrom the back to the tips
PARTICLE_CHOICE_MOTION_NONE = §fNone
PARTICLE_CHOICE_MOTION_FLAP_SLOW = §fFlap slowly
PARTICLE_CHOICE_MOTION_FLAP_FAST = §fFlap quickly
PARTICLE_CHOICE_MOTION_BEAT_ON_JUMP = §fBeat on jumping
PARTICLE_CHOICE_TRIGGER_JUMP = §fOn jumping
PARTICLE_CHOICE_TRIGGER_LAND = §fOn landing
PARTICLE_CHOICE_TRIGGER_SNEAK = §fOn sneaking
PARTICLE_CHOICE_BURSTSHAPE_PUFF = §fPuff
PARTICLE_CHOICE_BURSTSHAPE_RING = §fRing
# Particle types
PARTICLE_TYPE_DUST = §fDust
@@ -116,7 +116,6 @@ PARTICLE_SLOT_COST = §7Partikel pro Tick§8: §e{0}§8/§7{1}
PARTICLE_ENTRY_EDITOR = §0{0}
PARTICLE_ENTRY_DESIGN = §7Design§8: {0}
PARTICLE_ENTRY_PARTICLE = §7Partikel§8: {0}
PARTICLE_ENTRY_TOUCH = §7Akzent§8: {0}
PARTICLE_ENTRY_LOCKED = §cDesign gesperrt, wird nicht angezeigt
PARTICLE_ENTRY_COST = §7Partikel pro Tick§8: §e{0}
PARTICLE_ENTRY_COST_SHARE = §7Dieser Eintrag§8: §e{0}
@@ -136,9 +135,23 @@ PARTICLE_DESIGN_SIMPLE = §fEinfach
PARTICLE_DESIGN_CIRCLE = §fKreis
PARTICLE_DESIGN_WING = §fFlügel
PARTICLE_DESIGN_CLOUD = §fWolke
PARTICLE_DESIGN_BURST = §fStoß
PARTICLE_DESIGN_FOOTPRINTS = §fFußspuren
PARTICLE_DESIGN_TEAM_LINK = §fTeamband
PARTICLE_DESIGN_PULSE_SHIMMER = §fSchimmer
PARTICLE_DESIGN_HEART_BEAT = §fHerzschlag
PARTICLE_PARAM_PARTICLE = §fPartikel
PARTICLE_PARAM_TOUCH = §fAkzent
PARTICLE_PARAM_BURST_TRIGGER = §fWann
PARTICLE_PARAM_BURST_SHAPE = §fForm
PARTICLE_PARAM_BURST_RADIUS = §fRingradius
PARTICLE_PARAM_BURST_FALL = §fMindestfallhöhe
PARTICLE_PARAM_FOOTPRINT_LIFETIME = §fSpurdauer (Ticks)
PARTICLE_PARAM_FOOTPRINT_STEP = §fAbstand der Spuren
PARTICLE_PARAM_LINK_SPACING = §fAbstand der Partikel
PARTICLE_PARAM_PULSE_SPEED = §fPulsdauer (Ticks)
PARTICLE_PARAM_PULSE_ROTATION = §fDrehung
PARTICLE_PARAM_HEART_BEAT_SPEED = §fSchlagdauer (Ticks)
PARTICLE_PARAM_COUNT = §fAnzahl
PARTICLE_PARAM_SPREAD_X = §fStreuung X
PARTICLE_PARAM_SPREAD_Y = §fStreuung Y
@@ -204,28 +217,17 @@ PARTICLE_CHOICE_IMAGE_MWGL = §fMiniWarGear Liga
PARTICLE_CHOICE_IMAGE_SW = §fSteamWar
PARTICLE_CHOICE_IMAGE_WGS = §fWarGear Saison
PARTICLE_CHOICE_IMAGE_SWORD_CROSSED = §fGekreuzte Schwerter
PARTICLE_CHOICE_TOUCH_NONE = §7Keiner
PARTICLE_CHOICE_TOUCH_JUMP_CLOUD = §fWolke beim Springen
PARTICLE_CHOICE_TOUCH_JUMP_WINGS = §5Flügelschlag beim Springen
PARTICLE_CHOICE_TOUCH_LANDING_SPLASH = §bSpritzer beim Landen
PARTICLE_CHOICE_TOUCH_CRASH_LANDING = §cBruchlandung
PARTICLE_CHOICE_TOUCH_LAVA_LANDING = §6Lava beim Landen
PARTICLE_CHOICE_TOUCH_OBSIDIAN_LANDING = §5Obsidiantränen beim Landen
PARTICLE_CHOICE_TOUCH_GLYPH_BURST = §dGlyphen beim Schleichen
PARTICLE_CHOICE_TOUCH_WET_FOOTPRINTS = §bNasse Fußspuren
PARTICLE_CHOICE_TOUCH_CLOUD_FOOTPRINTS = §fWolkenfußspuren
PARTICLE_CHOICE_TOUCH_BLOSSOM_FOOTPRINTS = §aBlütenfußspuren
PARTICLE_CHOICE_TOUCH_WATER_LINK = §bWasserband zu anderen damit
PARTICLE_CHOICE_TOUCH_BUBBLE_LINK = §3Blasenband zu anderen damit
PARTICLE_CHOICE_TOUCH_GLYPH_PULL = §dGlyphen im Stillstand
PARTICLE_CHOICE_TOUCH_STILL_SHIMMER = §3Schimmer im Stillstand
PARTICLE_CHOICE_TOUCH_HEARTBEAT = §cHerzschlag im Stillstand
PARTICLE_CHOICE_TOUCH_DECEMBER_SNOW = §fSchnee im Dezember
PARTICLE_CHOICE_COLORING_OVER_TIME = §fWechseln mit der Zeit
PARTICLE_CHOICE_COLORING_ALONG_WING = §fVom Rücken zu den Spitzen
PARTICLE_CHOICE_MOTION_NONE = §fKeine
PARTICLE_CHOICE_MOTION_FLAP_SLOW = §fLangsam schlagen
PARTICLE_CHOICE_MOTION_FLAP_FAST = §fSchnell schlagen
PARTICLE_CHOICE_MOTION_BEAT_ON_JUMP = §fSchlag beim Springen
PARTICLE_CHOICE_TRIGGER_JUMP = §fBeim Springen
PARTICLE_CHOICE_TRIGGER_LAND = §fBeim Landen
PARTICLE_CHOICE_TRIGGER_SNEAK = §fBeim Schleichen
PARTICLE_CHOICE_BURSTSHAPE_PUFF = §fWolke
PARTICLE_CHOICE_BURSTSHAPE_RING = §fRing
# Particle types
PARTICLE_TYPE_DUST = §fStaub
@@ -44,10 +44,6 @@ public class CompiledSet {
for (ParticleEntry entry : particleSet.getEntries()) {
if (entry.isUnlocked(context)) {
compiledSet.add(entry.build());
ParticleElement touch = entry.buildTouch();
if (touch != null) {
compiledSet.add(touch);
}
}
}
return compiledSet;
@@ -244,6 +244,24 @@ public interface ParticleRequirement {
}.or(SERVER_TEAM);
}
// Met by meeting any of the requirements; the lore lists them one per line
static ParticleRequirement anyOf(ParticleRequirement... requirements) {
return new ParticleRequirement() {
@Override
public String getRequirementName(Player player) {
return java.util.Arrays.stream(requirements).map(requirement -> requirement.getRequirementName(player)).filter(Objects::nonNull).collect(Collectors.joining("\n"));
}
@Override
public boolean matches(UnlockContext context) {
for (ParticleRequirement requirement : requirements) {
if (requirement.test(context)) return true;
}
return false;
}
};
}
// The alternative is checked first, as it is usually the cheap server team check
default ParticleRequirement or(ParticleRequirement particleRequirement) {
return new ParticleRequirement() {
@@ -20,6 +20,7 @@
package de.steamwar.lobby.particle.design;
import de.steamwar.lobby.particle.ParticleElement;
import de.steamwar.lobby.particle.ParticleRequirement;
import de.steamwar.lobby.particle.DrawnWings;
import de.steamwar.lobby.particle.WingDesign;
import de.steamwar.lobby.particle.elements.Circle;
@@ -102,7 +103,8 @@ public class Designs {
public final ChoiceParameter<WingColoring> WING_COLORING = ChoiceParameter.ofEnum("coloring", "PARTICLE_PARAM_WING_COLORING", WingColoring.OVER_TIME);
// Percent of the points shown each tick; fewer makes the wing twinkle and costs less
public final IntParameter WING_SPARKLE = new IntParameter("sparkle", "PARTICLE_PARAM_WING_SPARKLE", 100, 10, 100);
public final ChoiceParameter<Wing.Motion> WING_MOTION = ChoiceParameter.ofEnum("motion", "PARTICLE_PARAM_WING_MOTION", Wing.Motion.NONE, motion -> motion == Wing.Motion.JUMP_BEAT);
public final ChoiceParameter<Wing.Motion> WING_MOTION = ChoiceParameter.ofEnum("motion", "PARTICLE_PARAM_WING_MOTION", Wing.Motion.NONE, motion -> false,
motion -> motion == Wing.Motion.BEAT_ON_JUMP ? Unlocks.WARGEAR_SEASON_2022.get() : ParticleRequirement.NO_REQUIRMENT);
// Moving wings need to be redrawn often to look smooth; still ones are fine once a second
private final int MOVING_WING_INTERVAL = 2;
private final int STILL_WING_INTERVAL = 20;
@@ -112,14 +114,12 @@ public class Designs {
public ParticleElement build(ParameterValues values, ParticleElement particle) {
WingShape shape = values.get(WING_SHAPE);
return new Wing(particle, values.get(WING_SIZE), shape.design(values.get(WING_DETAIL)),
values.get(WING_COLORING) == WingColoring.ALONG_WING, values.get(WING_SPARKLE), values.get(WING_MOTION),
values.get(EntryParameters.TOUCH) == Touches.JUMP_WINGS);
values.get(WING_COLORING) == WingColoring.ALONG_WING, values.get(WING_SPARKLE), values.get(WING_MOTION));
}
// The wing beat touch makes these wings beat instead of drawing wings of its own
@Override
public boolean absorbsTouch(Touch touch) {
return touch == Touches.JUMP_WINGS;
public boolean redrawsAfterJump(ParameterValues values) {
return values.get(WING_MOTION) == Wing.Motion.BEAT_ON_JUMP;
}
@Override
@@ -141,7 +141,8 @@ public class Designs {
@Override
public void adjust(Parameter<?> changed, ParameterValues values) {
if (changed != WING_MOTION) return;
boolean moving = values.get(WING_MOTION) != Wing.Motion.NONE;
// A beat on jumping is redrawn every tick only while it beats
boolean moving = values.get(WING_MOTION) == Wing.Motion.FLAP_SLOW || values.get(WING_MOTION) == Wing.Motion.FLAP_FAST;
int interval = values.get(EntryParameters.INTERVAL);
if (moving && interval > MOVING_WING_INTERVAL) {
values.set(EntryParameters.INTERVAL, MOVING_WING_INTERVAL);
@@ -204,10 +204,8 @@ public class EntryParameters {
public final ChoiceParameter<Sneak> SNEAK = ChoiceParameter.ofEnum("sneak", "PARTICLE_PARAM_SNEAK", Sneak.ANY);
public final ChoiceParameter<Visibility> VISIBILITY = ChoiceParameter.ofEnum("visibility", "PARTICLE_PARAM_VISIBILITY", Visibility.EVERYONE);
public final ChoiceParameter<Touch> TOUCH = new ChoiceParameter<>("touch", "PARTICLE_PARAM_TOUCH", Touches.NONE, Touches.choices());
public final List<Parameter<?>> ALL = List.of(
TOUCH, PARTICLE, COUNT, SPREAD_X, SPREAD_Y, SPREAD_Z, SPEED, COLORS, BLOCK,
PARTICLE, COUNT, SPREAD_X, SPREAD_Y, SPREAD_Z, SPEED, COLORS, BLOCK,
TRAIL_DURATION, TRAIL_TARGET_X, TRAIL_TARGET_Y, TRAIL_TARGET_Z,
OFFSET_X, OFFSET_Y, OFFSET_Z, PLACEMENT, INTERVAL, TICK_MODE,
MOVEMENT, GLIDING, GROUND, SNEAK, VISIBILITY
@@ -72,9 +72,9 @@ public abstract class ParticleDesign {
return false;
}
// Whether the design shows the touch itself instead of the touch drawing its own particles
public boolean absorbsTouch(Touch touch) {
// Whether the design must be drawn every tick for a moment after a jump, whatever its interval; these extra
// redraws do not count towards the budget
public boolean redrawsAfterJump(ParameterValues values) {
return false;
}
@@ -22,6 +22,12 @@ package de.steamwar.lobby.particle.design;
import de.steamwar.lobby.particle.ParticleElement;
import de.steamwar.lobby.particle.ParticleRequirement;
import de.steamwar.lobby.particle.elements.*;
import de.steamwar.lobby.particle.elements.custom.HearthBeat;
import de.steamwar.lobby.particle.elements.custom.PulseShimmer;
import de.steamwar.lobby.particle.elements.touch.EventBurst;
import de.steamwar.lobby.particle.elements.touch.Footprints;
import de.steamwar.lobby.particle.elements.touch.Ring;
import de.steamwar.lobby.particle.elements.touch.TeamLink;
import lombok.experimental.UtilityClass;
import org.bukkit.Material;
import org.bukkit.Particle;
@@ -33,12 +39,142 @@ import java.util.function.Consumer;
import static de.steamwar.lobby.particle.design.EntryParameters.*;
// Reward designs the builder cannot make. Other rewards unlock ingredients instead: wing images, particles and touches.
// Designs unlocked by event and egg hunt rewards. Most are styled like the basic designs, with a particle of the
// player's choice; the others have a fixed look. Rewards whose look the basic designs can already build unlock
// ingredients instead: wing images, a wing motion and particles.
@UtilityClass
public class RewardDesigns {
private final Consumer<ParameterValues> ALWAYS = values -> values.set(TICK_MODE, TickMode.ALWAYS);
public enum BurstShape {
PUFF,
RING,
}
private final int RING_POINTS = 16;
public final ChoiceParameter<EventBurst.Trigger> BURST_TRIGGER = ChoiceParameter.ofEnum("trigger", "PARTICLE_PARAM_BURST_TRIGGER", EventBurst.Trigger.JUMP, trigger -> false,
trigger -> switch (trigger) {
case JUMP -> Unlocks.AIRSHIP_PLACEMENT.get();
case LAND -> ParticleRequirement.anyOf(Unlocks.UNDERWATER_PLACEMENT.get(), Unlocks.ABSTURZ_PLACEMENT.get(), Unlocks.ANY_EVENT.get(), Unlocks.EGG_HUNT_EXTREME.get());
case SNEAK -> Unlocks.WARGEAR_SEASON.get();
});
public final ChoiceParameter<BurstShape> BURST_SHAPE = ChoiceParameter.ofEnum("burstShape", "PARTICLE_PARAM_BURST_SHAPE", BurstShape.PUFF);
public final DoubleParameter BURST_RADIUS = new DoubleParameter("burstRadius", "PARTICLE_PARAM_BURST_RADIUS", 1, 0.3, 2, 0.1);
public final DoubleParameter BURST_FALL = new DoubleParameter("minimumFall", "PARTICLE_PARAM_BURST_FALL", 1, 0, 10, 0.5);
// Once at the feet when the player jumps, lands or starts sneaking
public final ParticleDesign BURST = new ParticleDesign("burst", Material.FIREWORK_ROCKET, "PARTICLE_DESIGN_BURST", DesignVisibility.REWARD,
ParticleRequirement.anyOf(Unlocks.AIRSHIP_PLACEMENT.get(), Unlocks.UNDERWATER_PLACEMENT.get(), Unlocks.ABSTURZ_PLACEMENT.get(),
Unlocks.ANY_EVENT.get(), Unlocks.EGG_HUNT_EXTREME.get(), Unlocks.WARGEAR_SEASON.get()),
BURST_TRIGGER, BURST_SHAPE, BURST_RADIUS, BURST_FALL) {
@Override
public ParticleElement build(ParameterValues values, ParticleElement particle) {
ParticleElement burst = values.get(BURST_SHAPE) == BurstShape.RING ? new Ring(particle, values.get(BURST_RADIUS), RING_POINTS) : particle;
double minimumFall = values.get(BURST_TRIGGER) == EventBurst.Trigger.LAND ? values.get(BURST_FALL) : 0;
return new EventBurst(values.get(BURST_TRIGGER), minimumFall, burst);
}
// Shows only now and then, so it counts as a single point
@Override
public int points(ParameterValues values) {
return 1;
}
@Override
public boolean showsSetting(Parameter<?> parameter, ParameterValues values) {
if (parameter == BURST_RADIUS) return values.get(BURST_SHAPE) == BurstShape.RING;
if (parameter == BURST_FALL) return values.get(BURST_TRIGGER) == EventBurst.Trigger.LAND;
return true;
}
@Override
public void applyDefaults(ParameterValues values) {
values.set(TICK_MODE, TickMode.ALWAYS).set(COUNT, 8).set(SPREAD_X, 0.3).set(SPREAD_Y, 0.05).set(SPREAD_Z, 0.3).set(SPEED, 0.02);
}
};
public final IntParameter FOOTPRINT_LIFETIME = new IntParameter("printLifetime", "PARTICLE_PARAM_FOOTPRINT_LIFETIME", 40, 10, 100);
public final DoubleParameter FOOTPRINT_STEP = new DoubleParameter("printStep", "PARTICLE_PARAM_FOOTPRINT_STEP", 0.6, 0.3, 2, 0.1);
// Blocks per tick while walking, to estimate how many prints are shown at once
private final double WALKING_SPEED = 0.22;
public final ParticleDesign FOOTPRINTS = new ParticleDesign("footprints", Material.LEATHER_BOOTS, "PARTICLE_DESIGN_FOOTPRINTS", DesignVisibility.REWARD,
ParticleRequirement.anyOf(Unlocks.ADVENT_WAR_SHIP_PLACEMENT.get(), Unlocks.ADVENT_WAR_SHIP.get(), Unlocks.AIRSHIP.get(), Unlocks.EGG_HUNT_EASY.get()),
FOOTPRINT_LIFETIME, FOOTPRINT_STEP) {
@Override
public ParticleElement build(ParameterValues values, ParticleElement particle) {
return new Footprints(particle, values.get(FOOTPRINT_LIFETIME), values.get(FOOTPRINT_STEP));
}
@Override
public int points(ParameterValues values) {
return (int) Math.ceil(values.get(FOOTPRINT_LIFETIME) * WALKING_SPEED / values.get(FOOTPRINT_STEP));
}
@Override
public void applyDefaults(ParameterValues values) {
values.set(TICK_MODE, TickMode.ALWAYS).set(COUNT, 1).set(SPREAD_X, 0.05).set(SPREAD_Z, 0.05).set(SPEED, 0.0);
}
};
public final DoubleParameter LINK_SPACING = new DoubleParameter("linkSpacing", "PARTICLE_PARAM_LINK_SPACING", 0.5, 0.3, 2, 0.1);
// A line to every nearby player who shows this design too
public final ParticleDesign TEAM_LINK = new ParticleDesign("team_link", Material.LEAD, "PARTICLE_DESIGN_TEAM_LINK", DesignVisibility.REWARD,
ParticleRequirement.anyOf(Unlocks.ANY_EVENT.get(), Unlocks.UNDERWATER.get()),
LINK_SPACING) {
@Override
public ParticleElement build(ParameterValues values, ParticleElement particle) {
return new TeamLink("team_link", particle, values.get(LINK_SPACING));
}
// A line to one partner half the range away
@Override
public int points(ParameterValues values) {
return (int) Math.ceil(TeamLink.RANGE / 2 / values.get(LINK_SPACING));
}
@Override
public void applyDefaults(ParameterValues values) {
values.set(TICK_MODE, TickMode.ALWAYS).set(COUNT, 1).set(SPEED, 0.0);
}
};
public final ParticleDesign SHIMMER = new ParticleDesign("shimmer", Material.AMETHYST_CLUSTER, "PARTICLE_DESIGN_PULSE_SHIMMER", DesignVisibility.REWARD,
ParticleRequirement.anyOf(Unlocks.MINI_WARGEAR_LIGA.get(), Unlocks.WARGEAR_SEASON_PLACEMENT.get()),
InternalDesigns.PULSE_SPEED, InternalDesigns.PULSE_ROTATION) {
@Override
public ParticleElement build(ParameterValues values, ParticleElement particle) {
return new PulseShimmer(particle, values.get(InternalDesigns.PULSE_SPEED), values.get(InternalDesigns.PULSE_ROTATION));
}
@Override
public int points(ParameterValues values) {
return 3;
}
@Override
public void applyDefaults(ParameterValues values) {
values.set(TICK_MODE, TickMode.ALWAYS).set(MOVEMENT, Movement.STANDING_STILL).set(COUNT, 1).set(SPEED, 0.0);
}
};
public final ParticleDesign HEARTBEAT = new ParticleDesign("heartbeat", Material.RED_DYE, "PARTICLE_DESIGN_HEART_BEAT", DesignVisibility.REWARD,
Unlocks.EGG_HUNT_HALF.get(),
InternalDesigns.HEART_BEAT_SPEED) {
@Override
public ParticleElement build(ParameterValues values, ParticleElement particle) {
return new HearthBeat(particle, values.get(InternalDesigns.HEART_BEAT_SPEED));
}
@Override
public void applyDefaults(ParameterValues values) {
values.set(TICK_MODE, TickMode.ALWAYS).set(MOVEMENT, Movement.STANDING_STILL).set(COUNT, 1).set(SPEED, 0.0);
}
};
public final ParticleDesign EVENT_PARTICIPATION_UNDERWATER_MWG = new FixedDesign("event_participation_underwater_mwg", Material.BLUE_CARPET, "PARTICLE_EVENT_RAIN_CLOUD",
ParticleRequirement.eventParticipation(35), 15, ALWAYS,
() -> new RandomParticle(
@@ -81,6 +217,6 @@ public class RewardDesigns {
}
public List<ParticleDesign> all() {
return List.of(EVENT_PARTICIPATION_UNDERWATER_MWG, EGG_HUNT_MEDIUM, EGG_HUNT_HARD, EGG_HUNT_ADVANCED);
return List.of(BURST, FOOTPRINTS, TEAM_LINK, SHIMMER, HEARTBEAT, EVENT_PARTICIPATION_UNDERWATER_MWG, EGG_HUNT_MEDIUM, EGG_HUNT_HARD, EGG_HUNT_ADVANCED);
}
}
@@ -1,30 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.lobby.particle.design;
import de.steamwar.lobby.particle.ParticleElement;
import de.steamwar.lobby.particle.ParticleRequirement;
import org.bukkit.Material;
import java.util.function.Supplier;
// An extra effect an entry can carry next to its design, unlocked by a reward. Touches run every tick, independent
// of the entry's conditions and timing, so their points count fully towards the slot budget.
public record Touch(String id, Material icon, int points, Supplier<ParticleRequirement> requirement, Supplier<ParticleElement> element) {
}
@@ -1,92 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.lobby.particle.design;
import de.steamwar.lobby.particle.Gradient;
import de.steamwar.lobby.particle.ParticleRequirement;
import de.steamwar.lobby.particle.WingDesign;
import de.steamwar.lobby.particle.elements.*;
import de.steamwar.lobby.particle.elements.custom.HearthBeat;
import de.steamwar.lobby.particle.elements.custom.NonMoving;
import de.steamwar.lobby.particle.elements.custom.PulseShimmer;
import de.steamwar.lobby.particle.elements.touch.*;
import lombok.experimental.UtilityClass;
import org.bukkit.Material;
import org.bukkit.Particle;
import java.time.Month;
import java.util.List;
// The touches of the entries' touch setting, each unlocked by the event or egg hunt reward that used to carry it
@UtilityClass
public class Touches {
public final Touch NONE = new Touch("none", Material.GRAY_DYE, 0, () -> ParticleRequirement.NO_REQUIRMENT, () -> null);
// On a Wings entry the entry's own wings beat; on others it draws dragon breath wings for the beat
public final Touch JUMP_WINGS = new Touch("jump_wings", Material.DRAGON_BREATH, 2, Unlocks.WARGEAR_SEASON_2022,
() -> new Delayed(new Wing(new SimpleParticle(Particle.DRAGON_BREATH, 0, 0, 0, 0, 1), 0.15, WingDesign.COMPLEX, false, 100, Wing.Motion.JUMP_BEAT), 2));
private final List<Touch> ALL = List.of(
NONE,
// Bursts on jumping, landing or sneaking
new Touch("jump_cloud", Material.SNOWBALL, 1, Unlocks.AIRSHIP_PLACEMENT,
() -> new EventBurst(EventBurst.Trigger.JUMP, new SimpleParticle(Particle.CLOUD, 0.3F, 0.05F, 0.3F, 0.02, 12))),
JUMP_WINGS,
new Touch("landing_splash", Material.PRISMARINE_SHARD, 1, Unlocks.UNDERWATER_PLACEMENT,
() -> new EventBurst(EventBurst.Trigger.LAND, 1, new Ring(new SimpleParticle(Particle.SPLASH, 0, 0, 0, 0, 3), 1, 16))),
new Touch("crash_landing", Material.FEATHER, 1, Unlocks.ABSTURZ_PLACEMENT,
() -> new EventBurst(EventBurst.Trigger.LAND, 3, new Group(new SimpleParticle(Particle.EXPLOSION, 0, 0, 0, 0, 1),
new Ring(new SimpleParticle(Particle.CRIT, 0, 0.1F, 0, 0.1, 2), 1.2, 16)))),
new Touch("lava_landing", Material.LAVA_BUCKET, 1, Unlocks.ANY_EVENT,
() -> new EventBurst(EventBurst.Trigger.LAND, 1, new Group(new SimpleParticle(Particle.LAVA, 0.2F, 0, 0.2F, 0, 4),
new SimpleParticle(Particle.SMOKE, 0.2F, 0.1F, 0.2F, 0.02, 6)))),
new Touch("obsidian_landing", Material.CRYING_OBSIDIAN, 1, Unlocks.EGG_HUNT_EXTREME,
() -> new EventBurst(EventBurst.Trigger.LAND, 1, new Group(new Ring(new SimpleParticle(Particle.WITCH, 0, 0.05F, 0, 0, 2), 1, 16),
new SimpleParticle(Particle.LANDING_OBSIDIAN_TEAR, 0.4F, 0, 0.4F, 0, 6)))),
new Touch("glyph_burst", Material.BOOK, 1, Unlocks.WARGEAR_SEASON,
() -> new EventBurst(EventBurst.Trigger.SNEAK, new Glyphs(Glyphs.Path.OUT_OF_PLAYER, 16, 1.5))),
// Footprints
new Touch("wet_footprints", Material.PRISMARINE_CRYSTALS, 4, Unlocks.ADVENT_WAR_SHIP_PLACEMENT,
() -> new Footprints(new SimpleParticle(Particle.FISHING, 0.05F, 0, 0.05F, 0, 1), 40)),
new Touch("cloud_footprints", Material.SNOW_BLOCK, 4, Unlocks.AIRSHIP,
() -> new Footprints(new SimpleParticle(Particle.CLOUD, 0.05F, 0, 0.05F, 0, 1), 40)),
new Touch("blossom_footprints", Material.SPORE_BLOSSOM, 4, Unlocks.EGG_HUNT_EASY,
() -> new Footprints(new SimpleParticle(Particle.SPORE_BLOSSOM_AIR, 0.05F, 0, 0.05F, 0, 1), 40)),
// Lines to nearby players with the same touch
new Touch("water_link", Material.WATER_BUCKET, 5, Unlocks.ANY_EVENT,
() -> new TeamLink("water_link", new SimpleParticle(Particle.DRIPPING_WATER, 0, 0, 0, 0, 1))),
new Touch("bubble_link", Material.PRISMARINE_BRICKS, 5, Unlocks.UNDERWATER,
() -> new TeamLink("bubble_link", new SimpleParticle(Particle.BUBBLE_POP, 0, 0, 0, 0, 1))),
// While standing still
new Touch("glyph_pull", Material.ENCHANTING_TABLE, 3, Unlocks.WARGEAR_SEASON_PLACEMENT,
() -> new NonMoving(new Glyphs(Glyphs.Path.INTO_PLAYER, 3, 1.5))),
new Touch("still_shimmer", Material.PRISMARINE_SLAB, 3, Unlocks.MINI_WARGEAR_LIGA,
() -> new NonMoving(new PulseShimmer(new SimpleParticle(Particle.UNDERWATER, 0, 0, 0, 0, 1), 80, 2))),
new Touch("heartbeat", Material.POPPY, 1, Unlocks.EGG_HUNT_HALF,
() -> new NonMoving(new HearthBeat(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(java.awt.Color.RED)), 80))),
// Seasonal
new Touch("december_snow", Material.POWDER_SNOW_BUCKET, 2, Unlocks.ADVENT_WAR_SHIP,
() -> new Seasonal(Month.DECEMBER, new LocationMutator(new SimpleParticle(Particle.SNOWFLAKE, 1.2F, 0.3F, 1.2F, 0.01, 2), 0, 2.4, 0)))
);
public List<ChoiceParameter.Choice<Touch>> choices() {
return ALL.stream().map(touch -> new ChoiceParameter.Choice<>(touch.id(), touch, false, touch.requirement())).toList();
}
}
@@ -33,8 +33,8 @@ public class Wing extends DelegatingParticleElement {
NONE,
FLAP_SLOW,
FLAP_FAST,
// One wing beat after each jump and none otherwise; only for a reward
JUMP_BEAT,
// One wing beat after each jump; unlocked by a reward
BEAT_ON_JUMP,
}
// Wings stand slightly open when idle and open further the faster the player moves
@@ -51,8 +51,6 @@ public class Wing extends DelegatingParticleElement {
// Share of the points shown each tick, in percent; below 100 the wing twinkles
private int density = 100;
private Motion motion = Motion.NONE;
// An extra beat after each jump on top of the motion
private boolean beatOnJump;
private double widest;
public Wing(ParticleElement particleElement, double size, WingDesign wingDesign) {
@@ -71,16 +69,8 @@ public class Wing extends DelegatingParticleElement {
}
}
public Wing(ParticleElement particleElement, double size, WingDesign wingDesign, boolean colorAlongWing, int density, Motion motion, boolean beatOnJump) {
this(particleElement, size, wingDesign, colorAlongWing, density, motion);
this.beatOnJump = beatOnJump;
}
@Override
public void tick(ParticleTickData particleTickData) {
if (motion == Motion.JUMP_BEAT && BodyMotion.ticksSinceJump(particleTickData.getPlayer()) > JUMP_BEAT_TICKS) {
return;
}
double ticks = particleTickData.getDeg() * 10;
double open = Math.toRadians(openDegrees(particleTickData, ticks));
double cos = Math.cos(open), sin = Math.sin(open);
@@ -115,13 +105,10 @@ public class Wing extends DelegatingParticleElement {
private double openDegrees(ParticleTickData particleTickData, double ticks) {
double speed = Math.min(1, BodyMotion.speed(particleTickData.getPlayer()) / FULL_OPEN_SPEED);
double open = IDLE_OPEN_DEGREES + (MOVING_OPEN_DEGREES - IDLE_OPEN_DEGREES) * speed;
if (beatOnJump) {
open += jumpBeat(particleTickData);
}
return switch (motion) {
case FLAP_SLOW -> open + FLAP_DEGREES * Math.sin(2 * Math.PI * ticks / 20);
case FLAP_FAST -> open + FLAP_DEGREES * Math.sin(2 * Math.PI * ticks / 10);
case JUMP_BEAT -> open + jumpBeat(particleTickData);
case BEAT_ON_JUMP -> open + jumpBeat(particleTickData);
default -> open;
};
}
@@ -30,21 +30,21 @@ import java.util.Deque;
import java.util.Map;
import java.util.WeakHashMap;
// Leaves a print every half step on the ground and keeps showing it for a while after the player walked on
// Leaves a print every given distance on the ground and keeps showing it for a while after the player walked on
public class Footprints extends DelegatingParticleElement {
private static final double STEP = 0.6;
private record Print(Location location, int age) {
}
private final int lifetime;
private final double step;
// Weak keys drop players once they quit
private final Map<Player, Deque<Print>> prints = new WeakHashMap<>();
public Footprints(ParticleElement particleElement, int lifetime) {
public Footprints(ParticleElement particleElement, int lifetime, double step) {
super(particleElement);
this.lifetime = lifetime;
this.step = step;
}
@Override
@@ -54,7 +54,7 @@ public class Footprints extends DelegatingParticleElement {
Deque<Print> playerPrints = prints.computeIfAbsent(player, key -> new ArrayDeque<>());
Location feet = player.getLocation();
Print last = playerPrints.peekLast();
if (player.isOnGround() && (last == null || last.location().getWorld() != feet.getWorld() || last.location().distanceSquared(feet) > STEP * STEP)) {
if (player.isOnGround() && (last == null || last.location().getWorld() != feet.getWorld() || last.location().distanceSquared(feet) > step * step)) {
playerPrints.addLast(new Print(feet, 0));
}
@@ -1,72 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.lobby.particle.elements.touch;
import de.steamwar.lobby.particle.ParticleElement;
import de.steamwar.lobby.particle.ParticleTickData;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.util.Vector;
// Enchanting glyphs that fly along a path. Spawned with an amount of 0, the offset becomes the glyph's motion: it
// appears at location + offset and flies back to the location, like glyphs flying into an enchanting table.
public class Glyphs implements ParticleElement {
public enum Path {
// From random points on the ground around the player into the chest
INTO_PLAYER,
// From the chest outwards to a ring around the player
OUT_OF_PLAYER,
}
private static final double CHEST = 1.2;
private final Path path;
private final int amount;
private final double radius;
public Glyphs(Path path, int amount, double radius) {
this.path = path;
this.amount = amount;
this.radius = radius;
}
@Override
public void tick(ParticleTickData particleTickData) {
Location chest = particleTickData.getPlayer().getLocation().add(0, CHEST, 0);
for (int i = 0; i < amount; i++) {
Location target;
Vector from;
if (path == Path.INTO_PLAYER) {
double angle = RANDOM.nextDouble() * 2 * Math.PI;
double distance = radius * (0.5 + RANDOM.nextDouble() / 2);
target = chest;
from = new Vector(Math.cos(angle) * distance, -CHEST, Math.sin(angle) * distance);
} else {
double angle = 2 * Math.PI * i / amount;
Vector out = new Vector(Math.cos(angle) * radius, 0, Math.sin(angle) * radius);
target = chest.clone().add(out);
from = out.multiply(-1);
}
Location spawn = target;
display(particleTickData, player -> player.spawnParticle(Particle.ENCHANT, spawn, 0, from.getX(), from.getY(), from.getZ(), 1));
}
}
}
@@ -1,45 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.lobby.particle.elements.touch;
import de.steamwar.lobby.particle.ParticleElement;
import de.steamwar.lobby.particle.ParticleTickData;
import de.steamwar.lobby.particle.elements.DelegatingParticleElement;
import java.time.LocalDate;
import java.time.Month;
// Only shows during the given month
public class Seasonal extends DelegatingParticleElement {
private final Month month;
public Seasonal(Month month, ParticleElement particleElement) {
super(particleElement);
this.month = month;
}
@Override
public void tick(ParticleTickData particleTickData) {
if (LocalDate.now().getMonth() == month) {
particleElement.tick(particleTickData);
}
}
}
@@ -34,18 +34,19 @@ import java.util.WeakHashMap;
// A line of particles between nearby players who show the same reward. Each pair is drawn by only one of them.
public class TeamLink extends DelegatingParticleElement {
private static final double RANGE = 8;
private static final double SPACING = 0.5;
public static final double RANGE = 8;
private static final double CHEST = 1.2;
// Per reward, who showed it in which tick
private static final Map<String, Map<Player, Long>> SHOWING = new HashMap<>();
private final String reward;
private final double spacing;
public TeamLink(String reward, ParticleElement particleElement) {
public TeamLink(String reward, ParticleElement particleElement, double spacing) {
super(particleElement);
this.reward = reward;
this.spacing = spacing;
}
@Override
@@ -64,7 +65,7 @@ public class TeamLink extends DelegatingParticleElement {
if (to.getWorld() != from.getWorld() || to.distanceSquared(from) > RANGE * RANGE) continue;
Vector step = to.toVector().subtract(from.toVector());
int points = (int) Math.ceil(step.length() / SPACING);
int points = (int) Math.ceil(step.length() / spacing);
step.multiply(1.0 / points);
for (int i = 1; i < points; i++) {
particleElement.tick(particleTickData.withLocation(from.clone().add(step.clone().multiply(i))));
@@ -119,21 +119,20 @@ class EntryEditor {
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING, new SWItem(design.getIcon(), designName(design, player), List.of(msg("PARTICLE_VALUE_CLICK", player)), false,
clickType -> Pickers.design(player, design, picked -> changeDesign(context, picked), context::reopen)));
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 1, parameterItem(context, TOUCH));
if (design.usesEntryParticle()) {
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 2, parameterItem(context, PARTICLE));
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 1, parameterItem(context, PARTICLE));
ParticleType type = entry.getValues().get(PARTICLE);
if (type.usesColors()) {
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 3, parameterItem(context, COLORS));
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 2, parameterItem(context, COLORS));
} else if (type.usesBlock()) {
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 3, parameterItem(context, BLOCK));
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 2, parameterItem(context, BLOCK));
}
// The trail target follows the particle; its duration goes to the end of the particle row
List<Parameter<?>> particleRow = new ArrayList<>(design.replacesAmount(entry.getValues()) ? List.of(SPEED, SPREAD_X, SPREAD_Y, SPREAD_Z) : List.of(COUNT, SPEED, SPREAD_X, SPREAD_Y, SPREAD_Z));
if (type.isTrail()) {
List<Parameter<?>> target = List.of(TRAIL_TARGET_X, TRAIL_TARGET_Y, TRAIL_TARGET_Z);
for (int i = 0; i < target.size(); i++) {
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 4 + i, parameterItem(context, target.get(i)));
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 3 + i, parameterItem(context, target.get(i)));
}
particleRow.add(TRAIL_DURATION);
}
@@ -190,7 +189,6 @@ class EntryEditor {
if (parameter == COLORS) icon = Material.RED_DYE;
if (parameter == BLOCK) icon = (Material) value;
if (value instanceof WingShape shape) icon = shape.getIcon();
if (value instanceof Touch touch) icon = touch.icon();
return new SWItem(icon, name(parameter.getNameKey(), player), lore, false, clickType -> {
if (parameter instanceof BooleanParameter toggle) {
@@ -258,7 +256,6 @@ class EntryEditor {
private Material choiceIcon(Object value) {
if (value instanceof ParticleType type) return type.getIcon();
if (value instanceof WingShape shape) return shape.getIcon();
if (value instanceof Touch touch) return touch.icon();
if (value instanceof Material material) return material;
return Material.LEVER;
}
@@ -107,7 +107,7 @@ class ParticleGuiText {
String name = requirement.getRequirementName(player);
if (name != null) {
lore.add(msg("PARTICLE_UNLOCKED_BY", player));
lore.add(name);
lore.addAll(List.of(name.split("\n")));
}
return lore;
}
@@ -120,9 +120,6 @@ class ParticleGuiText {
if (entry.getDesign().usesEntryParticle()) {
lore.add(msg("PARTICLE_ENTRY_PARTICLE", player, particleName(values.get(PARTICLE), player)));
}
if (values.get(TOUCH) != Touches.NONE) {
lore.add(msg("PARTICLE_ENTRY_TOUCH", player, choiceName(TOUCH, values.get(TOUCH), player)));
}
lore.add("§8- " + choiceName(TICK_MODE, values.get(TICK_MODE), player));
addCondition(lore, MOVEMENT, values.get(MOVEMENT), Movement.ANY, player);
addCondition(lore, GLIDING, values.get(GLIDING), Gliding.ANY, player);
@@ -99,9 +99,7 @@ public class ParticleEntry {
element = values.get(VISIBILITY).wrap(element);
int interval = values.get(INTERVAL);
if (interval > 1 && design.absorbsTouch(values.get(TOUCH))) {
// The jump beat needs every tick to look smooth; outside of it the usual interval applies, and the
// extra redraws are not counted towards the budget
if (interval > 1 && design.redrawsAfterJump(values)) {
element = new AfterJump(element, new Delayed(element, interval), Wing.JUMP_BEAT_TICKS);
} else if (interval > 1) {
element = new Delayed(element, interval);
@@ -174,30 +172,12 @@ public class ParticleEntry {
return new LocationMutator(element, offsetX, offsetY, offsetZ);
}
// The entry's touch with its placement and visibility, or null; touches ignore conditions and timing
public ParticleElement buildTouch() {
if (design.absorbsTouch(values.get(TOUCH))) {
return null;
}
ParticleElement touch = values.get(TOUCH).element().get();
if (touch == null) {
return null;
}
if (values.get(PLACEMENT) == Placement.BODY) {
touch = new BodyRelative(touch);
}
return new Always(values.get(VISIBILITY).wrap(touch));
}
// Shown only when the design, its chosen options, the touch and, for designs that use it, the particle type are unlocked
// Shown only when the design, its chosen options and, for designs that use it, the particle type are unlocked
@SuppressWarnings("unchecked")
public boolean isUnlocked(UnlockContext context) {
if (!design.getRequirement().test(context)) {
return false;
}
if (!TOUCH.requirement(values.get(TOUCH)).test(context)) {
return false;
}
for (Parameter<?> parameter : design.getParameters()) {
if (parameter instanceof ChoiceParameter<?> choice && !((ChoiceParameter<Object>) choice).requirement(values.get(choice)).test(context)) {
return false;
@@ -214,8 +194,7 @@ public class ParticleEntry {
// Particle positions spawned per tick, used for the slot budget
public double cost() {
double perTick = design.usesEntryParticle() ? (double) design.points(values) * amount() : design.points(values);
int touchPoints = design.absorbsTouch(values.get(TOUCH)) ? 0 : values.get(TOUCH).points();
return perTick / values.get(INTERVAL) + touchPoints;
return perTick / values.get(INTERVAL);
}
public JsonObject toJson() {