forked from SteamWar/SteamWar
Add configurable particle slot model
Introduce parameters, particle types, designs and entries that build the existing particle elements, store each of the 7 particle slots in its own UserConfig key, and let LobbyPlayer select a slot next to the old enum particles. Slots 4-5 need a team, 6-7 the server team.
This commit is contained in:
@@ -19,6 +19,9 @@
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import de.steamwar.lobby.particle.slot.ParticleEntry;
|
||||
import de.steamwar.lobby.particle.slot.ParticleSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -35,6 +38,17 @@ public class CompiledSet {
|
||||
return compiledSet;
|
||||
}
|
||||
|
||||
// Entries whose design is not unlocked are skipped, not removed
|
||||
public static CompiledSet of(ParticleSet particleSet, UnlockContext context) {
|
||||
CompiledSet compiledSet = new CompiledSet();
|
||||
for (ParticleEntry entry : particleSet.getEntries()) {
|
||||
if (entry.getDesign().getRequirement().test(context)) {
|
||||
compiledSet.add(entry.build());
|
||||
}
|
||||
}
|
||||
return compiledSet;
|
||||
}
|
||||
|
||||
public void add(ParticleElement particleElement) {
|
||||
if (particleElement.tickType() == ParticleTickType.ALWAYS) {
|
||||
always.add(particleElement);
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
@@ -94,6 +95,12 @@ public class ParticleListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
// Ranks may have changed since the last session, which can lock or unlock the active slot
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
LobbyPlayer.getLobbyPlayer(event.getPlayer()).recompile();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
movingPlayers.remove(event.getPlayer());
|
||||
|
||||
@@ -41,6 +41,10 @@ public interface ParticleRequirement {
|
||||
|
||||
boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig);
|
||||
|
||||
default boolean test(UnlockContext context) {
|
||||
return test(context.getUser(), context.getEvents(), context.getEggHuntConfig());
|
||||
}
|
||||
|
||||
ParticleRequirement SERVER_TEAM = new ParticleRequirement() {
|
||||
@Override
|
||||
public String getRequirementName(Player player) {
|
||||
@@ -51,6 +55,11 @@ public interface ParticleRequirement {
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return user.hasPerm(UserPerm.TEAM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(UnlockContext context) {
|
||||
return context.getUser().hasPerm(UserPerm.TEAM);
|
||||
}
|
||||
};
|
||||
|
||||
ParticleRequirement NO_REQUIRMENT = new ParticleRequirement() {
|
||||
@@ -63,6 +72,29 @@ public interface ParticleRequirement {
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(UnlockContext context) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Unlike HAS_TEAM this does not also pass for the server team
|
||||
ParticleRequirement TEAM_MEMBER = new ParticleRequirement() {
|
||||
@Override
|
||||
public String getRequirementName(Player player) {
|
||||
return LobbySystem.getMessage().parse("PARTICLE_UNLOCKED_BY_TEAM", player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return user.getTeam() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(UnlockContext context) {
|
||||
return context.getUser().getTeam() != 0;
|
||||
}
|
||||
};
|
||||
ParticleRequirement HAS_TEAM = new ParticleRequirement() {
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import de.steamwar.lobby.special.easter.EggHunt;
|
||||
import de.steamwar.sql.Event;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.TeamTeilnahme;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// Everything requirements check against, loaded only when a requirement needs it
|
||||
public class UnlockContext {
|
||||
|
||||
@Getter
|
||||
private final SteamwarUser user;
|
||||
private Set<Integer> events;
|
||||
private String eggHuntConfig;
|
||||
private boolean eggHuntConfigLoaded;
|
||||
|
||||
public UnlockContext(SteamwarUser user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Set<Integer> getEvents() {
|
||||
if (events == null) {
|
||||
events = user.getTeam() == 0 ? Set.of() : TeamTeilnahme.getEvents(user.getTeam()).stream().map(Event::getEventID).collect(Collectors.toSet());
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public String getEggHuntConfig() {
|
||||
if (!eggHuntConfigLoaded) {
|
||||
eggHuntConfig = UserConfig.getConfig(user.getId(), EggHunt.EGG_HUNT_CONFIG_KEY);
|
||||
eggHuntConfigLoaded = true;
|
||||
}
|
||||
return eggHuntConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
public class BooleanParameter extends Parameter<Boolean> {
|
||||
|
||||
public BooleanParameter(String key, String nameKey, boolean defaultValue) {
|
||||
super(key, nameKey, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(Boolean value) {
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean read(JsonElement json) {
|
||||
return json.getAsBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Getter
|
||||
public class ChoiceParameter<E> extends Parameter<E> {
|
||||
|
||||
// Internal choices are usable by presets but never offered in the builder or accepted from storage
|
||||
public record Choice<E>(String id, E value, boolean internal) {
|
||||
}
|
||||
|
||||
private final List<Choice<E>> choices;
|
||||
|
||||
public ChoiceParameter(String key, String nameKey, E defaultValue, List<Choice<E>> choices) {
|
||||
super(key, nameKey, defaultValue);
|
||||
this.choices = choices;
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> ChoiceParameter<E> ofEnum(String key, String nameKey, E defaultValue, Predicate<E> internal) {
|
||||
List<Choice<E>> choices = new ArrayList<>();
|
||||
for (E value : defaultValue.getDeclaringClass().getEnumConstants()) {
|
||||
choices.add(new Choice<>(value.name(), value, internal.test(value)));
|
||||
}
|
||||
return new ChoiceParameter<>(key, nameKey, defaultValue, choices);
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> ChoiceParameter<E> ofEnum(String key, String nameKey, E defaultValue) {
|
||||
return ofEnum(key, nameKey, defaultValue, value -> false);
|
||||
}
|
||||
|
||||
public List<E> getBuildableValues() {
|
||||
return choices.stream().filter(choice -> !choice.internal()).map(Choice::value).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(E value) {
|
||||
return new JsonPrimitive(choiceOf(value).id());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected E read(JsonElement json) {
|
||||
String id = json.getAsString();
|
||||
return choices.stream()
|
||||
.filter(choice -> !choice.internal() && choice.id().equals(id))
|
||||
.map(Choice::value)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E validate(E value) {
|
||||
return choices.stream().anyMatch(choice -> choice.value() == value) ? value : getDefaultValue();
|
||||
}
|
||||
|
||||
private Choice<E> choiceOf(E value) {
|
||||
return choices.stream()
|
||||
.filter(choice -> choice.value() == value)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown choice for " + getKey()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public enum DesignVisibility {
|
||||
// Offered in the builder to everyone
|
||||
BASIC,
|
||||
// Offered in the builder, usable once its requirement is met
|
||||
REWARD,
|
||||
// Only used by presets, never offered in the builder or accepted from storage
|
||||
INTERNAL,
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.WingDesign;
|
||||
import de.steamwar.lobby.particle.elements.Circle;
|
||||
import de.steamwar.lobby.particle.elements.Cloud;
|
||||
import de.steamwar.lobby.particle.elements.Wing;
|
||||
import de.steamwar.lobby.particle.elements.custom.YOffset;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@UtilityClass
|
||||
public class Designs {
|
||||
|
||||
private final Map<String, ParticleDesign> DESIGNS = new LinkedHashMap<>();
|
||||
|
||||
public final ParticleDesign SIMPLE = register(new ParticleDesign("simple", Material.BLAZE_POWDER, "PARTICLE_DESIGN_SIMPLE") {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
return particle;
|
||||
}
|
||||
});
|
||||
|
||||
public final DoubleParameter CIRCLE_RADIUS = new DoubleParameter("radius", "PARTICLE_PARAM_RADIUS", 1, 0.2, 3, 0.1);
|
||||
public final DoubleParameter CIRCLE_SPEED = new DoubleParameter("circleSpeed", "PARTICLE_PARAM_CIRCLE_SPEED", 1, -20, 20, 0.5);
|
||||
public final DoubleParameter CIRCLE_PHASE = new DoubleParameter("phase", "PARTICLE_PARAM_PHASE", 0, 0, 345, 15);
|
||||
public final DoubleParameter CIRCLE_RISE = new DoubleParameter("rise", "PARTICLE_PARAM_RISE", 0, 0, 3, 0.1);
|
||||
public final DoubleParameter CIRCLE_RISE_SPEED = new DoubleParameter("riseSpeed", "PARTICLE_PARAM_RISE_SPEED", 20, 0, 100, 5);
|
||||
public final BooleanParameter CIRCLE_BOUNCE = new BooleanParameter("bounce", "PARTICLE_PARAM_BOUNCE", true);
|
||||
public final BooleanParameter CIRCLE_INVERTED = new BooleanParameter("inverted", "PARTICLE_PARAM_INVERTED", false);
|
||||
|
||||
public final ParticleDesign CIRCLE = register(new ParticleDesign("circle", Material.MAGMA_CREAM, "PARTICLE_DESIGN_CIRCLE",
|
||||
CIRCLE_RADIUS, CIRCLE_SPEED, CIRCLE_PHASE, CIRCLE_RISE, CIRCLE_RISE_SPEED, CIRCLE_BOUNCE, CIRCLE_INVERTED) {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
double rise = values.get(CIRCLE_RISE);
|
||||
if (rise > 0) {
|
||||
particle = new YOffset(particle, values.get(CIRCLE_RISE_SPEED), 0, rise, values.get(CIRCLE_INVERTED), values.get(CIRCLE_BOUNCE));
|
||||
}
|
||||
return new Circle(particle, values.get(CIRCLE_RADIUS), values.get(CIRCLE_SPEED), values.get(CIRCLE_PHASE));
|
||||
}
|
||||
});
|
||||
|
||||
public final ChoiceParameter<WingDesign> WING_IMAGE = new ChoiceParameter<>("image", "PARTICLE_PARAM_WING_IMAGE", WingDesign.SIMPLE, List.of(
|
||||
new ChoiceParameter.Choice<>("simple", WingDesign.SIMPLE, false),
|
||||
new ChoiceParameter.Choice<>("complex", WingDesign.COMPLEX, false),
|
||||
new ChoiceParameter.Choice<>("sword", WingDesign.SWORD, false),
|
||||
new ChoiceParameter.Choice<>("sw", WingDesign.SW, true),
|
||||
new ChoiceParameter.Choice<>("wgs", WingDesign.WGS, true),
|
||||
new ChoiceParameter.Choice<>("sword_crossed", WingDesign.SWORD_CROSSED, true),
|
||||
new ChoiceParameter.Choice<>("mwgl", WingDesign.MWGL, true),
|
||||
new ChoiceParameter.Choice<>("eclipse", WingDesign.ECLIPSE, true),
|
||||
new ChoiceParameter.Choice<>("pl", WingDesign.PL, true),
|
||||
new ChoiceParameter.Choice<>("plompa_easter", WingDesign.PlompaEasterWings, true),
|
||||
new ChoiceParameter.Choice<>("ely_e", WingDesign.ELY_E, true),
|
||||
new ChoiceParameter.Choice<>("ely_l", WingDesign.ELY_L, true),
|
||||
new ChoiceParameter.Choice<>("ely_y", WingDesign.ELY_Y, true),
|
||||
new ChoiceParameter.Choice<>("ev", WingDesign.EV, true)
|
||||
));
|
||||
public final DoubleParameter WING_SIZE = new DoubleParameter("size", "PARTICLE_PARAM_WING_SIZE", 0.15, 0.05, 0.3, 0.01);
|
||||
|
||||
public final ParticleDesign WING = register(new ParticleDesign("wing", Material.ELYTRA, "PARTICLE_DESIGN_WING", WING_IMAGE, WING_SIZE) {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
return new Wing(particle, values.get(WING_SIZE), values.get(WING_IMAGE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int points(ParameterValues values) {
|
||||
return values.get(WING_IMAGE).getVectors().length;
|
||||
}
|
||||
});
|
||||
|
||||
public final ParticleDesign CLOUD = register(new ParticleDesign("cloud", Material.WHITE_WOOL, "PARTICLE_DESIGN_CLOUD") {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
return new Cloud(particle);
|
||||
}
|
||||
});
|
||||
|
||||
private ParticleDesign register(ParticleDesign design) {
|
||||
if (DESIGNS.put(design.getId(), design) != null) {
|
||||
throw new IllegalStateException("Duplicate particle design " + design.getId());
|
||||
}
|
||||
return design;
|
||||
}
|
||||
|
||||
public ParticleDesign byId(String id) {
|
||||
return DESIGNS.get(id);
|
||||
}
|
||||
|
||||
public List<ParticleDesign> all() {
|
||||
return List.copyOf(DESIGNS.values());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class DoubleParameter extends Parameter<Double> {
|
||||
|
||||
private final double min;
|
||||
private final double max;
|
||||
private final double step;
|
||||
|
||||
public DoubleParameter(String key, String nameKey, double defaultValue, double min, double max, double step) {
|
||||
super(key, nameKey, defaultValue);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(Double value) {
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Double read(JsonElement json) {
|
||||
return json.getAsDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double validate(Double value) {
|
||||
if (value.isNaN()) {
|
||||
return getDefaultValue();
|
||||
}
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.elements.*;
|
||||
import de.steamwar.lobby.particle.elements.custom.NonMoving;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
// Parameters every entry has, independent of its design
|
||||
@UtilityClass
|
||||
public class EntryParameters {
|
||||
|
||||
public enum TickMode {
|
||||
MOVE,
|
||||
ALWAYS,
|
||||
}
|
||||
|
||||
public enum Movement {
|
||||
ANY(element -> element),
|
||||
MOVING(element -> data -> {
|
||||
if (data.isMoving()) element.tick(data);
|
||||
}),
|
||||
STANDING_STILL(NonMoving::new),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Movement(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Gliding {
|
||||
ANY(element -> element),
|
||||
GLIDING(Flying::new),
|
||||
NOT_GLIDING(NonFlying::new),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Gliding(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Ground {
|
||||
ANY(element -> element),
|
||||
ON_GROUND(Floor::new),
|
||||
IN_AIR(NonFloor::new),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Ground(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Sneak {
|
||||
ANY(element -> element),
|
||||
SNEAKING(Sneaking::new),
|
||||
NOT_SNEAKING(element -> data -> {
|
||||
if (!data.getPlayer().isSneaking()) element.tick(data);
|
||||
}),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Sneak(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Visibility {
|
||||
EVERYONE(element -> element),
|
||||
ONLY_SELF(OnlySelf::new),
|
||||
ONLY_OTHERS(OnlyOthers::new),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Visibility(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public final ChoiceParameter<ParticleType> PARTICLE = ChoiceParameter.ofEnum("particle", "PARTICLE_PARAM_PARTICLE", ParticleType.FLAME, type -> !type.isBuildable());
|
||||
public final IntParameter COUNT = new IntParameter("count", "PARTICLE_PARAM_COUNT", 5, 1, 20);
|
||||
public final DoubleParameter SPREAD_X = new DoubleParameter("spreadX", "PARTICLE_PARAM_SPREAD_X", 0, 0, 2, 0.05);
|
||||
public final DoubleParameter SPREAD_Y = new DoubleParameter("spreadY", "PARTICLE_PARAM_SPREAD_Y", 0, 0, 2, 0.05);
|
||||
public final DoubleParameter SPREAD_Z = new DoubleParameter("spreadZ", "PARTICLE_PARAM_SPREAD_Z", 0, 0, 2, 0.05);
|
||||
public final DoubleParameter SPEED = new DoubleParameter("speed", "PARTICLE_PARAM_SPEED", 0.01, 0, 1, 0.01);
|
||||
public final GradientParameter COLORS = new GradientParameter("colors", "PARTICLE_PARAM_COLORS", 8);
|
||||
public final DoubleParameter OFFSET_X = new DoubleParameter("offsetX", "PARTICLE_PARAM_OFFSET_X", 0, -3, 3, 0.1);
|
||||
public final DoubleParameter OFFSET_Y = new DoubleParameter("offsetY", "PARTICLE_PARAM_OFFSET_Y", 0, -3, 3, 0.1);
|
||||
public final DoubleParameter OFFSET_Z = new DoubleParameter("offsetZ", "PARTICLE_PARAM_OFFSET_Z", 0, -3, 3, 0.1);
|
||||
public final IntParameter INTERVAL = new IntParameter("interval", "PARTICLE_PARAM_INTERVAL", 1, 1, 100);
|
||||
public final ChoiceParameter<TickMode> TICK_MODE = ChoiceParameter.ofEnum("tickMode", "PARTICLE_PARAM_TICK_MODE", TickMode.MOVE);
|
||||
public final ChoiceParameter<Movement> MOVEMENT = ChoiceParameter.ofEnum("movement", "PARTICLE_PARAM_MOVEMENT", Movement.ANY);
|
||||
public final ChoiceParameter<Gliding> GLIDING = ChoiceParameter.ofEnum("gliding", "PARTICLE_PARAM_GLIDING", Gliding.ANY);
|
||||
public final ChoiceParameter<Ground> GROUND = ChoiceParameter.ofEnum("ground", "PARTICLE_PARAM_GROUND", Ground.ANY);
|
||||
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 List<Parameter<?>> ALL = List.of(
|
||||
PARTICLE, COUNT, SPREAD_X, SPREAD_Y, SPREAD_Z, SPEED, COLORS,
|
||||
OFFSET_X, OFFSET_Y, OFFSET_Z, INTERVAL, TICK_MODE,
|
||||
MOVEMENT, GLIDING, GROUND, SNEAK, VISIBILITY
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// An empty gradient means a random colour per spawn
|
||||
@Getter
|
||||
public class GradientParameter extends Parameter<List<Color>> {
|
||||
|
||||
private final int maxColors;
|
||||
|
||||
public GradientParameter(String key, String nameKey, int maxColors) {
|
||||
super(key, nameKey, List.of());
|
||||
this.maxColors = maxColors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(List<Color> value) {
|
||||
JsonArray array = new JsonArray();
|
||||
value.forEach(color -> array.add(color.getRGB() & 0xFFFFFF));
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Color> read(JsonElement json) {
|
||||
List<Color> colors = new ArrayList<>();
|
||||
for (JsonElement element : json.getAsJsonArray()) {
|
||||
colors.add(new Color(element.getAsInt() & 0xFFFFFF));
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Color> validate(List<Color> value) {
|
||||
return List.copyOf(value.size() > maxColors ? value.subList(0, maxColors) : value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class IntParameter extends Parameter<Integer> {
|
||||
|
||||
private final int min;
|
||||
private final int max;
|
||||
|
||||
public IntParameter(String key, String nameKey, int defaultValue, int min, int max) {
|
||||
super(key, nameKey, defaultValue);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(Integer value) {
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer read(JsonElement json) {
|
||||
return json.getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer validate(Integer value) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonElement;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public abstract class Parameter<T> {
|
||||
|
||||
private final String key;
|
||||
private final String nameKey;
|
||||
private final T defaultValue;
|
||||
|
||||
protected Parameter(String key, String nameKey, T defaultValue) {
|
||||
this.key = key;
|
||||
this.nameKey = nameKey;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public abstract JsonElement toJson(T value);
|
||||
|
||||
// Returns null when the stored value cannot be used
|
||||
protected abstract T read(JsonElement json);
|
||||
|
||||
public T fromJson(JsonElement json) {
|
||||
if (json == null || json.isJsonNull()) {
|
||||
return defaultValue;
|
||||
}
|
||||
try {
|
||||
T value = read(json);
|
||||
return value == null ? defaultValue : validate(value);
|
||||
} catch (RuntimeException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public T validate(T value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ParameterValues {
|
||||
|
||||
private final Map<String, Object> values = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(Parameter<T> parameter) {
|
||||
Object value = values.get(parameter.getKey());
|
||||
return value == null ? parameter.getDefaultValue() : (T) value;
|
||||
}
|
||||
|
||||
public <T> ParameterValues set(Parameter<T> parameter, T value) {
|
||||
values.put(parameter.getKey(), parameter.validate(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterValues copy() {
|
||||
ParameterValues copy = new ParameterValues();
|
||||
copy.values.putAll(values);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public JsonObject toJson(List<Parameter<?>> parameters) {
|
||||
JsonObject json = new JsonObject();
|
||||
parameters.forEach(parameter -> write(json, parameter));
|
||||
return json;
|
||||
}
|
||||
|
||||
public static ParameterValues fromJson(JsonObject json, List<Parameter<?>> parameters) {
|
||||
ParameterValues values = new ParameterValues();
|
||||
parameters.forEach(parameter -> values.values.put(parameter.getKey(), parameter.fromJson(json.get(parameter.getKey()))));
|
||||
return values;
|
||||
}
|
||||
|
||||
private <T> void write(JsonObject json, Parameter<T> parameter) {
|
||||
json.add(parameter.getKey(), parameter.toJson(get(parameter)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public abstract class ParticleDesign {
|
||||
|
||||
private final String id;
|
||||
private final Material icon;
|
||||
private final String nameKey;
|
||||
private final DesignVisibility visibility;
|
||||
private final ParticleRequirement requirement;
|
||||
private final List<Parameter<?>> parameters;
|
||||
|
||||
protected ParticleDesign(String id, Material icon, String nameKey, DesignVisibility visibility, ParticleRequirement requirement, Parameter<?>... parameters) {
|
||||
this.id = id;
|
||||
this.icon = icon;
|
||||
this.nameKey = nameKey;
|
||||
this.visibility = visibility;
|
||||
this.requirement = requirement;
|
||||
this.parameters = List.of(parameters);
|
||||
}
|
||||
|
||||
protected ParticleDesign(String id, Material icon, String nameKey, Parameter<?>... parameters) {
|
||||
this(id, icon, nameKey, DesignVisibility.BASIC, ParticleRequirement.NO_REQUIRMENT, parameters);
|
||||
}
|
||||
|
||||
// Wraps the entry's particle into this design's shape
|
||||
public abstract ParticleElement build(ParameterValues values, ParticleElement particle);
|
||||
|
||||
// Number of particle positions this design spawns per tick
|
||||
public int points(ParameterValues values) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
|
||||
// Particles that need extra data (BLOCK, ITEM, VIBRATION, DUST_COLOR_TRANSITION) or disturb other players are left out
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ParticleType {
|
||||
|
||||
DUST(Material.REDSTONE, Particle.DUST, true),
|
||||
FLAME(Material.FLINT_AND_STEEL, Particle.FLAME, true),
|
||||
SOUL(Material.ZOMBIE_HEAD, Particle.SOUL, true),
|
||||
SMOKE(Material.COBWEB, Particle.SMOKE, true),
|
||||
LARGE_SMOKE(Material.GRAY_WOOL, Particle.LARGE_SMOKE, true),
|
||||
CAMPFIRE_COSY_SMOKE(Material.CAMPFIRE, Particle.CAMPFIRE_COSY_SMOKE, true),
|
||||
CLOUD(Material.WHITE_WOOL, Particle.CLOUD, true),
|
||||
SNEEZE(Material.SLIME_BLOCK, Particle.SNEEZE, true),
|
||||
DRIPPING_LAVA(Material.LAVA_BUCKET, Particle.DRIPPING_LAVA, true),
|
||||
DRIPPING_WATER(Material.WATER_BUCKET, Particle.DRIPPING_WATER, true),
|
||||
DRIPPING_HONEY(Material.HONEY_BOTTLE, Particle.DRIPPING_HONEY, true),
|
||||
FALLING_HONEY(Material.HONEY_BLOCK, Particle.FALLING_HONEY, true),
|
||||
FALLING_NECTAR(Material.HONEYCOMB, Particle.FALLING_NECTAR, true),
|
||||
FALLING_SPORE_BLOSSOM(Material.SPORE_BLOSSOM, Particle.FALLING_SPORE_BLOSSOM, true),
|
||||
FALLING_OBSIDIAN_TEAR(Material.CRYING_OBSIDIAN, Particle.FALLING_OBSIDIAN_TEAR, true),
|
||||
RAIN(Material.CYAN_DYE, Particle.RAIN, true),
|
||||
UNDERWATER(Material.PRISMARINE_SHARD, Particle.UNDERWATER, true),
|
||||
BUBBLE_POP(Material.TUBE_CORAL, Particle.BUBBLE_POP, true),
|
||||
DOLPHIN(Material.BLUE_DYE, Particle.DOLPHIN, true),
|
||||
NAUTILUS(Material.NAUTILUS_SHELL, Particle.NAUTILUS, true),
|
||||
SQUID_INK(Material.INK_SAC, Particle.SQUID_INK, true),
|
||||
GLOW_SQUID_INK(Material.GLOW_INK_SAC, Particle.GLOW_SQUID_INK, true),
|
||||
SNOWFLAKE(Material.SNOWBALL, Particle.SNOWFLAKE, true),
|
||||
ITEM_SNOWBALL(Material.SNOW_BLOCK, Particle.ITEM_SNOWBALL, true),
|
||||
ITEM_SLIME(Material.SLIME_BALL, Particle.ITEM_SLIME, true),
|
||||
HEART(Material.RED_DYE, Particle.HEART, true),
|
||||
NOTE(Material.NOTE_BLOCK, Particle.NOTE, true),
|
||||
ANGRY_VILLAGER(Material.REDSTONE_BLOCK, Particle.ANGRY_VILLAGER, true),
|
||||
DAMAGE_INDICATOR(Material.SPIDER_EYE, Particle.DAMAGE_INDICATOR, true),
|
||||
CRIT(Material.CAULDRON, Particle.CRIT, true),
|
||||
ENCHANTED_HIT(Material.GLOWSTONE_DUST, Particle.ENCHANTED_HIT, true),
|
||||
ENCHANT(Material.ENCHANTING_TABLE, Particle.ENCHANT, true),
|
||||
WITCH(Material.EXPERIENCE_BOTTLE, Particle.WITCH, true),
|
||||
END_ROD(Material.END_ROD, Particle.END_ROD, true),
|
||||
DRAGON_BREATH(Material.DRAGON_BREATH, Particle.DRAGON_BREATH, true),
|
||||
FIREWORK(Material.FIRE_CHARGE, Particle.FIREWORK, true),
|
||||
TOTEM_OF_UNDYING(Material.TOTEM_OF_UNDYING, Particle.TOTEM_OF_UNDYING, true),
|
||||
RAID_OMEN(Material.FIREWORK_STAR, Particle.RAID_OMEN, true),
|
||||
EXPLOSION(Material.TNT, Particle.EXPLOSION, false),
|
||||
;
|
||||
|
||||
private final Material icon;
|
||||
private final Particle particle;
|
||||
private final boolean buildable;
|
||||
|
||||
public String getNameKey() {
|
||||
return "PARTICLE_TYPE_" + name();
|
||||
}
|
||||
|
||||
public boolean isDust() {
|
||||
return particle == Particle.DUST;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ public class Circle extends DelegatingParticleElement {
|
||||
|
||||
private double distance;
|
||||
private double speed;
|
||||
private double phase;
|
||||
|
||||
public Circle(ParticleElement particleElement) {
|
||||
super(particleElement);
|
||||
@@ -41,6 +42,11 @@ public class Circle extends DelegatingParticleElement {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public Circle(ParticleElement particleElement, double distance, double speed, double phaseDegrees) {
|
||||
this(particleElement, distance, speed);
|
||||
this.phase = Math.toRadians(phaseDegrees);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String attribute() {
|
||||
return "PARTICLE_ATTRIBUTE_CIRCLE";
|
||||
@@ -51,7 +57,7 @@ public class Circle extends DelegatingParticleElement {
|
||||
Location location = particleTickData.getLocation();
|
||||
|
||||
Vector vector = new Vector(distance, 0, 0);
|
||||
vector.rotateAroundY((particleTickData.getDeg() * speed) % 360);
|
||||
vector.rotateAroundY((particleTickData.getDeg() * speed) % 360 + phase);
|
||||
ParticleTickData nParticleTickData = particleTickData.withLocation(location.clone().add(vector));
|
||||
particleElement.tick(nParticleTickData);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.slot;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import de.steamwar.lobby.particle.Gradient;
|
||||
import de.steamwar.lobby.particle.ParticleElement;
|
||||
import de.steamwar.lobby.particle.design.*;
|
||||
import de.steamwar.lobby.particle.elements.*;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static de.steamwar.lobby.particle.design.EntryParameters.*;
|
||||
|
||||
@Getter
|
||||
public class ParticleEntry {
|
||||
|
||||
private final ParticleDesign design;
|
||||
private final ParameterValues values;
|
||||
|
||||
public ParticleEntry(ParticleDesign design, ParameterValues values) {
|
||||
this.design = design;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public ParticleEntry(ParticleDesign design) {
|
||||
this(design, new ParameterValues());
|
||||
}
|
||||
|
||||
public List<Parameter<?>> getParameters() {
|
||||
return parametersOf(design);
|
||||
}
|
||||
|
||||
private static List<Parameter<?>> parametersOf(ParticleDesign design) {
|
||||
List<Parameter<?>> parameters = new ArrayList<>(EntryParameters.ALL);
|
||||
parameters.addAll(design.getParameters());
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public ParticleElement build() {
|
||||
ParticleType type = values.get(PARTICLE);
|
||||
float spreadX = values.get(SPREAD_X).floatValue();
|
||||
float spreadY = values.get(SPREAD_Y).floatValue();
|
||||
float spreadZ = values.get(SPREAD_Z).floatValue();
|
||||
double speed = values.get(SPEED);
|
||||
int count = values.get(COUNT);
|
||||
|
||||
ParticleElement element;
|
||||
if (type.isDust()) {
|
||||
List<Color> colors = values.get(COLORS);
|
||||
Gradient gradient = colors.isEmpty() ? null : new Gradient(colors.toArray(new Color[0]));
|
||||
element = new DustParticle(type.getParticle(), spreadX, spreadY, spreadZ, speed, count, gradient);
|
||||
} else {
|
||||
element = new SimpleParticle(type.getParticle(), spreadX, spreadY, spreadZ, speed, count);
|
||||
}
|
||||
|
||||
double offsetX = values.get(OFFSET_X);
|
||||
double offsetY = values.get(OFFSET_Y);
|
||||
double offsetZ = values.get(OFFSET_Z);
|
||||
if (offsetX != 0 || offsetY != 0 || offsetZ != 0) {
|
||||
element = new LocationMutator(element, offsetX, offsetY, offsetZ);
|
||||
}
|
||||
|
||||
element = design.build(values, element);
|
||||
element = values.get(MOVEMENT).wrap(element);
|
||||
element = values.get(GLIDING).wrap(element);
|
||||
element = values.get(GROUND).wrap(element);
|
||||
element = values.get(SNEAK).wrap(element);
|
||||
element = values.get(VISIBILITY).wrap(element);
|
||||
|
||||
int interval = values.get(INTERVAL);
|
||||
if (interval > 1) {
|
||||
element = new Delayed(element, interval);
|
||||
}
|
||||
if (values.get(TICK_MODE) == TickMode.ALWAYS) {
|
||||
element = new Always(element);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
// Particles spawned per tick, used for the slot budget
|
||||
public double cost() {
|
||||
return (double) design.points(values) * values.get(COUNT) / values.get(INTERVAL);
|
||||
}
|
||||
|
||||
public JsonObject toJson() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("design", design.getId());
|
||||
json.add("values", values.toJson(getParameters()));
|
||||
return json;
|
||||
}
|
||||
|
||||
// Returns null for entries that cannot be loaded from player storage
|
||||
public static ParticleEntry fromJson(JsonObject json) {
|
||||
JsonElement designId = json.get("design");
|
||||
if (designId == null || !designId.isJsonPrimitive()) {
|
||||
return null;
|
||||
}
|
||||
ParticleDesign design = Designs.byId(designId.getAsString());
|
||||
if (design == null || design.getVisibility() == DesignVisibility.INTERNAL) {
|
||||
return null;
|
||||
}
|
||||
JsonElement values = json.get("values");
|
||||
JsonObject valuesJson = values != null && values.isJsonObject() ? values.getAsJsonObject() : new JsonObject();
|
||||
return new ParticleEntry(design, ParameterValues.fromJson(valuesJson, parametersOf(design)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.slot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// A list of entries that are shown together: a player's slot or a preset
|
||||
public interface ParticleSet {
|
||||
List<ParticleEntry> getEntries();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.slot;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import de.steamwar.lobby.particle.design.EntryParameters;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ParticleSlot implements ParticleSet {
|
||||
|
||||
public static final int VERSION = 1;
|
||||
private static final int MAX_NAME_LENGTH = 32;
|
||||
|
||||
private String name;
|
||||
// Null means the icon of the first entry's particle type
|
||||
private Material icon;
|
||||
private final List<ParticleEntry> entries = new ArrayList<>();
|
||||
|
||||
public ParticleSlot(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Material getDisplayIcon() {
|
||||
if (icon != null) {
|
||||
return icon;
|
||||
}
|
||||
if (entries.isEmpty()) {
|
||||
return Material.FIREWORK_STAR;
|
||||
}
|
||||
return entries.get(0).getValues().get(EntryParameters.PARTICLE).getIcon();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name.length() > MAX_NAME_LENGTH ? name.substring(0, MAX_NAME_LENGTH) : name;
|
||||
}
|
||||
|
||||
public JsonObject toJson() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("version", VERSION);
|
||||
json.addProperty("name", name);
|
||||
if (icon != null) {
|
||||
json.addProperty("icon", icon.name());
|
||||
}
|
||||
JsonArray array = new JsonArray();
|
||||
entries.forEach(entry -> array.add(entry.toJson()));
|
||||
json.add("entries", array);
|
||||
return json;
|
||||
}
|
||||
|
||||
public static ParticleSlot fromJson(JsonObject json, String defaultName) {
|
||||
JsonElement name = json.get("name");
|
||||
ParticleSlot slot = new ParticleSlot(defaultName);
|
||||
if (name != null && name.isJsonPrimitive()) {
|
||||
slot.setName(name.getAsString());
|
||||
}
|
||||
|
||||
JsonElement icon = json.get("icon");
|
||||
if (icon != null && icon.isJsonPrimitive()) {
|
||||
Material material = Material.matchMaterial(icon.getAsString());
|
||||
if (material != null && material.isItem()) {
|
||||
slot.icon = material;
|
||||
}
|
||||
}
|
||||
|
||||
JsonElement entries = json.get("entries");
|
||||
if (entries != null && entries.isJsonArray()) {
|
||||
for (JsonElement element : entries.getAsJsonArray()) {
|
||||
if (!element.isJsonObject()) continue;
|
||||
ParticleEntry entry = ParticleEntry.fromJson(element.getAsJsonObject());
|
||||
if (entry != null) {
|
||||
slot.entries.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.slot;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
import de.steamwar.lobby.LobbySystem;
|
||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
@UtilityClass
|
||||
public class ParticleSlots {
|
||||
|
||||
public final int COUNT = 7;
|
||||
private final String KEY_PREFIX = "lobby-particle-slot-";
|
||||
|
||||
// Slots 1-3 for everyone, 4-5 for team members, 6-7 for the server team
|
||||
public ParticleRequirement requirement(int index) {
|
||||
if (index < 3) return ParticleRequirement.NO_REQUIRMENT;
|
||||
if (index < 5) return ParticleRequirement.TEAM_MEMBER;
|
||||
return ParticleRequirement.SERVER_TEAM;
|
||||
}
|
||||
|
||||
public String defaultName(int index) {
|
||||
return "Slot " + (index + 1);
|
||||
}
|
||||
|
||||
public ParticleSlot load(int userId, int index) {
|
||||
String saved = UserConfig.getConfig(userId, key(index));
|
||||
if (saved == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return ParticleSlot.fromJson(JsonParser.parseString(saved).getAsJsonObject(), defaultName(index));
|
||||
} catch (RuntimeException e) {
|
||||
LobbySystem.getInstance().getLogger().log(Level.WARNING, "Could not load particle slot " + (index + 1) + " of user " + userId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void save(int userId, int index, ParticleSlot slot) {
|
||||
if (slot == null) {
|
||||
UserConfig.removePlayerConfig(userId, key(index));
|
||||
} else {
|
||||
UserConfig.updatePlayerConfig(userId, key(index), slot.toJson().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String key(int index) {
|
||||
if (index < 0 || index >= COUNT) {
|
||||
throw new IllegalArgumentException("Invalid particle slot " + index);
|
||||
}
|
||||
return KEY_PREFIX + (index + 1);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ package de.steamwar.lobby.util;
|
||||
import de.steamwar.lobby.particle.CompiledSet;
|
||||
import de.steamwar.lobby.particle.ParticleEnum;
|
||||
import de.steamwar.lobby.particle.ParticleInventory;
|
||||
import de.steamwar.lobby.particle.UnlockContext;
|
||||
import de.steamwar.lobby.particle.slot.ParticleSlot;
|
||||
import de.steamwar.lobby.particle.slot.ParticleSlots;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -32,15 +35,23 @@ import java.util.UUID;
|
||||
|
||||
public class LobbyPlayer {
|
||||
|
||||
private static final String ACTIVE_KEY = "lobby-particle-active";
|
||||
private static final String ACTIVE_SLOT_PREFIX = "slot:";
|
||||
|
||||
private static Map<UUID, LobbyPlayer> cache = new HashMap<>();
|
||||
|
||||
private final UUID uuid;
|
||||
private ParticleEnum particle;
|
||||
private final ParticleSlot[] slots = new ParticleSlot[ParticleSlots.COUNT];
|
||||
private final boolean[] loadedSlots = new boolean[ParticleSlots.COUNT];
|
||||
private int activeSlot = -1;
|
||||
private CompiledSet compiledSet;
|
||||
private int userId;
|
||||
|
||||
private boolean fly;
|
||||
|
||||
private LobbyPlayer(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
userId = SteamwarUser.get(uuid).getId();
|
||||
cache.put(uuid, this);
|
||||
particle = null;
|
||||
@@ -48,7 +59,18 @@ public class LobbyPlayer {
|
||||
if (saved != null) {
|
||||
particle = ParticleInventory.convertFromString(saved);
|
||||
}
|
||||
compile();
|
||||
String active = UserConfig.getConfig(userId, ACTIVE_KEY);
|
||||
if (active != null && active.startsWith(ACTIVE_SLOT_PREFIX)) {
|
||||
try {
|
||||
int index = Integer.parseInt(active.substring(ACTIVE_SLOT_PREFIX.length())) - 1;
|
||||
if (index >= 0 && index < ParticleSlots.COUNT) {
|
||||
activeSlot = index;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// Ignore unreadable selection, the player simply has no slot selected
|
||||
}
|
||||
}
|
||||
recompile();
|
||||
}
|
||||
|
||||
public boolean isFlying() {
|
||||
@@ -70,16 +92,76 @@ public class LobbyPlayer {
|
||||
String saved = ParticleInventory.convertToString(particle);
|
||||
UserConfig.updatePlayerConfig(userId, "lobby-particle", saved);
|
||||
}
|
||||
setActiveSlot(-1);
|
||||
this.particle = particle;
|
||||
compile();
|
||||
recompile();
|
||||
}
|
||||
|
||||
public ParticleSlot getSlot(int index) {
|
||||
if (!loadedSlots[index]) {
|
||||
slots[index] = ParticleSlots.load(userId, index);
|
||||
loadedSlots[index] = true;
|
||||
}
|
||||
return slots[index];
|
||||
}
|
||||
|
||||
// Null deletes the slot
|
||||
public void setSlot(int index, ParticleSlot slot) {
|
||||
ParticleSlots.save(userId, index, slot);
|
||||
slots[index] = slot;
|
||||
loadedSlots[index] = true;
|
||||
if (index == activeSlot) {
|
||||
recompile();
|
||||
}
|
||||
}
|
||||
|
||||
public int getActiveSlot() {
|
||||
return activeSlot;
|
||||
}
|
||||
|
||||
// Returns false when the slot is empty or locked for this player
|
||||
public boolean selectSlot(int index) {
|
||||
if (getSlot(index) == null || !ParticleSlots.requirement(index).test(unlockContext())) {
|
||||
return false;
|
||||
}
|
||||
if (particle != null) {
|
||||
UserConfig.removePlayerConfig(userId, "lobby-particle");
|
||||
particle = null;
|
||||
}
|
||||
setActiveSlot(index);
|
||||
recompile();
|
||||
return true;
|
||||
}
|
||||
|
||||
public CompiledSet getCompiledSet() {
|
||||
return compiledSet;
|
||||
}
|
||||
|
||||
private void compile() {
|
||||
compiledSet = particle == null ? null : CompiledSet.of(particle.getParticle().getParticleElement());
|
||||
// A locked active slot shows nothing but stays selected, so it comes back once the rank does
|
||||
public void recompile() {
|
||||
if (activeSlot >= 0) {
|
||||
ParticleSlot slot = getSlot(activeSlot);
|
||||
UnlockContext context = unlockContext();
|
||||
compiledSet = slot != null && ParticleSlots.requirement(activeSlot).test(context) ? CompiledSet.of(slot, context) : null;
|
||||
} else {
|
||||
compiledSet = particle == null ? null : CompiledSet.of(particle.getParticle().getParticleElement());
|
||||
}
|
||||
}
|
||||
|
||||
private void setActiveSlot(int index) {
|
||||
if (index == activeSlot) {
|
||||
return;
|
||||
}
|
||||
activeSlot = index;
|
||||
if (index < 0) {
|
||||
UserConfig.removePlayerConfig(userId, ACTIVE_KEY);
|
||||
} else {
|
||||
UserConfig.updatePlayerConfig(userId, ACTIVE_KEY, ACTIVE_SLOT_PREFIX + (index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private UnlockContext unlockContext() {
|
||||
return new UnlockContext(SteamwarUser.get(uuid));
|
||||
}
|
||||
|
||||
public static LobbyPlayer getLobbyPlayer(UUID uuid) {
|
||||
|
||||
Reference in New Issue
Block a user