forked from SteamWar/SteamWar
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f63390915b | ||
|
|
db939e27d9 | ||
|
|
36e6329f2b | ||
|
|
b2300c128e | ||
|
|
9cb4e0b7db | ||
|
|
8e4730bd12 | ||
|
|
baa278a1f5 | ||
|
|
8fd6fdf81e | ||
|
|
7d0f5aceae | ||
|
|
ad9087c038 | ||
|
|
872da5f62a | ||
|
|
e830d087c9 | ||
|
|
b4661ea345 | ||
|
|
4e5945d2bc | ||
|
|
a5b8f00c58 | ||
|
|
8e23490b04 | ||
|
|
4c6b5e654a | ||
|
|
38a33b7298 | ||
|
|
32797bd2a8 | ||
|
|
28b62bf27b | ||
|
|
db915f7be7 | ||
|
|
aef26b5e9f | ||
|
|
8009098965 | ||
|
|
47f0d9858b | ||
|
|
c3eda01e20 | ||
|
|
5e81081620 | ||
|
|
ed70d65bc4 | ||
|
|
2ce8799a19 | ||
|
|
15036a7ba0 | ||
|
|
d77c014409 | ||
|
|
6b5441bb04 | ||
|
|
54d30f4a42 | ||
|
|
e41f76937e | ||
|
|
0c7ee052e8 | ||
|
|
b60cdfc88d | ||
|
|
98f04451a4 | ||
|
|
c95901bc42 | ||
|
|
211e2693cf | ||
|
|
1980375e73 | ||
|
|
ef041272bb | ||
|
|
bfbf98c1d6 | ||
|
|
c4b46e4431 | ||
|
|
c238b9bdf2 | ||
|
|
fec4007120 |
@@ -617,6 +617,15 @@ public final class GameModeConfig<M, W> {
|
|||||||
*/
|
*/
|
||||||
public final List<SchematicType> SubTypes;
|
public final List<SchematicType> SubTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of publics that should be used. Mainly useful for an Event Mode
|
||||||
|
* where a subset of the regular publics should be used without duplicating
|
||||||
|
* those in the Database.
|
||||||
|
*
|
||||||
|
* @implSpec {@code []} by default, thus the GameModes publics are used.
|
||||||
|
*/
|
||||||
|
public final List<Integer> Publics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut of the schematic type
|
* Shortcut of the schematic type
|
||||||
*
|
*
|
||||||
@@ -725,6 +734,7 @@ public final class GameModeConfig<M, W> {
|
|||||||
Inset = new InsetConfig(loader.with("Inset"));
|
Inset = new InsetConfig(loader.with("Inset"));
|
||||||
Type = null;
|
Type = null;
|
||||||
SubTypesStrings = loader.getStringList("SubTypes");
|
SubTypesStrings = loader.getStringList("SubTypes");
|
||||||
|
Publics = loader.getIntList("Publics");
|
||||||
SubTypes = new ArrayList<>();
|
SubTypes = new ArrayList<>();
|
||||||
Shortcut = loader.getString("Shortcut", "");
|
Shortcut = loader.getString("Shortcut", "");
|
||||||
Material = loader.getMaterial("Material", "STONE_BUTTON");
|
Material = loader.getMaterial("Material", "STONE_BUTTON");
|
||||||
|
|||||||
@@ -20,19 +20,14 @@
|
|||||||
package de.steamwar.fightsystem.ai;
|
package de.steamwar.fightsystem.ai;
|
||||||
|
|
||||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
import de.steamwar.fightsystem.Config;
|
import de.steamwar.fightsystem.fight.FightSchematic;
|
||||||
import de.steamwar.fightsystem.fight.FightTeam;
|
import de.steamwar.fightsystem.fight.FightTeam;
|
||||||
import de.steamwar.fightsystem.states.FightState;
|
import de.steamwar.fightsystem.states.FightState;
|
||||||
import de.steamwar.sql.SchematicNode;
|
import de.steamwar.sql.SchematicNode;
|
||||||
import de.steamwar.sql.SteamwarUser;
|
import de.steamwar.sql.SteamwarUser;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
public class DummyAI extends AI {
|
public class DummyAI extends AI {
|
||||||
|
|
||||||
private static final Random random = new Random();
|
|
||||||
|
|
||||||
public DummyAI(FightTeam team) {
|
public DummyAI(FightTeam team) {
|
||||||
super(team, SteamwarUser.get("public"));
|
super(team, SteamwarUser.get("public"));
|
||||||
|
|
||||||
@@ -41,8 +36,7 @@ public class DummyAI extends AI {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SchematicNode chooseSchematic() {
|
public SchematicNode chooseSchematic() {
|
||||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.GameModeConfig.Schematic.Type.toDB());
|
return FightSchematic.getPublicSchematic().orElseThrow();
|
||||||
return publics.get(random.nextInt(publics.size()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -255,7 +255,15 @@ public class GUI {
|
|||||||
private static void schemDialog(Player p, SchematicType type, boolean publicSchems, boolean unchecked) {
|
private static void schemDialog(Player p, SchematicType type, boolean publicSchems, boolean unchecked) {
|
||||||
SchematicSelector selector = new SchematicSelector(p, Config.test() ? SchematicSelector.selectSchematic() : SchematicSelector.selectSchematicType(unchecked ? type.checkType() : type), node -> {
|
SchematicSelector selector = new SchematicSelector(p, Config.test() ? SchematicSelector.selectSchematic() : SchematicSelector.selectSchematicType(unchecked ? type.checkType() : type), node -> {
|
||||||
schemSelect(p, node);
|
schemSelect(p, node);
|
||||||
});
|
}) {
|
||||||
|
@Override
|
||||||
|
protected List<SchematicNode> getSchematicList(SchematicNode parent) {
|
||||||
|
if (!publicSchems || parent != null) return super.getSchematicList(parent);
|
||||||
|
List<Integer> publics = Config.GameModeConfig.Schematic.Publics;
|
||||||
|
if (publics.isEmpty()) return super.getSchematicList(null);
|
||||||
|
return publics.stream().map(SchematicNode::byId).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
};
|
||||||
selector.setPublicMode(publicSchems ? SchematicSelector.PublicMode.PUBLIC_ONLY : SchematicSelector.PublicMode.PRIVATE_ONLY);
|
selector.setPublicMode(publicSchems ? SchematicSelector.PublicMode.PUBLIC_ONLY : SchematicSelector.PublicMode.PRIVATE_ONLY);
|
||||||
selector.open();
|
selector.open();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.bukkit.util.Vector;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@@ -109,11 +110,18 @@ public class FightSchematic extends StateDependent {
|
|||||||
clipboard = null;
|
clipboard = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private static final Random random = new Random();
|
||||||
public void enable() {
|
|
||||||
if (FightState.getFightState() == FightState.SPECTATE) return;
|
public static Optional<SchematicNode> getPublicSchematic() {
|
||||||
|
List<Integer> publicsOverridden = Config.GameModeConfig.Schematic.Publics;
|
||||||
|
if (!publicsOverridden.isEmpty()) {
|
||||||
|
int publicId = publicsOverridden.get(random.nextInt(publicsOverridden.size()));
|
||||||
|
SchematicNode schematicNode = SchematicNode.getSchematicNode(publicId);
|
||||||
|
if (schematicNode != null) {
|
||||||
|
return Optional.of(schematicNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (clipboard == null) {
|
|
||||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.GameModeConfig.Schematic.Type.toDB());
|
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.GameModeConfig.Schematic.Type.toDB());
|
||||||
if (publics.isEmpty()) {
|
if (publics.isEmpty()) {
|
||||||
for (SchematicType type : Config.GameModeConfig.Schematic.SubTypes) {
|
for (SchematicType type : Config.GameModeConfig.Schematic.SubTypes) {
|
||||||
@@ -123,17 +131,26 @@ public class FightSchematic extends StateDependent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (publics.isEmpty()) {
|
if (publics.isEmpty()) {
|
||||||
return;
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Optional.of(publics.get(random.nextInt(publics.size())));
|
||||||
|
}
|
||||||
|
|
||||||
setSchematic(publics.get(new Random().nextInt(publics.size())));
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
if (FightState.getFightState() == FightState.SPECTATE) return;
|
||||||
|
|
||||||
|
if (clipboard == null) {
|
||||||
|
Optional<SchematicNode> schematicNode = getPublicSchematic();
|
||||||
|
if (schematicNode.isEmpty()) return;
|
||||||
|
setSchematic(schematicNode.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ArenaMode.AntiReplay.contains(Config.mode)) {
|
if (ArenaMode.AntiReplay.contains(Config.mode)) {
|
||||||
boolean changeRotation = false;
|
boolean changeRotation = false;
|
||||||
if (Config.GameModeConfig.WinConditions.contains(Winconditions.RANDOM_ROTATE)) {
|
if (Config.GameModeConfig.WinConditions.contains(Winconditions.RANDOM_ROTATE)) {
|
||||||
changeRotation = new Random().nextBoolean();
|
changeRotation = random.nextBoolean();
|
||||||
usedRotate = rotate ^ changeRotation;
|
usedRotate = rotate ^ changeRotation;
|
||||||
}
|
}
|
||||||
if (team.isBlue()) {
|
if (team.isBlue()) {
|
||||||
|
|||||||
@@ -49,76 +49,341 @@ PORTAL_NO_WORLDEDIT_SELECTION = §cNo WorldEdit selection
|
|||||||
PARTICLE_INVENTORY = §0Particle
|
PARTICLE_INVENTORY = §0Particle
|
||||||
PARTICLE_DESELECT = §8No particle
|
PARTICLE_DESELECT = §8No particle
|
||||||
|
|
||||||
PARTICLE_SHOW_HAS_ALL = §8You have all particles
|
|
||||||
PARTICLE_SHOW_ALL = §aShow all
|
|
||||||
PARTICLE_SHOW_UNLOCKED = §cShow unlocked
|
|
||||||
|
|
||||||
PARTICLE_UNLOCKED_BY = §eUnlocked by
|
PARTICLE_UNLOCKED_BY = §eUnlocked by
|
||||||
PARTICLE_UNLOCKED_BY_TEAM = §fJoin a team
|
PARTICLE_UNLOCKED_BY_TEAM = §fJoin a team
|
||||||
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM = §fTeam {0}
|
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM = §fTeam {0}
|
||||||
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM_EASTER = §fTeam {0} (Easter Egg Hunt)
|
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM_EASTER = §fTeam {0} (Easter Egg Hunt)
|
||||||
PARTICLE_UNLOCKED_BY_SPECIFIC_USER = §fUser {0}
|
PARTICLE_UNLOCKED_BY_SPECIFIC_USER = §fUser {0}
|
||||||
PARTICLE_UNLOCKED_BY_SPECIFIC_USER_EASTER = §fUser {0} (Easter Egg Hunt)
|
PARTICLE_UNLOCKED_BY_SPECIFIC_USER_EASTER = §fUser {0} (Easter Egg Hunt)
|
||||||
PARTICLE_UNLOCKED_BY_EVENT = §fEvent participation
|
|
||||||
PARTICLE_UNLOCKED_BY_SERVER_TEAM = §fServer Team
|
PARTICLE_UNLOCKED_BY_SERVER_TEAM = §fServer Team
|
||||||
|
PARTICLE_UNLOCKED_BY_EVENT = §fEvent participation
|
||||||
PARTICLE_UNLOCKED_BY_EVENT_PLACEMENT = §f{0} 1., 2. or 3. Place
|
PARTICLE_UNLOCKED_BY_EVENT_PLACEMENT = §f{0} 1., 2. or 3. Place
|
||||||
PARTICLE_UNLOCKED_BY_EVENT_PARTICIPATION = §f{0}
|
PARTICLE_UNLOCKED_BY_EVENT_PARTICIPATION = §f{0}
|
||||||
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_HALF = §fFind 45 Easter Eggs
|
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_HALF = §fFind 45 Easter Eggs
|
||||||
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_DIFFICULTY = §fFind all Easter Eggs with difficulty {0}
|
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_DIFFICULTY = §fFind all Easter Eggs with difficulty {0}
|
||||||
|
|
||||||
PARTICLE_ATTRIBUTE = §eAttributes§7:
|
|
||||||
PARTICLE_ATTRIBUTE_CIRCLE = §8-§f Ring
|
|
||||||
PARTICLE_ATTRIBUTE_BI_CIRCLE = §8-§f Double ring
|
|
||||||
PARTICLE_ATTRIBUTE_TRI_CIRCLE = §8-§f Tripple ring
|
|
||||||
PARTICLE_ATTRIBUTE_CLOUD = §8-§f Cloud
|
|
||||||
PARTICLE_ATTRIBUTE_TICK = §8-§f Always active
|
|
||||||
PARTICLE_ATTRIBUTE_NON_FLOOR = §8-§f In air
|
|
||||||
PARTICLE_ATTRIBUTE_FLOOR = §8-§f On ground
|
|
||||||
PARTICLE_ATTRIBUTE_NON_FLYING = §8-§f Not flying
|
|
||||||
PARTICLE_ATTRIBUTE_FLYING = §8-§f Flying
|
|
||||||
PARTICLE_ATTRIBUTE_WING = §8-§f Wings
|
|
||||||
PARTICLE_ATTRIBUTE_SNEAKING = §8-§f Sneaking
|
|
||||||
PARTICLE_ATTRIBUTE_NON_MOVING = §8-§f Not moving
|
|
||||||
PARTICLE_ATTRIBUTE_SEPARATOR = §f
|
|
||||||
|
|
||||||
PARTICLE_SELECT = §eClick to select
|
PARTICLE_SELECT = §eClick to select
|
||||||
PARTICLE_LOCKED = §c§lLocked
|
PARTICLE_LOCKED = §c§lLocked
|
||||||
PARTICLE_DEACTIVATED = §cThere are too many players online, particles are deactivated
|
PARTICLE_DEACTIVATED = §cThere are too many players online, particles are deactivated
|
||||||
|
|
||||||
PARTICLE_SNEEZE = §aSneeze
|
PARTICLE_BACK = §7Back
|
||||||
PARTICLE_SMOKE = §7Smoke
|
PARTICLE_SLOT_OPEN = §eClick to open
|
||||||
PARTICLE_FIRE = §cFire
|
PARTICLE_SLOT_CLICK_USE = §eClick to use
|
||||||
PARTICLE_WATER = §bWater
|
PARTICLE_SLOT_CLICK_STOP = §eClick to stop using
|
||||||
PARTICLE_HEART = §cHearts
|
PARTICLE_SLOT_SHIFT_EDIT = §eShift click to edit
|
||||||
PARTICLE_NOTES = §eNotes
|
PARTICLE_SLOT_NAME = §f{0} §8(§e{1}§8/§7{2}§8)
|
||||||
PARTICLE_NAUTILUS = §aNautilus
|
PARTICLE_SLOT_TITLE = §0{0} §8({1}/{2})
|
||||||
PARTICLE_SNOWBALL = §fSnowball
|
PARTICLE_SHIFT_TO_DELETE = §7Shift click to delete
|
||||||
PARTICLE_EFFECT = §5Effect
|
PARTICLE_SECTION_DESIGN = §6§lDesign
|
||||||
PARTICLE_CAMPFIRE = §7Smoke
|
PARTICLE_SECTION_PARTICLE = §6§lParticle
|
||||||
PARTICLE_MAGIC = §5Magic
|
PARTICLE_SECTION_SHAPE = §6§lShape
|
||||||
PARTICLE_ANGRY = §4Angry
|
PARTICLE_SECTION_TIMING = §6§lTiming and position
|
||||||
PARTICLE_SLIME = §aSlime
|
PARTICLE_SECTION_CONDITIONS = §6§lConditions
|
||||||
PARTICLE_MOB = §7Mob
|
PARTICLE_FIXED_LOOK = §7This design always looks the same
|
||||||
PARTICLE_SQUID = §8Squid
|
PARTICLE_VALUE_CLICK = §eClick to change
|
||||||
PARTICLE_BUBBLE = §aBubbles
|
PARTICLE_VALUE_TOGGLE = §eClick to toggle
|
||||||
PARTICLE_HONEY = §6Honey
|
PARTICLE_VALUE_CYCLE = §eLeft click§8: §7next, §eright click§8: §7previous
|
||||||
PARTICLE_NECTAR = §6Nectar
|
PARTICLE_VALUE_SELECTED = §aSelected
|
||||||
PARTICLE_FIREWORK = §7Firework
|
PARTICLE_VALUE_RESET = §eReset to {0}
|
||||||
PARTICLE_DRAGON_BREATH = §5Dragon breath
|
PARTICLE_NUMBER_RANGE = §7Range§8: §e{0} §8- §e{1}
|
||||||
PARTICLE_DAMAGE = §5Damage
|
PARTICLE_NUMBER_TYPE = §eClick to type a value
|
||||||
PARTICLE_DOLPHIN = §dDolphin
|
PARTICLE_NUMBER_RESULT = §7→ §e{0}
|
||||||
PARTICLE_WITCH = §5Witch
|
PARTICLE_NUMBER_AT_MAX = §7→ §e{0} §8(max)
|
||||||
PARTICLE_ENCHANTING = §eEnchantment
|
PARTICLE_NUMBER_AT_MIN = §7→ §e{0} §8(min)
|
||||||
PARTICLE_HAPPY = §2Joy
|
PARTICLE_COLOR_EDITOR = §0Colours
|
||||||
PARTICLE_FLAME = §7Flames
|
PARTICLE_COLOR_REMOVE = §eClick to remove
|
||||||
PARTICLE_END_ROD = §fEnd Rod
|
PARTICLE_COLOR_ADD = §aAdd colour
|
||||||
PARTICLE_CLOUD = §7Cloud
|
PARTICLE_COLOR_HEX = §eEnter hex code
|
||||||
PARTICLE_TOTEM = §aTotem
|
PARTICLE_COLOR_HEX_EXAMPLE = §7For example #FF8800
|
||||||
PARTICLE_TOWN = §5Town
|
PARTICLE_COLOR_HEX_TITLE = Hex colour
|
||||||
PARTICLE_ENCHANTING_CIRCLE = §fEnchanted
|
PARTICLE_COLOR_HEX_INVALID = §cThat is not a hex colour like #FF8800
|
||||||
PARTICLE_WATER_FIRE = §bWater§7/§cFire
|
PARTICLE_COLOR_RANDOM = §eUse random colours
|
||||||
PARTICLE_MAGIC_ENCHANTING = §5Magic/Enchantment
|
PARTICLE_SLOT_EMPTY = §7Empty slot
|
||||||
PARTICLE_WINGS_EVIL = §5Purple wings
|
PARTICLE_SLOT_CREATE = §eClick to create
|
||||||
|
PARTICLE_SLOT_ENTRIES = §7{0} entries
|
||||||
|
PARTICLE_SLOT_ACTIVE = §aActive
|
||||||
|
PARTICLE_SLOT_VIEW = §eClick to view
|
||||||
|
PARTICLE_SLOT_ADD_ENTRY = §aAdd entry
|
||||||
|
PARTICLE_SLOT_MAX_ENTRIES = §cThis slot is full
|
||||||
|
PARTICLE_SLOT_OVER_BUDGET = §cThat would spawn too many particles for this slot
|
||||||
|
PARTICLE_SLOT_RENAME = §eRename
|
||||||
|
PARTICLE_SLOT_RENAME_TITLE = Slot name
|
||||||
|
PARTICLE_SLOT_ICON = §eChange icon
|
||||||
|
PARTICLE_SLOT_ICON_TITLE = §0Slot icon
|
||||||
|
PARTICLE_SLOT_DELETE = §cDelete slot
|
||||||
|
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_LOCKED = §cDesign locked, not shown
|
||||||
|
PARTICLE_ENTRY_COST = §7Particles per tick§8: §e{0}
|
||||||
|
PARTICLE_ENTRY_COST_SHARE = §7This entry§8: §e{0}
|
||||||
|
PARTICLE_ENTRY_EDIT = §eClick to edit
|
||||||
|
PARTICLE_ENTRY_DELETE = §cDelete entry
|
||||||
|
|
||||||
|
PARTICLE_DESIGN_PICKER = §0Choose design
|
||||||
|
PARTICLE_CHOICE_PICKER = §0{0}
|
||||||
|
PARTICLE_COLOR_PICKER = §0Choose colour
|
||||||
|
|
||||||
|
PARTICLE_VALUE = §7Value§8: {0}
|
||||||
|
PARTICLE_VALUE_RANDOM = §7Random colours
|
||||||
|
PARTICLE_VALUE_YES = §aYes
|
||||||
|
PARTICLE_VALUE_NO = §cNo
|
||||||
|
|
||||||
|
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_GLYPH_FLIGHT = §fGlyph flight
|
||||||
|
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
|
||||||
|
PARTICLE_PARAM_SPREAD_Z = §fSpread Z
|
||||||
|
PARTICLE_PARAM_SPEED = §fSpeed
|
||||||
|
PARTICLE_PARAM_COLORS = §fColours
|
||||||
|
PARTICLE_PARAM_BLOCK = §fBlock
|
||||||
|
PARTICLE_PARAM_TRAIL_DURATION = §fTrail duration (ticks)
|
||||||
|
PARTICLE_PARAM_TRAIL_TARGET_X = §fTrail target X
|
||||||
|
PARTICLE_PARAM_TRAIL_TARGET_Y = §fTrail target Y
|
||||||
|
PARTICLE_PARAM_TRAIL_TARGET_Z = §fTrail target Z
|
||||||
|
PARTICLE_PARAM_OFFSET_X = §fOffset X
|
||||||
|
PARTICLE_PARAM_OFFSET_Y = §fOffset Y
|
||||||
|
PARTICLE_PARAM_OFFSET_Z = §fOffset Z
|
||||||
|
PARTICLE_PARAM_INTERVAL = §fEvery n ticks
|
||||||
|
PARTICLE_PARAM_PLACEMENT = §fRelative to
|
||||||
|
PARTICLE_PARAM_TICK_MODE = §fShown
|
||||||
|
PARTICLE_PARAM_MOVEMENT = §fMovement
|
||||||
|
PARTICLE_PARAM_GLIDING = §fElytra
|
||||||
|
PARTICLE_PARAM_GROUND = §fGround
|
||||||
|
PARTICLE_PARAM_SNEAK = §fSneaking
|
||||||
|
PARTICLE_PARAM_VISIBILITY = §fVisible to
|
||||||
|
PARTICLE_PARAM_RADIUS = §fRadius
|
||||||
|
PARTICLE_PARAM_CIRCLE_SPEED = §fRotation speed
|
||||||
|
PARTICLE_PARAM_PHASE = §fStart angle
|
||||||
|
PARTICLE_PARAM_RISE = §fUp and down height
|
||||||
|
PARTICLE_PARAM_RISE_SPEED = §fUp and down speed
|
||||||
|
PARTICLE_PARAM_BOUNCE = §fBounce back
|
||||||
|
PARTICLE_PARAM_INVERTED = §fStart at the top
|
||||||
|
PARTICLE_PARAM_WING_IMAGE = §fWing shape
|
||||||
|
PARTICLE_PARAM_WING_SIZE = §fWing size
|
||||||
|
PARTICLE_PARAM_WING_DETAIL = §fPoints per wing
|
||||||
|
PARTICLE_PARAM_WING_COLORING = §fColour mode
|
||||||
|
PARTICLE_PARAM_WING_SPARKLE = §fSparkle (points shown, %)
|
||||||
|
PARTICLE_PARAM_WING_MOTION = §fMotion
|
||||||
|
|
||||||
|
PARTICLE_CHOICE_TICKMODE_MOVE = §fWhile moving
|
||||||
|
PARTICLE_CHOICE_TICKMODE_ALWAYS = §fAlways
|
||||||
|
PARTICLE_CHOICE_PLACEMENT_WORLD = §fWorld (X east, Y up, Z south)
|
||||||
|
PARTICLE_CHOICE_PLACEMENT_BODY = §fBody (X right, Y up, Z forward)
|
||||||
|
PARTICLE_CHOICE_MOVEMENT_ANY = §fAny movement
|
||||||
|
PARTICLE_CHOICE_MOVEMENT_MOVING = §fOnly while moving
|
||||||
|
PARTICLE_CHOICE_MOVEMENT_STANDING_STILL = §fOnly while standing still
|
||||||
|
PARTICLE_CHOICE_GLIDING_ANY = §fWith or without elytra
|
||||||
|
PARTICLE_CHOICE_GLIDING_GLIDING = §fOnly while gliding
|
||||||
|
PARTICLE_CHOICE_GLIDING_NOT_GLIDING = §fNot while gliding
|
||||||
|
PARTICLE_CHOICE_GROUND_ANY = §fOn ground and in air
|
||||||
|
PARTICLE_CHOICE_GROUND_ON_GROUND = §fOnly on ground
|
||||||
|
PARTICLE_CHOICE_GROUND_IN_AIR = §fOnly in air
|
||||||
|
PARTICLE_CHOICE_SNEAK_ANY = §fSneaking or not
|
||||||
|
PARTICLE_CHOICE_SNEAK_SNEAKING = §fOnly while sneaking
|
||||||
|
PARTICLE_CHOICE_SNEAK_NOT_SNEAKING = §fNot while sneaking
|
||||||
|
PARTICLE_CHOICE_VISIBILITY_EVERYONE = §fEveryone
|
||||||
|
PARTICLE_CHOICE_VISIBILITY_ONLY_SELF = §fOnly me
|
||||||
|
PARTICLE_CHOICE_VISIBILITY_ONLY_OTHERS = §fOnly others
|
||||||
|
PARTICLE_CHOICE_IMAGE_SIMPLE = §fSimple
|
||||||
|
PARTICLE_CHOICE_IMAGE_COMPLEX = §fComplex
|
||||||
|
PARTICLE_CHOICE_IMAGE_SWORD = §fSword
|
||||||
|
PARTICLE_CHOICE_IMAGE_BAT = §fBat
|
||||||
|
PARTICLE_CHOICE_IMAGE_BUTTERFLY = §fButterfly
|
||||||
|
PARTICLE_CHOICE_IMAGE_FAIRY = §fFairy
|
||||||
|
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_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_CHOICE_GLYPHFLIGHT_NORMAL = §fFloat down
|
||||||
|
PARTICLE_CHOICE_GLYPHFLIGHT_INTO_PLAYER = §fInto the player
|
||||||
|
PARTICLE_CHOICE_GLYPHFLIGHT_OUT_OF_PLAYER = §fOut of the player
|
||||||
|
|
||||||
|
# Particle types
|
||||||
|
PARTICLE_TYPE_DUST = §fDust
|
||||||
|
PARTICLE_TYPE_DUST_COLOR_TRANSITION = §fColour fading dust
|
||||||
|
PARTICLE_TYPE_ENTITY_EFFECT = §fPotion swirl
|
||||||
|
PARTICLE_TYPE_TINTED_LEAVES = §fTinted leaves
|
||||||
|
PARTICLE_TYPE_BLOCK = §fBlock breaking
|
||||||
|
PARTICLE_TYPE_BLOCK_CRUMBLE = §fBlock crumbling
|
||||||
|
PARTICLE_TYPE_BLOCK_MARKER = §fBlock marker
|
||||||
|
PARTICLE_TYPE_DUST_PILLAR = §fDust pillar
|
||||||
|
PARTICLE_TYPE_FALLING_DUST = §fFalling dust
|
||||||
|
PARTICLE_TYPE_ITEM = §fItem breaking
|
||||||
|
PARTICLE_TYPE_TRAIL = §fTrail
|
||||||
|
PARTICLE_TYPE_FLAME = §fFlame
|
||||||
|
PARTICLE_TYPE_SMALL_FLAME = §fSmall flame
|
||||||
|
PARTICLE_TYPE_SOUL_FIRE_FLAME = §fSoul fire flame
|
||||||
|
PARTICLE_TYPE_SOUL = §fSoul
|
||||||
|
PARTICLE_TYPE_LAVA = §fLava pop
|
||||||
|
PARTICLE_TYPE_SMOKE = §fSmoke
|
||||||
|
PARTICLE_TYPE_LARGE_SMOKE = §fLarge smoke
|
||||||
|
PARTICLE_TYPE_WHITE_SMOKE = §fWhite smoke
|
||||||
|
PARTICLE_TYPE_CAMPFIRE_COSY_SMOKE = §fCampfire smoke
|
||||||
|
PARTICLE_TYPE_CAMPFIRE_SIGNAL_SMOKE = §fSignal smoke
|
||||||
|
PARTICLE_TYPE_ASH = §fAsh
|
||||||
|
PARTICLE_TYPE_WHITE_ASH = §fWhite ash
|
||||||
|
PARTICLE_TYPE_POOF = §fPoof
|
||||||
|
PARTICLE_TYPE_CLOUD = §fCloud
|
||||||
|
PARTICLE_TYPE_SNEEZE = §fSneeze
|
||||||
|
PARTICLE_TYPE_SPIT = §fSpit
|
||||||
|
PARTICLE_TYPE_EXPLOSION = §fExplosion
|
||||||
|
PARTICLE_TYPE_SONIC_BOOM = §fSonic boom
|
||||||
|
PARTICLE_TYPE_SWEEP_ATTACK = §fSweep attack
|
||||||
|
PARTICLE_TYPE_GUST = §fGust
|
||||||
|
PARTICLE_TYPE_SMALL_GUST = §fSmall gust
|
||||||
|
PARTICLE_TYPE_DRIPPING_WATER = §fDripping water
|
||||||
|
PARTICLE_TYPE_FALLING_WATER = §fFalling water
|
||||||
|
PARTICLE_TYPE_DRIPPING_LAVA = §fDripping lava
|
||||||
|
PARTICLE_TYPE_FALLING_LAVA = §fFalling lava
|
||||||
|
PARTICLE_TYPE_LANDING_LAVA = §fLanding lava
|
||||||
|
PARTICLE_TYPE_DRIPPING_HONEY = §fDripping honey
|
||||||
|
PARTICLE_TYPE_FALLING_HONEY = §fFalling honey
|
||||||
|
PARTICLE_TYPE_LANDING_HONEY = §fLanding honey
|
||||||
|
PARTICLE_TYPE_FALLING_NECTAR = §fFalling nectar
|
||||||
|
PARTICLE_TYPE_DRIPPING_OBSIDIAN_TEAR = §fDripping obsidian tear
|
||||||
|
PARTICLE_TYPE_FALLING_OBSIDIAN_TEAR = §fFalling obsidian tear
|
||||||
|
PARTICLE_TYPE_LANDING_OBSIDIAN_TEAR = §fLanding obsidian tear
|
||||||
|
PARTICLE_TYPE_DRIPPING_DRIPSTONE_WATER = §fDripstone water
|
||||||
|
PARTICLE_TYPE_FALLING_DRIPSTONE_WATER = §fFalling dripstone water
|
||||||
|
PARTICLE_TYPE_DRIPPING_DRIPSTONE_LAVA = §fDripstone lava
|
||||||
|
PARTICLE_TYPE_FALLING_DRIPSTONE_LAVA = §fFalling dripstone lava
|
||||||
|
PARTICLE_TYPE_RAIN = §fRain
|
||||||
|
PARTICLE_TYPE_SPLASH = §fSplash
|
||||||
|
PARTICLE_TYPE_FISHING = §fFishing wake
|
||||||
|
PARTICLE_TYPE_UNDERWATER = §fUnderwater
|
||||||
|
PARTICLE_TYPE_BUBBLE_POP = §fBubble pop
|
||||||
|
PARTICLE_TYPE_DOLPHIN = §fDolphin trail
|
||||||
|
PARTICLE_TYPE_NAUTILUS = §fNautilus
|
||||||
|
PARTICLE_TYPE_SQUID_INK = §fSquid ink
|
||||||
|
PARTICLE_TYPE_GLOW_SQUID_INK = §fGlow squid ink
|
||||||
|
PARTICLE_TYPE_GLOW = §fGlow
|
||||||
|
PARTICLE_TYPE_FALLING_SPORE_BLOSSOM = §fFalling spore blossom
|
||||||
|
PARTICLE_TYPE_SPORE_BLOSSOM_AIR = §fFloating spores
|
||||||
|
PARTICLE_TYPE_CHERRY_LEAVES = §fCherry leaves
|
||||||
|
PARTICLE_TYPE_PALE_OAK_LEAVES = §fPale oak leaves
|
||||||
|
PARTICLE_TYPE_CRIMSON_SPORE = §fCrimson spores
|
||||||
|
PARTICLE_TYPE_WARPED_SPORE = §fWarped spores
|
||||||
|
PARTICLE_TYPE_MYCELIUM = §fMycelium
|
||||||
|
PARTICLE_TYPE_COMPOSTER = §fComposter
|
||||||
|
PARTICLE_TYPE_FIREFLY = §fFirefly
|
||||||
|
PARTICLE_TYPE_SNOWFLAKE = §fSnowflake
|
||||||
|
PARTICLE_TYPE_ITEM_SNOWBALL = §fSnowball
|
||||||
|
PARTICLE_TYPE_ITEM_SLIME = §fSlime
|
||||||
|
PARTICLE_TYPE_ITEM_COBWEB = §fCobweb
|
||||||
|
PARTICLE_TYPE_EGG_CRACK = §fEgg crack
|
||||||
|
PARTICLE_TYPE_DUST_PLUME = §fDust plume
|
||||||
|
PARTICLE_TYPE_INFESTED = §fInfested
|
||||||
|
PARTICLE_TYPE_HEART = §fHeart
|
||||||
|
PARTICLE_TYPE_NOTE = §fNote
|
||||||
|
PARTICLE_TYPE_HAPPY_VILLAGER = §fHappy villager
|
||||||
|
PARTICLE_TYPE_ANGRY_VILLAGER = §fAngry villager
|
||||||
|
PARTICLE_TYPE_DAMAGE_INDICATOR = §fDamage
|
||||||
|
PARTICLE_TYPE_CRIT = §fCritical hit
|
||||||
|
PARTICLE_TYPE_ENCHANTED_HIT = §fEnchanted hit
|
||||||
|
PARTICLE_TYPE_ENCHANT = §fEnchanting glyphs
|
||||||
|
PARTICLE_TYPE_EFFECT = §fPotion effect
|
||||||
|
PARTICLE_TYPE_INSTANT_EFFECT = §fInstant effect
|
||||||
|
PARTICLE_TYPE_WITCH = §fWitch magic
|
||||||
|
PARTICLE_TYPE_END_ROD = §fEnd rod
|
||||||
|
PARTICLE_TYPE_PORTAL = §fPortal
|
||||||
|
PARTICLE_TYPE_REVERSE_PORTAL = §fReverse portal
|
||||||
|
PARTICLE_TYPE_DRAGON_BREATH = §fDragon breath
|
||||||
|
PARTICLE_TYPE_FIREWORK = §fFirework spark
|
||||||
|
PARTICLE_TYPE_TOTEM_OF_UNDYING = §fTotem
|
||||||
|
PARTICLE_TYPE_ELECTRIC_SPARK = §fElectric spark
|
||||||
|
PARTICLE_TYPE_SCRAPE = §fScrape
|
||||||
|
PARTICLE_TYPE_WAX_ON = §fWax on
|
||||||
|
PARTICLE_TYPE_WAX_OFF = §fWax off
|
||||||
|
PARTICLE_TYPE_SCULK_SOUL = §fSculk soul
|
||||||
|
PARTICLE_TYPE_SCULK_CHARGE = §fSculk charge
|
||||||
|
PARTICLE_TYPE_SCULK_CHARGE_POP = §fSculk charge pop
|
||||||
|
PARTICLE_TYPE_SHRIEK = §fShriek
|
||||||
|
PARTICLE_TYPE_RAID_OMEN = §fRaid omen
|
||||||
|
PARTICLE_TYPE_TRIAL_OMEN = §fTrial omen
|
||||||
|
PARTICLE_TYPE_OMINOUS_SPAWNING = §fOminous spawning
|
||||||
|
PARTICLE_TYPE_TRIAL_SPAWNER_DETECTION = §fTrial spawner flame
|
||||||
|
PARTICLE_TYPE_TRIAL_SPAWNER_DETECTION_OMINOUS = §fOminous trial spawner flame
|
||||||
|
PARTICLE_TYPE_VAULT_CONNECTION = §fVault connection
|
||||||
|
|
||||||
|
# Particle blocks
|
||||||
|
PARTICLE_CHOICE_BLOCK_STONE = §fStone
|
||||||
|
PARTICLE_CHOICE_BLOCK_COBBLESTONE = §fCobblestone
|
||||||
|
PARTICLE_CHOICE_BLOCK_DIRT = §fDirt
|
||||||
|
PARTICLE_CHOICE_BLOCK_GRASS_BLOCK = §fGrass block
|
||||||
|
PARTICLE_CHOICE_BLOCK_SAND = §fSand
|
||||||
|
PARTICLE_CHOICE_BLOCK_RED_SAND = §fRed sand
|
||||||
|
PARTICLE_CHOICE_BLOCK_GRAVEL = §fGravel
|
||||||
|
PARTICLE_CHOICE_BLOCK_OAK_PLANKS = §fOak planks
|
||||||
|
PARTICLE_CHOICE_BLOCK_OAK_LEAVES = §fOak leaves
|
||||||
|
PARTICLE_CHOICE_BLOCK_CHERRY_LEAVES = §fCherry leaves
|
||||||
|
PARTICLE_CHOICE_BLOCK_SNOW_BLOCK = §fSnow block
|
||||||
|
PARTICLE_CHOICE_BLOCK_ICE = §fIce
|
||||||
|
PARTICLE_CHOICE_BLOCK_GLASS = §fGlass
|
||||||
|
PARTICLE_CHOICE_BLOCK_TNT = §fTNT
|
||||||
|
PARTICLE_CHOICE_BLOCK_PUMPKIN = §fPumpkin
|
||||||
|
PARTICLE_CHOICE_BLOCK_MELON = §fMelon
|
||||||
|
PARTICLE_CHOICE_BLOCK_HONEY_BLOCK = §fHoney block
|
||||||
|
PARTICLE_CHOICE_BLOCK_SLIME_BLOCK = §fSlime block
|
||||||
|
PARTICLE_CHOICE_BLOCK_NETHERRACK = §fNetherrack
|
||||||
|
PARTICLE_CHOICE_BLOCK_SOUL_SAND = §fSoul sand
|
||||||
|
PARTICLE_CHOICE_BLOCK_MAGMA_BLOCK = §fMagma block
|
||||||
|
PARTICLE_CHOICE_BLOCK_END_STONE = §fEnd stone
|
||||||
|
PARTICLE_CHOICE_BLOCK_OBSIDIAN = §fObsidian
|
||||||
|
PARTICLE_CHOICE_BLOCK_PRISMARINE = §fPrismarine
|
||||||
|
PARTICLE_CHOICE_BLOCK_SCULK = §fSculk
|
||||||
|
PARTICLE_CHOICE_BLOCK_AMETHYST_BLOCK = §fAmethyst block
|
||||||
|
PARTICLE_CHOICE_BLOCK_COAL_BLOCK = §fBlock of coal
|
||||||
|
PARTICLE_CHOICE_BLOCK_IRON_BLOCK = §fBlock of iron
|
||||||
|
PARTICLE_CHOICE_BLOCK_GOLD_BLOCK = §fBlock of gold
|
||||||
|
PARTICLE_CHOICE_BLOCK_REDSTONE_BLOCK = §fBlock of redstone
|
||||||
|
PARTICLE_CHOICE_BLOCK_LAPIS_BLOCK = §fBlock of lapis lazuli
|
||||||
|
PARTICLE_CHOICE_BLOCK_EMERALD_BLOCK = §fBlock of emerald
|
||||||
|
PARTICLE_CHOICE_BLOCK_DIAMOND_BLOCK = §fBlock of diamond
|
||||||
|
PARTICLE_CHOICE_BLOCK_WHITE_WOOL = §fWhite wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_RED_WOOL = §fRed wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_ORANGE_WOOL = §fOrange wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_YELLOW_WOOL = §fYellow wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_LIME_WOOL = §fLime wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_LIGHT_BLUE_WOOL = §fLight blue wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_BLUE_WOOL = §fBlue wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_PURPLE_WOOL = §fPurple wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_PINK_WOOL = §fPink wool
|
||||||
|
PARTICLE_CHOICE_BLOCK_BLACK_WOOL = §fBlack wool
|
||||||
|
|
||||||
PARTICLE_PLAYER_HAYLIM_AURA = §fHaylim\'s Aura
|
PARTICLE_PLAYER_HAYLIM_AURA = §fHaylim\'s Aura
|
||||||
PARTICLE_PLAYER_1063 = §7Circling Circle
|
PARTICLE_PLAYER_1063 = §7Circling Circle
|
||||||
@@ -136,34 +401,12 @@ PARTICLE_TEAM_PULSE_AURA_3 = §fPulse Aura §fEnchanted
|
|||||||
PARTICLE_TEAM_PULSE_LOGO = §fPulse Logo
|
PARTICLE_TEAM_PULSE_LOGO = §fPulse Logo
|
||||||
PARTICLE_TEAM_PULSE_HEART_BEAT = §cHeart Beat
|
PARTICLE_TEAM_PULSE_HEART_BEAT = §cHeart Beat
|
||||||
|
|
||||||
PARTICLE_RAINCLOUD_NORMAL = §fRaincloud
|
|
||||||
PARTICLE_RAINCLOUD_RED = §fLava cloud
|
|
||||||
PARTICLE_RAINCLOUD_YELLOW = §fNectar cloud
|
|
||||||
PARTICLE_RAINCLOUD_ORANGE = §fHoney cloud
|
|
||||||
PARTICLE_RAINCLOUD_GREEN = §fSpore Blossom cloud
|
|
||||||
PARTICLE_RAINCLOUD_PURPLE = §fObsidian Tears cloud
|
|
||||||
PARTICLE_RAINCLOUD_NAUTILUS = §fNautilus cloud
|
|
||||||
PARTICLE_RAINCLOUD_ENCHANTMENT = §fEnchantment cloud
|
|
||||||
PARTICLE_RAINCLOUD_SLIME = §fSlime cloud
|
|
||||||
PARTICLE_RAINCLOUD_HAIL = §fHail cloud
|
|
||||||
PARTICLE_RAINCLOUD_SNOW = §fSnow cloud
|
|
||||||
PARTICLE_RAINCLOUD_TOTEM = §fTotem cloud
|
|
||||||
|
|
||||||
PARTICLE_EVENT_ENCHANTING = §cEnchantment
|
PARTICLE_EVENT_ENCHANTING = §cEnchantment
|
||||||
PARTICLE_EVENT_CLOUD = §fClouds
|
|
||||||
PARTICLE_EVENT_SMOKE = §7Smoke
|
|
||||||
PARTICLE_EVENT_WATER = §bWater
|
|
||||||
PARTICLE_EVENT_WINGS = §fWings
|
|
||||||
PARTICLE_EVENT_RAIN_CLOUD = §fRandom Raincloud
|
PARTICLE_EVENT_RAIN_CLOUD = §fRandom Raincloud
|
||||||
PARTICLE_EVENT_WGS = §fWGS
|
|
||||||
PARTICLE_EVENT_WARGEARCLASH = §fClash
|
|
||||||
|
|
||||||
PARTICLE_EGG_HUNT_EASY = §aGreenery
|
|
||||||
PARTICLE_EGG_HUNT_MEDIUM = §eHide and Seek
|
PARTICLE_EGG_HUNT_MEDIUM = §eHide and Seek
|
||||||
PARTICLE_EGG_HUNT_HARD = §cOrbit
|
PARTICLE_EGG_HUNT_HARD = §cOrbit
|
||||||
PARTICLE_EGG_HUNT_EXTREME = §5Purple rain
|
|
||||||
PARTICLE_EGG_HUNT_ADVANCED = §5Lift off
|
PARTICLE_EGG_HUNT_ADVANCED = §5Lift off
|
||||||
PARTICLE_EGG_HUNT_HALF = §fThe hunt is on
|
|
||||||
|
|
||||||
JUMP_AND_RUN_PROGRESS = §e{0}§8/§f{1} §c{2} §7{3}
|
JUMP_AND_RUN_PROGRESS = §e{0}§8/§f{1} §c{2} §7{3}
|
||||||
JUMP_AND_RUN_CANCEL = {0}
|
JUMP_AND_RUN_CANCEL = {0}
|
||||||
|
|||||||
@@ -49,109 +49,353 @@ PORTAL_NO_WORLDEDIT_SELECTION = §cKeine WorldEdit Selection
|
|||||||
PARTICLE_INVENTORY = §0Partikel
|
PARTICLE_INVENTORY = §0Partikel
|
||||||
PARTICLE_DESELECT = §8Keine Partikel
|
PARTICLE_DESELECT = §8Keine Partikel
|
||||||
|
|
||||||
PARTICLE_SHOW_HAS_ALL = §8Du hast alle Partikel
|
|
||||||
PARTICLE_SHOW_ALL = §aZeige alle
|
|
||||||
PARTICLE_SHOW_UNLOCKED = §cZeige freigeschaltete
|
|
||||||
|
|
||||||
PARTICLE_UNLOCKED_BY = §eFreigeschaltet durch
|
PARTICLE_UNLOCKED_BY = §eFreigeschaltet durch
|
||||||
PARTICLE_UNLOCKED_BY_TEAM = §fTeambeitritt
|
PARTICLE_UNLOCKED_BY_TEAM = §fTeambeitritt
|
||||||
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM = §fTeam {0}
|
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM = §fTeam {0}
|
||||||
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM_EASTER = §fTeam {0} (Oster-Eierer-Suche)
|
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM_EASTER = §fTeam {0} (Oster-Eierer-Suche)
|
||||||
PARTICLE_UNLOCKED_BY_SPECIFIC_USER = §fUser {0}
|
PARTICLE_UNLOCKED_BY_SPECIFIC_USER = §fUser {0}
|
||||||
PARTICLE_UNLOCKED_BY_SPECIFIC_USER_EASTER = §fUser {0} (Oster-Eierer-Suche)
|
PARTICLE_UNLOCKED_BY_SPECIFIC_USER_EASTER = §fUser {0} (Oster-Eierer-Suche)
|
||||||
PARTICLE_UNLOCKED_BY_EVENT = §fEventteilnahme
|
|
||||||
PARTICLE_UNLOCKED_BY_SERVER_TEAM = §fServerteam
|
PARTICLE_UNLOCKED_BY_SERVER_TEAM = §fServerteam
|
||||||
|
PARTICLE_UNLOCKED_BY_EVENT = §fEventteilnahme
|
||||||
PARTICLE_UNLOCKED_BY_EVENT_PLACEMENT = §f{0} 1., 2. oder 3. Platz
|
PARTICLE_UNLOCKED_BY_EVENT_PLACEMENT = §f{0} 1., 2. oder 3. Platz
|
||||||
PARTICLE_UNLOCKED_BY_EVENT_PARTICIPATION = §f{0}
|
PARTICLE_UNLOCKED_BY_EVENT_PARTICIPATION = §f{0}
|
||||||
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_HALF = §fFinde 45 Oster-Eierer
|
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_HALF = §fFinde 45 Oster-Eierer
|
||||||
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_DIFFICULTY = §fAlle Eierer mit Schwierigkeit {0} finden
|
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_DIFFICULTY = §fAlle Eierer mit Schwierigkeit {0} finden
|
||||||
|
|
||||||
PARTICLE_ATTRIBUTE_CIRCLE = §8-§f Ring
|
|
||||||
PARTICLE_ATTRIBUTE_BI_CIRCLE = §8-§f Doppelring
|
|
||||||
PARTICLE_ATTRIBUTE_TRI_CIRCLE = §8-§f Dreifachring
|
|
||||||
PARTICLE_ATTRIBUTE_CLOUD = §8-§f Wolke
|
|
||||||
PARTICLE_ATTRIBUTE_TICK = §8-§f Immer aktiv
|
|
||||||
PARTICLE_ATTRIBUTE_NON_FLOOR = §8-§f In der Luft
|
|
||||||
PARTICLE_ATTRIBUTE_FLOOR = §8-§f Auf dem Boden
|
|
||||||
PARTICLE_ATTRIBUTE_NON_FLYING = §8-§f Nicht am Fliegen
|
|
||||||
PARTICLE_ATTRIBUTE_FLYING = §8-§f Am Fliegen
|
|
||||||
PARTICLE_ATTRIBUTE_WING = §8-§f Flügel
|
|
||||||
PARTICLE_ATTRIBUTE_SNEAKING = §8-§f Ducken
|
|
||||||
PARTICLE_ATTRIBUTE_NON_MOVING = §8-§f Beim Stehen
|
|
||||||
PARTICLE_ATTRIBUTE = §eAttribute§7:
|
|
||||||
|
|
||||||
PARTICLE_SELECT = §eZum Auswählen klicken
|
PARTICLE_SELECT = §eZum Auswählen klicken
|
||||||
PARTICLE_LOCKED = §c§lGesperrt
|
PARTICLE_LOCKED = §c§lGesperrt
|
||||||
PARTICLE_DEACTIVATED = §cEs sind zu viele Spieler online! Partikel sind deaktiviert.
|
PARTICLE_DEACTIVATED = §cEs sind zu viele Spieler online! Partikel sind deaktiviert.
|
||||||
|
|
||||||
PARTICLE_SNEEZE = §aSneeze
|
PARTICLE_BACK = §7Zurück
|
||||||
PARTICLE_SMOKE = §7Rauch
|
PARTICLE_SLOT_OPEN = §eZum Öffnen klicken
|
||||||
PARTICLE_FIRE = §cFeuer
|
PARTICLE_SLOT_CLICK_USE = §eZum Verwenden klicken
|
||||||
PARTICLE_WATER = §bWasser
|
PARTICLE_SLOT_CLICK_STOP = §eZum Beenden klicken
|
||||||
PARTICLE_HEART = §cHerzen
|
PARTICLE_SLOT_SHIFT_EDIT = §eZum Bearbeiten Shift-Klick
|
||||||
PARTICLE_NOTES = §eNoten
|
PARTICLE_SLOT_NAME = §f{0} §8(§e{1}§8/§7{2}§8)
|
||||||
PARTICLE_NAUTILUS = §aNautilus
|
PARTICLE_SLOT_TITLE = §0{0} §8({1}/{2})
|
||||||
PARTICLE_SNOWBALL = §fSchneeball
|
PARTICLE_SHIFT_TO_DELETE = §7Zum Löschen Shift-Klick
|
||||||
PARTICLE_EFFECT = §5Effekt
|
PARTICLE_SECTION_DESIGN = §6§lDesign
|
||||||
PARTICLE_CAMPFIRE = §7Rauch
|
PARTICLE_SECTION_PARTICLE = §6§lPartikel
|
||||||
PARTICLE_MAGIC = §5Magie
|
PARTICLE_SECTION_SHAPE = §6§lForm
|
||||||
PARTICLE_ANGRY = §4Wut
|
PARTICLE_SECTION_TIMING = §6§lZeit und Position
|
||||||
PARTICLE_SLIME = §aSchleim
|
PARTICLE_SECTION_CONDITIONS = §6§lBedingungen
|
||||||
PARTICLE_MOB = §7Mob
|
PARTICLE_FIXED_LOOK = §7Dieses Design sieht immer gleich aus
|
||||||
PARTICLE_SQUID = §8Tintenfisch
|
PARTICLE_VALUE_CLICK = §eZum Ändern klicken
|
||||||
PARTICLE_BUBBLE = §aBlasen
|
PARTICLE_VALUE_TOGGLE = §eZum Umschalten klicken
|
||||||
PARTICLE_HONEY = §6Honig
|
PARTICLE_VALUE_CYCLE = §eLinksklick§8: §7weiter, §eRechtsklick§8: §7zurück
|
||||||
PARTICLE_NECTAR = §6Nektar
|
PARTICLE_VALUE_SELECTED = §aAusgewählt
|
||||||
PARTICLE_FIREWORK = §7Feuerwerk
|
PARTICLE_VALUE_RESET = §eAuf {0} zurücksetzen
|
||||||
PARTICLE_DRAGON_BREATH = §5Drachenatem
|
PARTICLE_NUMBER_RANGE = §7Bereich§8: §e{0} §8- §e{1}
|
||||||
PARTICLE_DAMAGE = §5Schaden
|
PARTICLE_NUMBER_TYPE = §eZum Eingeben klicken
|
||||||
PARTICLE_DOLPHIN = §dDelphin
|
PARTICLE_NUMBER_RESULT = §7→ §e{0}
|
||||||
PARTICLE_WITCH = §5Hexe
|
PARTICLE_NUMBER_AT_MAX = §7→ §e{0} §8(Max)
|
||||||
PARTICLE_ENCHANTING = §eZauber
|
PARTICLE_NUMBER_AT_MIN = §7→ §e{0} §8(Min)
|
||||||
PARTICLE_HAPPY = §2Freude
|
PARTICLE_COLOR_EDITOR = §0Farben
|
||||||
PARTICLE_FLAME = §7Flammen
|
PARTICLE_COLOR_REMOVE = §eZum Entfernen klicken
|
||||||
PARTICLE_END_ROD = §fEnd Rod
|
PARTICLE_COLOR_ADD = §aFarbe hinzufügen
|
||||||
PARTICLE_CLOUD = §7Wolke
|
PARTICLE_COLOR_HEX = §eHex-Code eingeben
|
||||||
PARTICLE_TOTEM = §aTotem
|
PARTICLE_COLOR_HEX_EXAMPLE = §7Zum Beispiel #FF8800
|
||||||
PARTICLE_TOWN = §5Town
|
PARTICLE_COLOR_HEX_TITLE = Hex-Farbe
|
||||||
PARTICLE_ENCHANTING_CIRCLE = §fVerzaubert
|
PARTICLE_COLOR_HEX_INVALID = §cDas ist keine Hex-Farbe wie #FF8800
|
||||||
PARTICLE_WATER_FIRE = §bWasser§7/§cFeuer
|
PARTICLE_COLOR_RANDOM = §eZufällige Farben verwenden
|
||||||
PARTICLE_MAGIC_ENCHANTING = §5Magie§7/§eZauber
|
PARTICLE_SLOT_EMPTY = §7Leerer Slot
|
||||||
PARTICLE_WINGS_EVIL = §5Lila Flügel
|
PARTICLE_SLOT_CREATE = §eZum Erstellen klicken
|
||||||
|
PARTICLE_SLOT_ENTRIES = §7{0} Einträge
|
||||||
|
PARTICLE_SLOT_ACTIVE = §aAktiv
|
||||||
|
PARTICLE_SLOT_VIEW = §eZum Ansehen klicken
|
||||||
|
PARTICLE_SLOT_ADD_ENTRY = §aEintrag hinzufügen
|
||||||
|
PARTICLE_SLOT_MAX_ENTRIES = §cDieser Slot ist voll
|
||||||
|
PARTICLE_SLOT_OVER_BUDGET = §cDamit würde dieser Slot zu viele Partikel erzeugen
|
||||||
|
PARTICLE_SLOT_RENAME = §eUmbenennen
|
||||||
|
PARTICLE_SLOT_RENAME_TITLE = Slotname
|
||||||
|
PARTICLE_SLOT_ICON = §eSymbol ändern
|
||||||
|
PARTICLE_SLOT_ICON_TITLE = §0Slotsymbol
|
||||||
|
PARTICLE_SLOT_DELETE = §cSlot löschen
|
||||||
|
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_LOCKED = §cDesign gesperrt, wird nicht angezeigt
|
||||||
|
PARTICLE_ENTRY_COST = §7Partikel pro Tick§8: §e{0}
|
||||||
|
PARTICLE_ENTRY_COST_SHARE = §7Dieser Eintrag§8: §e{0}
|
||||||
|
PARTICLE_ENTRY_EDIT = §eZum Bearbeiten klicken
|
||||||
|
PARTICLE_ENTRY_DELETE = §cEintrag löschen
|
||||||
|
|
||||||
|
PARTICLE_DESIGN_PICKER = §0Design wählen
|
||||||
|
PARTICLE_CHOICE_PICKER = §0{0}
|
||||||
|
PARTICLE_COLOR_PICKER = §0Farbe wählen
|
||||||
|
|
||||||
|
PARTICLE_VALUE = §7Wert§8: {0}
|
||||||
|
PARTICLE_VALUE_RANDOM = §7Zufällige Farben
|
||||||
|
PARTICLE_VALUE_YES = §aJa
|
||||||
|
PARTICLE_VALUE_NO = §cNein
|
||||||
|
|
||||||
|
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_GLYPH_FLIGHT = §fGlyphenflug
|
||||||
|
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
|
||||||
|
PARTICLE_PARAM_SPREAD_Z = §fStreuung Z
|
||||||
|
PARTICLE_PARAM_SPEED = §fGeschwindigkeit
|
||||||
|
PARTICLE_PARAM_COLORS = §fFarben
|
||||||
|
PARTICLE_PARAM_BLOCK = §fBlock
|
||||||
|
PARTICLE_PARAM_TRAIL_DURATION = §fSpurdauer (Ticks)
|
||||||
|
PARTICLE_PARAM_TRAIL_TARGET_X = §fSpurziel X
|
||||||
|
PARTICLE_PARAM_TRAIL_TARGET_Y = §fSpurziel Y
|
||||||
|
PARTICLE_PARAM_TRAIL_TARGET_Z = §fSpurziel Z
|
||||||
|
PARTICLE_PARAM_OFFSET_X = §fVersatz X
|
||||||
|
PARTICLE_PARAM_OFFSET_Y = §fVersatz Y
|
||||||
|
PARTICLE_PARAM_OFFSET_Z = §fVersatz Z
|
||||||
|
PARTICLE_PARAM_INTERVAL = §fAlle n Ticks
|
||||||
|
PARTICLE_PARAM_PLACEMENT = §fAusgerichtet an
|
||||||
|
PARTICLE_PARAM_TICK_MODE = §fAnzeige
|
||||||
|
PARTICLE_PARAM_MOVEMENT = §fBewegung
|
||||||
|
PARTICLE_PARAM_GLIDING = §fElytra
|
||||||
|
PARTICLE_PARAM_GROUND = §fBoden
|
||||||
|
PARTICLE_PARAM_SNEAK = §fDucken
|
||||||
|
PARTICLE_PARAM_VISIBILITY = §fSichtbar für
|
||||||
|
PARTICLE_PARAM_RADIUS = §fRadius
|
||||||
|
PARTICLE_PARAM_CIRCLE_SPEED = §fDrehgeschwindigkeit
|
||||||
|
PARTICLE_PARAM_PHASE = §fStartwinkel
|
||||||
|
PARTICLE_PARAM_RISE = §fAuf und ab Höhe
|
||||||
|
PARTICLE_PARAM_RISE_SPEED = §fAuf und ab Geschwindigkeit
|
||||||
|
PARTICLE_PARAM_BOUNCE = §fZurückspringen
|
||||||
|
PARTICLE_PARAM_INVERTED = §fOben beginnen
|
||||||
|
PARTICLE_PARAM_WING_IMAGE = §fFlügelform
|
||||||
|
PARTICLE_PARAM_WING_SIZE = §fFlügelgröße
|
||||||
|
PARTICLE_PARAM_WING_DETAIL = §fPunkte pro Flügel
|
||||||
|
PARTICLE_PARAM_WING_COLORING = §fFarbmodus
|
||||||
|
PARTICLE_PARAM_WING_SPARKLE = §fFunkeln (sichtbare Punkte, %)
|
||||||
|
PARTICLE_PARAM_WING_MOTION = §fBewegung
|
||||||
|
|
||||||
|
PARTICLE_CHOICE_TICKMODE_MOVE = §fBeim Bewegen
|
||||||
|
PARTICLE_CHOICE_TICKMODE_ALWAYS = §fImmer
|
||||||
|
PARTICLE_CHOICE_PLACEMENT_WORLD = §fWelt (X Osten, Y oben, Z Süden)
|
||||||
|
PARTICLE_CHOICE_PLACEMENT_BODY = §fKörper (X rechts, Y oben, Z vorne)
|
||||||
|
PARTICLE_CHOICE_MOVEMENT_ANY = §fJede Bewegung
|
||||||
|
PARTICLE_CHOICE_MOVEMENT_MOVING = §fNur beim Bewegen
|
||||||
|
PARTICLE_CHOICE_MOVEMENT_STANDING_STILL = §fNur beim Stehen
|
||||||
|
PARTICLE_CHOICE_GLIDING_ANY = §fMit oder ohne Elytra
|
||||||
|
PARTICLE_CHOICE_GLIDING_GLIDING = §fNur beim Gleiten
|
||||||
|
PARTICLE_CHOICE_GLIDING_NOT_GLIDING = §fNicht beim Gleiten
|
||||||
|
PARTICLE_CHOICE_GROUND_ANY = §fAm Boden und in der Luft
|
||||||
|
PARTICLE_CHOICE_GROUND_ON_GROUND = §fNur am Boden
|
||||||
|
PARTICLE_CHOICE_GROUND_IN_AIR = §fNur in der Luft
|
||||||
|
PARTICLE_CHOICE_SNEAK_ANY = §fDucken egal
|
||||||
|
PARTICLE_CHOICE_SNEAK_SNEAKING = §fNur beim Ducken
|
||||||
|
PARTICLE_CHOICE_SNEAK_NOT_SNEAKING = §fNicht beim Ducken
|
||||||
|
PARTICLE_CHOICE_VISIBILITY_EVERYONE = §fAlle
|
||||||
|
PARTICLE_CHOICE_VISIBILITY_ONLY_SELF = §fNur ich
|
||||||
|
PARTICLE_CHOICE_VISIBILITY_ONLY_OTHERS = §fNur andere
|
||||||
|
PARTICLE_CHOICE_IMAGE_SIMPLE = §fEinfach
|
||||||
|
PARTICLE_CHOICE_IMAGE_COMPLEX = §fKomplex
|
||||||
|
PARTICLE_CHOICE_IMAGE_SWORD = §fSchwert
|
||||||
|
PARTICLE_CHOICE_IMAGE_BAT = §fFledermaus
|
||||||
|
PARTICLE_CHOICE_IMAGE_BUTTERFLY = §fSchmetterling
|
||||||
|
PARTICLE_CHOICE_IMAGE_FAIRY = §fFee
|
||||||
|
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_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_CHOICE_GLYPHFLIGHT_NORMAL = §fHerabschweben
|
||||||
|
PARTICLE_CHOICE_GLYPHFLIGHT_INTO_PLAYER = §fIn den Spieler
|
||||||
|
PARTICLE_CHOICE_GLYPHFLIGHT_OUT_OF_PLAYER = §fAus dem Spieler
|
||||||
|
|
||||||
|
# Particle types
|
||||||
|
PARTICLE_TYPE_DUST = §fStaub
|
||||||
|
PARTICLE_TYPE_DUST_COLOR_TRANSITION = §fFarbverlaufsstaub
|
||||||
|
PARTICLE_TYPE_ENTITY_EFFECT = §fTrankwirbel
|
||||||
|
PARTICLE_TYPE_TINTED_LEAVES = §fGefärbte Blätter
|
||||||
|
PARTICLE_TYPE_BLOCK = §fBlocksplitter
|
||||||
|
PARTICLE_TYPE_BLOCK_CRUMBLE = §fBröckelnder Block
|
||||||
|
PARTICLE_TYPE_BLOCK_MARKER = §fBlockmarkierung
|
||||||
|
PARTICLE_TYPE_DUST_PILLAR = §fStaubsäule
|
||||||
|
PARTICLE_TYPE_FALLING_DUST = §fFallender Staub
|
||||||
|
PARTICLE_TYPE_ITEM = §fGegenstandssplitter
|
||||||
|
PARTICLE_TYPE_TRAIL = §fSpur
|
||||||
|
PARTICLE_TYPE_FLAME = §fFlamme
|
||||||
|
PARTICLE_TYPE_SMALL_FLAME = §fKleine Flamme
|
||||||
|
PARTICLE_TYPE_SOUL_FIRE_FLAME = §fSeelenfeuerflamme
|
||||||
|
PARTICLE_TYPE_SOUL = §fSeele
|
||||||
|
PARTICLE_TYPE_LAVA = §fLavafunken
|
||||||
|
PARTICLE_TYPE_SMOKE = §fRauch
|
||||||
|
PARTICLE_TYPE_LARGE_SMOKE = §fGroßer Rauch
|
||||||
|
PARTICLE_TYPE_WHITE_SMOKE = §fWeißer Rauch
|
||||||
|
PARTICLE_TYPE_CAMPFIRE_COSY_SMOKE = §fLagerfeuerrauch
|
||||||
|
PARTICLE_TYPE_CAMPFIRE_SIGNAL_SMOKE = §fSignalrauch
|
||||||
|
PARTICLE_TYPE_ASH = §fAsche
|
||||||
|
PARTICLE_TYPE_WHITE_ASH = §fWeiße Asche
|
||||||
|
PARTICLE_TYPE_POOF = §fPuff
|
||||||
|
PARTICLE_TYPE_CLOUD = §fWolke
|
||||||
|
PARTICLE_TYPE_SNEEZE = §fNiesen
|
||||||
|
PARTICLE_TYPE_SPIT = §fSpucke
|
||||||
|
PARTICLE_TYPE_EXPLOSION = §fExplosion
|
||||||
|
PARTICLE_TYPE_SONIC_BOOM = §fSchallwelle
|
||||||
|
PARTICLE_TYPE_SWEEP_ATTACK = §fSchwungangriff
|
||||||
|
PARTICLE_TYPE_GUST = §fWindstoß
|
||||||
|
PARTICLE_TYPE_SMALL_GUST = §fKleiner Windstoß
|
||||||
|
PARTICLE_TYPE_DRIPPING_WATER = §fTropfendes Wasser
|
||||||
|
PARTICLE_TYPE_FALLING_WATER = §fFallendes Wasser
|
||||||
|
PARTICLE_TYPE_DRIPPING_LAVA = §fTropfende Lava
|
||||||
|
PARTICLE_TYPE_FALLING_LAVA = §fFallende Lava
|
||||||
|
PARTICLE_TYPE_LANDING_LAVA = §fLandende Lava
|
||||||
|
PARTICLE_TYPE_DRIPPING_HONEY = §fTropfender Honig
|
||||||
|
PARTICLE_TYPE_FALLING_HONEY = §fFallender Honig
|
||||||
|
PARTICLE_TYPE_LANDING_HONEY = §fLandender Honig
|
||||||
|
PARTICLE_TYPE_FALLING_NECTAR = §fFallender Nektar
|
||||||
|
PARTICLE_TYPE_DRIPPING_OBSIDIAN_TEAR = §fTropfende Obsidianträne
|
||||||
|
PARTICLE_TYPE_FALLING_OBSIDIAN_TEAR = §fFallende Obsidianträne
|
||||||
|
PARTICLE_TYPE_LANDING_OBSIDIAN_TEAR = §fLandende Obsidianträne
|
||||||
|
PARTICLE_TYPE_DRIPPING_DRIPSTONE_WATER = §fTropfsteinwasser
|
||||||
|
PARTICLE_TYPE_FALLING_DRIPSTONE_WATER = §fFallendes Tropfsteinwasser
|
||||||
|
PARTICLE_TYPE_DRIPPING_DRIPSTONE_LAVA = §fTropfsteinlava
|
||||||
|
PARTICLE_TYPE_FALLING_DRIPSTONE_LAVA = §fFallende Tropfsteinlava
|
||||||
|
PARTICLE_TYPE_RAIN = §fRegen
|
||||||
|
PARTICLE_TYPE_SPLASH = §fSpritzer
|
||||||
|
PARTICLE_TYPE_FISHING = §fAngelspur
|
||||||
|
PARTICLE_TYPE_UNDERWATER = §fUnterwasser
|
||||||
|
PARTICLE_TYPE_BUBBLE_POP = §fPlatzende Blase
|
||||||
|
PARTICLE_TYPE_DOLPHIN = §fDelfinspur
|
||||||
|
PARTICLE_TYPE_NAUTILUS = §fNautilus
|
||||||
|
PARTICLE_TYPE_SQUID_INK = §fTintenfischtinte
|
||||||
|
PARTICLE_TYPE_GLOW_SQUID_INK = §fLeuchttintenfischtinte
|
||||||
|
PARTICLE_TYPE_GLOW = §fLeuchten
|
||||||
|
PARTICLE_TYPE_FALLING_SPORE_BLOSSOM = §fFallende Sporenblüte
|
||||||
|
PARTICLE_TYPE_SPORE_BLOSSOM_AIR = §fSchwebende Sporen
|
||||||
|
PARTICLE_TYPE_CHERRY_LEAVES = §fKirschblätter
|
||||||
|
PARTICLE_TYPE_PALE_OAK_LEAVES = §fBlasseichenblätter
|
||||||
|
PARTICLE_TYPE_CRIMSON_SPORE = §fKarmesinsporen
|
||||||
|
PARTICLE_TYPE_WARPED_SPORE = §fWirrsporen
|
||||||
|
PARTICLE_TYPE_MYCELIUM = §fMyzel
|
||||||
|
PARTICLE_TYPE_COMPOSTER = §fKomposter
|
||||||
|
PARTICLE_TYPE_FIREFLY = §fGlühwürmchen
|
||||||
|
PARTICLE_TYPE_SNOWFLAKE = §fSchneeflocke
|
||||||
|
PARTICLE_TYPE_ITEM_SNOWBALL = §fSchneeball
|
||||||
|
PARTICLE_TYPE_ITEM_SLIME = §fSchleim
|
||||||
|
PARTICLE_TYPE_ITEM_COBWEB = §fSpinnennetz
|
||||||
|
PARTICLE_TYPE_EGG_CRACK = §fEierschale
|
||||||
|
PARTICLE_TYPE_DUST_PLUME = §fStaubwolke
|
||||||
|
PARTICLE_TYPE_INFESTED = §fBefallen
|
||||||
|
PARTICLE_TYPE_HEART = §fHerz
|
||||||
|
PARTICLE_TYPE_NOTE = §fNote
|
||||||
|
PARTICLE_TYPE_HAPPY_VILLAGER = §fFröhlicher Dorfbewohner
|
||||||
|
PARTICLE_TYPE_ANGRY_VILLAGER = §fWütender Dorfbewohner
|
||||||
|
PARTICLE_TYPE_DAMAGE_INDICATOR = §fSchaden
|
||||||
|
PARTICLE_TYPE_CRIT = §fVolltreffer
|
||||||
|
PARTICLE_TYPE_ENCHANTED_HIT = §fVerzauberter Treffer
|
||||||
|
PARTICLE_TYPE_ENCHANT = §fZauberzeichen
|
||||||
|
PARTICLE_TYPE_EFFECT = §fTrankeffekt
|
||||||
|
PARTICLE_TYPE_INSTANT_EFFECT = §fSoforteffekt
|
||||||
|
PARTICLE_TYPE_WITCH = §fHexenmagie
|
||||||
|
PARTICLE_TYPE_END_ROD = §fEndstab
|
||||||
|
PARTICLE_TYPE_PORTAL = §fPortal
|
||||||
|
PARTICLE_TYPE_REVERSE_PORTAL = §fUmgekehrtes Portal
|
||||||
|
PARTICLE_TYPE_DRAGON_BREATH = §fDrachenatem
|
||||||
|
PARTICLE_TYPE_FIREWORK = §fFeuerwerksfunke
|
||||||
|
PARTICLE_TYPE_TOTEM_OF_UNDYING = §fTotem
|
||||||
|
PARTICLE_TYPE_ELECTRIC_SPARK = §fElektrischer Funke
|
||||||
|
PARTICLE_TYPE_SCRAPE = §fAbkratzen
|
||||||
|
PARTICLE_TYPE_WAX_ON = §fWachsen
|
||||||
|
PARTICLE_TYPE_WAX_OFF = §fEntwachsen
|
||||||
|
PARTICLE_TYPE_SCULK_SOUL = §fSculk-Seele
|
||||||
|
PARTICLE_TYPE_SCULK_CHARGE = §fSculk-Ladung
|
||||||
|
PARTICLE_TYPE_SCULK_CHARGE_POP = §fSculk-Entladung
|
||||||
|
PARTICLE_TYPE_SHRIEK = §fKreischen
|
||||||
|
PARTICLE_TYPE_RAID_OMEN = §fÜberfall-Omen
|
||||||
|
PARTICLE_TYPE_TRIAL_OMEN = §fPrüfungs-Omen
|
||||||
|
PARTICLE_TYPE_OMINOUS_SPAWNING = §fUnheilvolles Erscheinen
|
||||||
|
PARTICLE_TYPE_TRIAL_SPAWNER_DETECTION = §fPrüfungsspawner-Flamme
|
||||||
|
PARTICLE_TYPE_TRIAL_SPAWNER_DETECTION_OMINOUS = §fUnheilvolle Prüfungsspawner-Flamme
|
||||||
|
PARTICLE_TYPE_VAULT_CONNECTION = §fTresorverbindung
|
||||||
|
|
||||||
|
# Particle blocks
|
||||||
|
PARTICLE_CHOICE_BLOCK_STONE = §fStein
|
||||||
|
PARTICLE_CHOICE_BLOCK_COBBLESTONE = §fBruchstein
|
||||||
|
PARTICLE_CHOICE_BLOCK_DIRT = §fErde
|
||||||
|
PARTICLE_CHOICE_BLOCK_GRASS_BLOCK = §fGrasblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_SAND = §fSand
|
||||||
|
PARTICLE_CHOICE_BLOCK_RED_SAND = §fRoter Sand
|
||||||
|
PARTICLE_CHOICE_BLOCK_GRAVEL = §fKies
|
||||||
|
PARTICLE_CHOICE_BLOCK_OAK_PLANKS = §fEichenholzbretter
|
||||||
|
PARTICLE_CHOICE_BLOCK_OAK_LEAVES = §fEichenlaub
|
||||||
|
PARTICLE_CHOICE_BLOCK_CHERRY_LEAVES = §fKirschlaub
|
||||||
|
PARTICLE_CHOICE_BLOCK_SNOW_BLOCK = §fSchneeblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_ICE = §fEis
|
||||||
|
PARTICLE_CHOICE_BLOCK_GLASS = §fGlas
|
||||||
|
PARTICLE_CHOICE_BLOCK_TNT = §fTNT
|
||||||
|
PARTICLE_CHOICE_BLOCK_PUMPKIN = §fKürbis
|
||||||
|
PARTICLE_CHOICE_BLOCK_MELON = §fMelone
|
||||||
|
PARTICLE_CHOICE_BLOCK_HONEY_BLOCK = §fHonigblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_SLIME_BLOCK = §fSchleimblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_NETHERRACK = §fNetherrack
|
||||||
|
PARTICLE_CHOICE_BLOCK_SOUL_SAND = §fSeelensand
|
||||||
|
PARTICLE_CHOICE_BLOCK_MAGMA_BLOCK = §fMagmablock
|
||||||
|
PARTICLE_CHOICE_BLOCK_END_STONE = §fEndstein
|
||||||
|
PARTICLE_CHOICE_BLOCK_OBSIDIAN = §fObsidian
|
||||||
|
PARTICLE_CHOICE_BLOCK_PRISMARINE = §fPrismarin
|
||||||
|
PARTICLE_CHOICE_BLOCK_SCULK = §fSculk
|
||||||
|
PARTICLE_CHOICE_BLOCK_AMETHYST_BLOCK = §fAmethystblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_COAL_BLOCK = §fKohleblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_IRON_BLOCK = §fEisenblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_GOLD_BLOCK = §fGoldblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_REDSTONE_BLOCK = §fRedstone-Block
|
||||||
|
PARTICLE_CHOICE_BLOCK_LAPIS_BLOCK = §fLapislazuliblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_EMERALD_BLOCK = §fSmaragdblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_DIAMOND_BLOCK = §fDiamantblock
|
||||||
|
PARTICLE_CHOICE_BLOCK_WHITE_WOOL = §fWeiße Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_RED_WOOL = §fRote Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_ORANGE_WOOL = §fOrange Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_YELLOW_WOOL = §fGelbe Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_LIME_WOOL = §fHellgrüne Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_LIGHT_BLUE_WOOL = §fHellblaue Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_BLUE_WOOL = §fBlaue Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_PURPLE_WOOL = §fViolette Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_PINK_WOOL = §fRosa Wolle
|
||||||
|
PARTICLE_CHOICE_BLOCK_BLACK_WOOL = §fSchwarze Wolle
|
||||||
|
|
||||||
PARTICLE_PLAYER_1063 = §7Kreisende Kreise
|
PARTICLE_PLAYER_1063 = §7Kreisende Kreise
|
||||||
PARTICLE_PLAYER_10697_AURA = §7Rauchgranate
|
PARTICLE_PLAYER_10697_AURA = §7Rauchgranate
|
||||||
|
|
||||||
PARTICLE_TEAM_PULSE_HEART_BEAT = §cHerzschlag
|
PARTICLE_TEAM_PULSE_HEART_BEAT = §cHerzschlag
|
||||||
|
|
||||||
PARTICLE_RAINCLOUD_NORMAL = §fRegenwolke
|
|
||||||
PARTICLE_RAINCLOUD_RED = §fLavawolke
|
|
||||||
PARTICLE_RAINCLOUD_YELLOW = §fNektarwolke
|
|
||||||
PARTICLE_RAINCLOUD_ORANGE = §fHonigwolke
|
|
||||||
PARTICLE_RAINCLOUD_GREEN = §fSporenwolke
|
|
||||||
PARTICLE_RAINCLOUD_PURPLE = §fObsidanwolke
|
|
||||||
PARTICLE_RAINCLOUD_NAUTILUS = §fNautiluswolke
|
|
||||||
PARTICLE_RAINCLOUD_ENCHANTMENT = §fVerzauberungswolke
|
|
||||||
PARTICLE_RAINCLOUD_SLIME = §fSchleimwolke
|
|
||||||
PARTICLE_RAINCLOUD_HAIL = §fHagelwolke
|
|
||||||
PARTICLE_RAINCLOUD_SNOW = §fSchneewolke
|
|
||||||
PARTICLE_RAINCLOUD_TOTEM = §fTotemwolke
|
|
||||||
|
|
||||||
PARTICLE_EVENT_ENCHANTING = §cVerzaubert
|
PARTICLE_EVENT_ENCHANTING = §cVerzaubert
|
||||||
PARTICLE_EVENT_CLOUD = §fWolken
|
|
||||||
PARTICLE_EVENT_SMOKE = §7Rauch
|
|
||||||
PARTICLE_EVENT_WATER = §bWasser
|
|
||||||
PARTICLE_EVENT_WINGS = §fFlügel
|
|
||||||
PARTICLE_EVENT_RAIN_CLOUD = §fRegenwolke
|
PARTICLE_EVENT_RAIN_CLOUD = §fRegenwolke
|
||||||
PARTICLE_EVENT_WGS = §fWGS
|
|
||||||
PARTICLE_EVENT_WARGEARCLASH = §fClash
|
|
||||||
|
|
||||||
PARTICLE_EGG_HUNT_EASY = §aGrünplfanzen
|
|
||||||
PARTICLE_EGG_HUNT_MEDIUM = §eVersteckspiel
|
PARTICLE_EGG_HUNT_MEDIUM = §eVersteckspiel
|
||||||
PARTICLE_EGG_HUNT_HARD = §cOrbit
|
PARTICLE_EGG_HUNT_HARD = §cOrbit
|
||||||
PARTICLE_EGG_HUNT_EXTREME = §5Lila regen
|
|
||||||
PARTICLE_EGG_HUNT_ADVANCED = §5Abheben
|
PARTICLE_EGG_HUNT_ADVANCED = §5Abheben
|
||||||
PARTICLE_EGG_HUNT_HALF = §fDie Jagd ist eröffnet
|
|
||||||
|
|
||||||
JUMP_AND_RUN_FINISHED = §aBeendet in {0} mit {1} Fails
|
JUMP_AND_RUN_FINISHED = §aBeendet in {0} mit {1} Fails
|
||||||
JUMP_AND_RUN_PERSONAL_BEST = §aNice! Du hast deinen Rekord um {0} verbessert!
|
JUMP_AND_RUN_PERSONAL_BEST = §aNice! Du hast deinen Rekord um {0} verbessert!
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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 lombok.Getter;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Pose;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
// The player's body as a coordinate system: origin at the feet, up towards the head, forward out of the chest.
|
||||||
|
// Standing it turns with the body (as BodyMotion tracks it); gliding or swimming the body lies along the look direction.
|
||||||
|
@Getter
|
||||||
|
public class BodyFrame {
|
||||||
|
|
||||||
|
private static final Vector WORLD_UP = new Vector(0, 1, 0);
|
||||||
|
private static final double HALF_BODY = 0.9;
|
||||||
|
|
||||||
|
private final Location origin;
|
||||||
|
private final Vector right;
|
||||||
|
private final Vector up;
|
||||||
|
private final Vector forward;
|
||||||
|
// Reference directions of rings around the body; they do not turn with the body
|
||||||
|
private final Vector ringStart;
|
||||||
|
private final Vector ringSide;
|
||||||
|
|
||||||
|
private BodyFrame(Location origin, Vector right, Vector up, Vector forward) {
|
||||||
|
this.origin = origin;
|
||||||
|
this.right = right;
|
||||||
|
this.up = up;
|
||||||
|
this.forward = forward;
|
||||||
|
|
||||||
|
// World east projected onto the plane around the body, so a standing ring matches the world ring
|
||||||
|
Vector start = new Vector(1, 0, 0).subtract(up.clone().multiply(up.getX()));
|
||||||
|
if (start.lengthSquared() < 1e-6) {
|
||||||
|
start = new Vector(0, 0, 1).subtract(up.clone().multiply(up.getZ()));
|
||||||
|
}
|
||||||
|
this.ringStart = start.normalize();
|
||||||
|
this.ringSide = up.clone().crossProduct(ringStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BodyFrame of(Player player) {
|
||||||
|
Location location = player.getLocation();
|
||||||
|
Pose pose = player.getPose();
|
||||||
|
if (pose == Pose.FALL_FLYING || pose == Pose.SWIMMING) {
|
||||||
|
Vector up = location.getDirection().normalize();
|
||||||
|
Vector right = up.clone().crossProduct(WORLD_UP);
|
||||||
|
if (right.lengthSquared() < 1e-6) {
|
||||||
|
right = horizontal(BodyMotion.yaw(player)).crossProduct(WORLD_UP);
|
||||||
|
}
|
||||||
|
right.normalize();
|
||||||
|
Vector forward = up.clone().crossProduct(right).normalize();
|
||||||
|
// The lying body is centred in its short hitbox, its feet lie behind the centre
|
||||||
|
Location origin = location.clone().add(0, player.getHeight() / 2, 0).subtract(up.clone().multiply(HALF_BODY));
|
||||||
|
return new BodyFrame(origin, right, up, forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector forward = horizontal(BodyMotion.yaw(player));
|
||||||
|
Vector right = forward.clone().crossProduct(WORLD_UP).normalize();
|
||||||
|
return new BodyFrame(location, right, WORLD_UP.clone(), forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector horizontal(float yaw) {
|
||||||
|
double radians = Math.toRadians(yaw);
|
||||||
|
return new Vector(-Math.sin(radians), 0, Math.cos(radians));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector local(double right, double up, double forward) {
|
||||||
|
return this.right.clone().multiply(right)
|
||||||
|
.add(this.up.clone().multiply(up))
|
||||||
|
.add(this.forward.clone().multiply(forward));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A point on a ring around the up axis; angle 0 and the turning direction match the world rings
|
||||||
|
public Vector ring(double distance, double angle) {
|
||||||
|
return ringStart.clone().multiply(Math.cos(angle) * distance).add(ringSide.clone().multiply(Math.sin(angle) * distance));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* 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 lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
// How a player's body moves: the direction it faces, worked out the way the Minecraft client turns the body, because
|
||||||
|
// the server's own body rotation for players does not follow what other players see, and how fast it moves.
|
||||||
|
// Walking turns the body towards the walking direction; standing still it only turns once the head looks more than
|
||||||
|
// 50 degrees to the side. It also notices jumps, landings and starting to sneak, which reward touches react to.
|
||||||
|
@UtilityClass
|
||||||
|
public class BodyMotion {
|
||||||
|
|
||||||
|
private final float MAX_HEAD_TURN = 50;
|
||||||
|
private final double MOVING = 0.0025;
|
||||||
|
|
||||||
|
private final Map<Player, State> states = new WeakHashMap<>();
|
||||||
|
// Counts server ticks, so events can be matched to the tick they happened in
|
||||||
|
private long tick;
|
||||||
|
|
||||||
|
private static class State {
|
||||||
|
private float body;
|
||||||
|
private double x;
|
||||||
|
private double y;
|
||||||
|
private double z;
|
||||||
|
// Blocks per tick, smoothed so single steps do not jerk the wings
|
||||||
|
private double speed;
|
||||||
|
private boolean onGround = true;
|
||||||
|
private boolean sneaking;
|
||||||
|
// Highest point of the current time in the air
|
||||||
|
private double peakY;
|
||||||
|
private long jumpTick = Long.MIN_VALUE;
|
||||||
|
private long landTick = Long.MIN_VALUE;
|
||||||
|
private double landFall;
|
||||||
|
private long sneakTick = Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called once per server tick before the players are updated
|
||||||
|
public void nextTick() {
|
||||||
|
tick++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called once per server tick for every online player
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void tick(Player player) {
|
||||||
|
Location location = player.getLocation();
|
||||||
|
State state = states.get(player);
|
||||||
|
if (state == null) {
|
||||||
|
state = new State();
|
||||||
|
state.body = location.getYaw();
|
||||||
|
state.x = location.getX();
|
||||||
|
state.y = location.getY();
|
||||||
|
state.z = location.getZ();
|
||||||
|
states.put(player, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float head = location.getYaw();
|
||||||
|
double dx = location.getX() - state.x;
|
||||||
|
double dy = location.getY() - state.y;
|
||||||
|
double dz = location.getZ() - state.z;
|
||||||
|
state.x = location.getX();
|
||||||
|
state.y = location.getY();
|
||||||
|
state.z = location.getZ();
|
||||||
|
state.speed = state.speed * 0.8 + Math.sqrt(dx * dx + dy * dy + dz * dz) * 0.2;
|
||||||
|
|
||||||
|
boolean onGround = player.isOnGround();
|
||||||
|
if (state.onGround && !onGround && dy > 0) {
|
||||||
|
state.jumpTick = tick;
|
||||||
|
}
|
||||||
|
if (!onGround) {
|
||||||
|
state.peakY = state.onGround ? location.getY() : Math.max(state.peakY, location.getY());
|
||||||
|
} else if (!state.onGround) {
|
||||||
|
state.landTick = tick;
|
||||||
|
state.landFall = state.peakY - location.getY();
|
||||||
|
}
|
||||||
|
state.onGround = onGround;
|
||||||
|
if (player.isSneaking() && !state.sneaking) {
|
||||||
|
state.sneakTick = tick;
|
||||||
|
}
|
||||||
|
state.sneaking = player.isSneaking();
|
||||||
|
|
||||||
|
float target = state.body;
|
||||||
|
if (dx * dx + dz * dz > MOVING) {
|
||||||
|
target = (float) Math.toDegrees(Math.atan2(dz, dx)) - 90;
|
||||||
|
// Walking backwards keeps the body facing forwards
|
||||||
|
float away = Math.abs(wrap(head) - target);
|
||||||
|
if (away > 95 && away < 265) {
|
||||||
|
target -= 180;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.body += wrap(target - state.body) * 0.3f;
|
||||||
|
float headTurn = wrap(head - state.body);
|
||||||
|
if (Math.abs(headTurn) > MAX_HEAD_TURN) {
|
||||||
|
state.body += headTurn - Math.signum(headTurn) * MAX_HEAD_TURN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The tracked body direction in degrees, or the head direction for players not tracked yet
|
||||||
|
public float yaw(Player player) {
|
||||||
|
State state = states.get(player);
|
||||||
|
return state == null ? player.getLocation().getYaw() : state.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smoothed speed in blocks per tick; sprinting is about 0.28, fast elytra flight well above 1
|
||||||
|
public double speed(Player player) {
|
||||||
|
State state = states.get(player);
|
||||||
|
return state == null ? 0 : state.speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long currentTick() {
|
||||||
|
return tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean jumped(Player player) {
|
||||||
|
State state = states.get(player);
|
||||||
|
return state != null && state.jumpTick == tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ticks since the last jump, or a large number if there was none
|
||||||
|
public long ticksSinceJump(Player player) {
|
||||||
|
State state = states.get(player);
|
||||||
|
return state == null || state.jumpTick == Long.MIN_VALUE ? Long.MAX_VALUE : tick - state.jumpTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How far the player fell if they landed this tick, otherwise -1
|
||||||
|
public double landed(Player player) {
|
||||||
|
State state = states.get(player);
|
||||||
|
return state != null && state.landTick == tick ? state.landFall : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean startedSneaking(Player player) {
|
||||||
|
State state = states.get(player);
|
||||||
|
return state != null && state.sneakTick == tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float wrap(float degrees) {
|
||||||
|
float wrapped = degrees % 360;
|
||||||
|
if (wrapped >= 180) wrapped -= 360;
|
||||||
|
if (wrapped < -180) wrapped += 360;
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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.particle.slot.ParticleEntry;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSet;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CompiledSet {
|
||||||
|
|
||||||
|
private final List<ParticleElement> always = new ArrayList<>();
|
||||||
|
private final List<ParticleElement> move = new ArrayList<>();
|
||||||
|
|
||||||
|
public static CompiledSet of(ParticleElement... particleElements) {
|
||||||
|
CompiledSet compiledSet = new CompiledSet();
|
||||||
|
for (ParticleElement particleElement : particleElements) {
|
||||||
|
compiledSet.add(particleElement);
|
||||||
|
}
|
||||||
|
return compiledSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries whose design or particle type 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.isUnlocked(context)) {
|
||||||
|
compiledSet.add(entry.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compiledSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(ParticleElement particleElement) {
|
||||||
|
if (particleElement.tickType() == ParticleTickType.ALWAYS) {
|
||||||
|
always.add(particleElement);
|
||||||
|
} else {
|
||||||
|
move.add(particleElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAlways() {
|
||||||
|
return !always.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMove() {
|
||||||
|
return !move.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tickAlways(ParticleTickData particleTickData) {
|
||||||
|
always.forEach(particleElement -> particleElement.tick(particleTickData));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tickMove(ParticleTickData particleTickData) {
|
||||||
|
move.forEach(particleElement -> particleElement.tick(particleTickData));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
// Wing shapes drawn from lines and curves instead of images, so their number of points can be chosen.
|
||||||
|
// Coordinates use the same units as the wing images: x is sideways from the spine, y upwards, about 11 by 13.
|
||||||
|
public enum DrawnWings {
|
||||||
|
|
||||||
|
// An arm bone along the top, fingers spreading from the wrist and skin hanging between them
|
||||||
|
BAT {
|
||||||
|
@Override
|
||||||
|
List<List<Vector>> rightWing() {
|
||||||
|
List<List<Vector>> lines = new ArrayList<>();
|
||||||
|
lines.add(curve(p(0.5, 7), p(3, 9.5), p(6, 11), p(11, 12.5)));
|
||||||
|
lines.add(curve(p(6, 11), p(7, 4)));
|
||||||
|
lines.add(curve(p(6, 11), p(9, 6)));
|
||||||
|
lines.add(curve(p(6, 11), p(10.8, 9)));
|
||||||
|
lines.add(curve(p(0.5, 5), p(3.5, 4.6), p(5.5, 3.4), p(7, 4), p(7.6, 5.4), p(9, 6), p(9.6, 7.6), p(10.8, 9), p(11.2, 10.8), p(11, 12.5)));
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// The butterfly curve r = e^cos(t) - 2 cos(4t), which already holds both wings; its small loops around the body are left out
|
||||||
|
BUTTERFLY {
|
||||||
|
@Override
|
||||||
|
List<List<Vector>> rightWing() {
|
||||||
|
List<List<Vector>> lines = new ArrayList<>();
|
||||||
|
List<Vector> current = new ArrayList<>();
|
||||||
|
for (int i = 0; i <= 400; i++) {
|
||||||
|
double t = Math.PI * i / 400;
|
||||||
|
double r = Math.exp(Math.cos(t)) - 2 * Math.cos(4 * t);
|
||||||
|
if (Math.abs(r) < 0.9) {
|
||||||
|
if (current.size() > 1) lines.add(current);
|
||||||
|
current = new ArrayList<>();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current.add(p(Math.sin(t) * r * 3, Math.cos(t) * r * 2 + 6));
|
||||||
|
}
|
||||||
|
if (current.size() > 1) lines.add(current);
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Two thin rounded pairs, a long one above and a short one below
|
||||||
|
FAIRY {
|
||||||
|
@Override
|
||||||
|
List<List<Vector>> rightWing() {
|
||||||
|
return List.of(ellipse(5.2, 9.3, 5, 1.9, 35), ellipse(3.8, 4.6, 3.6, 1.5, -30));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
;
|
||||||
|
|
||||||
|
// The right wing as lines; the left wing mirrors it
|
||||||
|
abstract List<List<Vector>> rightWing();
|
||||||
|
|
||||||
|
private static final Map<String, WingDesign> CACHE = new HashMap<>();
|
||||||
|
// The shapes are drawn with the shoulder joint at y = 7; moving it to 3.3 puts it at the shoulder blades
|
||||||
|
// (about 1.3 blocks above the feet at the default size) instead of at head height
|
||||||
|
private static final double SHOULDER_SHIFT = 7 - 3.3;
|
||||||
|
|
||||||
|
// Both wings with the given number of points per wing, spread evenly along the lines
|
||||||
|
public WingDesign withDetail(int pointsPerWing) {
|
||||||
|
return CACHE.computeIfAbsent(name() + "@" + pointsPerWing, key -> {
|
||||||
|
List<Vector> right = sample(rightWing(), pointsPerWing);
|
||||||
|
right.forEach(point -> point.setY(point.getY() - SHOULDER_SHIFT));
|
||||||
|
List<Vector> both = new ArrayList<>(right);
|
||||||
|
right.forEach(point -> both.add(new Vector(-point.getX(), point.getY(), 0)));
|
||||||
|
return new WingDesign.WingDesignImpl(both.toArray(new Vector[0]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Vector> sample(List<List<Vector>> lines, int count) {
|
||||||
|
double total = lines.stream().mapToDouble(DrawnWings::length).sum();
|
||||||
|
List<Vector> points = new ArrayList<>();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
double distance = total * (i + 0.5) / count;
|
||||||
|
for (List<Vector> line : lines) {
|
||||||
|
double length = length(line);
|
||||||
|
if (distance <= length) {
|
||||||
|
points.add(at(line, distance));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
distance -= length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double length(List<Vector> line) {
|
||||||
|
double length = 0;
|
||||||
|
for (int i = 1; i < line.size(); i++) length += line.get(i).distance(line.get(i - 1));
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector at(List<Vector> line, double distance) {
|
||||||
|
for (int i = 1; i < line.size(); i++) {
|
||||||
|
double segment = line.get(i).distance(line.get(i - 1));
|
||||||
|
if (distance <= segment) {
|
||||||
|
return line.get(i - 1).clone().add(line.get(i).clone().subtract(line.get(i - 1)).multiply(segment == 0 ? 0 : distance / segment));
|
||||||
|
}
|
||||||
|
distance -= segment;
|
||||||
|
}
|
||||||
|
return line.get(line.size() - 1).clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector p(double x, double y) {
|
||||||
|
return new Vector(x, y, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A smooth curve through the given points (Catmull-Rom), as many short segments
|
||||||
|
private static List<Vector> curve(Vector... points) {
|
||||||
|
List<Vector> result = new ArrayList<>();
|
||||||
|
for (int i = 0; i < points.length - 1; i++) {
|
||||||
|
Vector p0 = points[Math.max(0, i - 1)], p1 = points[i], p2 = points[i + 1], p3 = points[Math.min(points.length - 1, i + 2)];
|
||||||
|
for (int step = 0; step < 16; step++) {
|
||||||
|
double t = step / 16.0, t2 = t * t, t3 = t2 * t;
|
||||||
|
result.add(p1.clone().multiply(2)
|
||||||
|
.add(p2.clone().subtract(p0).multiply(t))
|
||||||
|
.add(p0.clone().multiply(2).subtract(p1.clone().multiply(5)).add(p2.clone().multiply(4)).subtract(p3).multiply(t2))
|
||||||
|
.add(p1.clone().multiply(3).subtract(p0).subtract(p2.clone().multiply(3)).add(p3).multiply(t3))
|
||||||
|
.multiply(0.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(points[points.length - 1].clone());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Vector> ellipse(double centerX, double centerY, double radiusX, double radiusY, double tiltDegrees) {
|
||||||
|
double tilt = Math.toRadians(tiltDegrees);
|
||||||
|
List<Vector> result = new ArrayList<>();
|
||||||
|
for (int i = 0; i <= 64; i++) {
|
||||||
|
double a = 2 * Math.PI * i / 64;
|
||||||
|
double x = Math.cos(a) * radiusX, y = Math.sin(a) * radiusY;
|
||||||
|
result.add(p(centerX + x * Math.cos(tilt) - y * Math.sin(tilt), centerY + x * Math.sin(tilt) + y * Math.cos(tilt)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.inventory.SWItem;
|
|
||||||
import de.steamwar.lobby.LobbySystem;
|
|
||||||
import de.steamwar.sql.SteamwarUser;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class ParticleData {
|
|
||||||
|
|
||||||
private final Material material;
|
|
||||||
private final String name;
|
|
||||||
private final List<String> attributes = new ArrayList<>();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final ParticleRequirement requirement;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final ParticleElement particleElement;
|
|
||||||
|
|
||||||
public ParticleData(Material material, String name, ParticleElement particleElement) {
|
|
||||||
this.material = material;
|
|
||||||
this.name = name;
|
|
||||||
this.requirement = ParticleRequirement.NO_REQUIRMENT;
|
|
||||||
this.particleElement = particleElement;
|
|
||||||
particleElement.aggregateAttributes(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ParticleData(Material material, String name, ParticleRequirement requirement, ParticleElement particleElement) {
|
|
||||||
this.material = material;
|
|
||||||
this.name = name;
|
|
||||||
this.requirement = requirement;
|
|
||||||
this.particleElement = particleElement;
|
|
||||||
particleElement.aggregateAttributes(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ParticleData add(String attribute) {
|
|
||||||
attributes.add(attribute);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SWItem toSWItem(Player player, SteamwarUser user, Set<Integer> eventTeilnahme, String eggHuntConfig) {
|
|
||||||
String translatedName;
|
|
||||||
try {
|
|
||||||
translatedName = LobbySystem.getMessage().parse(name, player);
|
|
||||||
} catch (Exception e) {
|
|
||||||
translatedName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> lore = new ArrayList<>();
|
|
||||||
lore.add("");
|
|
||||||
if (!attributes.isEmpty()) {
|
|
||||||
lore.add(LobbySystem.getMessage().parse("PARTICLE_ATTRIBUTE", player));
|
|
||||||
attributes.forEach(attribute -> lore.add(LobbySystem.getMessage().parse(attribute, player)));
|
|
||||||
lore.add("");
|
|
||||||
}
|
|
||||||
|
|
||||||
String unlockedBy = requirement.getRequirementName(player);
|
|
||||||
if (unlockedBy != null) {
|
|
||||||
lore.add(LobbySystem.getMessage().parse("PARTICLE_UNLOCKED_BY", player));
|
|
||||||
lore.add(unlockedBy);
|
|
||||||
lore.add("");
|
|
||||||
}
|
|
||||||
if (requirement.test(user, eventTeilnahme, eggHuntConfig)) {
|
|
||||||
lore.add(LobbySystem.getMessage().parse("PARTICLE_SELECT", player));
|
|
||||||
} else {
|
|
||||||
lore.add(LobbySystem.getMessage().parse("PARTICLE_LOCKED", player));
|
|
||||||
}
|
|
||||||
return new SWItem(material, translatedName, lore, false, clickType -> {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,9 +19,7 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle;
|
package de.steamwar.lobby.particle;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Color;
|
import org.bukkit.Color;
|
||||||
import org.bukkit.Location;
|
|
||||||
import org.bukkit.Particle;
|
import org.bukkit.Particle;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
@@ -47,23 +45,18 @@ public interface ParticleElement {
|
|||||||
return new Particle.DustOptions(Color.fromRGB(r, g, b), (float) (RANDOM.nextFloat() / 2 + 0.5));
|
return new Particle.DustOptions(Color.fromRGB(r, g, b), (float) (RANDOM.nextFloat() / 2 + 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
default void display(Location location, Player root, boolean onlySelf, boolean onlyOther, Consumer<Player> consumer) {
|
default void display(ParticleTickData particleTickData, Consumer<Player> consumer) {
|
||||||
if (onlySelf) {
|
Player root = particleTickData.getPlayer();
|
||||||
|
if (particleTickData.isOnlySelf()) {
|
||||||
consumer.accept(root);
|
consumer.accept(root);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
for (Player player : particleTickData.getViewers()) {
|
||||||
if (onlyOther && player == root) {
|
if (particleTickData.isOnlyOthers() && player == root) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
int viewDistance = player.getClientViewDistance() * 16;
|
|
||||||
if (location.distanceSquared(player.getLocation()) <= viewDistance * viewDistance) {
|
|
||||||
consumer.accept(player);
|
consumer.accept(player);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
default void aggregateAttributes(ParticleData particleData) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default ParticleTickType tickType() {
|
default ParticleTickType tickType() {
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.inventory.SWItem;
|
|
||||||
import de.steamwar.inventory.SWListInv;
|
|
||||||
import de.steamwar.lobby.LobbySystem;
|
|
||||||
import de.steamwar.lobby.particle.particles.*;
|
|
||||||
import de.steamwar.lobby.particle.particles.custom.CustomEasterParticle;
|
|
||||||
import de.steamwar.lobby.particle.particles.custom.CustomPlayerParticle;
|
|
||||||
import de.steamwar.lobby.particle.particles.custom.CustomTeamParticle;
|
|
||||||
import de.steamwar.lobby.special.easter.EggHunt;
|
|
||||||
import de.steamwar.lobby.util.LobbyPlayer;
|
|
||||||
import de.steamwar.sql.Event;
|
|
||||||
import de.steamwar.sql.SteamwarUser;
|
|
||||||
import de.steamwar.sql.TeamTeilnahme;
|
|
||||||
import de.steamwar.sql.UserConfig;
|
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@UtilityClass
|
|
||||||
public class ParticleInventory {
|
|
||||||
|
|
||||||
private final Class<?>[] PARTICLES = {
|
|
||||||
PlayerParticle.class,
|
|
||||||
CustomPlayerParticle.class,
|
|
||||||
TeamParticle.class,
|
|
||||||
CustomTeamParticle.class,
|
|
||||||
RainCloudParticle.class,
|
|
||||||
ServerTeamParticle.class,
|
|
||||||
EventParticle.class,
|
|
||||||
EventParticlePlacement.class,
|
|
||||||
EventParticleParticipation.class,
|
|
||||||
EasterParticle.class,
|
|
||||||
CustomEasterParticle.class,
|
|
||||||
};
|
|
||||||
|
|
||||||
public String convertToString(ParticleEnum particleEnum) {
|
|
||||||
return particleEnum.getClass().getSimpleName() + "@" + ((Enum<?>) particleEnum).name();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ParticleEnum convertFromString(String string) {
|
|
||||||
String[] split = string.split("@");
|
|
||||||
Class<?> clazz = null;
|
|
||||||
for (Class<?> aClass : PARTICLES) {
|
|
||||||
if (aClass.getSimpleName().equals(split[0])) {
|
|
||||||
clazz = aClass;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (clazz != null) {
|
|
||||||
try {
|
|
||||||
return (ParticleEnum) Enum.valueOf((Class<? extends Enum>) clazz, split[1]);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openInventory(Player player, boolean onlyUnlocked) {
|
|
||||||
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId());
|
|
||||||
|
|
||||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
|
||||||
Set<Integer> events = user.getTeam() == 0 ? new HashSet<>() : TeamTeilnahme.getEvents(user.getTeam()).stream().map(Event::getEventID).collect(Collectors.toSet());
|
|
||||||
String eggHuntConfig = UserConfig.getConfig(user.getId(), EggHunt.EGG_HUNT_CONFIG_KEY);
|
|
||||||
|
|
||||||
AtomicBoolean hasOneLocked = new AtomicBoolean(false);
|
|
||||||
List<SWListInv.SWListEntry<ParticleEnum>> particleList = new ArrayList<>();
|
|
||||||
for (Class<?> clazz : PARTICLES) {
|
|
||||||
addParticles(particleList, (ParticleEnum[]) clazz.getEnumConstants(), player, onlyUnlocked, hasOneLocked, user, events, eggHuntConfig);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < particleList.size() % 45; i++) {
|
|
||||||
particleList.add(new SWListInv.SWListEntry<>(new SWItem(Material.BLACK_STAINED_GLASS_PANE, " "), null));
|
|
||||||
}
|
|
||||||
|
|
||||||
SWListInv<ParticleEnum> particleSWListInv = new SWListInv<>(player, LobbySystem.getMessage().parse("PARTICLE_INVENTORY", player), false, particleList, (clickType, particle) -> {
|
|
||||||
if (particle == null) return;
|
|
||||||
lobbyPlayer.setParticle(particle);
|
|
||||||
player.closeInventory();
|
|
||||||
if (ParticleListener.disabled(player)) {
|
|
||||||
LobbySystem.getMessage().send("PARTICLE_DEACTIVATED", player);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
particleSWListInv.setItem(48, Material.BARRIER, LobbySystem.getMessage().parse("PARTICLE_DESELECT", player), new ArrayList<>(), false, clickType -> {
|
|
||||||
lobbyPlayer.setParticle(null);
|
|
||||||
});
|
|
||||||
if (!hasOneLocked.get()) {
|
|
||||||
particleSWListInv.setItem(50, Material.GRAY_BANNER, LobbySystem.getMessage().parse("PARTICLE_SHOW_HAS_ALL", player), new ArrayList<>(), false, clickType -> {
|
|
||||||
});
|
|
||||||
} else if (onlyUnlocked) {
|
|
||||||
particleSWListInv.setItem(50, Material.LIME_BANNER, LobbySystem.getMessage().parse("PARTICLE_SHOW_ALL", player), new ArrayList<>(), false, clickType -> {
|
|
||||||
openInventory(player, false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
particleSWListInv.setItem(50, Material.RED_BANNER, LobbySystem.getMessage().parse("PARTICLE_SHOW_UNLOCKED", player), new ArrayList<>(), false, clickType -> {
|
|
||||||
openInventory(player, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
particleSWListInv.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addParticles(List<SWListInv.SWListEntry<ParticleEnum>> particleList, ParticleEnum[] particleEnums, Player player, boolean onlyUnlocked, AtomicBoolean hasOneLocked, SteamwarUser user, Set<Integer> events, String eggHuntConfig) {
|
|
||||||
for (ParticleEnum particle : particleEnums) {
|
|
||||||
ParticleData particleData = particle.getParticle();
|
|
||||||
SWItem swItem = particleData.toSWItem(player, user, events, eggHuntConfig);
|
|
||||||
if (particleData.getRequirement().test(user, events, eggHuntConfig)) {
|
|
||||||
particleList.add(new SWListInv.SWListEntry<>(swItem, particle));
|
|
||||||
} else {
|
|
||||||
hasOneLocked.set(true);
|
|
||||||
if (!onlyUnlocked) {
|
|
||||||
particleList.add(new SWListInv.SWListEntry<>(swItem, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < particleList.size() % 9; i++) {
|
|
||||||
particleList.add(new SWListInv.SWListEntry<>(new SWItem(Material.BLACK_STAINED_GLASS_PANE, " "), null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,20 +23,25 @@ import de.steamwar.linkage.Linked;
|
|||||||
import de.steamwar.lobby.LobbySystem;
|
import de.steamwar.lobby.LobbySystem;
|
||||||
import de.steamwar.lobby.jumpandrun.JumpAndRun;
|
import de.steamwar.lobby.jumpandrun.JumpAndRun;
|
||||||
import de.steamwar.lobby.listener.PlayerSpawn;
|
import de.steamwar.lobby.listener.PlayerSpawn;
|
||||||
|
import de.steamwar.lobby.particle.gui.ParticleMenu;
|
||||||
import de.steamwar.lobby.util.LobbyPlayer;
|
import de.steamwar.lobby.util.LobbyPlayer;
|
||||||
import de.steamwar.sql.SteamwarUser;
|
import de.steamwar.sql.SteamwarUser;
|
||||||
import de.steamwar.sql.UserPerm;
|
import de.steamwar.sql.UserPerm;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
import org.bukkit.event.player.PlayerMoveEvent;
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Linked
|
@Linked
|
||||||
@@ -55,17 +60,14 @@ public class ParticleListener implements Listener {
|
|||||||
movingPlayers.entrySet().removeIf(entry -> entry.getValue() <= 0);
|
movingPlayers.entrySet().removeIf(entry -> entry.getValue() <= 0);
|
||||||
deg += 0.1;
|
deg += 0.1;
|
||||||
if (deg > 360) deg = 0;
|
if (deg > 360) deg = 0;
|
||||||
|
BodyMotion.nextTick();
|
||||||
|
Bukkit.getOnlinePlayers().forEach(BodyMotion::tick);
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||||
if (disabled(player)) return;
|
if (disabled(player)) return;
|
||||||
if (JumpAndRun.isPlayerInJumpAndRun(player)) return;
|
if (JumpAndRun.isPlayerInJumpAndRun(player)) return;
|
||||||
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId());
|
CompiledSet compiledSet = LobbyPlayer.getLobbyPlayer(player.getUniqueId()).getCompiledSet();
|
||||||
ParticleEnum particle = lobbyPlayer.getParticle();
|
if (compiledSet == null || !compiledSet.hasAlways()) return;
|
||||||
if (particle == null) return;
|
compiledSet.tickAlways(new ParticleTickData(player.getWorld(), player, deg, movingPlayers.containsKey(player), viewers(player)));
|
||||||
ParticleData particleData = particle.getParticle();
|
|
||||||
ParticleElement particleElement = particleData.getParticleElement();
|
|
||||||
if (particleElement.tickType() == ParticleTickType.ALWAYS) {
|
|
||||||
particleElement.tick(new ParticleTickData(player.getWorld(), player, deg, movingPlayers.containsKey(player)));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, 0, 1);
|
}, 0, 1);
|
||||||
}
|
}
|
||||||
@@ -77,7 +79,7 @@ public class ParticleListener implements Listener {
|
|||||||
if (!PlayerSpawn.PARTICLE.equals(event.getItem())) return;
|
if (!PlayerSpawn.PARTICLE.equals(event.getItem())) return;
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
|
|
||||||
ParticleInventory.openInventory(player, true);
|
ParticleMenu.open(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
@@ -85,20 +87,23 @@ public class ParticleListener implements Listener {
|
|||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
if (disabled(player)) return;
|
if (disabled(player)) return;
|
||||||
if (JumpAndRun.isPlayerInJumpAndRun(player)) return;
|
if (JumpAndRun.isPlayerInJumpAndRun(player)) return;
|
||||||
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId());
|
CompiledSet compiledSet = LobbyPlayer.getLobbyPlayer(player.getUniqueId()).getCompiledSet();
|
||||||
ParticleEnum particle = lobbyPlayer.getParticle();
|
if (compiledSet == null) return;
|
||||||
if (particle == null) return;
|
|
||||||
|
|
||||||
ParticleData particleData = particle.getParticle();
|
if (compiledSet.hasMove()) {
|
||||||
ParticleElement particleElement = particleData.getParticleElement();
|
compiledSet.tickMove(new ParticleTickData(player.getWorld(), player, deg, true, viewers(player)));
|
||||||
if (particleElement.tickType() == ParticleTickType.MOVE) {
|
|
||||||
particleElement.tick(new ParticleTickData(player.getWorld(), player, deg, true));
|
|
||||||
}
|
}
|
||||||
if (event.getFrom().getX() != event.getTo().getX() || event.getFrom().getY() != event.getTo().getY() || event.getFrom().getZ() != event.getTo().getZ()) {
|
if (event.getFrom().getX() != event.getTo().getX() || event.getFrom().getY() != event.getTo().getY() || event.getFrom().getZ() != event.getTo().getZ()) {
|
||||||
movingPlayers.put(player, 5);
|
movingPlayers.put(player, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
@EventHandler(ignoreCancelled = true)
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
movingPlayers.remove(event.getPlayer());
|
movingPlayers.remove(event.getPlayer());
|
||||||
@@ -111,6 +116,18 @@ public class ParticleListener implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<Player> viewers(Player root) {
|
||||||
|
Location location = root.getLocation();
|
||||||
|
List<Player> viewers = new ArrayList<>();
|
||||||
|
for (Player player : root.getWorld().getPlayers()) {
|
||||||
|
int viewDistance = player.getClientViewDistance() * 16;
|
||||||
|
if (location.distanceSquared(player.getLocation()) <= viewDistance * viewDistance) {
|
||||||
|
viewers.add(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return viewers;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean disabled(Player player) {
|
public static boolean disabled(Player player) {
|
||||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||||
if (Bukkit.getOnlinePlayers().size() > SERVER_TEAM_MAX_SIZE) return true;
|
if (Bukkit.getOnlinePlayers().size() > SERVER_TEAM_MAX_SIZE) return true;
|
||||||
|
|||||||
@@ -39,7 +39,13 @@ public interface ParticleRequirement {
|
|||||||
|
|
||||||
String getRequirementName(Player player);
|
String getRequirementName(Player player);
|
||||||
|
|
||||||
boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig);
|
// Whether the player meets this requirement
|
||||||
|
boolean matches(UnlockContext context);
|
||||||
|
|
||||||
|
// Administrators pass every requirement; lore still names the regular requirement
|
||||||
|
default boolean test(UnlockContext context) {
|
||||||
|
return context.getUser().hasPerm(UserPerm.ADMINISTRATION) || matches(context);
|
||||||
|
}
|
||||||
|
|
||||||
ParticleRequirement SERVER_TEAM = new ParticleRequirement() {
|
ParticleRequirement SERVER_TEAM = new ParticleRequirement() {
|
||||||
@Override
|
@Override
|
||||||
@@ -48,8 +54,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return user.hasPerm(UserPerm.TEAM);
|
return context.getUser().hasPerm(UserPerm.TEAM);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,21 +66,23 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ParticleRequirement HAS_TEAM = new ParticleRequirement() {
|
|
||||||
|
// Does not also pass for the server team
|
||||||
|
ParticleRequirement TEAM_MEMBER = new ParticleRequirement() {
|
||||||
@Override
|
@Override
|
||||||
public String getRequirementName(Player player) {
|
public String getRequirementName(Player player) {
|
||||||
return LobbySystem.getMessage().parse("PARTICLE_UNLOCKED_BY_TEAM", player);
|
return LobbySystem.getMessage().parse("PARTICLE_UNLOCKED_BY_TEAM", player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return user.getTeam() != 0;
|
return context.getUser().getTeam() != 0;
|
||||||
}
|
}
|
||||||
}.or(SERVER_TEAM);
|
};
|
||||||
ParticleRequirement EVENT_PARTICIPATION = new ParticleRequirement() {
|
ParticleRequirement EVENT_PARTICIPATION = new ParticleRequirement() {
|
||||||
@Override
|
@Override
|
||||||
public String getRequirementName(Player player) {
|
public String getRequirementName(Player player) {
|
||||||
@@ -82,8 +90,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return !eventTeilname.isEmpty();
|
return !context.getEvents().isEmpty();
|
||||||
}
|
}
|
||||||
}.or(SERVER_TEAM);
|
}.or(SERVER_TEAM);
|
||||||
ParticleRequirement EGG_HUNT_EASY = _easterEggHuntDifficulty(EggDifficulty.EASY);
|
ParticleRequirement EGG_HUNT_EASY = _easterEggHuntDifficulty(EggDifficulty.EASY);
|
||||||
@@ -98,7 +106,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
|
String eggHuntConfig = context.getEggHuntConfig();
|
||||||
if (eggHuntConfig == null) return false;
|
if (eggHuntConfig == null) return false;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < eggHuntConfig.length(); i++) {
|
for (int i = 0; i < eggHuntConfig.length(); i++) {
|
||||||
@@ -120,8 +129,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return user.getTeam() == teamId;
|
return context.getUser().getTeam() == teamId;
|
||||||
}
|
}
|
||||||
}.or(SERVER_TEAM);
|
}.or(SERVER_TEAM);
|
||||||
}
|
}
|
||||||
@@ -135,8 +144,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return eventTeilname.contains(eventId);
|
return context.getEvents().contains(eventId);
|
||||||
}
|
}
|
||||||
}.or(SERVER_TEAM);
|
}.or(SERVER_TEAM);
|
||||||
}
|
}
|
||||||
@@ -152,9 +161,9 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
if (!eventTeilname.contains(eventId)) return false;
|
if (!teams.contains(context.getUser().getTeam())) return false;
|
||||||
return teams.contains(user.getTeam());
|
return context.getEvents().contains(eventId);
|
||||||
}
|
}
|
||||||
}.or(SERVER_TEAM);
|
}.or(SERVER_TEAM);
|
||||||
}
|
}
|
||||||
@@ -181,7 +190,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
|
String eggHuntConfig = context.getEggHuntConfig();
|
||||||
if (eggHuntConfig == null) return false;
|
if (eggHuntConfig == null) return false;
|
||||||
return eggs.stream().allMatch(id -> eggHuntConfig.length() > id && eggHuntConfig.charAt(id) == '1');
|
return eggs.stream().allMatch(id -> eggHuntConfig.length() > id && eggHuntConfig.charAt(id) == '1');
|
||||||
}
|
}
|
||||||
@@ -197,8 +207,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return user.getId() == userId;
|
return context.getUser().getId() == userId;
|
||||||
}
|
}
|
||||||
}.or(SERVER_TEAM);
|
}.or(SERVER_TEAM);
|
||||||
}
|
}
|
||||||
@@ -212,8 +222,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return user.getId() == userId;
|
return context.getUser().getId() == userId;
|
||||||
}
|
}
|
||||||
}.or(SERVER_TEAM);
|
}.or(SERVER_TEAM);
|
||||||
}
|
}
|
||||||
@@ -228,12 +238,31 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return user.getTeam() == teamId;
|
return context.getUser().getTeam() == teamId;
|
||||||
}
|
}
|
||||||
}.or(SERVER_TEAM);
|
}.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) {
|
default ParticleRequirement or(ParticleRequirement particleRequirement) {
|
||||||
return new ParticleRequirement() {
|
return new ParticleRequirement() {
|
||||||
@Override
|
@Override
|
||||||
@@ -242,8 +271,8 @@ public interface ParticleRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
public boolean matches(UnlockContext context) {
|
||||||
return ParticleRequirement.this.test(user, eventTeilname, eggHuntConfig) || particleRequirement.test(user, eventTeilname, eggHuntConfig);
|
return particleRequirement.test(context) || ParticleRequirement.this.test(context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import org.bukkit.Location;
|
|||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@@ -36,8 +38,13 @@ public class ParticleTickData {
|
|||||||
private Location location;
|
private Location location;
|
||||||
private final double deg;
|
private final double deg;
|
||||||
private final boolean isMoving;
|
private final boolean isMoving;
|
||||||
|
private final List<Player> viewers;
|
||||||
private boolean onlySelf = false;
|
private boolean onlySelf = false;
|
||||||
private boolean onlyOthers = false;
|
private boolean onlyOthers = false;
|
||||||
|
// Set for entries placed relative to the body, null for world placement
|
||||||
|
private BodyFrame frame;
|
||||||
|
// Where a point sits along its shape, from 0 to 1; when set, gradients colour by it instead of by time
|
||||||
|
private Double shade;
|
||||||
|
|
||||||
public ParticleTickData withLocation(Location location) {
|
public ParticleTickData withLocation(Location location) {
|
||||||
ParticleTickData particleTickData = copy();
|
ParticleTickData particleTickData = copy();
|
||||||
@@ -45,6 +52,24 @@ public class ParticleTickData {
|
|||||||
return particleTickData;
|
return particleTickData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ParticleTickData withFrame(BodyFrame frame) {
|
||||||
|
ParticleTickData particleTickData = copy();
|
||||||
|
particleTickData.frame = frame;
|
||||||
|
particleTickData.location = frame.getOrigin().clone();
|
||||||
|
return particleTickData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleTickData withShade(double shade) {
|
||||||
|
ParticleTickData particleTickData = copy();
|
||||||
|
particleTickData.shade = shade;
|
||||||
|
return particleTickData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The gradient position for this tick: along the shape when a shade is set, otherwise moving with time
|
||||||
|
public double gradientPosition() {
|
||||||
|
return shade != null ? Math.min(shade, 0.9999) * 360 : (deg * 10) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
public ParticleTickData onlySelf() {
|
public ParticleTickData onlySelf() {
|
||||||
ParticleTickData particleTickData = copy();
|
ParticleTickData particleTickData = copy();
|
||||||
particleTickData.onlySelf = true;
|
particleTickData.onlySelf = true;
|
||||||
@@ -65,6 +90,6 @@ public class ParticleTickData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ParticleTickData copy() {
|
public ParticleTickData copy() {
|
||||||
return new ParticleTickData(world, player, location, deg, isMoving, onlySelf, onlyOthers);
|
return new ParticleTickData(world, player, location, deg, isMoving, viewers, onlySelf, onlyOthers, frame, shade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,102 @@
|
|||||||
|
/*
|
||||||
|
* 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 de.steamwar.lobby.particle.ParticleRequirement;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class ChoiceParameter<E> extends Parameter<E> {
|
||||||
|
|
||||||
|
// Internal choices are usable by presets but never offered in the builder or accepted from storage. Locked choices
|
||||||
|
// are offered with their requirement; the requirement is looked up lazily as it may live in a reward design.
|
||||||
|
public record Choice<E>(String id, E value, boolean internal, Supplier<ParticleRequirement> requirement) {
|
||||||
|
|
||||||
|
public Choice(String id, E value, boolean internal) {
|
||||||
|
this(id, value, internal, () -> ParticleRequirement.NO_REQUIRMENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Function<E, ParticleRequirement> requirement) {
|
||||||
|
List<Choice<E>> choices = new ArrayList<>();
|
||||||
|
for (E value : defaultValue.getDeclaringClass().getEnumConstants()) {
|
||||||
|
choices.add(new Choice<>(value.name(), value, internal.test(value), () -> requirement.apply(value)));
|
||||||
|
}
|
||||||
|
return new ChoiceParameter<>(key, nameKey, defaultValue, choices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends Enum<E>> ChoiceParameter<E> ofEnum(String key, String nameKey, E defaultValue, Predicate<E> internal) {
|
||||||
|
return ofEnum(key, nameKey, defaultValue, internal, value -> ParticleRequirement.NO_REQUIRMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleRequirement requirement(E value) {
|
||||||
|
return choiceOf(value).requirement().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,201 @@
|
|||||||
|
/*
|
||||||
|
* 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 de.steamwar.lobby.particle.DrawnWings;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
// Built on first use, so the design classes never depend on each other's initialisation order
|
||||||
|
private Map<String, ParticleDesign> registry;
|
||||||
|
|
||||||
|
public final ParticleDesign SIMPLE = 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 = 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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Images keep their old ids, so saved wings still load; preset and tournament images stay out of the builder
|
||||||
|
private final List<ChoiceParameter.Choice<WingShape>> WING_SHAPES = List.of(
|
||||||
|
new ChoiceParameter.Choice<>("simple", WingShape.image(WingDesign.SIMPLE, Material.FEATHER), false),
|
||||||
|
new ChoiceParameter.Choice<>("complex", WingShape.image(WingDesign.COMPLEX, Material.PHANTOM_MEMBRANE), false),
|
||||||
|
new ChoiceParameter.Choice<>("sword", WingShape.image(WingDesign.SWORD, Material.STONE_SWORD), false),
|
||||||
|
new ChoiceParameter.Choice<>("bat", WingShape.drawn(DrawnWings.BAT, Material.BAT_SPAWN_EGG), false),
|
||||||
|
new ChoiceParameter.Choice<>("butterfly", WingShape.drawn(DrawnWings.BUTTERFLY, Material.PINK_PETALS), false),
|
||||||
|
new ChoiceParameter.Choice<>("fairy", WingShape.drawn(DrawnWings.FAIRY, Material.ALLAY_SPAWN_EGG), false),
|
||||||
|
// Tournament wing images, unlocked by the placement that earns their reward
|
||||||
|
new ChoiceParameter.Choice<>("mwgl", WingShape.image(WingDesign.MWGL, Material.IRON_SWORD), false, Unlocks.MINI_WARGEAR_LIGA_PLACEMENT),
|
||||||
|
new ChoiceParameter.Choice<>("sw", WingShape.image(WingDesign.SW, Material.CYAN_CARPET), false, Unlocks.UNDERWATER_MWG_PLACEMENT),
|
||||||
|
new ChoiceParameter.Choice<>("wgs", WingShape.image(WingDesign.WGS, Material.DIAMOND_HELMET), false, Unlocks.WARGEAR_SEASON_2022_PLACEMENT),
|
||||||
|
new ChoiceParameter.Choice<>("sword_crossed", WingShape.image(WingDesign.SWORD_CROSSED, Material.GOLDEN_SWORD), false, Unlocks.WARGEAR_CLASH_PLACEMENT),
|
||||||
|
new ChoiceParameter.Choice<>("eclipse", WingShape.image(WingDesign.ECLIPSE), true),
|
||||||
|
new ChoiceParameter.Choice<>("pl", WingShape.image(WingDesign.PL), true),
|
||||||
|
new ChoiceParameter.Choice<>("plompa_easter", WingShape.image(WingDesign.PlompaEasterWings), true),
|
||||||
|
new ChoiceParameter.Choice<>("ely_e", WingShape.image(WingDesign.ELY_E), true),
|
||||||
|
new ChoiceParameter.Choice<>("ely_l", WingShape.image(WingDesign.ELY_L), true),
|
||||||
|
new ChoiceParameter.Choice<>("ely_y", WingShape.image(WingDesign.ELY_Y), true),
|
||||||
|
new ChoiceParameter.Choice<>("ev", WingShape.image(WingDesign.EV), true)
|
||||||
|
);
|
||||||
|
public final ChoiceParameter<WingShape> WING_SHAPE = new ChoiceParameter<>("image", "PARTICLE_PARAM_WING_IMAGE", WING_SHAPES.get(0).value(), WING_SHAPES);
|
||||||
|
// Points per wing of drawn shapes; it replaces the amount, as every point spawns one particle
|
||||||
|
public final IntParameter WING_DETAIL = new IntParameter("detail", "PARTICLE_PARAM_WING_DETAIL", 20, 6, 60);
|
||||||
|
public final DoubleParameter WING_SIZE = new DoubleParameter("size", "PARTICLE_PARAM_WING_SIZE", 0.15, 0.05, 0.3, 0.01);
|
||||||
|
|
||||||
|
public enum WingColoring {
|
||||||
|
// The whole wing changes colour through the entry's colours over time
|
||||||
|
OVER_TIME,
|
||||||
|
// The colours run from the spine to the wing tips
|
||||||
|
ALONG_WING,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 -> 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;
|
||||||
|
|
||||||
|
public final ParticleDesign WING = new ParticleDesign("wing", Material.ELYTRA, "PARTICLE_DESIGN_WING", WING_SHAPE, WING_DETAIL, WING_SIZE, WING_COLORING, WING_SPARKLE, WING_MOTION) {
|
||||||
|
@Override
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean redrawsAfterJump(ParameterValues values) {
|
||||||
|
return values.get(WING_MOTION) == Wing.Motion.BEAT_ON_JUMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int points(ParameterValues values) {
|
||||||
|
int points = values.get(WING_SHAPE).points(values.get(WING_DETAIL));
|
||||||
|
return (int) Math.ceil(points * values.get(WING_SPARKLE) / 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean showsSetting(Parameter<?> parameter, ParameterValues values) {
|
||||||
|
return parameter != WING_DETAIL || values.get(WING_SHAPE).isDrawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean replacesAmount(ParameterValues values) {
|
||||||
|
return values.get(WING_SHAPE).isDrawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void adjust(Parameter<?> changed, ParameterValues values) {
|
||||||
|
if (changed != WING_MOTION) return;
|
||||||
|
// 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);
|
||||||
|
} else if (!moving && interval == MOVING_WING_INTERVAL) {
|
||||||
|
values.set(EntryParameters.INTERVAL, STILL_WING_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image wings spawn a particle on every pixel, so they start sparse, and sit on the back even while gliding
|
||||||
|
@Override
|
||||||
|
public void applyDefaults(ParameterValues values) {
|
||||||
|
values.set(EntryParameters.COUNT, 1)
|
||||||
|
.set(EntryParameters.SPEED, 0.0)
|
||||||
|
.set(EntryParameters.TICK_MODE, EntryParameters.TickMode.ALWAYS)
|
||||||
|
.set(EntryParameters.INTERVAL, STILL_WING_INTERVAL)
|
||||||
|
.set(EntryParameters.PLACEMENT, EntryParameters.Placement.BODY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The wing shape that shows the given image, for presets
|
||||||
|
public WingShape wingShape(WingDesign image) {
|
||||||
|
return WING_SHAPE.getChoices().stream().map(ChoiceParameter.Choice::value).filter(shape -> shape.shows(image)).findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ParticleDesign CLOUD = new ParticleDesign("cloud", Material.WHITE_WOOL, "PARTICLE_DESIGN_CLOUD") {
|
||||||
|
@Override
|
||||||
|
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||||
|
return new Cloud(particle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private Map<String, ParticleDesign> registry() {
|
||||||
|
if (registry == null) {
|
||||||
|
Map<String, ParticleDesign> designs = new LinkedHashMap<>();
|
||||||
|
List.of(SIMPLE, CIRCLE, WING, CLOUD).forEach(design -> register(designs, design));
|
||||||
|
RewardDesigns.all().forEach(design -> register(designs, design));
|
||||||
|
InternalDesigns.all().forEach(design -> register(designs, design));
|
||||||
|
registry = designs;
|
||||||
|
}
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(Map<String, ParticleDesign> designs, ParticleDesign design) {
|
||||||
|
if (designs.put(design.getId(), design) != null) {
|
||||||
|
throw new IllegalStateException("Duplicate particle design " + design.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleDesign byId(String id) {
|
||||||
|
return registry().get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ParticleDesign> all() {
|
||||||
|
return List.copyOf(registry().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,227 @@
|
|||||||
|
/*
|
||||||
|
* 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.ParticleRequirement;
|
||||||
|
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 org.bukkit.Material;
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body places offsets, heights, rings and wings around the player's body, even while gliding or swimming
|
||||||
|
public enum Placement {
|
||||||
|
WORLD,
|
||||||
|
BODY,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enchanting glyphs can fly between each point and the player instead of floating down
|
||||||
|
public enum GlyphFlight {
|
||||||
|
NORMAL,
|
||||||
|
INTO_PLAYER,
|
||||||
|
OUT_OF_PLAYER,
|
||||||
|
}
|
||||||
|
|
||||||
|
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(), ParticleType::getRequirement);
|
||||||
|
public final IntParameter COUNT = new IntParameter("count", "PARTICLE_PARAM_COUNT", 5, 1, 20);
|
||||||
|
// Minecraft scatters particles around their point by about this many blocks; beyond half a block a design dissolves
|
||||||
|
public final DoubleParameter SPREAD_X = new DoubleParameter("spreadX", "PARTICLE_PARAM_SPREAD_X", 0, 0, 0.5, 0.01);
|
||||||
|
public final DoubleParameter SPREAD_Y = new DoubleParameter("spreadY", "PARTICLE_PARAM_SPREAD_Y", 0, 0, 0.5, 0.01);
|
||||||
|
public final DoubleParameter SPREAD_Z = new DoubleParameter("spreadZ", "PARTICLE_PARAM_SPREAD_Z", 0, 0, 0.5, 0.01);
|
||||||
|
// For most particles the speed of their random spray; above 0.2 they shoot blocks away
|
||||||
|
public final DoubleParameter SPEED = new DoubleParameter("speed", "PARTICLE_PARAM_SPEED", 0.01, 0, 0.05, 0.005);
|
||||||
|
public final GradientParameter COLORS = new GradientParameter("colors", "PARTICLE_PARAM_COLORS", 8);
|
||||||
|
// Blocks that are also items, so they work for block and item particles alike
|
||||||
|
public final ChoiceParameter<Material> BLOCK = new ChoiceParameter<>("block", "PARTICLE_PARAM_BLOCK", Material.STONE, List.of(
|
||||||
|
new ChoiceParameter.Choice<>("stone", Material.STONE, false),
|
||||||
|
new ChoiceParameter.Choice<>("cobblestone", Material.COBBLESTONE, false),
|
||||||
|
new ChoiceParameter.Choice<>("dirt", Material.DIRT, false),
|
||||||
|
new ChoiceParameter.Choice<>("grass_block", Material.GRASS_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("sand", Material.SAND, false),
|
||||||
|
new ChoiceParameter.Choice<>("red_sand", Material.RED_SAND, false),
|
||||||
|
new ChoiceParameter.Choice<>("gravel", Material.GRAVEL, false),
|
||||||
|
new ChoiceParameter.Choice<>("oak_planks", Material.OAK_PLANKS, false),
|
||||||
|
new ChoiceParameter.Choice<>("oak_leaves", Material.OAK_LEAVES, false),
|
||||||
|
new ChoiceParameter.Choice<>("cherry_leaves", Material.CHERRY_LEAVES, false),
|
||||||
|
new ChoiceParameter.Choice<>("snow_block", Material.SNOW_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("ice", Material.ICE, false),
|
||||||
|
new ChoiceParameter.Choice<>("glass", Material.GLASS, false),
|
||||||
|
new ChoiceParameter.Choice<>("tnt", Material.TNT, false),
|
||||||
|
new ChoiceParameter.Choice<>("pumpkin", Material.PUMPKIN, false),
|
||||||
|
new ChoiceParameter.Choice<>("melon", Material.MELON, false),
|
||||||
|
new ChoiceParameter.Choice<>("honey_block", Material.HONEY_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("slime_block", Material.SLIME_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("netherrack", Material.NETHERRACK, false),
|
||||||
|
new ChoiceParameter.Choice<>("soul_sand", Material.SOUL_SAND, false),
|
||||||
|
new ChoiceParameter.Choice<>("magma_block", Material.MAGMA_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("end_stone", Material.END_STONE, false),
|
||||||
|
new ChoiceParameter.Choice<>("obsidian", Material.OBSIDIAN, false),
|
||||||
|
new ChoiceParameter.Choice<>("prismarine", Material.PRISMARINE, false),
|
||||||
|
new ChoiceParameter.Choice<>("sculk", Material.SCULK, false),
|
||||||
|
new ChoiceParameter.Choice<>("amethyst_block", Material.AMETHYST_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("coal_block", Material.COAL_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("iron_block", Material.IRON_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("gold_block", Material.GOLD_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("redstone_block", Material.REDSTONE_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("lapis_block", Material.LAPIS_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("emerald_block", Material.EMERALD_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("diamond_block", Material.DIAMOND_BLOCK, false),
|
||||||
|
new ChoiceParameter.Choice<>("white_wool", Material.WHITE_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("red_wool", Material.RED_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("orange_wool", Material.ORANGE_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("yellow_wool", Material.YELLOW_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("lime_wool", Material.LIME_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("light_blue_wool", Material.LIGHT_BLUE_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("blue_wool", Material.BLUE_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("purple_wool", Material.PURPLE_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("pink_wool", Material.PINK_WOOL, false),
|
||||||
|
new ChoiceParameter.Choice<>("black_wool", Material.BLACK_WOOL, false)
|
||||||
|
));
|
||||||
|
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 IntParameter TRAIL_DURATION = new IntParameter("trailDuration", "PARTICLE_PARAM_TRAIL_DURATION", 20, 1, 200);
|
||||||
|
// Where a trail particle flies to, relative to where it spawns
|
||||||
|
public final DoubleParameter TRAIL_TARGET_X = new DoubleParameter("trailTargetX", "PARTICLE_PARAM_TRAIL_TARGET_X", 0, -5, 5, 0.1);
|
||||||
|
public final DoubleParameter TRAIL_TARGET_Y = new DoubleParameter("trailTargetY", "PARTICLE_PARAM_TRAIL_TARGET_Y", 1, -5, 5, 0.1);
|
||||||
|
public final DoubleParameter TRAIL_TARGET_Z = new DoubleParameter("trailTargetZ", "PARTICLE_PARAM_TRAIL_TARGET_Z", 0, -5, 5, 0.1);
|
||||||
|
public final ChoiceParameter<GlyphFlight> GLYPH_FLIGHT = ChoiceParameter.ofEnum("glyphFlight", "PARTICLE_PARAM_GLYPH_FLIGHT", GlyphFlight.NORMAL, flight -> false,
|
||||||
|
flight -> switch (flight) {
|
||||||
|
case NORMAL -> ParticleRequirement.NO_REQUIRMENT;
|
||||||
|
case INTO_PLAYER -> Unlocks.WARGEAR_SEASON_PLACEMENT.get();
|
||||||
|
case OUT_OF_PLAYER -> Unlocks.WARGEAR_SEASON.get();
|
||||||
|
});
|
||||||
|
public final ChoiceParameter<Placement> PLACEMENT = ChoiceParameter.ofEnum("placement", "PARTICLE_PARAM_PLACEMENT", Placement.WORLD);
|
||||||
|
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, GLYPH_FLIGHT, 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
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
// A design whose look, including its particles, is fixed; only the entry's conditions, timing and offset apply
|
||||||
|
public class FixedDesign extends ParticleDesign {
|
||||||
|
|
||||||
|
private final int points;
|
||||||
|
private final Consumer<ParameterValues> defaults;
|
||||||
|
private final Supplier<ParticleElement> shape;
|
||||||
|
|
||||||
|
public FixedDesign(String id, Material icon, String nameKey, ParticleRequirement requirement, int points, Consumer<ParameterValues> defaults, Supplier<ParticleElement> shape) {
|
||||||
|
super(id, icon, nameKey, DesignVisibility.REWARD, requirement);
|
||||||
|
this.points = points;
|
||||||
|
this.defaults = defaults;
|
||||||
|
this.shape = shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||||
|
return shape.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int points(ParameterValues values) {
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean usesEntryParticle() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyDefaults(ParameterValues values) {
|
||||||
|
defaults.accept(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,85 @@
|
|||||||
|
/*
|
||||||
|
* 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 de.steamwar.lobby.particle.elements.Circle;
|
||||||
|
import de.steamwar.lobby.particle.elements.custom.HearthBeat;
|
||||||
|
import de.steamwar.lobby.particle.elements.custom.PulseShimmer;
|
||||||
|
import de.steamwar.lobby.particle.elements.custom.YOffset;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Shapes only presets use; they are never offered in the builder
|
||||||
|
@UtilityClass
|
||||||
|
public class InternalDesigns {
|
||||||
|
|
||||||
|
public final IntParameter PULSE_SPEED = new IntParameter("pulseSpeed", "PARTICLE_PARAM_PULSE_SPEED", 80, 1, 200);
|
||||||
|
public final IntParameter PULSE_ROTATION = new IntParameter("pulseRotation", "PARTICLE_PARAM_PULSE_ROTATION", 2, 0, 20);
|
||||||
|
|
||||||
|
public final ParticleDesign PULSE_SHIMMER = new ParticleDesign("pulse_shimmer", Material.GRAY_CANDLE, "PARTICLE_DESIGN_PULSE_SHIMMER", DesignVisibility.INTERNAL, ParticleRequirement.NO_REQUIRMENT,
|
||||||
|
PULSE_SPEED, PULSE_ROTATION) {
|
||||||
|
@Override
|
||||||
|
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||||
|
return new PulseShimmer(particle, values.get(PULSE_SPEED), values.get(PULSE_ROTATION));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int points(ParameterValues values) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public final DoubleParameter HEART_BEAT_SPEED = new DoubleParameter("heartBeatSpeed", "PARTICLE_PARAM_HEART_BEAT_SPEED", 80, 1, 200, 5);
|
||||||
|
|
||||||
|
public final ParticleDesign HEART_BEAT = new ParticleDesign("heart_beat", Material.APPLE, "PARTICLE_DESIGN_HEART_BEAT", DesignVisibility.INTERNAL, ParticleRequirement.NO_REQUIRMENT,
|
||||||
|
HEART_BEAT_SPEED) {
|
||||||
|
@Override
|
||||||
|
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||||
|
return new HearthBeat(particle, values.get(HEART_BEAT_SPEED));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public final DoubleParameter ORBIT_RADIUS = new DoubleParameter("orbitRadius", "PARTICLE_PARAM_ORBIT_RADIUS", 1.5, 0.2, 3, 0.1);
|
||||||
|
public final DoubleParameter ORBIT_SPEED = new DoubleParameter("orbitSpeed", "PARTICLE_PARAM_ORBIT_SPEED", 1, -20, 20, 0.5);
|
||||||
|
public final DoubleParameter ORBIT_PHASE = new DoubleParameter("orbitPhase", "PARTICLE_PARAM_ORBIT_PHASE", 0, 0, 345, 15);
|
||||||
|
|
||||||
|
// A circle whose centre itself moves on a circle around the player
|
||||||
|
public final ParticleDesign ORBIT = new ParticleDesign("orbit", Material.ENDER_PEARL, "PARTICLE_DESIGN_ORBIT", DesignVisibility.INTERNAL, ParticleRequirement.NO_REQUIRMENT,
|
||||||
|
ORBIT_RADIUS, ORBIT_SPEED, ORBIT_PHASE,
|
||||||
|
Designs.CIRCLE_RADIUS, Designs.CIRCLE_SPEED, Designs.CIRCLE_PHASE, Designs.CIRCLE_RISE, Designs.CIRCLE_RISE_SPEED, Designs.CIRCLE_BOUNCE, Designs.CIRCLE_INVERTED) {
|
||||||
|
@Override
|
||||||
|
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||||
|
double rise = values.get(Designs.CIRCLE_RISE);
|
||||||
|
if (rise > 0) {
|
||||||
|
particle = new YOffset(particle, values.get(Designs.CIRCLE_RISE_SPEED), 0, rise, values.get(Designs.CIRCLE_INVERTED), values.get(Designs.CIRCLE_BOUNCE));
|
||||||
|
}
|
||||||
|
ParticleElement inner = new Circle(particle, values.get(Designs.CIRCLE_RADIUS), values.get(Designs.CIRCLE_SPEED), values.get(Designs.CIRCLE_PHASE));
|
||||||
|
return new Circle(inner, values.get(ORBIT_RADIUS), values.get(ORBIT_SPEED), values.get(ORBIT_PHASE));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public List<ParticleDesign> all() {
|
||||||
|
return List.of(PULSE_SHIMMER, HEART_BEAT, ORBIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,76 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skips the builder's limits; only for presets, which are never stored
|
||||||
|
public <T> ParameterValues setUnchecked(Parameter<T> parameter, T value) {
|
||||||
|
values.put(parameter.getKey(), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Puts back values saved with copy()
|
||||||
|
public void restore(ParameterValues saved) {
|
||||||
|
values.clear();
|
||||||
|
values.putAll(saved.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,88 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// False for designs with a fixed look that ignore the entry's particle type, count, spread, speed and colours
|
||||||
|
public boolean usesEntryParticle() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether the entry editor shows this design setting; some only matter for some values of others
|
||||||
|
public boolean showsSetting(Parameter<?> parameter, ParameterValues values) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True when the design decides how many particles each point spawns, so the amount setting is hidden and counts as 1
|
||||||
|
public boolean replacesAmount(ParameterValues values) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called after a setting changed, to adjust others that depend on it
|
||||||
|
public void adjust(Parameter<?> changed, ParameterValues values) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the conditions and timing a new entry of this design starts with
|
||||||
|
public void applyDefaults(ParameterValues values) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* 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.ParticleRequirement;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
|
||||||
|
// Left out: ELDER_GUARDIAN, FLASH and the explosion/gust emitters cover other players' screens,
|
||||||
|
// BUBBLE, BUBBLE_COLUMN_UP and CURRENT_DOWN only show under water, VIBRATION needs a block or entity to fly to.
|
||||||
|
// Not buildable: particles a block or more in size or rising for seconds, which wall in players once a design
|
||||||
|
// spawns many of them (a wing spawns over a hundred); presets may still use them.
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum ParticleType {
|
||||||
|
|
||||||
|
DUST(Material.REDSTONE, Particle.DUST, Data.DUST, true),
|
||||||
|
DUST_COLOR_TRANSITION(Material.REDSTONE_TORCH, Particle.DUST_COLOR_TRANSITION, Data.COLOR_TRANSITION, true),
|
||||||
|
ENTITY_EFFECT(Material.BREWING_STAND, Particle.ENTITY_EFFECT, Data.COLOR, true),
|
||||||
|
TINTED_LEAVES(Material.OAK_LEAVES, Particle.TINTED_LEAVES, Data.COLOR, true),
|
||||||
|
BLOCK(Material.STONE, Particle.BLOCK, Data.BLOCK, true),
|
||||||
|
BLOCK_CRUMBLE(Material.CRACKED_STONE_BRICKS, Particle.BLOCK_CRUMBLE, Data.BLOCK, true),
|
||||||
|
BLOCK_MARKER(Material.GLASS, Particle.BLOCK_MARKER, Data.BLOCK, false),
|
||||||
|
DUST_PILLAR(Material.MUD, Particle.DUST_PILLAR, Data.BLOCK, false),
|
||||||
|
FALLING_DUST(Material.SAND, Particle.FALLING_DUST, Data.BLOCK, true),
|
||||||
|
ITEM(Material.ITEM_FRAME, Particle.ITEM, Data.ITEM, true),
|
||||||
|
TRAIL(Material.LEAD, Particle.TRAIL, Data.TRAIL, true),
|
||||||
|
|
||||||
|
FLAME(Material.FLINT_AND_STEEL, Particle.FLAME, Data.NONE, true),
|
||||||
|
SMALL_FLAME(Material.TORCH, Particle.SMALL_FLAME, Data.NONE, true),
|
||||||
|
SOUL_FIRE_FLAME(Material.SOUL_TORCH, Particle.SOUL_FIRE_FLAME, Data.NONE, true),
|
||||||
|
SOUL(Material.ZOMBIE_HEAD, Particle.SOUL, Data.NONE, true),
|
||||||
|
LAVA(Material.MAGMA_CREAM, Particle.LAVA, Data.NONE, true),
|
||||||
|
SMOKE(Material.COBWEB, Particle.SMOKE, Data.NONE, true),
|
||||||
|
LARGE_SMOKE(Material.GRAY_WOOL, Particle.LARGE_SMOKE, Data.NONE, false),
|
||||||
|
WHITE_SMOKE(Material.LIGHT_GRAY_DYE, Particle.WHITE_SMOKE, Data.NONE, true),
|
||||||
|
CAMPFIRE_COSY_SMOKE(Material.CAMPFIRE, Particle.CAMPFIRE_COSY_SMOKE, Data.NONE, true),
|
||||||
|
CAMPFIRE_SIGNAL_SMOKE(Material.HAY_BLOCK, Particle.CAMPFIRE_SIGNAL_SMOKE, Data.NONE, false),
|
||||||
|
ASH(Material.BASALT, Particle.ASH, Data.NONE, true),
|
||||||
|
WHITE_ASH(Material.BONE_MEAL, Particle.WHITE_ASH, Data.NONE, true),
|
||||||
|
POOF(Material.WHITE_DYE, Particle.POOF, Data.NONE, true),
|
||||||
|
CLOUD(Material.WHITE_WOOL, Particle.CLOUD, Data.NONE, true),
|
||||||
|
SNEEZE(Material.SLIME_BLOCK, Particle.SNEEZE, Data.NONE, true),
|
||||||
|
SPIT(Material.LLAMA_SPAWN_EGG, Particle.SPIT, Data.NONE, true),
|
||||||
|
EXPLOSION(Material.TNT, Particle.EXPLOSION, Data.NONE, false),
|
||||||
|
SONIC_BOOM(Material.ECHO_SHARD, Particle.SONIC_BOOM, Data.NONE, false),
|
||||||
|
SWEEP_ATTACK(Material.IRON_SWORD, Particle.SWEEP_ATTACK, Data.NONE, false),
|
||||||
|
GUST(Material.WIND_CHARGE, Particle.GUST, Data.NONE, false),
|
||||||
|
SMALL_GUST(Material.BREEZE_ROD, Particle.SMALL_GUST, Data.NONE, false),
|
||||||
|
|
||||||
|
DRIPPING_WATER(Material.WATER_BUCKET, Particle.DRIPPING_WATER, Data.NONE, true),
|
||||||
|
FALLING_WATER(Material.LIGHT_BLUE_DYE, Particle.FALLING_WATER, Data.NONE, true),
|
||||||
|
DRIPPING_LAVA(Material.LAVA_BUCKET, Particle.DRIPPING_LAVA, Data.NONE, true),
|
||||||
|
FALLING_LAVA(Material.ORANGE_DYE, Particle.FALLING_LAVA, Data.NONE, true),
|
||||||
|
LANDING_LAVA(Material.FIRE_CORAL, Particle.LANDING_LAVA, Data.NONE, true),
|
||||||
|
DRIPPING_HONEY(Material.HONEY_BOTTLE, Particle.DRIPPING_HONEY, Data.NONE, true),
|
||||||
|
FALLING_HONEY(Material.HONEY_BLOCK, Particle.FALLING_HONEY, Data.NONE, true),
|
||||||
|
LANDING_HONEY(Material.HONEYCOMB_BLOCK, Particle.LANDING_HONEY, Data.NONE, true),
|
||||||
|
FALLING_NECTAR(Material.HONEYCOMB, Particle.FALLING_NECTAR, Data.NONE, true),
|
||||||
|
DRIPPING_OBSIDIAN_TEAR(Material.OBSIDIAN, Particle.DRIPPING_OBSIDIAN_TEAR, Data.NONE, true),
|
||||||
|
FALLING_OBSIDIAN_TEAR(Material.CRYING_OBSIDIAN, Particle.FALLING_OBSIDIAN_TEAR, Data.NONE, true),
|
||||||
|
LANDING_OBSIDIAN_TEAR(Material.RESPAWN_ANCHOR, Particle.LANDING_OBSIDIAN_TEAR, Data.NONE, true),
|
||||||
|
DRIPPING_DRIPSTONE_WATER(Material.DRIPSTONE_BLOCK, Particle.DRIPPING_DRIPSTONE_WATER, Data.NONE, true),
|
||||||
|
FALLING_DRIPSTONE_WATER(Material.BLUE_ICE, Particle.FALLING_DRIPSTONE_WATER, Data.NONE, true),
|
||||||
|
DRIPPING_DRIPSTONE_LAVA(Material.POINTED_DRIPSTONE, Particle.DRIPPING_DRIPSTONE_LAVA, Data.NONE, true),
|
||||||
|
FALLING_DRIPSTONE_LAVA(Material.MAGMA_BLOCK, Particle.FALLING_DRIPSTONE_LAVA, Data.NONE, true),
|
||||||
|
RAIN(Material.CYAN_DYE, Particle.RAIN, Data.NONE, true),
|
||||||
|
SPLASH(Material.KELP, Particle.SPLASH, Data.NONE, true),
|
||||||
|
FISHING(Material.FISHING_ROD, Particle.FISHING, Data.NONE, true),
|
||||||
|
UNDERWATER(Material.PRISMARINE_SHARD, Particle.UNDERWATER, Data.NONE, true),
|
||||||
|
BUBBLE_POP(Material.TUBE_CORAL, Particle.BUBBLE_POP, Data.NONE, true),
|
||||||
|
DOLPHIN(Material.BLUE_DYE, Particle.DOLPHIN, Data.NONE, true),
|
||||||
|
NAUTILUS(Material.NAUTILUS_SHELL, Particle.NAUTILUS, Data.NONE, true),
|
||||||
|
SQUID_INK(Material.INK_SAC, Particle.SQUID_INK, Data.NONE, true),
|
||||||
|
GLOW_SQUID_INK(Material.GLOW_INK_SAC, Particle.GLOW_SQUID_INK, Data.NONE, true),
|
||||||
|
GLOW(Material.GLOW_BERRIES, Particle.GLOW, Data.NONE, true),
|
||||||
|
|
||||||
|
FALLING_SPORE_BLOSSOM(Material.SPORE_BLOSSOM, Particle.FALLING_SPORE_BLOSSOM, Data.NONE, true),
|
||||||
|
SPORE_BLOSSOM_AIR(Material.PINK_PETALS, Particle.SPORE_BLOSSOM_AIR, Data.NONE, true),
|
||||||
|
CHERRY_LEAVES(Material.CHERRY_LEAVES, Particle.CHERRY_LEAVES, Data.NONE, true),
|
||||||
|
PALE_OAK_LEAVES(Material.PALE_OAK_LEAVES, Particle.PALE_OAK_LEAVES, Data.NONE, true),
|
||||||
|
CRIMSON_SPORE(Material.CRIMSON_FUNGUS, Particle.CRIMSON_SPORE, Data.NONE, true),
|
||||||
|
WARPED_SPORE(Material.WARPED_FUNGUS, Particle.WARPED_SPORE, Data.NONE, true),
|
||||||
|
MYCELIUM(Material.MYCELIUM, Particle.MYCELIUM, Data.NONE, true),
|
||||||
|
COMPOSTER(Material.COMPOSTER, Particle.COMPOSTER, Data.NONE, true),
|
||||||
|
FIREFLY(Material.FIREFLY_BUSH, Particle.FIREFLY, Data.NONE, true),
|
||||||
|
SNOWFLAKE(Material.SNOWBALL, Particle.SNOWFLAKE, Data.NONE, true),
|
||||||
|
ITEM_SNOWBALL(Material.SNOW_BLOCK, Particle.ITEM_SNOWBALL, Data.NONE, true),
|
||||||
|
ITEM_SLIME(Material.SLIME_BALL, Particle.ITEM_SLIME, Data.NONE, true),
|
||||||
|
ITEM_COBWEB(Material.STRING, Particle.ITEM_COBWEB, Data.NONE, true),
|
||||||
|
EGG_CRACK(Material.TURTLE_EGG, Particle.EGG_CRACK, Data.NONE, true),
|
||||||
|
DUST_PLUME(Material.SUSPICIOUS_SAND, Particle.DUST_PLUME, Data.NONE, true),
|
||||||
|
INFESTED(Material.INFESTED_STONE, Particle.INFESTED, Data.NONE, true),
|
||||||
|
|
||||||
|
HEART(Material.RED_DYE, Particle.HEART, Data.NONE, true),
|
||||||
|
NOTE(Material.NOTE_BLOCK, Particle.NOTE, Data.NONE, true),
|
||||||
|
HAPPY_VILLAGER(Material.EMERALD, Particle.HAPPY_VILLAGER, Data.NONE, true),
|
||||||
|
ANGRY_VILLAGER(Material.REDSTONE_BLOCK, Particle.ANGRY_VILLAGER, Data.NONE, true),
|
||||||
|
DAMAGE_INDICATOR(Material.SPIDER_EYE, Particle.DAMAGE_INDICATOR, Data.NONE, true),
|
||||||
|
CRIT(Material.CAULDRON, Particle.CRIT, Data.NONE, true),
|
||||||
|
ENCHANTED_HIT(Material.GLOWSTONE_DUST, Particle.ENCHANTED_HIT, Data.NONE, true),
|
||||||
|
ENCHANT(Material.ENCHANTING_TABLE, Particle.ENCHANT, Data.NONE, true),
|
||||||
|
EFFECT(Material.POTION, Particle.EFFECT, Data.NONE, true),
|
||||||
|
INSTANT_EFFECT(Material.SPLASH_POTION, Particle.INSTANT_EFFECT, Data.NONE, true),
|
||||||
|
WITCH(Material.EXPERIENCE_BOTTLE, Particle.WITCH, Data.NONE, true),
|
||||||
|
END_ROD(Material.END_ROD, Particle.END_ROD, Data.NONE, true),
|
||||||
|
PORTAL(Material.ENDER_PEARL, Particle.PORTAL, Data.NONE, true),
|
||||||
|
REVERSE_PORTAL(Material.ENDER_EYE, Particle.REVERSE_PORTAL, Data.NONE, true),
|
||||||
|
DRAGON_BREATH(Material.DRAGON_BREATH, Particle.DRAGON_BREATH, Data.NONE, true),
|
||||||
|
FIREWORK(Material.FIRE_CHARGE, Particle.FIREWORK, Data.NONE, true),
|
||||||
|
TOTEM_OF_UNDYING(Material.TOTEM_OF_UNDYING, Particle.TOTEM_OF_UNDYING, Data.NONE, true),
|
||||||
|
ELECTRIC_SPARK(Material.LIGHTNING_ROD, Particle.ELECTRIC_SPARK, Data.NONE, true),
|
||||||
|
SCRAPE(Material.COPPER_BLOCK, Particle.SCRAPE, Data.NONE, true),
|
||||||
|
WAX_ON(Material.WAXED_COPPER_BLOCK, Particle.WAX_ON, Data.NONE, true),
|
||||||
|
WAX_OFF(Material.WAXED_EXPOSED_COPPER, Particle.WAX_OFF, Data.NONE, true),
|
||||||
|
SCULK_SOUL(Material.SCULK_CATALYST, Particle.SCULK_SOUL, Data.NONE, true),
|
||||||
|
SCULK_CHARGE(Material.SCULK_SENSOR, Particle.SCULK_CHARGE, Data.ZERO_FLOAT, true),
|
||||||
|
SCULK_CHARGE_POP(Material.SCULK_VEIN, Particle.SCULK_CHARGE_POP, Data.NONE, true),
|
||||||
|
SHRIEK(Material.SCULK_SHRIEKER, Particle.SHRIEK, Data.ZERO_INT, false),
|
||||||
|
RAID_OMEN(Material.FIREWORK_STAR, Particle.RAID_OMEN, Data.NONE, true),
|
||||||
|
TRIAL_OMEN(Material.TRIAL_KEY, Particle.TRIAL_OMEN, Data.NONE, true),
|
||||||
|
OMINOUS_SPAWNING(Material.OMINOUS_TRIAL_KEY, Particle.OMINOUS_SPAWNING, Data.NONE, true),
|
||||||
|
TRIAL_SPAWNER_DETECTION(Material.TRIAL_SPAWNER, Particle.TRIAL_SPAWNER_DETECTION, Data.NONE, true),
|
||||||
|
TRIAL_SPAWNER_DETECTION_OMINOUS(Material.VAULT, Particle.TRIAL_SPAWNER_DETECTION_OMINOUS, Data.NONE, true),
|
||||||
|
VAULT_CONNECTION(Material.HEAVY_CORE, Particle.VAULT_CONNECTION, Data.NONE, true),
|
||||||
|
;
|
||||||
|
|
||||||
|
// The extra data a particle needs when it is spawned
|
||||||
|
public enum Data {
|
||||||
|
NONE,
|
||||||
|
// Colour and size, handled by the dust element
|
||||||
|
DUST,
|
||||||
|
// A colour from the entry's colours
|
||||||
|
COLOR,
|
||||||
|
// Fades between the entry's first two colours
|
||||||
|
COLOR_TRANSITION,
|
||||||
|
// Block data of the entry's block
|
||||||
|
BLOCK,
|
||||||
|
// An item of the entry's block
|
||||||
|
ITEM,
|
||||||
|
// Flies to the entry's trail target in its colour, taking the trail duration
|
||||||
|
TRAIL,
|
||||||
|
ZERO_INT,
|
||||||
|
ZERO_FLOAT,
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Material icon;
|
||||||
|
private final Particle particle;
|
||||||
|
private final Data data;
|
||||||
|
private final boolean buildable;
|
||||||
|
|
||||||
|
public String getNameKey() {
|
||||||
|
return "PARTICLE_TYPE_" + name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDust() {
|
||||||
|
return data == Data.DUST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean usesColors() {
|
||||||
|
return data == Data.DUST || data == Data.COLOR || data == Data.COLOR_TRANSITION || data == Data.TRAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaves drift down slowly and pile up around a player, so only the server team may build with them. Cosy smoke is
|
||||||
|
// earned by taking part in Hells Bells.
|
||||||
|
public ParticleRequirement getRequirement() {
|
||||||
|
return switch (this) {
|
||||||
|
case CHERRY_LEAVES, PALE_OAK_LEAVES, TINTED_LEAVES -> ParticleRequirement.SERVER_TEAM;
|
||||||
|
case CAMPFIRE_COSY_SMOKE -> Unlocks.HELLS_BELLS.get();
|
||||||
|
default -> ParticleRequirement.NO_REQUIRMENT;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTrail() {
|
||||||
|
return data == Data.TRAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean usesBlock() {
|
||||||
|
return data == Data.BLOCK || data == Data.ITEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* 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 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;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static de.steamwar.lobby.particle.design.EntryParameters.*;
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
rainCloud(Particle.RAIN, Particle.DRIPPING_WATER, 5),
|
||||||
|
rainCloud(null, Particle.DRIPPING_LAVA, 5),
|
||||||
|
rainCloud(null, Particle.FALLING_NECTAR, 5),
|
||||||
|
rainCloud(null, Particle.FALLING_HONEY, 5),
|
||||||
|
rainCloud(null, Particle.FALLING_SPORE_BLOSSOM, 5),
|
||||||
|
rainCloud(null, Particle.FALLING_OBSIDIAN_TEAR, 5),
|
||||||
|
rainCloud(null, Particle.NAUTILUS, 5),
|
||||||
|
rainCloud(null, Particle.ENCHANT, 5),
|
||||||
|
rainCloud(null, Particle.ITEM_SLIME, 5),
|
||||||
|
rainCloud(null, Particle.ITEM_SNOWBALL, 5),
|
||||||
|
rainCloud(null, Particle.SNOWFLAKE, 5),
|
||||||
|
rainCloud(null, Particle.TOTEM_OF_UNDYING, 2)
|
||||||
|
));
|
||||||
|
public final ParticleDesign EGG_HUNT_MEDIUM = new FixedDesign("egg_hunt_medium", Material.YELLOW_CONCRETE_POWDER, "PARTICLE_EGG_HUNT_MEDIUM",
|
||||||
|
ParticleRequirement.EGG_HUNT_MEDIUM, 5, ALWAYS.andThen(values -> values.set(GROUND, Ground.ON_GROUND).set(SNEAK, Sneak.SNEAKING)),
|
||||||
|
() -> new SimpleParticle(Particle.GLOW_SQUID_INK, 0.4F, 0.9F, 0.4F, 0.01));
|
||||||
|
public final ParticleDesign EGG_HUNT_HARD = new FixedDesign("egg_hunt_hard", Material.RED_CONCRETE_POWDER, "PARTICLE_EGG_HUNT_HARD",
|
||||||
|
ParticleRequirement.EGG_HUNT_HARD, 2, ALWAYS,
|
||||||
|
() -> new LocationMutator(new Circle(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0.01, 1, null), new DustParticle(Particle.DUST, 0, 0, 0, 0.01, 1, null)), 0.8, 0.5), 0, 0.5, 0));
|
||||||
|
public final ParticleDesign EGG_HUNT_ADVANCED = new FixedDesign("egg_hunt_advanced", Material.BLACK_CONCRETE_POWDER, "PARTICLE_EGG_HUNT_ADVANCED",
|
||||||
|
ParticleRequirement.EGG_HUNT_ADVANCED, 2, ALWAYS.andThen(values -> values.set(SNEAK, Sneak.SNEAKING)),
|
||||||
|
() -> new LocationMutator(new Group(
|
||||||
|
new SimpleParticle(Particle.SMOKE, 0.02F, 0, 0.02F, 0.05, 1),
|
||||||
|
new SimpleParticle(Particle.LARGE_SMOKE, 0.02F, 0, 0.02F, 0.05, 1),
|
||||||
|
particleTickData -> {
|
||||||
|
Player player = particleTickData.getPlayer();
|
||||||
|
player.setVelocity(player.getVelocity().add(new Vector(0, 0.1, 0)));
|
||||||
|
}
|
||||||
|
), 0, -0.2, 0));
|
||||||
|
|
||||||
|
// A cloud above the head that drips the given particle; rain is an optional second particle inside the cloud
|
||||||
|
private ParticleElement rainCloud(Particle rain, Particle drip, int dripCount) {
|
||||||
|
ParticleElement cloud = new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01);
|
||||||
|
ParticleElement drops = new LocationMutator(new SimpleParticle(drip, 0.3F, 0.0F, 0.3F, 0.01, dripCount), 0, -0.3, 0);
|
||||||
|
ParticleElement group = rain == null ? new Group(cloud, drops) : new Group(cloud, new SimpleParticle(rain, 0.3F, 0.1F, 0.3F, 0.01), drops);
|
||||||
|
return new LocationMutator(group, 0, 2.4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ParticleDesign> all() {
|
||||||
|
return List.of(BURST, FOOTPRINTS, TEAM_LINK, SHIMMER, HEARTBEAT, EVENT_PARTICIPATION_UNDERWATER_MWG, EGG_HUNT_MEDIUM, EGG_HUNT_HARD, EGG_HUNT_ADVANCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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.ParticleRequirement;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
// Requirements of the reward ingredients: wing images, particles and touches. Each is created on first use, as event
|
||||||
|
// requirements look up the event's name.
|
||||||
|
@UtilityClass
|
||||||
|
public class Unlocks {
|
||||||
|
|
||||||
|
public final Supplier<ParticleRequirement> WARGEAR_SEASON_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(22, 12, 285, 54));
|
||||||
|
public final Supplier<ParticleRequirement> AIRSHIP_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(26, 205, 9, 54, 120, 292));
|
||||||
|
public final Supplier<ParticleRequirement> UNDERWATER_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(31, 9, 210, 520));
|
||||||
|
public final Supplier<ParticleRequirement> ADVENT_WAR_SHIP_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(32, 9, 205, 210));
|
||||||
|
public final Supplier<ParticleRequirement> MINI_WARGEAR_LIGA_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(33, 9, 34, 205));
|
||||||
|
public final Supplier<ParticleRequirement> ABSTURZ_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(34, 210, 205, 527, 286));
|
||||||
|
public final Supplier<ParticleRequirement> UNDERWATER_MWG_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(35, 9, 210));
|
||||||
|
public final Supplier<ParticleRequirement> WARGEAR_SEASON_2022_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(37, 285, 210, 122));
|
||||||
|
public final Supplier<ParticleRequirement> WARGEAR_CLASH_PLACEMENT = lazy(() -> ParticleRequirement.eventPlacement(38, 210, 158, 167, 286));
|
||||||
|
|
||||||
|
public final Supplier<ParticleRequirement> ANY_EVENT = () -> ParticleRequirement.EVENT_PARTICIPATION;
|
||||||
|
public final Supplier<ParticleRequirement> WARGEAR_SEASON = lazy(() -> ParticleRequirement.eventParticipation(22));
|
||||||
|
public final Supplier<ParticleRequirement> AIRSHIP = lazy(() -> ParticleRequirement.eventParticipation(26));
|
||||||
|
public final Supplier<ParticleRequirement> HELLS_BELLS = lazy(() -> ParticleRequirement.eventParticipation(28));
|
||||||
|
public final Supplier<ParticleRequirement> UNDERWATER = lazy(() -> ParticleRequirement.eventParticipation(31));
|
||||||
|
public final Supplier<ParticleRequirement> ADVENT_WAR_SHIP = lazy(() -> ParticleRequirement.eventParticipation(32));
|
||||||
|
public final Supplier<ParticleRequirement> MINI_WARGEAR_LIGA = lazy(() -> ParticleRequirement.eventParticipation(33));
|
||||||
|
public final Supplier<ParticleRequirement> WARGEAR_SEASON_2022 = lazy(() -> ParticleRequirement.eventParticipation(37));
|
||||||
|
|
||||||
|
public final Supplier<ParticleRequirement> EGG_HUNT_EASY = () -> ParticleRequirement.EGG_HUNT_EASY;
|
||||||
|
public final Supplier<ParticleRequirement> EGG_HUNT_EXTREME = () -> ParticleRequirement.EGG_HUNT_EXTREME;
|
||||||
|
public final Supplier<ParticleRequirement> EGG_HUNT_HALF = () -> ParticleRequirement.EGG_HUNT_FOUND_HALF;
|
||||||
|
|
||||||
|
private Supplier<ParticleRequirement> lazy(Supplier<ParticleRequirement> create) {
|
||||||
|
return new Supplier<>() {
|
||||||
|
private ParticleRequirement requirement;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParticleRequirement get() {
|
||||||
|
if (requirement == null) requirement = create.get();
|
||||||
|
return requirement;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.DrawnWings;
|
||||||
|
import de.steamwar.lobby.particle.WingDesign;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
// A wing shape of the wings design: either a fixed image or a shape drawn with a chosen number of points
|
||||||
|
public class WingShape {
|
||||||
|
|
||||||
|
private final WingDesign image;
|
||||||
|
private final DrawnWings drawn;
|
||||||
|
@Getter
|
||||||
|
private final Material icon;
|
||||||
|
|
||||||
|
private WingShape(WingDesign image, DrawnWings drawn, Material icon) {
|
||||||
|
this.image = image;
|
||||||
|
this.drawn = drawn;
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WingShape image(WingDesign image, Material icon) {
|
||||||
|
return new WingShape(image, null, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shapes only presets use, never shown in the builder
|
||||||
|
public static WingShape image(WingDesign image) {
|
||||||
|
return image(image, Material.PHANTOM_MEMBRANE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WingShape drawn(DrawnWings drawn, Material icon) {
|
||||||
|
return new WingShape(null, drawn, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDrawn() {
|
||||||
|
return drawn != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shows(WingDesign image) {
|
||||||
|
return this.image == image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WingDesign design(int pointsPerWing) {
|
||||||
|
return isDrawn() ? drawn.withDetail(pointsPerWing) : image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int points(int pointsPerWing) {
|
||||||
|
return isDrawn() ? 2 * pointsPerWing : image.getVectors().length;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,11 +29,6 @@ public class Always extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_TICK";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParticleTickType tickType() {
|
public ParticleTickType tickType() {
|
||||||
return ParticleTickType.ALWAYS;
|
return ParticleTickType.ALWAYS;
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is a part of the SteamWar software.
|
* This file is a part of the SteamWar software.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
@@ -19,19 +19,19 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
import de.steamwar.lobby.particle.BodyFrame;
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
|
|
||||||
public class Separator implements ParticleElement {
|
// Places everything inside relative to the player's body instead of the world
|
||||||
|
public class BodyRelative extends DelegatingParticleElement {
|
||||||
|
|
||||||
@Override
|
public BodyRelative(ParticleElement particleElement) {
|
||||||
public void aggregateAttributes(ParticleData particleData) {
|
super(particleElement);
|
||||||
particleData.add("PARTICLE_ATTRIBUTE_SEPARATOR");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
// do nothing
|
particleElement.tick(particleTickData.withFrame(BodyFrame.of(particleTickData.getPlayer())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.particle.BodyFrame;
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
@@ -28,6 +29,7 @@ public class Circle extends DelegatingParticleElement {
|
|||||||
|
|
||||||
private double distance;
|
private double distance;
|
||||||
private double speed;
|
private double speed;
|
||||||
|
private double phase;
|
||||||
|
|
||||||
public Circle(ParticleElement particleElement) {
|
public Circle(ParticleElement particleElement) {
|
||||||
super(particleElement);
|
super(particleElement);
|
||||||
@@ -41,17 +43,22 @@ public class Circle extends DelegatingParticleElement {
|
|||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Circle(ParticleElement particleElement, double distance, double speed, double phaseDegrees) {
|
||||||
public String attribute() {
|
this(particleElement, distance, speed);
|
||||||
return "PARTICLE_ATTRIBUTE_CIRCLE";
|
this.phase = Math.toRadians(phaseDegrees);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
Location location = particleTickData.getLocation();
|
Location location = particleTickData.getLocation();
|
||||||
|
BodyFrame frame = particleTickData.getFrame();
|
||||||
|
if (frame != null) {
|
||||||
|
particleElement.tick(particleTickData.withLocation(location.clone().add(frame.ring(distance, (particleTickData.getDeg() * speed) % 360 + phase))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vector vector = new Vector(distance, 0, 0);
|
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));
|
ParticleTickData nParticleTickData = particleTickData.withLocation(location.clone().add(vector));
|
||||||
particleElement.tick(nParticleTickData);
|
particleElement.tick(nParticleTickData);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,6 @@ public class Cloud extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_CLOUD";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
if (particleTickData.getWorld().getBlockAt(particleTickData.getPlayer().getLocation().subtract(0, 0.5, 0)).getType() == Material.AIR) {
|
if (particleTickData.getWorld().getBlockAt(particleTickData.getPlayer().getLocation().subtract(0, 0.5, 0)).getType() == Material.AIR) {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.particle.Gradient;
|
||||||
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
// A particle that needs extra data, computed per tick from the tick and spawn location so colours can follow a gradient
|
||||||
|
public class DataParticle implements ParticleElement {
|
||||||
|
|
||||||
|
private final Particle particle;
|
||||||
|
private final float vx;
|
||||||
|
private final float vy;
|
||||||
|
private final float vz;
|
||||||
|
private final double speed;
|
||||||
|
private final int count;
|
||||||
|
private final BiFunction<ParticleTickData, Location, Object> data;
|
||||||
|
|
||||||
|
public DataParticle(Particle particle, float vx, float vy, float vz, double speed, int count, BiFunction<ParticleTickData, Location, Object> data) {
|
||||||
|
this.particle = particle;
|
||||||
|
this.vx = vx;
|
||||||
|
this.vy = vy;
|
||||||
|
this.vz = vz;
|
||||||
|
this.speed = speed;
|
||||||
|
this.count = count;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The gradient's colour at this tick, the same way dust particles pick it; a random colour without gradient
|
||||||
|
public static Color color(Gradient gradient, ParticleTickData particleTickData) {
|
||||||
|
if (gradient == null) {
|
||||||
|
return Color.fromRGB(RANDOM.nextInt(256), RANDOM.nextInt(256), RANDOM.nextInt(256));
|
||||||
|
}
|
||||||
|
java.awt.Color color = gradient.getColor(particleTickData.gradientPosition(), 360);
|
||||||
|
return Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
Location location = particleTickData.getLocation().add(0.0, 0.2, 0.0);
|
||||||
|
Object value = data.apply(particleTickData, location);
|
||||||
|
display(particleTickData, player -> player.spawnParticle(particle, location, count, vx, vy, vz, speed, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,50 +19,31 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
import de.steamwar.lobby.LobbySystem;
|
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
public class Delayed extends DelegatingParticleElement {
|
public class Delayed extends DelegatingParticleElement {
|
||||||
|
|
||||||
private DelayedData delayedData = new DelayedData();
|
// Weak keys drop players once they quit, so no quit listener has to be registered per instance
|
||||||
|
private final Map<Player, Integer> data = new WeakHashMap<>();
|
||||||
private int interval;
|
private int interval;
|
||||||
|
|
||||||
private static class DelayedData implements Listener {
|
|
||||||
private Map<Player, Integer> data = new HashMap<>();
|
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true)
|
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
|
||||||
data.remove(event.getPlayer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Delayed(ParticleElement particleElement, int interval) {
|
public Delayed(ParticleElement particleElement, int interval) {
|
||||||
super(particleElement);
|
super(particleElement);
|
||||||
Bukkit.getPluginManager().registerEvents(delayedData, LobbySystem.getInstance());
|
|
||||||
this.interval = interval;
|
this.interval = interval;
|
||||||
if (interval <= 0) {
|
if (interval <= 0) {
|
||||||
throw new IllegalArgumentException("Interval must be greater than 0");
|
throw new IllegalArgumentException("Interval must be greater than 0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
int currentNumber = delayedData.data.getOrDefault(particleTickData.getPlayer(), 0) % interval;
|
int currentNumber = data.getOrDefault(particleTickData.getPlayer(), 0) % interval;
|
||||||
delayedData.data.put(particleTickData.getPlayer(), currentNumber + 1);
|
data.put(particleTickData.getPlayer(), currentNumber + 1);
|
||||||
if (currentNumber != 0) {
|
if (currentNumber != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickType;
|
import de.steamwar.lobby.particle.ParticleTickType;
|
||||||
|
|
||||||
@@ -31,17 +30,6 @@ public abstract class DelegatingParticleElement implements ParticleElement {
|
|||||||
this.particleElement = particleElement;
|
this.particleElement = particleElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String attribute();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void aggregateAttributes(ParticleData particleData) {
|
|
||||||
String attribute = attribute();
|
|
||||||
if (attribute != null) {
|
|
||||||
particleData.add(attribute);
|
|
||||||
}
|
|
||||||
particleElement.aggregateAttributes(particleData);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParticleTickType tickType() {
|
public ParticleTickType tickType() {
|
||||||
return particleElement.tickType();
|
return particleElement.tickType();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.particle.BodyFrame;
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import de.steamwar.lobby.particle.ParticleTickType;
|
import de.steamwar.lobby.particle.ParticleTickType;
|
||||||
@@ -46,11 +47,6 @@ public class DoubleCircle extends Circle {
|
|||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_BI_CIRCLE";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParticleTickType tickType() {
|
public ParticleTickType tickType() {
|
||||||
if (particleElement.tickType() == second.tickType()) {
|
if (particleElement.tickType() == second.tickType()) {
|
||||||
@@ -62,6 +58,13 @@ public class DoubleCircle extends Circle {
|
|||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
Location location = particleTickData.getLocation();
|
Location location = particleTickData.getLocation();
|
||||||
|
BodyFrame frame = particleTickData.getFrame();
|
||||||
|
if (frame != null) {
|
||||||
|
double angle = (particleTickData.getDeg() * speed) % 360;
|
||||||
|
particleElement.tick(particleTickData.withLocation(location.clone().add(frame.ring(distance, angle))));
|
||||||
|
second.tick(particleTickData.withLocation(location.clone().add(frame.ring(distance, angle + Math.PI))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vector vector = new Vector(distance, 0, 0);
|
Vector vector = new Vector(distance, 0, 0);
|
||||||
vector.rotateAroundY((particleTickData.getDeg() * speed) % 360);
|
vector.rotateAroundY((particleTickData.getDeg() * speed) % 360);
|
||||||
|
|||||||
@@ -65,13 +65,12 @@ public class DustParticle implements ParticleElement {
|
|||||||
Location location = particleTickData.getLocation().add(0.0, 0.2, 0.0);
|
Location location = particleTickData.getLocation().add(0.0, 0.2, 0.0);
|
||||||
Particle.DustOptions dustOptions;
|
Particle.DustOptions dustOptions;
|
||||||
if (gradient != null) {
|
if (gradient != null) {
|
||||||
double result = (particleTickData.getDeg() * 10) % 360;
|
Color color = gradient.getColor(particleTickData.gradientPosition(), 360);
|
||||||
Color color = gradient.getColor(result, 360);
|
|
||||||
dustOptions = withColor(color.getRed(), color.getGreen(), color.getBlue());
|
dustOptions = withColor(color.getRed(), color.getGreen(), color.getBlue());
|
||||||
} else {
|
} else {
|
||||||
dustOptions = randomParticleDust();
|
dustOptions = randomParticleDust();
|
||||||
}
|
}
|
||||||
display(location, particleTickData.getPlayer(), particleTickData.isOnlySelf(), particleTickData.isOnlyOthers(), player -> {
|
display(particleTickData, player -> {
|
||||||
player.spawnParticle(particle, location, count, vx, vy, vz, speed, dustOptions);
|
player.spawnParticle(particle, location, count, vx, vy, vz, speed, dustOptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ public class Floor extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_FLOOR";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
if (!particleTickData.getWorld().getBlockAt(particleTickData.getPlayer().getLocation().subtract(0, 0.5, 0)).getType().isAir()) {
|
if (!particleTickData.getWorld().getBlockAt(particleTickData.getPlayer().getLocation().subtract(0, 0.5, 0)).getType().isAir()) {
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ public class Flying extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_FLYING";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
if (!particleTickData.getPlayer().isGliding()) {
|
if (!particleTickData.getPlayer().isGliding()) {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
|
|
||||||
@@ -32,19 +31,6 @@ public class Group extends DelegatingParticleElement {
|
|||||||
this.rest = rest;
|
this.rest = rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void aggregateAttributes(ParticleData particleData) {
|
|
||||||
particleElement.aggregateAttributes(particleData);
|
|
||||||
for (ParticleElement particleElement : rest) {
|
|
||||||
particleElement.aggregateAttributes(particleData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
particleElement.tick(particleTickData);
|
particleElement.tick(particleTickData);
|
||||||
|
|||||||
@@ -19,15 +19,19 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.particle.BodyFrame;
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
public class LocationMutator extends DelegatingParticleElement {
|
public class LocationMutator extends DelegatingParticleElement {
|
||||||
|
|
||||||
private UnaryOperator<Location> mutator;
|
private UnaryOperator<Location> mutator;
|
||||||
|
// Set for plain offsets, which turn into right/up/forward when placed relative to the body
|
||||||
|
private Vector offset;
|
||||||
|
|
||||||
public LocationMutator(ParticleElement particleElement, UnaryOperator<Location> mutator) {
|
public LocationMutator(ParticleElement particleElement, UnaryOperator<Location> mutator) {
|
||||||
super(particleElement);
|
super(particleElement);
|
||||||
@@ -36,15 +40,17 @@ public class LocationMutator extends DelegatingParticleElement {
|
|||||||
|
|
||||||
public LocationMutator(ParticleElement particleElement, double vx, double vy, double vz) {
|
public LocationMutator(ParticleElement particleElement, double vx, double vy, double vz) {
|
||||||
this(particleElement, location -> location.add(vx, vy, vz));
|
this(particleElement, location -> location.add(vx, vy, vz));
|
||||||
}
|
this.offset = new Vector(vx, vy, vz);
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
BodyFrame frame = particleTickData.getFrame();
|
||||||
|
if (frame != null && offset != null) {
|
||||||
|
Location location = particleTickData.getLocation().clone().add(frame.local(offset.getX(), offset.getY(), offset.getZ()));
|
||||||
|
particleElement.tick(particleTickData.withLocation(location));
|
||||||
|
return;
|
||||||
|
}
|
||||||
ParticleTickData nParticleTickData = particleTickData.withLocation(mutator.apply(particleTickData.getLocation()));
|
ParticleTickData nParticleTickData = particleTickData.withLocation(mutator.apply(particleTickData.getLocation()));
|
||||||
particleElement.tick(nParticleTickData);
|
particleElement.tick(nParticleTickData);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ public class NonFloor extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_NON_FLOOR";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
if (particleTickData.getWorld().getBlockAt(particleTickData.getPlayer().getLocation().subtract(0, 0.5, 0)).getType().isAir()) {
|
if (particleTickData.getWorld().getBlockAt(particleTickData.getPlayer().getLocation().subtract(0, 0.5, 0)).getType().isAir()) {
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ public class NonFlying extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_NON_FLYING";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
if (particleTickData.getPlayer().isGliding()) {
|
if (particleTickData.getPlayer().isGliding()) {
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ public class OnlyOthers extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
particleElement.tick(particleTickData.onlyOthers());
|
particleElement.tick(particleTickData.onlyOthers());
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ public class OnlySelf extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
particleElement.tick(particleTickData.onlySelf());
|
particleElement.tick(particleTickData.onlySelf());
|
||||||
|
|||||||
@@ -19,58 +19,29 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
import de.steamwar.lobby.LobbySystem;
|
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
public class RandomParticle implements ParticleElement {
|
public class RandomParticle implements ParticleElement {
|
||||||
|
|
||||||
private RandomData randomData;
|
// Weak keys drop players once they quit, so each player rolls again on their next session
|
||||||
private ParticleEnum[] particleEnums;
|
private final Map<Player, Integer> data = new WeakHashMap<>();
|
||||||
|
private final ParticleElement[] particleElements;
|
||||||
|
|
||||||
private static class RandomData implements Listener {
|
public RandomParticle(ParticleElement... particleElements) {
|
||||||
private Random random = new Random();
|
if (particleElements.length == 0) {
|
||||||
private int max;
|
throw new IllegalArgumentException("ParticleElements must not be empty");
|
||||||
private Map<Player, Integer> data = new HashMap<>();
|
|
||||||
|
|
||||||
public RandomData(int max) {
|
|
||||||
this.max = max;
|
|
||||||
}
|
}
|
||||||
|
this.particleElements = particleElements;
|
||||||
@EventHandler(ignoreCancelled = true)
|
|
||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
|
||||||
data.put(event.getPlayer(), random.nextInt(max));
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true)
|
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
|
||||||
data.remove(event.getPlayer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public RandomParticle(ParticleEnum... particleEnums) {
|
|
||||||
if (particleEnums.length == 0) {
|
|
||||||
throw new IllegalArgumentException("ParticleEnums must not be empty");
|
|
||||||
}
|
|
||||||
this.randomData = new RandomData(particleEnums.length);
|
|
||||||
Bukkit.getPluginManager().registerEvents(randomData, LobbySystem.getInstance());
|
|
||||||
this.particleEnums = particleEnums;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
int currentNumber = randomData.data.getOrDefault(particleTickData.getPlayer(), 0);
|
int currentNumber = data.computeIfAbsent(particleTickData.getPlayer(), player -> RANDOM.nextInt(particleElements.length));
|
||||||
particleEnums[currentNumber].getParticle().getParticleElement().tick(particleTickData);
|
particleElements[currentNumber].tick(particleTickData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class SimpleParticle implements ParticleElement {
|
|||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
Location location = particleTickData.getLocation().add(0.0, 0.2, 0.0);
|
Location location = particleTickData.getLocation().add(0.0, 0.2, 0.0);
|
||||||
display(location, particleTickData.getPlayer(), particleTickData.isOnlySelf(), particleTickData.isOnlyOthers(), player -> {
|
display(particleTickData, player -> {
|
||||||
if (customVelocity) {
|
if (customVelocity) {
|
||||||
player.spawnParticle(particle, location, count, vx, vy, vz, time);
|
player.spawnParticle(particle, location, count, vx, vy, vz, time);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ public class Sneaking extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_SNEAKING";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
if (!particleTickData.getPlayer().isSneaking()) {
|
if (!particleTickData.getPlayer().isSneaking()) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.particle.BodyFrame;
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import de.steamwar.lobby.particle.ParticleTickType;
|
import de.steamwar.lobby.particle.ParticleTickType;
|
||||||
@@ -48,11 +49,6 @@ public class TrippleCircle extends Circle {
|
|||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_TRI_CIRCLE";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParticleTickType tickType() {
|
public ParticleTickType tickType() {
|
||||||
if (particleElement.tickType() == second.tickType() && particleElement.tickType() == third.tickType()) {
|
if (particleElement.tickType() == second.tickType() && particleElement.tickType() == third.tickType()) {
|
||||||
@@ -64,6 +60,14 @@ public class TrippleCircle extends Circle {
|
|||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
Location location = particleTickData.getLocation();
|
Location location = particleTickData.getLocation();
|
||||||
|
BodyFrame frame = particleTickData.getFrame();
|
||||||
|
if (frame != null) {
|
||||||
|
double angle = (particleTickData.getDeg() * speed) % 360;
|
||||||
|
particleElement.tick(particleTickData.withLocation(location.clone().add(frame.ring(distance, angle))));
|
||||||
|
second.tick(particleTickData.withLocation(location.clone().add(frame.ring(distance, angle + Math.toRadians(120)))));
|
||||||
|
third.tick(particleTickData.withLocation(location.clone().add(frame.ring(distance, angle + Math.toRadians(240)))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vector vector = new Vector(distance, 0, 0);
|
Vector vector = new Vector(distance, 0, 0);
|
||||||
vector.rotateAroundY((particleTickData.getDeg() * speed) % 360);
|
vector.rotateAroundY((particleTickData.getDeg() * speed) % 360);
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements;
|
package de.steamwar.lobby.particle.elements;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.particle.BodyFrame;
|
||||||
|
import de.steamwar.lobby.particle.BodyMotion;
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import de.steamwar.lobby.particle.WingDesign;
|
import de.steamwar.lobby.particle.WingDesign;
|
||||||
@@ -27,8 +29,29 @@ import org.bukkit.util.Vector;
|
|||||||
|
|
||||||
public class Wing extends DelegatingParticleElement {
|
public class Wing extends DelegatingParticleElement {
|
||||||
|
|
||||||
|
public enum Motion {
|
||||||
|
NONE,
|
||||||
|
FLAP_SLOW,
|
||||||
|
FLAP_FAST,
|
||||||
|
// 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
|
||||||
|
private static final double IDLE_OPEN_DEGREES = 5;
|
||||||
|
private static final double MOVING_OPEN_DEGREES = 15;
|
||||||
|
private static final double FULL_OPEN_SPEED = 0.6;
|
||||||
|
private static final double FLAP_DEGREES = 25;
|
||||||
|
public static final int JUMP_BEAT_TICKS = 10;
|
||||||
|
|
||||||
private double size;
|
private double size;
|
||||||
private WingDesign wingDesign;
|
private WingDesign wingDesign;
|
||||||
|
// Colour each point by its distance from the spine instead of by time
|
||||||
|
private boolean colorAlongWing;
|
||||||
|
// Share of the points shown each tick, in percent; below 100 the wing twinkles
|
||||||
|
private int density = 100;
|
||||||
|
private Motion motion = Motion.NONE;
|
||||||
|
private double widest;
|
||||||
|
|
||||||
public Wing(ParticleElement particleElement, double size, WingDesign wingDesign) {
|
public Wing(ParticleElement particleElement, double size, WingDesign wingDesign) {
|
||||||
super(particleElement);
|
super(particleElement);
|
||||||
@@ -36,21 +59,75 @@ public class Wing extends DelegatingParticleElement {
|
|||||||
this.wingDesign = wingDesign;
|
this.wingDesign = wingDesign;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Wing(ParticleElement particleElement, double size, WingDesign wingDesign, boolean colorAlongWing, int density, Motion motion) {
|
||||||
public String attribute() {
|
this(particleElement, size, wingDesign);
|
||||||
return "PARTICLE_ATTRIBUTE_WING";
|
this.colorAlongWing = colorAlongWing;
|
||||||
|
this.density = density;
|
||||||
|
this.motion = motion;
|
||||||
|
for (Vector vector : wingDesign.getVectors()) {
|
||||||
|
widest = Math.max(widest, Math.abs(vector.getX()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
double ticks = particleTickData.getDeg() * 10;
|
||||||
|
double open = Math.toRadians(openDegrees(particleTickData, ticks));
|
||||||
|
double cos = Math.cos(open), sin = Math.sin(open);
|
||||||
|
double sneak = particleTickData.getPlayer().isSneaking() ? 0.5 : 0;
|
||||||
|
|
||||||
|
BodyFrame frame = particleTickData.getFrame();
|
||||||
|
if (frame != null) {
|
||||||
|
// Half a block behind the back, so the wings stay on the back even while gliding
|
||||||
|
Location base = particleTickData.getLocation();
|
||||||
for (Vector dVector : wingDesign.getVectors()) {
|
for (Vector dVector : wingDesign.getVectors()) {
|
||||||
Vector vector = new Vector(dVector.getX() * size, 0.6 + dVector.getY() * size - (particleTickData.getPlayer().isSneaking() ? 0.5 : 0), 0.5);
|
if (hidden()) continue;
|
||||||
vector.rotateAroundY(Math.toRadians(particleTickData.getPlayer().getLocation().getYaw() * -1));
|
double out = dVector.getX() * size;
|
||||||
|
Vector local = frame.local(out * cos, 0.6 + dVector.getY() * size - sneak, -0.5 - Math.abs(out) * sin);
|
||||||
|
particleElement.tick(shaded(particleTickData, dVector).withLocation(base.clone().add(local)));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Vector dVector : wingDesign.getVectors()) {
|
||||||
|
if (hidden()) continue;
|
||||||
|
double out = dVector.getX() * size;
|
||||||
|
Vector vector = new Vector(out * cos, 0.6 + dVector.getY() * size - sneak, 0.5 + Math.abs(out) * sin);
|
||||||
|
vector.rotateAroundY(Math.toRadians(BodyMotion.yaw(particleTickData.getPlayer()) * -1));
|
||||||
vector.setX(-vector.getX());
|
vector.setX(-vector.getX());
|
||||||
vector.setZ(-vector.getZ());
|
vector.setZ(-vector.getZ());
|
||||||
Location location = particleTickData.getPlayer().getLocation().clone().add(vector);
|
Location location = particleTickData.getPlayer().getLocation().clone().add(vector);
|
||||||
ParticleTickData current = particleTickData.withLocation(location);
|
ParticleTickData current = shaded(particleTickData, dVector).withLocation(location);
|
||||||
particleElement.tick(current);
|
particleElement.tick(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// How far each half is turned back around the spine; flapping swings it back and forth
|
||||||
|
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;
|
||||||
|
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 BEAT_ON_JUMP -> open + jumpBeat(particleTickData);
|
||||||
|
default -> open;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// One swing back and forth during the ticks after a jump
|
||||||
|
private double jumpBeat(ParticleTickData particleTickData) {
|
||||||
|
long sinceJump = BodyMotion.ticksSinceJump(particleTickData.getPlayer());
|
||||||
|
return sinceJump > JUMP_BEAT_TICKS ? 0 : FLAP_DEGREES * Math.sin(Math.PI * sinceJump / JUMP_BEAT_TICKS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full wings never draw a random number, so presets keep their exact look
|
||||||
|
private boolean hidden() {
|
||||||
|
return density < 100 && RANDOM.nextInt(100) >= density;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleTickData shaded(ParticleTickData particleTickData, Vector point) {
|
||||||
|
if (!colorAlongWing || widest == 0) {
|
||||||
|
return particleTickData;
|
||||||
|
}
|
||||||
|
return particleTickData.withShade(Math.abs(point.getX()) / widest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements.custom;
|
package de.steamwar.lobby.particle.elements.custom;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.particle.BodyMotion;
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import de.steamwar.lobby.particle.elements.DelegatingParticleElement;
|
import de.steamwar.lobby.particle.elements.DelegatingParticleElement;
|
||||||
@@ -41,11 +42,6 @@ public class HearthBeat extends DelegatingParticleElement {
|
|||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
double time = ((particleTickData.getDeg() * speed) % 360) / 360.0;
|
double time = ((particleTickData.getDeg() * speed) % 360) / 360.0;
|
||||||
@@ -53,7 +49,7 @@ public class HearthBeat extends DelegatingParticleElement {
|
|||||||
double x = (time - 0.5) * 2;
|
double x = (time - 0.5) * 2;
|
||||||
|
|
||||||
Vector vector = new Vector(x, 1.1 + y - (particleTickData.getPlayer().isSneaking() ? 0.5 : 0), 0.7);
|
Vector vector = new Vector(x, 1.1 + y - (particleTickData.getPlayer().isSneaking() ? 0.5 : 0), 0.7);
|
||||||
vector.rotateAroundY(Math.toRadians(particleTickData.getPlayer().getLocation().getYaw() * -1));
|
vector.rotateAroundY(Math.toRadians(BodyMotion.yaw(particleTickData.getPlayer()) * -1));
|
||||||
vector.setX(-vector.getX());
|
vector.setX(-vector.getX());
|
||||||
vector.setZ(-vector.getZ());
|
vector.setZ(-vector.getZ());
|
||||||
Location location = particleTickData.getPlayer().getLocation().clone().add(vector);
|
Location location = particleTickData.getPlayer().getLocation().clone().add(vector);
|
||||||
|
|||||||
@@ -29,11 +29,6 @@ public class NonMoving extends DelegatingParticleElement {
|
|||||||
super(particleElement);
|
super(particleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return "PARTICLE_ATTRIBUTE_NON_MOVING";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
if (particleTickData.isMoving()) {
|
if (particleTickData.isMoving()) {
|
||||||
|
|||||||
@@ -47,11 +47,6 @@ public class PulseShimmer extends DelegatingParticleElement {
|
|||||||
this.rotationSpeed = rotationSpeed;
|
this.rotationSpeed = rotationSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
double time = ((particleTickData.getDeg() * speed) % 360) / 360.0;
|
double time = ((particleTickData.getDeg() * speed) % 360) / 360.0;
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.particle.elements.custom;
|
package de.steamwar.lobby.particle.elements.custom;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.particle.BodyFrame;
|
||||||
import de.steamwar.lobby.particle.ParticleElement;
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
import de.steamwar.lobby.particle.ParticleTickData;
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
import de.steamwar.lobby.particle.elements.DelegatingParticleElement;
|
import de.steamwar.lobby.particle.elements.DelegatingParticleElement;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
|
||||||
public class YOffset extends DelegatingParticleElement {
|
public class YOffset extends DelegatingParticleElement {
|
||||||
|
|
||||||
@@ -40,11 +42,6 @@ public class YOffset extends DelegatingParticleElement {
|
|||||||
this.bounce = bounce;
|
this.bounce = bounce;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String attribute() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(ParticleTickData particleTickData) {
|
public void tick(ParticleTickData particleTickData) {
|
||||||
double y;
|
double y;
|
||||||
@@ -71,7 +68,9 @@ public class YOffset extends DelegatingParticleElement {
|
|||||||
y = minY + (maxY - minY) * value;
|
y = minY + (maxY - minY) * value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParticleTickData current = particleTickData.withLocation(particleTickData.getLocation().clone().add(0, y, 0));
|
BodyFrame frame = particleTickData.getFrame();
|
||||||
|
Location location = particleTickData.getLocation().clone();
|
||||||
|
ParticleTickData current = particleTickData.withLocation(frame == null ? location.add(0, y, 0) : location.add(frame.getUp().clone().multiply(y)));
|
||||||
particleElement.tick(current);
|
particleElement.tick(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 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.BodyMotion;
|
||||||
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
|
import de.steamwar.lobby.particle.elements.DelegatingParticleElement;
|
||||||
|
|
||||||
|
// Ticks the element every tick for a while after each jump, and as usual otherwise
|
||||||
|
public class AfterJump extends DelegatingParticleElement {
|
||||||
|
|
||||||
|
private final ParticleElement otherwise;
|
||||||
|
private final int ticks;
|
||||||
|
|
||||||
|
public AfterJump(ParticleElement particleElement, ParticleElement otherwise, int ticks) {
|
||||||
|
super(particleElement);
|
||||||
|
this.otherwise = otherwise;
|
||||||
|
this.ticks = ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
if (BodyMotion.ticksSinceJump(particleTickData.getPlayer()) <= ticks) {
|
||||||
|
particleElement.tick(particleTickData);
|
||||||
|
} else {
|
||||||
|
otherwise.tick(particleTickData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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.BodyMotion;
|
||||||
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
|
import de.steamwar.lobby.particle.elements.DelegatingParticleElement;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
// Shows the burst once at the player's feet in the tick the event happens
|
||||||
|
public class EventBurst extends DelegatingParticleElement {
|
||||||
|
|
||||||
|
public enum Trigger {
|
||||||
|
JUMP,
|
||||||
|
LAND,
|
||||||
|
SNEAK,
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Trigger trigger;
|
||||||
|
// Landings from lower than this many blocks are ignored
|
||||||
|
private final double minimumFall;
|
||||||
|
|
||||||
|
public EventBurst(Trigger trigger, ParticleElement burst) {
|
||||||
|
this(trigger, 0, burst);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventBurst(Trigger trigger, double minimumFall, ParticleElement burst) {
|
||||||
|
super(burst);
|
||||||
|
this.trigger = trigger;
|
||||||
|
this.minimumFall = minimumFall;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
Player player = particleTickData.getPlayer();
|
||||||
|
boolean happened = switch (trigger) {
|
||||||
|
case JUMP -> BodyMotion.jumped(player);
|
||||||
|
case LAND -> BodyMotion.landed(player) >= minimumFall;
|
||||||
|
case SNEAK -> BodyMotion.startedSneaking(player);
|
||||||
|
};
|
||||||
|
if (happened) {
|
||||||
|
particleElement.tick(particleTickData.withLocation(player.getLocation()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
// 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 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, double step) {
|
||||||
|
super(particleElement);
|
||||||
|
this.lifetime = lifetime;
|
||||||
|
this.step = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
Player player = particleTickData.getPlayer();
|
||||||
|
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)) {
|
||||||
|
playerPrints.addLast(new Print(feet, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = playerPrints.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
Print print = playerPrints.pollFirst();
|
||||||
|
if (print.age() >= lifetime) continue;
|
||||||
|
particleElement.tick(particleTickData.withLocation(print.location().clone()));
|
||||||
|
playerPrints.addLast(new Print(print.location(), print.age() + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 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 between each point and the player's chest. Spawned with an amount of 0, the offset becomes
|
||||||
|
// the glyph's path: it appears at location + offset and flies to the location, like glyphs flying into an enchanting
|
||||||
|
// table.
|
||||||
|
public class GlyphPath implements ParticleElement {
|
||||||
|
|
||||||
|
public enum Direction {
|
||||||
|
INTO_PLAYER,
|
||||||
|
OUT_OF_PLAYER,
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final double CHEST = 1.2;
|
||||||
|
|
||||||
|
private final Direction direction;
|
||||||
|
private final double spreadX;
|
||||||
|
private final double spreadY;
|
||||||
|
private final double spreadZ;
|
||||||
|
private final int count;
|
||||||
|
|
||||||
|
public GlyphPath(Direction direction, double spreadX, double spreadY, double spreadZ, int count) {
|
||||||
|
this.direction = direction;
|
||||||
|
this.spreadX = spreadX;
|
||||||
|
this.spreadY = spreadY;
|
||||||
|
this.spreadZ = spreadZ;
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
Location chest = particleTickData.getPlayer().getLocation().add(0, CHEST, 0);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
// Spread like the game spreads particles, as the game does not spread those spawned with an amount of 0
|
||||||
|
Location point = particleTickData.getLocation().clone().add(RANDOM.nextGaussian() * spreadX, RANDOM.nextGaussian() * spreadY, RANDOM.nextGaussian() * spreadZ);
|
||||||
|
Vector fromChest = point.toVector().subtract(chest.toVector());
|
||||||
|
Location target = direction == Direction.INTO_PLAYER ? chest : point;
|
||||||
|
Vector path = direction == Direction.INTO_PLAYER ? fromChest : fromChest.multiply(-1);
|
||||||
|
display(particleTickData, player -> player.spawnParticle(Particle.ENCHANT, target, 0, path.getX(), path.getY(), path.getZ(), 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.bukkit.Location;
|
||||||
|
|
||||||
|
// A flat ring of points around the location, all at once, for bursts
|
||||||
|
public class Ring extends DelegatingParticleElement {
|
||||||
|
|
||||||
|
private final double radius;
|
||||||
|
private final int points;
|
||||||
|
|
||||||
|
public Ring(ParticleElement particleElement, double radius, int points) {
|
||||||
|
super(particleElement);
|
||||||
|
this.radius = radius;
|
||||||
|
this.points = points;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
Location center = particleTickData.getLocation();
|
||||||
|
for (int i = 0; i < points; i++) {
|
||||||
|
double angle = 2 * Math.PI * i / points;
|
||||||
|
particleElement.tick(particleTickData.withLocation(center.clone().add(Math.cos(angle) * radius, 0, Math.sin(angle) * radius)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 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.BodyMotion;
|
||||||
|
import de.steamwar.lobby.particle.ParticleElement;
|
||||||
|
import de.steamwar.lobby.particle.ParticleTickData;
|
||||||
|
import de.steamwar.lobby.particle.elements.DelegatingParticleElement;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
// A line of particles between nearby players who show the same link. It starts in one player's particle and blends
|
||||||
|
// into the other's towards them. Each pair is drawn by only one of them.
|
||||||
|
public class TeamLink extends DelegatingParticleElement {
|
||||||
|
|
||||||
|
public static final double RANGE = 8;
|
||||||
|
private static final double CHEST = 1.2;
|
||||||
|
// Spreads the blend evenly along the line, so it does not flicker from tick to tick
|
||||||
|
private static final double GOLDEN_RATIO = 0.6180339887;
|
||||||
|
|
||||||
|
private record Showing(long tick, ParticleElement particle) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per link, who showed it in which tick with which particle
|
||||||
|
private static final Map<String, Map<Player, Showing>> SHOWING = new HashMap<>();
|
||||||
|
|
||||||
|
private final String link;
|
||||||
|
private final double spacing;
|
||||||
|
|
||||||
|
public TeamLink(String link, ParticleElement particleElement, double spacing) {
|
||||||
|
super(particleElement);
|
||||||
|
this.link = link;
|
||||||
|
this.spacing = spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(ParticleTickData particleTickData) {
|
||||||
|
Player player = particleTickData.getPlayer();
|
||||||
|
long now = BodyMotion.currentTick();
|
||||||
|
Map<Player, Showing> showing = SHOWING.computeIfAbsent(link, key -> new WeakHashMap<>());
|
||||||
|
showing.put(player, new Showing(now, particleElement));
|
||||||
|
|
||||||
|
Location from = player.getLocation().add(0, CHEST, 0);
|
||||||
|
for (Map.Entry<Player, Showing> other : showing.entrySet()) {
|
||||||
|
Player partner = other.getKey();
|
||||||
|
// Still showing it, and drawn by the player with the smaller id only
|
||||||
|
if (partner == player || other.getValue().tick() < now - 1 || player.getUniqueId().compareTo(partner.getUniqueId()) > 0) continue;
|
||||||
|
Location to = partner.getLocation().add(0, CHEST, 0);
|
||||||
|
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);
|
||||||
|
step.multiply(1.0 / points);
|
||||||
|
for (int i = 1; i < points; i++) {
|
||||||
|
double towardsPartner = (double) i / points;
|
||||||
|
ParticleElement particle = (i * GOLDEN_RATIO) % 1 < towardsPartner ? other.getValue().particle() : particleElement;
|
||||||
|
particle.tick(particleTickData.withLocation(from.clone().add(step.clone().multiply(i))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.gui;
|
||||||
|
|
||||||
|
import de.steamwar.inventory.SWInventory;
|
||||||
|
import de.steamwar.inventory.SWItem;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
// Every particle menu puts the same button in the same column of its last row
|
||||||
|
@UtilityClass
|
||||||
|
class BottomBar {
|
||||||
|
|
||||||
|
// Taken by the page arrows of list menus
|
||||||
|
final int PREVIOUS_PAGE = 0;
|
||||||
|
final int BACK = 1;
|
||||||
|
// Shows why a locked slot cannot be edited
|
||||||
|
final int STATUS = 2;
|
||||||
|
final int ADD = 3;
|
||||||
|
final int RENAME = 4;
|
||||||
|
final int ICON = 5;
|
||||||
|
final int COST = 6;
|
||||||
|
// Delete in editors, reset in value screens; no menu has both
|
||||||
|
final int DELETE = 7;
|
||||||
|
final int RESET = 7;
|
||||||
|
final int NEXT_PAGE = 8;
|
||||||
|
|
||||||
|
int position(int inventorySize, int column) {
|
||||||
|
return inventorySize - 9 + column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fills the columns this menu does not use, so the buttons it does use stay recognisable by position.
|
||||||
|
// List menus keep the outer columns free for the page arrows they add when opened.
|
||||||
|
void fillUnused(SWInventory inventory, int inventorySize, boolean listMenu) {
|
||||||
|
for (int column = listMenu ? 1 : 0; column < (listMenu ? 8 : 9); column++) {
|
||||||
|
int position = position(inventorySize, column);
|
||||||
|
if (!inventory.hasItem(position)) {
|
||||||
|
inventory.setItem(position, new SWItem(Material.BLACK_STAINED_GLASS_PANE, " ", clickType -> {
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
/*
|
||||||
|
* 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.gui;
|
||||||
|
|
||||||
|
import de.steamwar.inventory.SWInventory;
|
||||||
|
import de.steamwar.inventory.SWItem;
|
||||||
|
import de.steamwar.lobby.LobbySystem;
|
||||||
|
import de.steamwar.lobby.particle.UnlockContext;
|
||||||
|
import de.steamwar.lobby.particle.design.*;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleEntry;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlot;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlots;
|
||||||
|
import de.steamwar.lobby.util.LobbyPlayer;
|
||||||
|
import de.steamwar.sql.SteamwarUser;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static de.steamwar.lobby.particle.design.EntryParameters.*;
|
||||||
|
import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
|
||||||
|
|
||||||
|
// Settings of one entry in labelled rows; a click on a setting toggles it or opens its own screen
|
||||||
|
@UtilityClass
|
||||||
|
class EntryEditor {
|
||||||
|
|
||||||
|
private final int ROW_DESIGN = 0;
|
||||||
|
private final int ROW_PARTICLE = 1;
|
||||||
|
private final int ROW_SHAPE = 2;
|
||||||
|
private final int ROW_TIMING = 3;
|
||||||
|
private final int ROW_CONDITIONS = 4;
|
||||||
|
// Settings start after the row header and one column of space
|
||||||
|
private final int FIRST_SETTING = 2;
|
||||||
|
private final int SIZE = 54;
|
||||||
|
private final int BACK = BottomBar.position(SIZE, BottomBar.BACK);
|
||||||
|
private final int COST = BottomBar.position(SIZE, BottomBar.COST);
|
||||||
|
private final int DELETE = BottomBar.position(SIZE, BottomBar.DELETE);
|
||||||
|
private final int MAX_CYCLED_OPTIONS = 5;
|
||||||
|
|
||||||
|
private final Map<Parameter<?>, Material> ICONS = Map.ofEntries(
|
||||||
|
Map.entry(COUNT, Material.GLOWSTONE_DUST),
|
||||||
|
Map.entry(SPEED, Material.FEATHER),
|
||||||
|
Map.entry(SPREAD_X, Material.GUNPOWDER),
|
||||||
|
Map.entry(SPREAD_Y, Material.GUNPOWDER),
|
||||||
|
Map.entry(SPREAD_Z, Material.GUNPOWDER),
|
||||||
|
Map.entry(OFFSET_X, Material.SPECTRAL_ARROW),
|
||||||
|
Map.entry(OFFSET_Y, Material.SPECTRAL_ARROW),
|
||||||
|
Map.entry(OFFSET_Z, Material.SPECTRAL_ARROW),
|
||||||
|
Map.entry(INTERVAL, Material.CLOCK),
|
||||||
|
Map.entry(TICK_MODE, Material.REPEATER),
|
||||||
|
Map.entry(PLACEMENT, Material.PLAYER_HEAD),
|
||||||
|
Map.entry(TRAIL_DURATION, Material.CLOCK),
|
||||||
|
Map.entry(TRAIL_TARGET_X, Material.LEAD),
|
||||||
|
Map.entry(TRAIL_TARGET_Y, Material.LEAD),
|
||||||
|
Map.entry(TRAIL_TARGET_Z, Material.LEAD),
|
||||||
|
Map.entry(MOVEMENT, Material.LEATHER_BOOTS),
|
||||||
|
Map.entry(GLIDING, Material.ELYTRA),
|
||||||
|
Map.entry(GROUND, Material.GRASS_BLOCK),
|
||||||
|
Map.entry(SNEAK, Material.LEATHER_LEGGINGS),
|
||||||
|
Map.entry(VISIBILITY, Material.SPYGLASS),
|
||||||
|
Map.entry(Designs.CIRCLE_RADIUS, Material.COMPASS),
|
||||||
|
Map.entry(Designs.CIRCLE_SPEED, Material.RECOVERY_COMPASS),
|
||||||
|
Map.entry(Designs.CIRCLE_PHASE, Material.TARGET),
|
||||||
|
Map.entry(Designs.CIRCLE_RISE, Material.LADDER),
|
||||||
|
Map.entry(Designs.CIRCLE_RISE_SPEED, Material.RABBIT_FOOT),
|
||||||
|
Map.entry(Designs.WING_SHAPE, Material.PHANTOM_MEMBRANE),
|
||||||
|
Map.entry(Designs.WING_SIZE, Material.SCAFFOLDING),
|
||||||
|
Map.entry(Designs.WING_DETAIL, Material.AMETHYST_SHARD),
|
||||||
|
Map.entry(Designs.WING_COLORING, Material.CYAN_DYE),
|
||||||
|
Map.entry(Designs.WING_SPARKLE, Material.AMETHYST_CLUSTER),
|
||||||
|
Map.entry(Designs.WING_MOTION, Material.FEATHER)
|
||||||
|
);
|
||||||
|
|
||||||
|
private record Context(Player player, SteamwarUser user, int index, int entryIndex, ParticleSlot slot, ParticleEntry entry) {
|
||||||
|
void reopen() {
|
||||||
|
open(player, index, entryIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void open(Player player, int index, int entryIndex) {
|
||||||
|
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player);
|
||||||
|
ParticleSlot slot = lobbyPlayer.getSlot(index);
|
||||||
|
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||||
|
if (slot == null || entryIndex >= slot.getEntries().size() || !ParticleSlots.requirement(index).test(new UnlockContext(user))) {
|
||||||
|
SlotEditor.open(player, index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Context context = new Context(player, user, index, entryIndex, slot, slot.getEntries().get(entryIndex));
|
||||||
|
ParticleEntry entry = context.entry();
|
||||||
|
ParticleDesign design = entry.getDesign();
|
||||||
|
|
||||||
|
SWInventory inventory = new SWInventory(player, SIZE, msg("PARTICLE_ENTRY_EDITOR", player, designName(design, player)));
|
||||||
|
header(inventory, ROW_DESIGN, Material.PAINTING, "PARTICLE_SECTION_DESIGN", player);
|
||||||
|
header(inventory, ROW_PARTICLE, Material.FIREWORK_STAR, "PARTICLE_SECTION_PARTICLE", player);
|
||||||
|
header(inventory, ROW_SHAPE, Material.ARMOR_STAND, "PARTICLE_SECTION_SHAPE", player);
|
||||||
|
header(inventory, ROW_TIMING, Material.DAYLIGHT_DETECTOR, "PARTICLE_SECTION_TIMING", player);
|
||||||
|
header(inventory, ROW_CONDITIONS, Material.OBSERVER, "PARTICLE_SECTION_CONDITIONS", player);
|
||||||
|
|
||||||
|
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)));
|
||||||
|
if (design.usesEntryParticle()) {
|
||||||
|
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 + 2, parameterItem(context, COLORS));
|
||||||
|
} else if (type.usesBlock()) {
|
||||||
|
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 2, parameterItem(context, BLOCK));
|
||||||
|
} else if (type == ParticleType.ENCHANT) {
|
||||||
|
inventory.setItem(ROW_DESIGN * 9 + FIRST_SETTING + 2, parameterItem(context, GLYPH_FLIGHT));
|
||||||
|
}
|
||||||
|
// 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 + 3 + i, parameterItem(context, target.get(i)));
|
||||||
|
}
|
||||||
|
particleRow.add(TRAIL_DURATION);
|
||||||
|
}
|
||||||
|
row(inventory, context, ROW_PARTICLE, particleRow);
|
||||||
|
} else {
|
||||||
|
inventory.setItem(ROW_PARTICLE * 9 + FIRST_SETTING, new SWItem(Material.STRUCTURE_VOID, msg("PARTICLE_FIXED_LOOK", player), clickType -> {
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
row(inventory, context, ROW_SHAPE, design.getParameters().stream().filter(parameter -> design.showsSetting(parameter, entry.getValues())).toList());
|
||||||
|
row(inventory, context, ROW_TIMING, List.of(TICK_MODE, INTERVAL, PLACEMENT, OFFSET_X, OFFSET_Y, OFFSET_Z));
|
||||||
|
row(inventory, context, ROW_CONDITIONS, List.of(MOVEMENT, GLIDING, GROUND, SNEAK, VISIBILITY));
|
||||||
|
|
||||||
|
inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> SlotEditor.open(player, index)));
|
||||||
|
inventory.setItem(COST, SlotEditor.costItem(player, user, slot, entry));
|
||||||
|
inventory.setItem(DELETE, new SWItem(Material.LAVA_BUCKET, msg("PARTICLE_ENTRY_DELETE", player), List.of(msg("PARTICLE_SHIFT_TO_DELETE", player)), false, clickType -> {
|
||||||
|
if (!clickType.isShiftClick()) return;
|
||||||
|
slot.getEntries().remove(entryIndex);
|
||||||
|
SlotEditor.save(player, index, slot);
|
||||||
|
SlotEditor.open(player, index);
|
||||||
|
}));
|
||||||
|
BottomBar.fillUnused(inventory, SIZE, false);
|
||||||
|
SlotEditor.fill(inventory, 0, SIZE);
|
||||||
|
inventory.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glowing so the row labels stand apart from the settings next to them
|
||||||
|
private void header(SWInventory inventory, int row, Material icon, String key, Player player) {
|
||||||
|
inventory.setItem(row * 9, new SWItem(icon, msg(key, player), List.of(), true, clickType -> {
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void row(SWInventory inventory, Context context, int row, List<Parameter<?>> parameters) {
|
||||||
|
for (int i = 0; i < parameters.size() && FIRST_SETTING + i < 9; i++) {
|
||||||
|
inventory.setItem(row * 9 + FIRST_SETTING + i, parameterItem(context, parameters.get(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private SWItem parameterItem(Context context, Parameter<?> parameter) {
|
||||||
|
Player player = context.player();
|
||||||
|
Object value = context.entry().getValues().get(parameter);
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
if (parameter instanceof ChoiceParameter<?> choice && isCycled(choice)) {
|
||||||
|
lore.addAll(optionLines((ChoiceParameter<Object>) choice, value, player));
|
||||||
|
} else {
|
||||||
|
lore.add(msg("PARTICLE_VALUE", player, valueText(parameter, value, player)));
|
||||||
|
}
|
||||||
|
lore.add("");
|
||||||
|
lore.add(msg(isToggle(parameter) ? "PARTICLE_VALUE_TOGGLE" : isCycled(parameter) ? "PARTICLE_VALUE_CYCLE" : "PARTICLE_VALUE_CLICK", player));
|
||||||
|
|
||||||
|
Material icon = ICONS.getOrDefault(parameter, Material.PAPER);
|
||||||
|
if (parameter instanceof BooleanParameter) icon = (Boolean) value ? Material.LIME_DYE : Material.GRAY_DYE;
|
||||||
|
if (parameter == PARTICLE) icon = ((ParticleType) value).getIcon();
|
||||||
|
if (parameter == COLORS) icon = Material.RED_DYE;
|
||||||
|
if (parameter == BLOCK) icon = (Material) value;
|
||||||
|
if (value instanceof WingShape shape) icon = shape.getIcon();
|
||||||
|
|
||||||
|
return new SWItem(icon, name(parameter.getNameKey(), player), lore, false, clickType -> {
|
||||||
|
if (parameter instanceof BooleanParameter toggle) {
|
||||||
|
apply(context, toggle, !(Boolean) value);
|
||||||
|
context.reopen();
|
||||||
|
} else if (parameter instanceof DoubleParameter number) {
|
||||||
|
ValueEditors.editNumber(player, name(number.getNameKey(), player), () -> context.entry().getValues().get(number),
|
||||||
|
number.getStep(), number.getMin(), number.getMax(), number.getDefaultValue(), false,
|
||||||
|
next -> apply(context, number, next), context::reopen);
|
||||||
|
} else if (parameter instanceof IntParameter number) {
|
||||||
|
ValueEditors.editNumber(player, name(number.getNameKey(), player), () -> (double) context.entry().getValues().get(number),
|
||||||
|
1, number.getMin(), number.getMax(), number.getDefaultValue(), true,
|
||||||
|
next -> apply(context, number, (int) Math.round(next)), context::reopen);
|
||||||
|
} else if (parameter instanceof GradientParameter gradient) {
|
||||||
|
ValueEditors.editColors(player, () -> context.entry().getValues().get(gradient), gradient.getMaxColors(),
|
||||||
|
colors -> apply(context, gradient, colors), context::reopen);
|
||||||
|
} else if (parameter instanceof ChoiceParameter<?> choice) {
|
||||||
|
if (isCycled(choice)) {
|
||||||
|
cycle(context, (ChoiceParameter<Object>) choice, value, clickType.isRightClick() ? -1 : 1);
|
||||||
|
} else {
|
||||||
|
pick(context, (ChoiceParameter<Object>) choice, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choices with a few options cycle through them on click instead of opening a list
|
||||||
|
private boolean isCycled(Parameter<?> parameter) {
|
||||||
|
return parameter instanceof ChoiceParameter<?> choice && choice.getBuildableValues().size() <= MAX_CYCLED_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// With two options both click directions lead to the same value, so it reads as a plain toggle
|
||||||
|
private boolean isToggle(Parameter<?> parameter) {
|
||||||
|
return parameter instanceof BooleanParameter || (isCycled(parameter) && ((ChoiceParameter<?>) parameter).getBuildableValues().size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All options of a cycled choice, the current one highlighted
|
||||||
|
private <E> List<String> optionLines(ChoiceParameter<E> parameter, E current, Player player) {
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
for (E option : parameter.getBuildableValues()) {
|
||||||
|
String name = choiceName(parameter, option, player).replaceAll("§.", "");
|
||||||
|
lines.add(option == current ? "§a» " + name : "§8» §7" + name);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left click moves forward, right click backward, skipping locked options
|
||||||
|
private <E> void cycle(Context context, ChoiceParameter<E> parameter, E current, int direction) {
|
||||||
|
UnlockContext unlock = new UnlockContext(SteamwarUser.get(context.player().getUniqueId()));
|
||||||
|
List<E> values = parameter.getBuildableValues().stream()
|
||||||
|
.filter(value -> value == current || parameter.requirement(value).test(unlock))
|
||||||
|
.toList();
|
||||||
|
apply(context, parameter, values.get(Math.floorMod(values.indexOf(current) + direction, values.size())));
|
||||||
|
context.reopen();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <E> void pick(Context context, ChoiceParameter<E> parameter, E current) {
|
||||||
|
Pickers.choice(context.player(), parameter, current, EntryEditor::choiceIcon,
|
||||||
|
parameter::requirement, picked -> {
|
||||||
|
apply(context, parameter, picked);
|
||||||
|
context.reopen();
|
||||||
|
}, context::reopen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Material choiceIcon(Object value) {
|
||||||
|
if (value instanceof ParticleType type) return type.getIcon();
|
||||||
|
if (value instanceof WingShape shape) return shape.getIcon();
|
||||||
|
if (value instanceof Material material) return material;
|
||||||
|
return Material.LEVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves the change, or reverts it and tells the player when the slot would go over its budget
|
||||||
|
private <T> boolean apply(Context context, Parameter<T> parameter, T value) {
|
||||||
|
T previous = context.entry().getValues().get(parameter);
|
||||||
|
if (Objects.equals(previous, parameter.validate(value))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
double previousCost = context.slot().cost();
|
||||||
|
ParameterValues saved = context.entry().getValues().copy();
|
||||||
|
context.entry().getValues().set(parameter, value);
|
||||||
|
context.entry().getDesign().adjust(parameter, context.entry().getValues());
|
||||||
|
if (!SlotEditor.allows(context.user(), context.slot(), previousCost)) {
|
||||||
|
context.entry().getValues().restore(saved);
|
||||||
|
LobbySystem.getMessage().send("PARTICLE_SLOT_OVER_BUDGET", context.player());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SlotEditor.save(context.player(), context.index(), context.slot());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keeps the particle, offset and conditions; a fixed design brings its own conditions and timing
|
||||||
|
private void changeDesign(Context context, ParticleDesign design) {
|
||||||
|
ParticleEntry previous = context.entry();
|
||||||
|
ParticleEntry next = new ParticleEntry(design);
|
||||||
|
for (Parameter<?> parameter : EntryParameters.ALL) {
|
||||||
|
copy(parameter, previous.getValues(), next.getValues());
|
||||||
|
}
|
||||||
|
if (!design.usesEntryParticle()) {
|
||||||
|
design.applyDefaults(next.getValues());
|
||||||
|
}
|
||||||
|
List<ParticleEntry> entries = context.slot().getEntries();
|
||||||
|
double previousCost = context.slot().cost();
|
||||||
|
entries.set(context.entryIndex(), next);
|
||||||
|
if (!SlotEditor.allows(context.user(), context.slot(), previousCost)) {
|
||||||
|
entries.set(context.entryIndex(), previous);
|
||||||
|
LobbySystem.getMessage().send("PARTICLE_SLOT_OVER_BUDGET", context.player());
|
||||||
|
} else {
|
||||||
|
SlotEditor.save(context.player(), context.index(), context.slot());
|
||||||
|
}
|
||||||
|
context.reopen();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void copy(Parameter<T> parameter, ParameterValues from, ParameterValues to) {
|
||||||
|
to.set(parameter, from.get(parameter));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* 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.gui;
|
||||||
|
|
||||||
|
import de.steamwar.lobby.LobbySystem;
|
||||||
|
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||||
|
import de.steamwar.lobby.particle.UnlockContext;
|
||||||
|
import de.steamwar.lobby.particle.design.*;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleEntry;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlot;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlots;
|
||||||
|
import de.steamwar.sql.SteamwarUser;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.MissingResourceException;
|
||||||
|
|
||||||
|
import static de.steamwar.lobby.particle.design.EntryParameters.*;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
class ParticleGuiText {
|
||||||
|
|
||||||
|
String msg(String key, Player player, Object... params) {
|
||||||
|
return LobbySystem.getMessage().parse(key, player, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Falls back to a readable form of the key for names without a translation
|
||||||
|
String name(String key, Player player) {
|
||||||
|
try {
|
||||||
|
return LobbySystem.getMessage().parse(key, player);
|
||||||
|
} catch (MissingResourceException e) {
|
||||||
|
return "§f" + prettify(key.replaceFirst("^PARTICLE_(TYPE_|DESIGN_|PARAM_)?", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String prettify(String id) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (String word : id.toLowerCase(Locale.ROOT).split("_")) {
|
||||||
|
if (word.isEmpty()) continue;
|
||||||
|
if (!builder.isEmpty()) builder.append(' ');
|
||||||
|
builder.append(Character.toUpperCase(word.charAt(0))).append(word.substring(1));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String designName(ParticleDesign design, Player player) {
|
||||||
|
return name(design.getNameKey(), player);
|
||||||
|
}
|
||||||
|
|
||||||
|
String particleName(ParticleType type, Player player) {
|
||||||
|
return name(type.getNameKey(), player);
|
||||||
|
}
|
||||||
|
|
||||||
|
<E> String choiceName(ChoiceParameter<E> parameter, E value, Player player) {
|
||||||
|
for (ChoiceParameter.Choice<E> choice : parameter.getChoices()) {
|
||||||
|
if (choice.value() != value) continue;
|
||||||
|
if (value instanceof ParticleType type) return particleName(type, player);
|
||||||
|
return name("PARTICLE_CHOICE_" + parameter.getKey().toUpperCase(Locale.ROOT) + "_" + choice.id().toUpperCase(Locale.ROOT), player);
|
||||||
|
}
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
String number(double value) {
|
||||||
|
return value == Math.rint(value) ? String.valueOf((long) value) : String.format(Locale.ROOT, "%.3f", value).replaceAll("0+$", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
String valueText(Parameter<?> parameter, Object value, Player player) {
|
||||||
|
if (parameter instanceof DoubleParameter) return "§e" + number((Double) value);
|
||||||
|
if (parameter instanceof IntParameter) return "§e" + value;
|
||||||
|
if (parameter instanceof BooleanParameter) return msg((Boolean) value ? "PARTICLE_VALUE_YES" : "PARTICLE_VALUE_NO", player);
|
||||||
|
if (parameter instanceof ChoiceParameter<?> choice) return choiceName((ChoiceParameter<Object>) choice, value, player);
|
||||||
|
if (parameter instanceof GradientParameter) {
|
||||||
|
List<Color> colors = (List<Color>) value;
|
||||||
|
if (colors.isEmpty()) return msg("PARTICLE_VALUE_RANDOM", player);
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
colors.forEach(color -> builder.append(net.md_5.bungee.api.ChatColor.of(color)).append('█'));
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requirement lines for an item's lore, empty when there is no requirement
|
||||||
|
List<String> requirement(ParticleRequirement requirement, Player player) {
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
String name = requirement.getRequirementName(player);
|
||||||
|
if (name != null) {
|
||||||
|
lore.add(msg("PARTICLE_UNLOCKED_BY", player));
|
||||||
|
lore.addAll(List.of(name.split("\n")));
|
||||||
|
}
|
||||||
|
return lore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short description of an entry: design, particle and the conditions that are set
|
||||||
|
List<String> entrySummary(ParticleEntry entry, UnlockContext context, Player player) {
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
ParameterValues values = entry.getValues();
|
||||||
|
lore.add(msg("PARTICLE_ENTRY_DESIGN", player, designName(entry.getDesign(), player)));
|
||||||
|
if (entry.getDesign().usesEntryParticle()) {
|
||||||
|
lore.add(msg("PARTICLE_ENTRY_PARTICLE", player, particleName(values.get(PARTICLE), 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);
|
||||||
|
addCondition(lore, GROUND, values.get(GROUND), Ground.ANY, player);
|
||||||
|
addCondition(lore, SNEAK, values.get(SNEAK), Sneak.ANY, player);
|
||||||
|
addCondition(lore, VISIBILITY, values.get(VISIBILITY), Visibility.EVERYONE, player);
|
||||||
|
lore.add(msg("PARTICLE_ENTRY_COST", player, number(entry.cost())));
|
||||||
|
if (!entry.isUnlocked(context)) {
|
||||||
|
lore.add(msg("PARTICLE_ENTRY_LOCKED", player));
|
||||||
|
}
|
||||||
|
return lore;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <E> void addCondition(List<String> lore, ChoiceParameter<E> parameter, E value, E none, Player player) {
|
||||||
|
if (value != none) {
|
||||||
|
lore.add("§8- " + choiceName(parameter, value, player));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The slot's name followed by how many of its allowed particles per tick it uses
|
||||||
|
String slotName(ParticleSlot slot, SteamwarUser user, Player player) {
|
||||||
|
return msg("PARTICLE_SLOT_NAME", player, slot.getName(), number(slot.cost()), number(ParticleSlots.maxCost(user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
String slotTitle(ParticleSlot slot, SteamwarUser user, Player player) {
|
||||||
|
return msg("PARTICLE_SLOT_TITLE", player, slot.getName(), number(slot.cost()), number(ParticleSlots.maxCost(user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> slotSummary(ParticleSlot slot, Player player) {
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(msg("PARTICLE_SLOT_ENTRIES", player, slot.getEntries().size()));
|
||||||
|
for (ParticleEntry entry : slot.getEntries()) {
|
||||||
|
String particle = entry.getDesign().usesEntryParticle() ? " §8(" + particleName(entry.getValues().get(PARTICLE), player) + "§8)" : "";
|
||||||
|
lore.add("§8- " + designName(entry.getDesign(), player) + particle);
|
||||||
|
}
|
||||||
|
return lore;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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.gui;
|
||||||
|
|
||||||
|
import de.steamwar.inventory.SWInventory;
|
||||||
|
import de.steamwar.inventory.SWItem;
|
||||||
|
import de.steamwar.lobby.LobbySystem;
|
||||||
|
import de.steamwar.lobby.particle.ParticleListener;
|
||||||
|
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||||
|
import de.steamwar.lobby.particle.UnlockContext;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticlePreset;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlot;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlots;
|
||||||
|
import de.steamwar.lobby.particle.slot.Presets;
|
||||||
|
import de.steamwar.lobby.util.LobbyPlayer;
|
||||||
|
import de.steamwar.sql.SteamwarUser;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
|
||||||
|
|
||||||
|
// The particle item's menu: the player's slots in the top row, their unlocked presets below
|
||||||
|
@UtilityClass
|
||||||
|
public class ParticleMenu {
|
||||||
|
|
||||||
|
private final int NO_PARTICLE_POSITION = 8;
|
||||||
|
private final int PRESET_START = 18;
|
||||||
|
|
||||||
|
public void open(Player player) {
|
||||||
|
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player);
|
||||||
|
UnlockContext context = new UnlockContext(SteamwarUser.get(player.getUniqueId()));
|
||||||
|
List<ParticlePreset> presets = Presets.all().stream().filter(preset -> preset.getRequirement().test(context)).toList();
|
||||||
|
int rows = presets.isEmpty() ? 1 : Math.min(6, 2 + (presets.size() + 8) / 9);
|
||||||
|
|
||||||
|
SWInventory inventory = new SWInventory(player, rows * 9, msg("PARTICLE_INVENTORY", player));
|
||||||
|
for (int index = 0; index < ParticleSlots.COUNT; index++) {
|
||||||
|
inventory.setItem(index, slotItem(player, lobbyPlayer, context, index));
|
||||||
|
}
|
||||||
|
inventory.setItem(NO_PARTICLE_POSITION, new SWItem(Material.BARRIER, msg("PARTICLE_DESELECT", player), clickType -> {
|
||||||
|
lobbyPlayer.deselect();
|
||||||
|
player.closeInventory();
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (int i = 0; i < presets.size() && PRESET_START + i < rows * 9; i++) {
|
||||||
|
ParticlePreset preset = presets.get(i);
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add("");
|
||||||
|
lore.add(msg("PARTICLE_SELECT", player));
|
||||||
|
inventory.setItem(PRESET_START + i, new SWItem(preset.getIcon(), ParticleGuiText.name(preset.getNameKey(), player), lore, preset == lobbyPlayer.getActivePreset(), clickType -> {
|
||||||
|
lobbyPlayer.selectPreset(preset);
|
||||||
|
selected(player);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
inventory.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SWItem slotItem(Player player, LobbyPlayer lobbyPlayer, UnlockContext context, int index) {
|
||||||
|
ParticleSlot slot = lobbyPlayer.getSlot(index);
|
||||||
|
ParticleRequirement requirement = ParticleSlots.requirement(index);
|
||||||
|
boolean unlocked = requirement.test(context);
|
||||||
|
|
||||||
|
if (slot == null) {
|
||||||
|
if (!unlocked) {
|
||||||
|
List<String> lore = new ArrayList<>(ParticleGuiText.requirement(requirement, player));
|
||||||
|
return new SWItem(Material.BARRIER, msg("PARTICLE_LOCKED", player), lore, false, clickType -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
List<String> lore = List.of(msg("PARTICLE_SLOT_CREATE", player));
|
||||||
|
return new SWItem(Material.FIREWORK_STAR, msg("PARTICLE_SLOT_EMPTY", player), lore, false, clickType -> {
|
||||||
|
lobbyPlayer.setSlot(index, new ParticleSlot(ParticleSlots.defaultName(index)));
|
||||||
|
SlotEditor.open(player, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> lore = new ArrayList<>(ParticleGuiText.slotSummary(slot, player));
|
||||||
|
lore.add("");
|
||||||
|
String name = slotName(slot, context.getUser(), player);
|
||||||
|
if (!unlocked) {
|
||||||
|
lore.add(msg("PARTICLE_LOCKED", player));
|
||||||
|
lore.addAll(ParticleGuiText.requirement(requirement, player));
|
||||||
|
lore.add(msg("PARTICLE_SLOT_VIEW", player));
|
||||||
|
return new SWItem(slot.getDisplayIcon(), name, lore, false, clickType -> SlotEditor.open(player, index));
|
||||||
|
}
|
||||||
|
if (slot.getEntries().isEmpty()) {
|
||||||
|
lore.add(msg("PARTICLE_SLOT_OPEN", player));
|
||||||
|
return new SWItem(slot.getDisplayIcon(), name, lore, false, clickType -> SlotEditor.open(player, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A slot with entries is switched on and off with a click; shift click edits it
|
||||||
|
boolean active = lobbyPlayer.getActiveSlot() == index;
|
||||||
|
if (active) {
|
||||||
|
lore.add(msg("PARTICLE_SLOT_ACTIVE", player));
|
||||||
|
}
|
||||||
|
lore.add(msg(active ? "PARTICLE_SLOT_CLICK_STOP" : "PARTICLE_SLOT_CLICK_USE", player));
|
||||||
|
lore.add(msg("PARTICLE_SLOT_SHIFT_EDIT", player));
|
||||||
|
return new SWItem(slot.getDisplayIcon(), name, lore, active, clickType -> {
|
||||||
|
if (clickType.isShiftClick()) {
|
||||||
|
SlotEditor.open(player, index);
|
||||||
|
} else if (active) {
|
||||||
|
lobbyPlayer.deselect();
|
||||||
|
open(player);
|
||||||
|
} else if (lobbyPlayer.selectSlot(index)) {
|
||||||
|
selected(player);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void selected(Player player) {
|
||||||
|
player.closeInventory();
|
||||||
|
if (ParticleListener.disabled(player)) {
|
||||||
|
LobbySystem.getMessage().send("PARTICLE_DEACTIVATED", player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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.gui;
|
||||||
|
|
||||||
|
import de.steamwar.inventory.SWInventory;
|
||||||
|
import de.steamwar.inventory.SWItem;
|
||||||
|
import de.steamwar.inventory.SWListInv;
|
||||||
|
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||||
|
import de.steamwar.lobby.particle.UnlockContext;
|
||||||
|
import de.steamwar.lobby.particle.design.*;
|
||||||
|
import de.steamwar.sql.SteamwarUser;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.DyeColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
class Pickers {
|
||||||
|
|
||||||
|
private final int SIZE = 54;
|
||||||
|
private final int BACK = BottomBar.position(SIZE, BottomBar.BACK);
|
||||||
|
|
||||||
|
// Basic designs first, then rewards; locked rewards are shown with their requirement but cannot be picked.
|
||||||
|
// The current design, if any, glows.
|
||||||
|
void design(Player player, ParticleDesign current, Consumer<ParticleDesign> onPick, Runnable onBack) {
|
||||||
|
UnlockContext context = new UnlockContext(SteamwarUser.get(player.getUniqueId()));
|
||||||
|
List<SWListInv.SWListEntry<ParticleDesign>> list = new ArrayList<>();
|
||||||
|
for (DesignVisibility visibility : List.of(DesignVisibility.BASIC, DesignVisibility.REWARD)) {
|
||||||
|
for (ParticleDesign design : Designs.all()) {
|
||||||
|
if (design.getVisibility() != visibility) continue;
|
||||||
|
List<String> lore = new ArrayList<>(requirement(design.getRequirement(), player));
|
||||||
|
boolean unlocked = design.getRequirement().test(context);
|
||||||
|
if (!lore.isEmpty()) lore.add("");
|
||||||
|
lore.add(msg(design == current ? "PARTICLE_VALUE_SELECTED" : unlocked ? "PARTICLE_SELECT" : "PARTICLE_LOCKED", player));
|
||||||
|
list.add(new SWListInv.SWListEntry<>(new SWItem(design.getIcon(), designName(design, player), lore, design == current, clickType -> {
|
||||||
|
}), unlocked ? design : null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open(player, msg("PARTICLE_DESIGN_PICKER", player), list, onPick, onBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lists every option; the current one glows, locked ones show their requirement and cannot be picked
|
||||||
|
<E> void choice(Player player, ChoiceParameter<E> parameter, E current, Function<E, Material> icon, Function<E, ParticleRequirement> requirement,
|
||||||
|
Consumer<E> onPick, Runnable onBack) {
|
||||||
|
UnlockContext context = new UnlockContext(SteamwarUser.get(player.getUniqueId()));
|
||||||
|
List<SWListInv.SWListEntry<E>> list = new ArrayList<>();
|
||||||
|
for (E value : parameter.getBuildableValues()) {
|
||||||
|
boolean unlocked = requirement.apply(value).test(context);
|
||||||
|
List<String> lore = new ArrayList<>(requirement(requirement.apply(value), player));
|
||||||
|
if (!lore.isEmpty()) lore.add("");
|
||||||
|
lore.add(msg(value == current ? "PARTICLE_VALUE_SELECTED" : unlocked ? "PARTICLE_SELECT" : "PARTICLE_LOCKED", player));
|
||||||
|
list.add(new SWListInv.SWListEntry<>(new SWItem(icon.apply(value), choiceName(parameter, value, player), lore, value == current, clickType -> {
|
||||||
|
}), unlocked ? value : null));
|
||||||
|
}
|
||||||
|
open(player, msg("PARTICLE_CHOICE_PICKER", player, name(parameter.getNameKey(), player)), list, onPick, onBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void color(Player player, Consumer<Color> onPick, Runnable onBack) {
|
||||||
|
List<SWListInv.SWListEntry<Color>> list = new ArrayList<>();
|
||||||
|
for (DyeColor dye : DyeColor.values()) {
|
||||||
|
Color color = new Color(dye.getColor().asRGB());
|
||||||
|
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.valueOf(dye.name() + "_DYE"), net.md_5.bungee.api.ChatColor.of(color) + prettify(dye.name()), clickType -> {
|
||||||
|
}), color));
|
||||||
|
}
|
||||||
|
open(player, msg("PARTICLE_COLOR_PICKER", player), list, onPick, onBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void icon(Player player, Consumer<Material> onPick, Runnable onBack) {
|
||||||
|
Set<Material> icons = new LinkedHashSet<>();
|
||||||
|
for (ParticleType type : ParticleType.values()) {
|
||||||
|
if (type.isBuildable()) icons.add(type.getIcon());
|
||||||
|
}
|
||||||
|
for (ParticleDesign design : Designs.all()) {
|
||||||
|
if (design.getVisibility() == DesignVisibility.BASIC) icons.add(design.getIcon());
|
||||||
|
}
|
||||||
|
icons.addAll(List.of(Material.DIAMOND, Material.EMERALD, Material.GOLD_INGOT, Material.AMETHYST_SHARD, Material.HEART_OF_THE_SEA, Material.NETHER_STAR, Material.FEATHER, Material.TNT));
|
||||||
|
|
||||||
|
List<SWListInv.SWListEntry<Material>> list = new ArrayList<>();
|
||||||
|
for (Material material : icons) {
|
||||||
|
list.add(new SWListInv.SWListEntry<>(new SWItem(material, "§f" + prettify(material.name()), clickType -> {
|
||||||
|
}), material));
|
||||||
|
}
|
||||||
|
open(player, msg("PARTICLE_SLOT_ICON_TITLE", player), list, onPick, onBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lists that fit on one page get just enough rows for their options plus the bottom bar
|
||||||
|
private <T> void open(Player player, String title, List<SWListInv.SWListEntry<T>> list, Consumer<T> onPick, Runnable onBack) {
|
||||||
|
if (list.size() <= SIZE - 9) {
|
||||||
|
int size = (Math.max(1, (list.size() + 8) / 9) + 1) * 9;
|
||||||
|
SWInventory inventory = new SWInventory(player, size, title);
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
T value = list.get(i).getObject();
|
||||||
|
inventory.setItem(i, list.get(i).getItem().getItemStack(), clickType -> {
|
||||||
|
if (value != null) onPick.accept(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
inventory.setItem(BottomBar.position(size, BottomBar.BACK), new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> onBack.run()));
|
||||||
|
BottomBar.fillUnused(inventory, size, false);
|
||||||
|
SlotEditor.fill(inventory, 0, size);
|
||||||
|
inventory.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SWListInv<T> inventory = new SWListInv<>(player, title, false, list, (clickType, value) -> {
|
||||||
|
if (value != null) onPick.accept(value);
|
||||||
|
});
|
||||||
|
inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> onBack.run()));
|
||||||
|
BottomBar.fillUnused(inventory, SIZE, true);
|
||||||
|
inventory.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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.gui;
|
||||||
|
|
||||||
|
import de.steamwar.inventory.SWAnvilInv;
|
||||||
|
import de.steamwar.inventory.SWInventory;
|
||||||
|
import de.steamwar.inventory.SWItem;
|
||||||
|
import de.steamwar.lobby.LobbySystem;
|
||||||
|
import de.steamwar.lobby.particle.UnlockContext;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleEntry;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlot;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlots;
|
||||||
|
import de.steamwar.lobby.util.LobbyPlayer;
|
||||||
|
import de.steamwar.sql.SteamwarUser;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
|
||||||
|
|
||||||
|
// Lists a slot's entries; slots the player has lost access to are shown read-only but can still be deleted
|
||||||
|
@UtilityClass
|
||||||
|
public class SlotEditor {
|
||||||
|
|
||||||
|
// One row of entries above the bottom bar
|
||||||
|
private final int SIZE = (ParticleSlots.MAX_ENTRIES + 8) / 9 * 9 + 9;
|
||||||
|
private final int BACK = BottomBar.position(SIZE, BottomBar.BACK);
|
||||||
|
private final int STATUS = BottomBar.position(SIZE, BottomBar.STATUS);
|
||||||
|
private final int ADD_ENTRY = BottomBar.position(SIZE, BottomBar.ADD);
|
||||||
|
private final int RENAME = BottomBar.position(SIZE, BottomBar.RENAME);
|
||||||
|
private final int ICON = BottomBar.position(SIZE, BottomBar.ICON);
|
||||||
|
private final int COST = BottomBar.position(SIZE, BottomBar.COST);
|
||||||
|
private final int DELETE = BottomBar.position(SIZE, BottomBar.DELETE);
|
||||||
|
|
||||||
|
public void open(Player player, int index) {
|
||||||
|
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player);
|
||||||
|
ParticleSlot slot = lobbyPlayer.getSlot(index);
|
||||||
|
if (slot == null) {
|
||||||
|
ParticleMenu.open(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||||
|
UnlockContext context = new UnlockContext(user);
|
||||||
|
boolean editable = ParticleSlots.requirement(index).test(context);
|
||||||
|
|
||||||
|
SWInventory inventory = new SWInventory(player, SIZE, slotTitle(slot, user, player));
|
||||||
|
List<ParticleEntry> entries = slot.getEntries();
|
||||||
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
|
ParticleEntry entry = entries.get(i);
|
||||||
|
List<String> lore = new ArrayList<>(entrySummary(entry, context, player));
|
||||||
|
if (editable) {
|
||||||
|
lore.add("");
|
||||||
|
lore.add(msg("PARTICLE_ENTRY_EDIT", player));
|
||||||
|
}
|
||||||
|
int entryIndex = i;
|
||||||
|
inventory.setItem(i, new SWItem(entry.getDesign().getIcon(), designName(entry.getDesign(), player), lore, false, clickType -> {
|
||||||
|
if (editable) EntryEditor.open(player, index, entryIndex);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> ParticleMenu.open(player)));
|
||||||
|
inventory.setItem(COST, costItem(player, user, slot));
|
||||||
|
// Locked slots can still be deleted, so players can clear what they made before losing the rank
|
||||||
|
inventory.setItem(DELETE, new SWItem(Material.LAVA_BUCKET, msg("PARTICLE_SLOT_DELETE", player), List.of(msg("PARTICLE_SHIFT_TO_DELETE", player)), false, clickType -> {
|
||||||
|
if (!clickType.isShiftClick()) return;
|
||||||
|
if (lobbyPlayer.getActiveSlot() == index) {
|
||||||
|
lobbyPlayer.deselect();
|
||||||
|
}
|
||||||
|
lobbyPlayer.setSlot(index, null);
|
||||||
|
ParticleMenu.open(player);
|
||||||
|
}));
|
||||||
|
if (!editable) {
|
||||||
|
List<String> lore = new ArrayList<>(requirement(ParticleSlots.requirement(index), player));
|
||||||
|
inventory.setItem(STATUS, new SWItem(Material.BARRIER, msg("PARTICLE_LOCKED", player), lore, false, clickType -> {
|
||||||
|
}));
|
||||||
|
BottomBar.fillUnused(inventory, SIZE, false);
|
||||||
|
fill(inventory, 0, SIZE);
|
||||||
|
inventory.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory.setItem(ADD_ENTRY, new SWItem(Material.NETHER_STAR, msg("PARTICLE_SLOT_ADD_ENTRY", player), clickType -> {
|
||||||
|
if (entries.size() >= ParticleSlots.MAX_ENTRIES) {
|
||||||
|
LobbySystem.getMessage().send("PARTICLE_SLOT_MAX_ENTRIES", player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Pickers.design(player, null, design -> {
|
||||||
|
double previousCost = slot.cost();
|
||||||
|
entries.add(new ParticleEntry(design));
|
||||||
|
if (!allows(user, slot, previousCost)) {
|
||||||
|
entries.remove(entries.size() - 1);
|
||||||
|
LobbySystem.getMessage().send("PARTICLE_SLOT_OVER_BUDGET", player);
|
||||||
|
open(player, index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
save(player, index, slot);
|
||||||
|
EntryEditor.open(player, index, entries.size() - 1);
|
||||||
|
}, () -> open(player, index));
|
||||||
|
}));
|
||||||
|
inventory.setItem(RENAME, new SWItem(Material.NAME_TAG, msg("PARTICLE_SLOT_RENAME", player), clickType -> {
|
||||||
|
SWAnvilInv anvil = new SWAnvilInv(player, msg("PARTICLE_SLOT_RENAME_TITLE", player), slot.getName());
|
||||||
|
anvil.setItem(Material.NAME_TAG);
|
||||||
|
anvil.setCallback(name -> {
|
||||||
|
String cleaned = name.replace("§", "").trim();
|
||||||
|
slot.setName(cleaned.isEmpty() ? ParticleSlots.defaultName(index) : cleaned);
|
||||||
|
save(player, index, slot);
|
||||||
|
Bukkit.getScheduler().runTask(LobbySystem.getInstance(), () -> open(player, index));
|
||||||
|
});
|
||||||
|
anvil.open();
|
||||||
|
}));
|
||||||
|
inventory.setItem(ICON, new SWItem(slot.getDisplayIcon(), msg("PARTICLE_SLOT_ICON", player), clickType -> Pickers.icon(player, icon -> {
|
||||||
|
slot.setIcon(icon);
|
||||||
|
save(player, index, slot);
|
||||||
|
open(player, index);
|
||||||
|
}, () -> open(player, index))));
|
||||||
|
BottomBar.fillUnused(inventory, SIZE, false);
|
||||||
|
fill(inventory, 0, SIZE);
|
||||||
|
inventory.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
SWItem costItem(Player player, SteamwarUser user, ParticleSlot slot) {
|
||||||
|
return new SWItem(Material.PAPER, msg("PARTICLE_SLOT_COST", player, number(slot.cost()), number(ParticleSlots.maxCost(user))), clickType -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The slot's particles per tick, with the share of the entry being edited
|
||||||
|
SWItem costItem(Player player, SteamwarUser user, ParticleSlot slot, ParticleEntry entry) {
|
||||||
|
return costItem(player, user, slot).setLore(msg("PARTICLE_ENTRY_COST_SHARE", player, number(entry.cost())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A change is fine within the budget, and also when it does not add particles to a slot already over it
|
||||||
|
boolean allows(SteamwarUser user, ParticleSlot slot, double previousCost) {
|
||||||
|
double cost = slot.cost();
|
||||||
|
return cost <= ParticleSlots.maxCost(user) || cost <= previousCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(Player player, int index, ParticleSlot slot) {
|
||||||
|
LobbyPlayer.getLobbyPlayer(player).setSlot(index, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Puts gray filler panes on the empty positions in the given range; the bottom bar uses black ones
|
||||||
|
void fill(SWInventory inventory, int from, int to) {
|
||||||
|
for (int position = from; position < to; position++) {
|
||||||
|
if (!inventory.hasItem(position)) {
|
||||||
|
inventory.setItem(position, new SWItem(Material.GRAY_STAINED_GLASS_PANE, " ", clickType -> {
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* 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.gui;
|
||||||
|
|
||||||
|
import de.steamwar.inventory.SWAnvilInv;
|
||||||
|
import de.steamwar.inventory.SWInventory;
|
||||||
|
import de.steamwar.inventory.SWItem;
|
||||||
|
import de.steamwar.lobby.LobbySystem;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.DyeColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
|
||||||
|
|
||||||
|
// Screens for settings that need more than a click: numbers and colour lists
|
||||||
|
@UtilityClass
|
||||||
|
class ValueEditors {
|
||||||
|
|
||||||
|
private final int SIZE = 27;
|
||||||
|
private final int BACK = BottomBar.position(SIZE, BottomBar.BACK);
|
||||||
|
private final int ADD = BottomBar.position(SIZE, BottomBar.ADD);
|
||||||
|
private final int RESET = BottomBar.position(SIZE, BottomBar.RESET);
|
||||||
|
|
||||||
|
// Buttons step the value down or up; clicking the value itself lets the player type one
|
||||||
|
void editNumber(Player player, String title, Supplier<Double> current, double step, double min, double max, double defaultValue, boolean integer,
|
||||||
|
Predicate<Double> apply, Runnable back) {
|
||||||
|
double value = current.get();
|
||||||
|
Runnable reopen = () -> editNumber(player, title, current, step, min, max, defaultValue, integer, apply, back);
|
||||||
|
|
||||||
|
SWInventory inventory = new SWInventory(player, SIZE, "§0" + strip(title));
|
||||||
|
inventory.setItem(10, stepItem(player, Material.RED_CONCRETE, -10 * step, step, value, min, max, integer, apply, reopen));
|
||||||
|
inventory.setItem(11, stepItem(player, Material.RED_STAINED_GLASS_PANE, -step, step, value, min, max, integer, apply, reopen));
|
||||||
|
inventory.setItem(15, stepItem(player, Material.LIME_STAINED_GLASS_PANE, step, step, value, min, max, integer, apply, reopen));
|
||||||
|
inventory.setItem(16, stepItem(player, Material.LIME_CONCRETE, 10 * step, step, value, min, max, integer, apply, reopen));
|
||||||
|
|
||||||
|
List<String> lore = List.of(msg("PARTICLE_NUMBER_RANGE", player, number(min), number(max)), "", msg("PARTICLE_NUMBER_TYPE", player));
|
||||||
|
inventory.setItem(13, new SWItem(Material.PAPER, "§e" + number(value), lore, false, clickType -> {
|
||||||
|
SWAnvilInv anvil = new SWAnvilInv(player, strip(title), number(value));
|
||||||
|
anvil.setItem(Material.PAPER);
|
||||||
|
anvil.setCallback(input -> Bukkit.getScheduler().runTask(LobbySystem.getInstance(), () -> {
|
||||||
|
try {
|
||||||
|
double typed = Double.parseDouble(input.trim().replace(',', '.'));
|
||||||
|
apply.test(integer ? Math.round(typed) : typed);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// Keep the old value
|
||||||
|
}
|
||||||
|
reopen.run();
|
||||||
|
}));
|
||||||
|
anvil.open();
|
||||||
|
}));
|
||||||
|
|
||||||
|
inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> back.run()));
|
||||||
|
inventory.setItem(RESET, new SWItem(Material.STRUCTURE_VOID, msg("PARTICLE_VALUE_RESET", player, number(defaultValue)), clickType -> {
|
||||||
|
apply.test(defaultValue);
|
||||||
|
reopen.run();
|
||||||
|
}));
|
||||||
|
BottomBar.fillUnused(inventory, SIZE, false);
|
||||||
|
SlotEditor.fill(inventory, 0, SIZE);
|
||||||
|
inventory.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SWItem stepItem(Player player, Material material, double delta, double step, double value, double min, double max, boolean integer, Predicate<Double> apply, Runnable reopen) {
|
||||||
|
String label = (delta < 0 ? "§c-" : "§a+") + number(Math.abs(delta));
|
||||||
|
// Snap to the step grid and drop floating point noise, so repeated steps do not drift
|
||||||
|
double unclamped = integer ? Math.round(value + delta) : Math.round(Math.round((value + delta) / step) * step * 1e6) / 1e6;
|
||||||
|
double next = Math.max(min, Math.min(max, unclamped));
|
||||||
|
String limit = next == max && unclamped >= max ? "PARTICLE_NUMBER_AT_MAX" : next == min && unclamped <= min ? "PARTICLE_NUMBER_AT_MIN" : "PARTICLE_NUMBER_RESULT";
|
||||||
|
return new SWItem(material, label, List.of(msg(limit, player, number(next))), false, clickType -> {
|
||||||
|
// Stepping past the minimum or maximum changes nothing, so it does nothing
|
||||||
|
if (next == value) return;
|
||||||
|
apply.test(next);
|
||||||
|
reopen.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shows the colours in order; a click removes one, the buttons below add one or switch to random colours
|
||||||
|
void editColors(Player player, Supplier<List<Color>> current, int maxColors, Predicate<List<Color>> apply, Runnable back) {
|
||||||
|
List<Color> colors = current.get();
|
||||||
|
Runnable reopen = () -> editColors(player, current, maxColors, apply, back);
|
||||||
|
|
||||||
|
SWInventory inventory = new SWInventory(player, SIZE, msg("PARTICLE_COLOR_EDITOR", player));
|
||||||
|
for (int i = 0; i < colors.size() && i < 9; i++) {
|
||||||
|
int position = i;
|
||||||
|
Color color = colors.get(i);
|
||||||
|
inventory.setItem(i, new SWItem(dye(color), net.md_5.bungee.api.ChatColor.of(color) + "█ #" + String.format("%06X", color.getRGB() & 0xFFFFFF),
|
||||||
|
List.of(msg("PARTICLE_COLOR_REMOVE", player)), false, clickType -> {
|
||||||
|
List<Color> next = new ArrayList<>(colors);
|
||||||
|
next.remove(position);
|
||||||
|
apply.test(next);
|
||||||
|
reopen.run();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (colors.isEmpty()) {
|
||||||
|
inventory.setItem(4, new SWItem(Material.WHITE_DYE, msg("PARTICLE_VALUE_RANDOM", player), clickType -> {
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> back.run()));
|
||||||
|
if (colors.size() < maxColors) {
|
||||||
|
inventory.setItem(ADD, new SWItem(Material.NETHER_STAR, msg("PARTICLE_COLOR_ADD", player), clickType -> Pickers.color(player, color -> {
|
||||||
|
List<Color> next = new ArrayList<>(colors);
|
||||||
|
next.add(color);
|
||||||
|
apply.test(next);
|
||||||
|
reopen.run();
|
||||||
|
}, reopen)));
|
||||||
|
// Same position as typing a value on the number screen
|
||||||
|
inventory.setItem(13, new SWItem(Material.NAME_TAG, msg("PARTICLE_COLOR_HEX", player), List.of(msg("PARTICLE_COLOR_HEX_EXAMPLE", player)), false, clickType -> {
|
||||||
|
SWAnvilInv anvil = new SWAnvilInv(player, msg("PARTICLE_COLOR_HEX_TITLE", player), "#");
|
||||||
|
anvil.setItem(Material.NAME_TAG);
|
||||||
|
anvil.setCallback(input -> Bukkit.getScheduler().runTask(LobbySystem.getInstance(), () -> {
|
||||||
|
Color color = parseHex(input);
|
||||||
|
if (color == null) {
|
||||||
|
LobbySystem.getMessage().send("PARTICLE_COLOR_HEX_INVALID", player);
|
||||||
|
} else {
|
||||||
|
List<Color> next = new ArrayList<>(colors);
|
||||||
|
next.add(color);
|
||||||
|
apply.test(next);
|
||||||
|
}
|
||||||
|
reopen.run();
|
||||||
|
}));
|
||||||
|
anvil.open();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
inventory.setItem(RESET, new SWItem(Material.WHITE_DYE, msg("PARTICLE_COLOR_RANDOM", player), clickType -> {
|
||||||
|
apply.test(List.of());
|
||||||
|
reopen.run();
|
||||||
|
}));
|
||||||
|
BottomBar.fillUnused(inventory, SIZE, false);
|
||||||
|
SlotEditor.fill(inventory, 0, SIZE);
|
||||||
|
inventory.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accepts #RRGGBB and #RGB, with or without the #; null for anything else
|
||||||
|
Color parseHex(String input) {
|
||||||
|
String hex = input.trim();
|
||||||
|
if (hex.startsWith("#")) hex = hex.substring(1);
|
||||||
|
if (hex.length() == 3) {
|
||||||
|
hex = "" + hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
|
||||||
|
}
|
||||||
|
if (!hex.matches("[0-9a-fA-F]{6}")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Color(Integer.parseInt(hex, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The dye whose colour is closest to the given one, so typed hex colours get a matching icon
|
||||||
|
private Material dye(Color color) {
|
||||||
|
DyeColor closest = DyeColor.WHITE;
|
||||||
|
double best = Double.MAX_VALUE;
|
||||||
|
for (DyeColor dye : DyeColor.values()) {
|
||||||
|
double distance = distance(color, new Color(dye.getColor().asRGB()));
|
||||||
|
if (distance < best) {
|
||||||
|
best = distance;
|
||||||
|
closest = dye;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Material.valueOf(closest.name() + "_DYE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colour distance weighted by how sensitive the eye is to red, green and blue ("redmean")
|
||||||
|
private double distance(Color a, Color b) {
|
||||||
|
double redMean = (a.getRed() + b.getRed()) / 2.0;
|
||||||
|
int dr = a.getRed() - b.getRed(), dg = a.getGreen() - b.getGreen(), db = a.getBlue() - b.getBlue();
|
||||||
|
return (2 + redMean / 256) * dr * dr + 4 * dg * dg + (2 + (255 - redMean) / 256) * db * db;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String strip(String text) {
|
||||||
|
return text.replaceAll("§.", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
|
||||||
import de.steamwar.lobby.particle.elements.*;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.util.Vector;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum EasterParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
EGG_HUNT_EASY(new ParticleData(Material.LIME_CONCRETE_POWDER, "PARTICLE_EGG_HUNT_EASY", ParticleRequirement.EGG_HUNT_EASY,
|
|
||||||
new Always(new LocationMutator(new SimpleParticle(Particle.FALLING_SPORE_BLOSSOM, 0.5F, 0.1f, 0.5F, 1, 1), 0, 2.2, 0)))
|
|
||||||
),
|
|
||||||
EGG_HUNT_MEDIUM(new ParticleData(Material.YELLOW_CONCRETE_POWDER, "PARTICLE_EGG_HUNT_MEDIUM", ParticleRequirement.EGG_HUNT_MEDIUM,
|
|
||||||
new Always(new Floor(new Sneaking(new SimpleParticle(Particle.GLOW_SQUID_INK, 0.4F, 0.9F, 0.4F, 0.01)))))
|
|
||||||
),
|
|
||||||
EGG_HUNT_HARD(new ParticleData(Material.RED_CONCRETE_POWDER, "PARTICLE_EGG_HUNT_HARD", ParticleRequirement.EGG_HUNT_HARD,
|
|
||||||
new Always(new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0.01, 1, null), new DustParticle(Particle.DUST, 0, 0, 0, 0.01, 1, null)), 0, 0.5, 0)))
|
|
||||||
),
|
|
||||||
EGG_HUNT_EXTREME(new ParticleData(Material.PURPLE_CONCRETE_POWDER, "PARTICLE_EGG_HUNT_EXTREME", ParticleRequirement.EGG_HUNT_EXTREME,
|
|
||||||
new Always(new LocationMutator(new SimpleParticle(Particle.FALLING_OBSIDIAN_TEAR, 0.5F, 0.1F, 0.5F, 1, 1), 0, 2.2, 0)))
|
|
||||||
),
|
|
||||||
EGG_HUNT_ADVANCED(new ParticleData(Material.BLACK_CONCRETE_POWDER, "PARTICLE_EGG_HUNT_ADVANCED", ParticleRequirement.EGG_HUNT_ADVANCED,
|
|
||||||
new Always(new Sneaking(new LocationMutator(new Group(
|
|
||||||
new SimpleParticle(Particle.SMOKE, 0.02F, 0, 0.02F, 0.05, 1),
|
|
||||||
new SimpleParticle(Particle.LARGE_SMOKE, 0.02F, 0, 0.02F, 0.05, 1),
|
|
||||||
particleTickData -> {
|
|
||||||
Player player = particleTickData.getPlayer();
|
|
||||||
player.setVelocity(player.getVelocity().add(new Vector(0, 0.1, 0)));
|
|
||||||
}
|
|
||||||
), 0, -0.2, 0))))
|
|
||||||
),
|
|
||||||
EGG_HUNT_HALF(new ParticleData(Material.EGG, "PARTICLE_EGG_HUNT_HALF", ParticleRequirement.EGG_HUNT_FOUND_HALF,
|
|
||||||
new Group(
|
|
||||||
new Always(new Sneaking(new Floor(new LocationMutator(new SimpleParticle(Particle.HEART, 0, 0, 0, 0.01), 0, 1.7, 0)))),
|
|
||||||
new Separator(),
|
|
||||||
new Always(new NonFloor(new NonFlying(new LocationMutator(new Cloud(new Circle(new SimpleParticle(Particle.HEART, 0, 0, 0, 0.01))), 0, 0.5, 0))))
|
|
||||||
))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
|
||||||
import de.steamwar.lobby.particle.elements.Always;
|
|
||||||
import de.steamwar.lobby.particle.elements.Circle;
|
|
||||||
import de.steamwar.lobby.particle.elements.LocationMutator;
|
|
||||||
import de.steamwar.lobby.particle.elements.SimpleParticle;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum EventParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
WATER(new ParticleData(Material.WATER_BUCKET, "PARTICLE_WATER", ParticleRequirement.EVENT_PARTICIPATION,
|
|
||||||
new Always(new Circle(new LocationMutator(new SimpleParticle(Particle.DRIPPING_WATER), 0, 1.1, 0))))
|
|
||||||
),
|
|
||||||
LAVA(new ParticleData(Material.LAVA_BUCKET, "PARTICLE_FIRE", ParticleRequirement.EVENT_PARTICIPATION,
|
|
||||||
new Always(new Circle(new LocationMutator(new SimpleParticle(Particle.DRIPPING_LAVA), 0, 1.1, 0))))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
|
||||||
import de.steamwar.lobby.particle.WingDesign;
|
|
||||||
import de.steamwar.lobby.particle.elements.*;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum EventParticleParticipation implements ParticleEnum {
|
|
||||||
|
|
||||||
WarGearSeason(new ParticleData(Material.BOOK, "PARTICLE_EVENT_ENCHANTING", ParticleRequirement.eventParticipation(22),
|
|
||||||
new SimpleParticle(Particle.ENCHANT))
|
|
||||||
),
|
|
||||||
AirshipEvent(new ParticleData(Material.SNOW_BLOCK, "PARTICLE_EVENT_CLOUD", ParticleRequirement.eventParticipation(26),
|
|
||||||
new SimpleParticle(Particle.CLOUD))
|
|
||||||
),
|
|
||||||
HellsBellsWs(new ParticleData(Material.TNT, "PARTICLE_EVENT_SMOKE", ParticleRequirement.eventParticipation(28),
|
|
||||||
new Circle(new SimpleParticle(Particle.CAMPFIRE_COSY_SMOKE, 0, 0, 0, 0.01)))
|
|
||||||
),
|
|
||||||
Underwater(new ParticleData(Material.PRISMARINE_BRICKS, "PARTICLE_EVENT_WATER", ParticleRequirement.eventParticipation(31),
|
|
||||||
new SimpleParticle(Particle.DRIPPING_WATER))
|
|
||||||
),
|
|
||||||
AdventWarShip(new ParticleData(Material.PRISMARINE_WALL, "PARTICLE_EVENT_WATER", ParticleRequirement.eventParticipation(32),
|
|
||||||
new Group(new SimpleParticle(Particle.DRIPPING_WATER), new SimpleParticle(Particle.UNDERWATER, 0.2F, 0.2F, 0.2F, 0.01)))
|
|
||||||
),
|
|
||||||
MiniWarGearLiga(new ParticleData(Material.PRISMARINE_SLAB, "PARTICLE_EVENT_WATER", ParticleRequirement.eventParticipation(33),
|
|
||||||
new Circle(new SimpleParticle(Particle.UNDERWATER, 0, 0, 0, 0)))
|
|
||||||
),
|
|
||||||
UnderwaterMWG(new ParticleData(Material.BLUE_CARPET, "PARTICLE_EVENT_RAIN_CLOUD", ParticleRequirement.eventParticipation(35),
|
|
||||||
new Always(new RandomParticle(RainCloudParticle.values())))
|
|
||||||
),
|
|
||||||
WarGearSeason2022(new ParticleData(Material.DIAMOND_SWORD, "PARTICLE_EVENT_WINGS", ParticleRequirement.eventParticipation(37),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.DRAGON_BREATH, 0, 0, 0, 0, 1), 0.15, WingDesign.COMPLEX)), 20)))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
|
||||||
import de.steamwar.lobby.particle.WingDesign;
|
|
||||||
import de.steamwar.lobby.particle.elements.*;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum EventParticlePlacement implements ParticleEnum {
|
|
||||||
|
|
||||||
WarGearSeason(new ParticleData(Material.ENCHANTING_TABLE, "PARTICLE_EVENT_ENCHANTING", ParticleRequirement.eventPlacement(22, 12, 285, 54),
|
|
||||||
new Cloud(new Circle(new LocationMutator(new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.01), 0, 1.1, 0)))
|
|
||||||
)),
|
|
||||||
AirshipEvent(new ParticleData(Material.SNOWBALL, "PARTICLE_EVENT_CLOUD", ParticleRequirement.eventPlacement(26, 205, 9, 54, 120, 292),
|
|
||||||
new Circle(new LocationMutator(new SimpleParticle(Particle.CLOUD, 0, 0, 0, 0.01), 0, 2.2, 0))
|
|
||||||
)),
|
|
||||||
HellsBellsWs(new ParticleData(Material.TNT_MINECART, "PARTICLE_EVENT_SMOKE", ParticleRequirement.eventPlacement(28, 205, 9, 11),
|
|
||||||
new Cloud(new Circle(new LocationMutator(new SimpleParticle(Particle.CAMPFIRE_COSY_SMOKE, 0, 0, 0, 0.01), 0, 2.2, 0)))
|
|
||||||
)),
|
|
||||||
Underwater(new ParticleData(Material.PRISMARINE_SHARD, "PARTICLE_EVENT_WATER", ParticleRequirement.eventPlacement(31, 9, 210, 520),
|
|
||||||
new Cloud(new SimpleParticle(Particle.DRIPPING_WATER)))
|
|
||||||
),
|
|
||||||
AdventWarShip(new ParticleData(Material.PRISMARINE_CRYSTALS, "PARTICLE_EVENT_WATER", ParticleRequirement.eventPlacement(32, 9, 205, 210),
|
|
||||||
new Always(new LocationMutator(new Circle(new Group(new SimpleParticle(Particle.DRIPPING_WATER), new SimpleParticle(Particle.UNDERWATER, 0.2F, 0.2F, 0.2F, 0.01))), 0, 1.1, 0)))
|
|
||||||
),
|
|
||||||
MiniWarGearLiga(new ParticleData(Material.IRON_SWORD, "PARTICLE_EVENT_WINGS", ParticleRequirement.eventPlacement(33, 9, 34, 205),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.UNDERWATER, 0, 0, 0, 0, 1), 0.15, WingDesign.MWGL)), 20)))
|
|
||||||
),
|
|
||||||
Absturz(new ParticleData(Material.FEATHER, "PARTICLE_EVENT_WINGS", ParticleRequirement.eventPlacement(34, 210, 205, 527, 286),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0, 1), 0.15, WingDesign.SIMPLE)), 20)))
|
|
||||||
),
|
|
||||||
UnderwaterMWG(new ParticleData(Material.CYAN_CARPET, "PARTICLE_EVENT_RAIN_CLOUD", ParticleRequirement.eventPlacement(35, 9, 210),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0, 1), 0.15, WingDesign.SW)), 20)))
|
|
||||||
),
|
|
||||||
WarGearSeason2022(new ParticleData(Material.DIAMOND_HELMET, "PARTICLE_EVENT_WGS", ParticleRequirement.eventPlacement(37, 285, 210, 122),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.FIREWORK, 0, 0, 0, 0, 1), 0.15, WingDesign.WGS)), 20)))
|
|
||||||
),
|
|
||||||
WargearClash(new ParticleData(Material.GOLDEN_SWORD, "PARTICLE_EVENT_WARGEARCLASH", ParticleRequirement.eventPlacement(38, 210, 158, 167, 286),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0, 1), 0.1, WingDesign.SWORD_CROSSED)), 20)))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.elements.DustParticle;
|
|
||||||
import de.steamwar.lobby.particle.elements.LocationMutator;
|
|
||||||
import de.steamwar.lobby.particle.elements.SimpleParticle;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum PlayerParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
SNEEZE(new ParticleData(Material.SLIME_BLOCK, "PARTICLE_SNEEZE",
|
|
||||||
new SimpleParticle(Particle.SNEEZE, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
SMOKE(new ParticleData(Material.COBWEB, "PARTICLE_SMOKE",
|
|
||||||
new SimpleParticle(Particle.SMOKE, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
FIRE(new ParticleData(Material.LAVA_BUCKET, "PARTICLE_FIRE",
|
|
||||||
new SimpleParticle(Particle.DRIPPING_LAVA))
|
|
||||||
),
|
|
||||||
WATER(new ParticleData(Material.WATER_BUCKET, "PARTICLE_WATER",
|
|
||||||
new SimpleParticle(Particle.DRIPPING_WATER))
|
|
||||||
),
|
|
||||||
HEARTH(new ParticleData(Material.RED_DYE, "PARTICLE_HEART",
|
|
||||||
new LocationMutator(new SimpleParticle(Particle.HEART), 0, 2.2, 0))
|
|
||||||
),
|
|
||||||
NOTES(new ParticleData(Material.NOTE_BLOCK, "PARTICLE_NOTES",
|
|
||||||
new LocationMutator(new SimpleParticle(Particle.NOTE), 0, 2.2, 0))
|
|
||||||
),
|
|
||||||
NAUTILUS(new ParticleData(Material.NAUTILUS_SHELL, "PARTICLE_NAUTILUS",
|
|
||||||
new SimpleParticle(Particle.NAUTILUS, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
SNOWBALL(new ParticleData(Material.SNOWBALL, "PARTICLE_SNOWBALL",
|
|
||||||
new SimpleParticle(Particle.SNOWFLAKE, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
EFFECT(new ParticleData(Material.GLASS_BOTTLE, "PARTICLE_EFFECT",
|
|
||||||
new DustParticle(Particle.DUST, 0, 0.2F, 0, 0.01, 5, null))
|
|
||||||
),
|
|
||||||
CAMPFIRE(new ParticleData(Material.CAMPFIRE, "PARTICLE_CAMPFIRE",
|
|
||||||
new SimpleParticle(Particle.CAMPFIRE_COSY_SMOKE, 0, 0.2F, 0, 0.01))
|
|
||||||
),
|
|
||||||
MAGIC(new ParticleData(Material.CAULDRON, "PARTICLE_MAGIC",
|
|
||||||
new SimpleParticle(Particle.CRIT, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
ANGRY(new ParticleData(Material.REDSTONE_BLOCK, "PARTICLE_ANGRY",
|
|
||||||
new SimpleParticle(Particle.ANGRY_VILLAGER, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
SLIME(new ParticleData(Material.SLIME_BALL, "PARTICLE_SLIME",
|
|
||||||
new SimpleParticle(Particle.ITEM_SLIME))
|
|
||||||
),
|
|
||||||
MOB(new ParticleData(Material.ZOMBIE_HEAD, "PARTICLE_MOB",
|
|
||||||
new SimpleParticle(Particle.SOUL))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
|
||||||
import de.steamwar.lobby.particle.elements.Always;
|
|
||||||
import de.steamwar.lobby.particle.elements.Group;
|
|
||||||
import de.steamwar.lobby.particle.elements.LocationMutator;
|
|
||||||
import de.steamwar.lobby.particle.elements.SimpleParticle;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum RainCloudParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
Raincloud(new ParticleData(Material.BLUE_CARPET, "PARTICLE_RAINCLOUD_NORMAL", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new SimpleParticle(Particle.RAIN, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.DRIPPING_WATER, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Red_Raincloud(new ParticleData(Material.RED_CARPET, "PARTICLE_RAINCLOUD_RED", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.DRIPPING_LAVA, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Yellow_Raincloud(new ParticleData(Material.YELLOW_CARPET, "PARTICLE_RAINCLOUD_YELLOW", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.FALLING_NECTAR, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Orange_Raincloud(new ParticleData(Material.ORANGE_CARPET, "PARTICLE_RAINCLOUD_ORANGE", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.FALLING_HONEY, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Green_Raincloud(new ParticleData(Material.LIME_CARPET, "PARTICLE_RAINCLOUD_GREEN", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.FALLING_SPORE_BLOSSOM, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Purple_Raincloud(new ParticleData(Material.PURPLE_CARPET, "PARTICLE_RAINCLOUD_PURPLE", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.FALLING_OBSIDIAN_TEAR, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Nautilus_Raincloud(new ParticleData(Material.NAUTILUS_SHELL, "PARTICLE_RAINCLOUD_NAUTILUS", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.NAUTILUS, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Enchantment_Raincloud(new ParticleData(Material.ENCHANTED_BOOK, "PARTICLE_RAINCLOUD_ENCHANTMENT", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.ENCHANT, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Slime_Raincloud(new ParticleData(Material.GREEN_CARPET, "PARTICLE_RAINCLOUD_SLIME", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.ITEM_SLIME, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Hail_Raincloud(new ParticleData(Material.LIGHT_GRAY_CARPET, "PARTICLE_RAINCLOUD_HAIL", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.ITEM_SNOWBALL, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Snow_Raincloud(new ParticleData(Material.WHITE_CARPET, "PARTICLE_RAINCLOUD_SNOW", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.SNOWFLAKE, 0.3F, 0.0F, 0.3F, 0.01), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
Totem_Raincloud(new ParticleData(Material.TOTEM_OF_UNDYING, "PARTICLE_RAINCLOUD_TOTEM", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Always(new LocationMutator(new Group(new SimpleParticle(Particle.CLOUD, 0.3F, 0.1F, 0.3F, 0.01), new LocationMutator(new SimpleParticle(Particle.TOTEM_OF_UNDYING, 0.3F, 0.0F, 0.3F, 0.01, 2), 0, -0.3, 0)), 0, 2.4, 0)))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
|
||||||
import de.steamwar.lobby.particle.WingDesign;
|
|
||||||
import de.steamwar.lobby.particle.elements.*;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum ServerTeamParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
WITCH(new ParticleData(Material.EXPERIENCE_BOTTLE, "PARTICLE_WITCH", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new SimpleParticle(Particle.WITCH))
|
|
||||||
),
|
|
||||||
ENCHANTING(new ParticleData(Material.ENCHANTING_TABLE, "PARTICLE_ENCHANTING", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new SimpleParticle(Particle.ENCHANT))
|
|
||||||
),
|
|
||||||
HAPPY(new ParticleData(Material.EMERALD_BLOCK, "PARTICLE_HAPPY", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new SimpleParticle(Particle.HEART, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
FLAME(new ParticleData(Material.FLINT_AND_STEEL, "PARTICLE_FLAME", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new SimpleParticle(Particle.FLAME, 0, 0.2F, 0, 0.01))
|
|
||||||
),
|
|
||||||
END_ROD(new ParticleData(Material.END_ROD, "PARTICLE_END_ROD", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new SimpleParticle(Particle.END_ROD, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
CLOUD(new ParticleData(Material.WHITE_WOOL, "PARTICLE_CLOUD", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new SimpleParticle(Particle.CLOUD)))
|
|
||||||
),
|
|
||||||
TOTEM(new ParticleData(Material.TOTEM_OF_UNDYING, "PARTICLE_TOTEM", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new SimpleParticle(Particle.TOTEM_OF_UNDYING, 0.2F, 0.2F, 0.2F, 0.01)))
|
|
||||||
),
|
|
||||||
ENCHANTING_CLOUD(new ParticleData(Material.WHITE_DYE, "PARTICLE_ENCHANTING", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new SimpleParticle(Particle.ENCHANT)))
|
|
||||||
),
|
|
||||||
FLAME_CLOUD(new ParticleData(Material.FIRE_CORAL_BLOCK, "PARTICLE_FLAME", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new SimpleParticle(Particle.FLAME)))
|
|
||||||
),
|
|
||||||
SNEEZE_CLOUD(new ParticleData(Material.LIME_SHULKER_BOX, "PARTICLE_SNEEZE", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new SimpleParticle(Particle.SNEEZE)))
|
|
||||||
),
|
|
||||||
SLIME_CLOUD(new ParticleData(Material.GREEN_SHULKER_BOX, "PARTICLE_SLIME", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new SimpleParticle(Particle.ITEM_SLIME)))
|
|
||||||
),
|
|
||||||
SMOKE_CLOUD(new ParticleData(Material.DEAD_BRAIN_CORAL_BLOCK, "PARTICLE_SMOKE", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new SimpleParticle(Particle.CAMPFIRE_COSY_SMOKE, 0.2F, 0.2F, 0.2F, 0.01)))
|
|
||||||
),
|
|
||||||
TOWN_CLOUD(new ParticleData(Material.FIREWORK_STAR, "PARTICLE_TOWN", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new SimpleParticle(Particle.RAID_OMEN)))
|
|
||||||
),
|
|
||||||
FLAME_CIRCLE(new ParticleData(Material.MAGMA_BLOCK, "PARTICLE_FLAME", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Circle(new LocationMutator(new SimpleParticle(Particle.FLAME, 0, 0, 0, 0.01), 0, 1.1, 0)))
|
|
||||||
),
|
|
||||||
ENCHANTING_CIRCLE(new ParticleData(Material.WHITE_DYE, "PARTICLE_ENCHANTING_CIRCLE", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Circle(new LocationMutator(new SimpleParticle(Particle.ENCHANT, 0.2F, 0.2F, 0.2F, 0.01), 0, 1.1, 0)))
|
|
||||||
),
|
|
||||||
NOTES_CIRCLE(new ParticleData(Material.NOTE_BLOCK, "PARTICLE_NOTES", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Circle(new LocationMutator(new SimpleParticle(Particle.NOTE, 0, 0, 0, 0.01), 0, 2.2, 0)))
|
|
||||||
),
|
|
||||||
WATER_FIRE(new ParticleData(Material.GUARDIAN_SPAWN_EGG, "PARTICLE_WATER_FIRE", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new LocationMutator(new DoubleCircle(new SimpleParticle(Particle.DRIPPING_WATER, 0, 0, 0, 0.01), new SimpleParticle(Particle.DRIPPING_LAVA, 0, 0, 0, 0.01)), 0, 1.1, 0))
|
|
||||||
),
|
|
||||||
WATER_FIRE_ALWAYS(new ParticleData(Material.GUARDIAN_SPAWN_EGG, "PARTICLE_WATER_FIRE", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Always(new LocationMutator(new DoubleCircle(new SimpleParticle(Particle.DRIPPING_WATER, 0, 0, 0, 0.01), new SimpleParticle(Particle.DRIPPING_LAVA, 0, 0, 0, 0.01)), 0, 1.1, 0)))
|
|
||||||
),
|
|
||||||
MAGIC_ENCHANTING(new ParticleData(Material.DIAMOND_SWORD, "PARTICLE_MAGIC_ENCHANTING", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new LocationMutator(new DoubleCircle(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0.01), new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.01)), 0, 1.1, 0))
|
|
||||||
),
|
|
||||||
MAGIC_ENCHANTING_ALWAYS(new ParticleData(Material.WOODEN_SWORD, "PARTICLE_MAGIC_ENCHANTING", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Always(new LocationMutator(new DoubleCircle(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0.01), new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.01)), 0, 1.1, 0)))
|
|
||||||
),
|
|
||||||
MAGIC_CLOUD_CIRCLE(new ParticleData(Material.GLOWSTONE_DUST, "PARTICLE_MAGIC", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new Circle(new LocationMutator(new SimpleParticle(Particle.ENCHANTED_HIT, 0, 0, 0, 0.01), 0, 1.1, 0))))
|
|
||||||
),
|
|
||||||
FLAME_CLOUD_CIRCLE(new ParticleData(Material.FIRE_CORAL, "PARTICLE_FLAME", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new Circle(new LocationMutator(new SimpleParticle(Particle.FLAME, 0, 0, 0, 0.01), 0, 1.1, 0))))
|
|
||||||
),
|
|
||||||
FIREWORK_CLOUD_CIRCLE(new ParticleData(Material.FIREWORK_ROCKET, "PARTICLE_FIREWORK", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new Circle(new LocationMutator(new SimpleParticle(Particle.FIREWORK, 0, 0, 0, 0.01), 0, 1.1, 0))))
|
|
||||||
),
|
|
||||||
WATER_CLOUD_CIRCLE(new ParticleData(Material.CYAN_DYE, "PARTICLE_WATER_FIRE", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Cloud(new Circle(new LocationMutator(new SimpleParticle(Particle.RAIN, 0, 0, 0, 0.01), 0, 1.1, 0))))
|
|
||||||
),
|
|
||||||
ENCHANTING_DOUBLE_CIRCLE(new ParticleData(Material.BUBBLE_CORAL_BLOCK, "PARTICLE_ENCHANTING", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Always(new LocationMutator(new DoubleCircle(new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.01), new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.01)), 0, 2.2, 0)))
|
|
||||||
),
|
|
||||||
EVIL_WINGS(new ParticleData(Material.PURPUR_SLAB, "PARTICLE_WINGS_EVIL", ParticleRequirement.SERVER_TEAM,
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.WITCH, 0, 0, 0, 0, 1), 0.15, WingDesign.SIMPLE)), 20)))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
|
||||||
import de.steamwar.lobby.particle.elements.Circle;
|
|
||||||
import de.steamwar.lobby.particle.elements.LocationMutator;
|
|
||||||
import de.steamwar.lobby.particle.elements.NonFloor;
|
|
||||||
import de.steamwar.lobby.particle.elements.SimpleParticle;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum TeamParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
SQUID(new ParticleData(Material.INK_SAC, "PARTICLE_SQUID", ParticleRequirement.HAS_TEAM,
|
|
||||||
new SimpleParticle(Particle.SQUID_INK, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
BUBBLE(new ParticleData(Material.TUBE_CORAL, "PARTICLE_BUBBLE", ParticleRequirement.HAS_TEAM,
|
|
||||||
new SimpleParticle(Particle.BUBBLE_POP, 0.2F, 0.2F, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
HONEY(new ParticleData(Material.HONEY_BOTTLE, "PARTICLE_HONEY", ParticleRequirement.HAS_TEAM,
|
|
||||||
new SimpleParticle(Particle.DRIPPING_HONEY, 0.2F, 0.2F, 0.2F, 1))
|
|
||||||
),
|
|
||||||
NECTAR(new ParticleData(Material.HONEYCOMB, "PARTICLE_NECTAR", ParticleRequirement.HAS_TEAM,
|
|
||||||
new SimpleParticle(Particle.FALLING_NECTAR, 0.2F, 0.2F, 0.2F, 1))
|
|
||||||
),
|
|
||||||
FIREWORK(new ParticleData(Material.FIRE_CHARGE, "PARTICLE_FIREWORK", ParticleRequirement.HAS_TEAM,
|
|
||||||
new LocationMutator(new NonFloor(new SimpleParticle(Particle.FIREWORK, 0.1F, 0.1F, 0.1F, 0.2, 2)), 0, -0.2, 0))
|
|
||||||
),
|
|
||||||
DRAGON_BREATH(new ParticleData(Material.DRAGON_BREATH, "PARTICLE_DRAGON_BREATH", ParticleRequirement.HAS_TEAM,
|
|
||||||
new SimpleParticle(Particle.DRAGON_BREATH, 1F, 0.2F, 1F, 0.01))
|
|
||||||
),
|
|
||||||
DAMAGE(new ParticleData(Material.SPIDER_EYE, "PARTICLE_DAMAGE", ParticleRequirement.HAS_TEAM,
|
|
||||||
new SimpleParticle(Particle.DAMAGE_INDICATOR, 0.2F, 0, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
DOLPHIN(new ParticleData(Material.BLUE_DYE, "PARTICLE_DOLPHIN", ParticleRequirement.HAS_TEAM,
|
|
||||||
new SimpleParticle(Particle.DOLPHIN, 0.2F, 0, 0.2F, 0.01))
|
|
||||||
),
|
|
||||||
HEART(new ParticleData(Material.RED_CONCRETE, "PARTICLE_HEART", ParticleRequirement.HAS_TEAM,
|
|
||||||
new Circle(new LocationMutator(new SimpleParticle(Particle.HEART), 0, 2.2, 0)))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
-126
@@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles.custom;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.*;
|
|
||||||
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.custom.YOffset;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum CustomEasterParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
PLAYER_1063(new ParticleData(Material.ENDER_PEARL, "PARTICLE_PLAYER_1063", ParticleRequirement.easterEventSpecificPlayer(1063),
|
|
||||||
new Always(
|
|
||||||
new DoubleCircle(
|
|
||||||
new DoubleCircle(
|
|
||||||
new YOffset(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0.01, 1), 20, 0, 2, true, true),
|
|
||||||
new YOffset(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0.01, 1), 20, 0, 2, false, true), 0.5, 4),
|
|
||||||
new DoubleCircle(
|
|
||||||
new YOffset(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0.01, 1), 20, 0, 2, false, true),
|
|
||||||
new YOffset(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0.01, 1), 20, 0, 2, true, true), 0.5, 4), 1.5, 1)
|
|
||||||
))
|
|
||||||
),
|
|
||||||
PLAYER_10916(new ParticleData(Material.TORCHFLOWER, "PARTICLE_PLAYER_10916_AURA", ParticleRequirement.easterEventSpecificPlayer(10916),
|
|
||||||
new Always(new NonMoving(new Group(
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.5, 10), location -> location.add(0, 0.2, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.5, 10), location -> location.add(0, 0.3, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.55, 10), location -> location.add(0, 0.4, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.55, 10), location -> location.add(0, 0.5, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.65, 10), location -> location.add(0, 0.6, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.65, 10), location -> location.add(0, 0.7, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.7, 10), location -> location.add(0, 0.8, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.7, 10), location -> location.add(0, 0.9, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.7, 10), location -> location.add(0, 1.0, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.75, 10), location -> location.add(0, 1.1, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.75, 10), location -> location.add(0, 1.2, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.75, 10), location -> location.add(0, 1.3, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.8, 10), location -> location.add(0, 1.4, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.8, 10), location -> location.add(0, 1.5, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.8, 10), location -> location.add(0, 1.6, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.85, 10), location -> location.add(0, 1.7, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.85, 10), location -> location.add(0, 1.8, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.85, 10), location -> location.add(0, 1.9, 0)),
|
|
||||||
new LocationMutator(new DoubleCircle(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.RED)), 0.9, 10), location -> location.add(0, 2.0, 0))
|
|
||||||
))))
|
|
||||||
),
|
|
||||||
PLAYER_10697(new ParticleData(Material.GUNPOWDER, "PARTICLE_PLAYER_10697_AURA", ParticleRequirement.easterEventSpecificPlayer(10697),
|
|
||||||
new Always(new Sneaking(new Group(
|
|
||||||
new OnlySelf(new Delayed(new SimpleParticle(Particle.EXPLOSION, 0, 0, 0, 0.01, 1), 20)),
|
|
||||||
new OnlyOthers(new Delayed(new SimpleParticle(Particle.EXPLOSION, 0, 0, 0, 0.01, 1), 2))
|
|
||||||
))))
|
|
||||||
),
|
|
||||||
PLAYER_64(new ParticleData(Material.PUFFERFISH_BUCKET, "PARTICLE_PLAYER_64", ParticleRequirement.easterEventSpecificPlayer(64),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.END_ROD, 0, 0, 0, 0, 1), 0.15, WingDesign.PlompaEasterWings)), 80)))
|
|
||||||
),
|
|
||||||
TEAM_210_1(new ParticleData(Material.RED_CANDLE, "PARTICLE_TEAM_PULSE_AURA", ParticleRequirement.easterEventSpecificTeam(210),
|
|
||||||
new Always(new NonMoving(new PulseShimmer(new SimpleParticle(Particle.FLAME, 0, 0, 0, 0, 1), 80, 2))))
|
|
||||||
),
|
|
||||||
TEAM_210_2(new ParticleData(Material.APPLE, "PARTICLE_TEAM_PULSE_HEART_BEAT", ParticleRequirement.easterEventSpecificTeam(210),
|
|
||||||
new Always(new NonFlying(new HearthBeat(new SimpleParticle(Particle.FLAME, 0, 0, 0, 0, 1), 80))))
|
|
||||||
),
|
|
||||||
PLAYER_153(new ParticleData(Material.OAK_SIGN, "PARTICLE_PLAYER_153", ParticleRequirement.easterEventSpecificPlayer(153),
|
|
||||||
new Always(new Delayed(new NonFlying(new Group(
|
|
||||||
new Wing(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.DARK_GRAY)), 0.15, WingDesign.ELY_E),
|
|
||||||
new Wing(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.GREEN)), 0.15, WingDesign.ELY_L),
|
|
||||||
new Wing(new DustParticle(Particle.DUST, 0, 0, 0, 0, 1, new Gradient(Color.GRAY)), 0.15, WingDesign.ELY_Y)
|
|
||||||
)), 10)))
|
|
||||||
),
|
|
||||||
// TODO: Implement zSalos! -> Team
|
|
||||||
// TODO: Implement GroupXyz!
|
|
||||||
// TODO: Implement minecrafter_133 1817!
|
|
||||||
// TODO: Implement TheReaper22122!
|
|
||||||
// TODO: Implement Bosslar!
|
|
||||||
// TODO: Implement ATOM65!
|
|
||||||
PLAYER_3266(new ParticleData(Material.BREAD, "PARTICLE_PLAYER_3266", ParticleRequirement.easterEventSpecificPlayer(3266),
|
|
||||||
new Group(
|
|
||||||
new Always(new Sneaking(new LocationMutator(new None(), location -> location))),
|
|
||||||
new Always(new NonFlying(new LocationMutator(new TrippleCircle(
|
|
||||||
new DustParticle(Particle.DUST, new Gradient(Color.CYAN, Color.BLUE, Color.MAGENTA.darker(), Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN)),
|
|
||||||
new DustParticle(Particle.DUST, new Gradient(Color.CYAN, Color.BLUE, Color.MAGENTA.darker(), Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN)),
|
|
||||||
new DustParticle(Particle.DUST, new Gradient(Color.CYAN, Color.BLUE, Color.MAGENTA.darker(), Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN)),
|
|
||||||
0.7,
|
|
||||||
0.5), location -> location.add(0, 0.6, 0)
|
|
||||||
))))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
// TODO: Implement Gehfxhler!
|
|
||||||
// TODO: Implement SchwarzerFuerst
|
|
||||||
// TODO: Implement byVallu
|
|
||||||
TEAM_158_1(new ParticleData(Material.ENCHANTED_BOOK, "PARTICLE_TEAM_158_1", ParticleRequirement.easterEventSpecificTeam(158),
|
|
||||||
new Always(new NonFlying(new Delayed(new Wing(new SimpleParticle(Particle.CRIT, 0, 0, 0, 0, 1), 0.2, WingDesign.EV), 15))))
|
|
||||||
),
|
|
||||||
TEAM_158_2(new ParticleData(Material.ENDER_EYE, "PARTICLE_TEAM_158_2", ParticleRequirement.easterEventSpecificTeam(158),
|
|
||||||
new Always(new NonFlying(new YOffset(new DoubleCircle(new DustParticle(Particle.CRIT, 0.01f, 0.01f, 0.01f, 0.1, 1, new Gradient(Color.RED)), new DustParticle(Particle.DUST, 0.01f, 0.01f, 0.01f, 0.1, 1, new Gradient(Color.BLUE)), 0.7, 1), 40, 0, 2, false, false))))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles.custom;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
|
||||||
import de.steamwar.lobby.particle.elements.*;
|
|
||||||
import de.steamwar.lobby.particle.elements.custom.NonMoving;
|
|
||||||
import de.steamwar.lobby.particle.elements.custom.YOffset;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum CustomPlayerParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
Haylim_(new ParticleData(Material.WHITE_CANDLE, "PARTICLE_PLAYER_HAYLIM_AURA", ParticleRequirement.specificPlayer(9426),
|
|
||||||
new Always(new NonMoving(new NonFlying(new Group(
|
|
||||||
new DoubleCircle(new YOffset(new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.01, 5), 40, 0, 2, false, false), new YOffset(new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.0, 5), 40, 0, 2, true, false)),
|
|
||||||
new DoubleCircle(new YOffset(new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.01, 5), 40, 0, 2, true, false), new YOffset(new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0.0, 5), 40, 0, 2, false, false))
|
|
||||||
)))))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2025 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.particles.custom;
|
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleData;
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
|
||||||
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.NonMoving;
|
|
||||||
import de.steamwar.lobby.particle.elements.custom.PulseShimmer;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum CustomTeamParticle implements ParticleEnum {
|
|
||||||
|
|
||||||
Eclipse(new ParticleData(Material.ENDER_CHEST, "PARTICLE_EVENT_ENCHANTING", ParticleRequirement.specificTeam(34),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.END_ROD, 0, 0, 0, 0, 1), 0.15, WingDesign.ECLIPSE)), 80)))
|
|
||||||
),
|
|
||||||
Pulse_1(new ParticleData(Material.GRAY_CANDLE, "PARTICLE_TEAM_PULSE_AURA_2", ParticleRequirement.specificTeam(210),
|
|
||||||
new Always(new NonMoving(new PulseShimmer(new SimpleParticle(Particle.END_ROD, 0, 0, 0, 0, 1), 80, 2))))
|
|
||||||
),
|
|
||||||
Pulse_2(new ParticleData(Material.WHITE_CANDLE, "PARTICLE_TEAM_PULSE_AURA_3", ParticleRequirement.specificTeam(210),
|
|
||||||
new Always(new NonMoving(new PulseShimmer(new SimpleParticle(Particle.ENCHANT, 0, 0, 0, 0, 5), 80, 2))))
|
|
||||||
),
|
|
||||||
Pulse_Logo(new ParticleData(Material.ENDER_CHEST, "PARTICLE_TEAM_PULSE_LOGO", ParticleRequirement.specificTeam(210),
|
|
||||||
new Always(new Delayed(new NonFlying(new Wing(new SimpleParticle(Particle.END_ROD, 0, 0, 0, 0, 1), 0.15, WingDesign.PL)), 80)))
|
|
||||||
),
|
|
||||||
;
|
|
||||||
public static ParticleEnum[] particles = values();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private ParticleData particle;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* 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.ParticleTickData;
|
||||||
|
import de.steamwar.lobby.particle.UnlockContext;
|
||||||
|
import de.steamwar.lobby.particle.design.*;
|
||||||
|
import de.steamwar.lobby.particle.elements.*;
|
||||||
|
import de.steamwar.lobby.particle.elements.touch.AfterJump;
|
||||||
|
import de.steamwar.lobby.particle.elements.touch.GlyphPath;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A new entry with the design's default conditions and timing
|
||||||
|
public ParticleEntry(ParticleDesign design) {
|
||||||
|
this(design, new ParameterValues());
|
||||||
|
design.applyDefaults(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> ParticleEntry with(Parameter<T> parameter, T value) {
|
||||||
|
values.set(parameter, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For presets that go beyond what the builder allows
|
||||||
|
public <T> ParticleEntry withUnchecked(Parameter<T> parameter, T value) {
|
||||||
|
values.setUnchecked(parameter, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
ParticleElement element;
|
||||||
|
if (design.usesEntryParticle()) {
|
||||||
|
element = design.build(values, withOffset(particle()));
|
||||||
|
} else {
|
||||||
|
element = withOffset(design.build(values, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.get(PLACEMENT) == Placement.BODY) {
|
||||||
|
element = new BodyRelative(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 && design.redrawsAfterJump(values)) {
|
||||||
|
element = new AfterJump(element, new Delayed(element, interval), Wing.JUMP_BEAT_TICKS);
|
||||||
|
} else if (interval > 1) {
|
||||||
|
element = new Delayed(element, interval);
|
||||||
|
}
|
||||||
|
if (values.get(TICK_MODE) == TickMode.ALWAYS) {
|
||||||
|
element = new Always(element);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleElement particle() {
|
||||||
|
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 = amount();
|
||||||
|
|
||||||
|
List<Color> colors = values.get(COLORS);
|
||||||
|
Gradient gradient = colors.isEmpty() ? null : new Gradient(colors.toArray(new Color[0]));
|
||||||
|
Material block = values.get(BLOCK);
|
||||||
|
BiFunction<ParticleTickData, Location, Object> data = switch (type.getData()) {
|
||||||
|
// Dust keeps its own element below, so presets look exactly as before
|
||||||
|
case NONE, DUST -> null;
|
||||||
|
case COLOR -> (tick, location) -> DataParticle.color(gradient, tick);
|
||||||
|
case COLOR_TRANSITION -> (tick, location) -> new Particle.DustTransition(
|
||||||
|
colors.isEmpty() ? DataParticle.color(null, tick) : bukkitColor(colors.get(0)),
|
||||||
|
colors.size() < 2 ? DataParticle.color(null, tick) : bukkitColor(colors.get(1)),
|
||||||
|
1);
|
||||||
|
case BLOCK -> {
|
||||||
|
BlockData blockData = block.createBlockData();
|
||||||
|
yield (tick, location) -> blockData;
|
||||||
|
}
|
||||||
|
case ITEM -> {
|
||||||
|
ItemStack item = new ItemStack(block);
|
||||||
|
yield (tick, location) -> item;
|
||||||
|
}
|
||||||
|
case TRAIL -> {
|
||||||
|
int duration = values.get(TRAIL_DURATION);
|
||||||
|
Vector target = new Vector(values.get(TRAIL_TARGET_X), values.get(TRAIL_TARGET_Y), values.get(TRAIL_TARGET_Z));
|
||||||
|
// The target follows the body like offsets do when the entry is placed relative to it
|
||||||
|
yield (tick, location) -> new Particle.Trail(
|
||||||
|
location.clone().add(tick.getFrame() == null ? target : tick.getFrame().local(target.getX(), target.getY(), target.getZ())),
|
||||||
|
DataParticle.color(gradient, tick), duration);
|
||||||
|
}
|
||||||
|
case ZERO_INT -> (tick, location) -> 0;
|
||||||
|
case ZERO_FLOAT -> (tick, location) -> 0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type == ParticleType.ENCHANT && values.get(GLYPH_FLIGHT) != GlyphFlight.NORMAL) {
|
||||||
|
GlyphPath.Direction direction = values.get(GLYPH_FLIGHT) == GlyphFlight.INTO_PLAYER ? GlyphPath.Direction.INTO_PLAYER : GlyphPath.Direction.OUT_OF_PLAYER;
|
||||||
|
return new GlyphPath(direction, spreadX, spreadY, spreadZ, count);
|
||||||
|
}
|
||||||
|
if (type.isDust()) {
|
||||||
|
return new DustParticle(type.getParticle(), spreadX, spreadY, spreadZ, speed, count, gradient);
|
||||||
|
}
|
||||||
|
if (data != null) {
|
||||||
|
return new DataParticle(type.getParticle(), spreadX, spreadY, spreadZ, speed, count, data);
|
||||||
|
}
|
||||||
|
return new SimpleParticle(type.getParticle(), spreadX, spreadY, spreadZ, speed, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static org.bukkit.Color bukkitColor(Color color) {
|
||||||
|
return org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleElement withOffset(ParticleElement element) {
|
||||||
|
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) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
return new LocationMutator(element, offsetX, offsetY, offsetZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
for (Parameter<?> parameter : design.getParameters()) {
|
||||||
|
if (parameter instanceof ChoiceParameter<?> choice && !((ChoiceParameter<Object>) choice).requirement(values.get(choice)).test(context)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!design.usesEntryParticle()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ParticleType type = values.get(PARTICLE);
|
||||||
|
return type.getRequirement().test(context) && (type != ParticleType.ENCHANT || GLYPH_FLIGHT.requirement(values.get(GLYPH_FLIGHT)).test(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Particles each point spawns; designs that set their own point count replace the amount with 1
|
||||||
|
private int amount() {
|
||||||
|
return design.replacesAmount(values) ? 1 : values.get(COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
return perTick / 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,49 @@
|
|||||||
|
/*
|
||||||
|
* 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 de.steamwar.lobby.particle.ParticleRequirement;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// A read-only particle for a specific team or user, selectable without using a slot
|
||||||
|
@Getter
|
||||||
|
public class ParticlePreset implements ParticleSet {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final Material icon;
|
||||||
|
private final String nameKey;
|
||||||
|
private final ParticleRequirement requirement;
|
||||||
|
private final List<ParticleEntry> entries;
|
||||||
|
|
||||||
|
public ParticlePreset(String id, Material icon, String nameKey, ParticleRequirement requirement, List<ParticleEntry> entries) {
|
||||||
|
this.id = id;
|
||||||
|
this.icon = icon;
|
||||||
|
this.nameKey = nameKey;
|
||||||
|
this.requirement = requirement;
|
||||||
|
this.entries = List.copyOf(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticlePreset(String id, Material icon, String nameKey, ParticleRequirement requirement, ParticleEntry... entries) {
|
||||||
|
this(id, icon, nameKey, requirement, List.of(entries));
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-4
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is a part of the SteamWar software.
|
* This file is a part of the SteamWar software.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.steamwar.lobby.particle;
|
package de.steamwar.lobby.particle.slot;
|
||||||
|
|
||||||
public interface ParticleEnum {
|
import java.util.List;
|
||||||
ParticleData getParticle();
|
|
||||||
|
// A list of entries that are shown together: a player's slot or a preset
|
||||||
|
public interface ParticleSet {
|
||||||
|
List<ParticleEntry> getEntries();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* 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 double cost() {
|
||||||
|
return entries.stream().mapToDouble(ParticleEntry::cost).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (slot.entries.size() >= ParticleSlots.MAX_ENTRIES) break;
|
||||||
|
if (!element.isJsonObject()) continue;
|
||||||
|
ParticleEntry entry = ParticleEntry.fromJson(element.getAsJsonObject());
|
||||||
|
if (entry != null) {
|
||||||
|
slot.entries.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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.SteamwarUser;
|
||||||
|
import de.steamwar.sql.UserConfig;
|
||||||
|
import de.steamwar.sql.UserPerm;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class ParticleSlots {
|
||||||
|
|
||||||
|
public final int COUNT = 7;
|
||||||
|
// One row in the slot editor
|
||||||
|
public final int MAX_ENTRIES = 9;
|
||||||
|
private final String KEY_PREFIX = "lobby-particle-slot-";
|
||||||
|
|
||||||
|
// Particles per tick a slot may spawn. A default entry costs 5; the largest preset costs 38.
|
||||||
|
public double maxCost(SteamwarUser user) {
|
||||||
|
return user.hasPerm(UserPerm.TEAM) ? 90 : 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* 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 de.steamwar.lobby.particle.ParticleRequirement;
|
||||||
|
import de.steamwar.lobby.particle.WingDesign;
|
||||||
|
import de.steamwar.lobby.particle.design.Designs;
|
||||||
|
import de.steamwar.lobby.particle.design.InternalDesigns;
|
||||||
|
import de.steamwar.lobby.particle.design.ParticleDesign;
|
||||||
|
import de.steamwar.lobby.particle.design.ParticleType;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static de.steamwar.lobby.particle.design.EntryParameters.*;
|
||||||
|
|
||||||
|
// Particles made for specific teams and users
|
||||||
|
@UtilityClass
|
||||||
|
public class Presets {
|
||||||
|
|
||||||
|
private final Map<String, ParticlePreset> PRESETS = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
public final ParticlePreset TEAM_34_ECLIPSE = register(new ParticlePreset("team_34_eclipse", Material.ENDER_CHEST, "PARTICLE_EVENT_ENCHANTING", ParticleRequirement.specificTeam(34),
|
||||||
|
wing(ParticleType.END_ROD, 0.15, WingDesign.ECLIPSE, 80)));
|
||||||
|
public final ParticlePreset TEAM_210_PULSE_1 = register(new ParticlePreset("team_210_pulse_1", Material.GRAY_CANDLE, "PARTICLE_TEAM_PULSE_AURA_2", ParticleRequirement.specificTeam(210),
|
||||||
|
pulse(ParticleType.END_ROD, 1)));
|
||||||
|
public final ParticlePreset TEAM_210_PULSE_2 = register(new ParticlePreset("team_210_pulse_2", Material.WHITE_CANDLE, "PARTICLE_TEAM_PULSE_AURA_3", ParticleRequirement.specificTeam(210),
|
||||||
|
pulse(ParticleType.ENCHANT, 5)));
|
||||||
|
public final ParticlePreset TEAM_210_PULSE_LOGO = register(new ParticlePreset("team_210_pulse_logo", Material.ENDER_CHEST, "PARTICLE_TEAM_PULSE_LOGO", ParticleRequirement.specificTeam(210),
|
||||||
|
wing(ParticleType.END_ROD, 0.15, WingDesign.PL, 80)));
|
||||||
|
|
||||||
|
public final ParticlePreset PLAYER_9426_AURA = register(new ParticlePreset("player_9426_aura", Material.WHITE_CANDLE, "PARTICLE_PLAYER_HAYLIM_AURA", ParticleRequirement.specificPlayer(9426),
|
||||||
|
haylimRing(0, 0.01, false),
|
||||||
|
haylimRing(180, 0, true),
|
||||||
|
haylimRing(0, 0.01, true),
|
||||||
|
haylimRing(180, 0, false)));
|
||||||
|
|
||||||
|
public final ParticlePreset EASTER_PLAYER_1063 = register(new ParticlePreset("easter_player_1063", Material.ENDER_PEARL, "PARTICLE_PLAYER_1063", ParticleRequirement.easterEventSpecificPlayer(1063),
|
||||||
|
orbit(0, 0, true),
|
||||||
|
orbit(0, 180, false),
|
||||||
|
orbit(180, 0, false),
|
||||||
|
orbit(180, 180, true)));
|
||||||
|
public final ParticlePreset EASTER_PLAYER_10916 = register(new ParticlePreset("easter_player_10916", Material.TORCHFLOWER, "PARTICLE_PLAYER_10916_AURA", ParticleRequirement.easterEventSpecificPlayer(10916),
|
||||||
|
redRingTower()));
|
||||||
|
public final ParticlePreset EASTER_PLAYER_10697 = register(new ParticlePreset("easter_player_10697", Material.GUNPOWDER, "PARTICLE_PLAYER_10697_AURA", ParticleRequirement.easterEventSpecificPlayer(10697),
|
||||||
|
explosion(Visibility.ONLY_SELF, 20),
|
||||||
|
explosion(Visibility.ONLY_OTHERS, 2)));
|
||||||
|
public final ParticlePreset EASTER_PLAYER_64 = register(new ParticlePreset("easter_player_64", Material.PUFFERFISH_BUCKET, "PARTICLE_PLAYER_64", ParticleRequirement.easterEventSpecificPlayer(64),
|
||||||
|
wing(ParticleType.END_ROD, 0.15, WingDesign.PlompaEasterWings, 80)));
|
||||||
|
public final ParticlePreset EASTER_TEAM_210_AURA = register(new ParticlePreset("easter_team_210_aura", Material.RED_CANDLE, "PARTICLE_TEAM_PULSE_AURA", ParticleRequirement.easterEventSpecificTeam(210),
|
||||||
|
pulse(ParticleType.FLAME, 1)));
|
||||||
|
public final ParticlePreset EASTER_TEAM_210_HEART_BEAT = register(new ParticlePreset("easter_team_210_heart_beat", Material.APPLE, "PARTICLE_TEAM_PULSE_HEART_BEAT", ParticleRequirement.easterEventSpecificTeam(210),
|
||||||
|
particle(InternalDesigns.HEART_BEAT, ParticleType.FLAME, 0, 0, 1)
|
||||||
|
.with(InternalDesigns.HEART_BEAT_SPEED, 80.0)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS)
|
||||||
|
.with(GLIDING, Gliding.NOT_GLIDING)));
|
||||||
|
public final ParticlePreset EASTER_PLAYER_153 = register(new ParticlePreset("easter_player_153", Material.OAK_SIGN, "PARTICLE_PLAYER_153", ParticleRequirement.easterEventSpecificPlayer(153),
|
||||||
|
dustWing(Color.DARK_GRAY, WingDesign.ELY_E),
|
||||||
|
dustWing(Color.GREEN, WingDesign.ELY_L),
|
||||||
|
dustWing(Color.GRAY, WingDesign.ELY_Y)));
|
||||||
|
public final ParticlePreset EASTER_PLAYER_3266 = register(new ParticlePreset("easter_player_3266", Material.BREAD, "PARTICLE_PLAYER_3266", ParticleRequirement.easterEventSpecificPlayer(3266),
|
||||||
|
rainbowRing(0),
|
||||||
|
rainbowRing(120),
|
||||||
|
rainbowRing(240)));
|
||||||
|
public final ParticlePreset EASTER_TEAM_158_WINGS = register(new ParticlePreset("easter_team_158_wings", Material.ENCHANTED_BOOK, "PARTICLE_TEAM_158_1", ParticleRequirement.easterEventSpecificTeam(158),
|
||||||
|
wing(ParticleType.CRIT, 0.2, WingDesign.EV, 15)));
|
||||||
|
// The red half used to pass dust options to CRIT, which CRIT does not take; it is a plain CRIT now
|
||||||
|
public final ParticlePreset EASTER_TEAM_158_RINGS = register(new ParticlePreset("easter_team_158_rings", Material.ENDER_EYE, "PARTICLE_TEAM_158_2", ParticleRequirement.easterEventSpecificTeam(158),
|
||||||
|
team158Ring(particle(Designs.CIRCLE, ParticleType.CRIT, 0.01, 0.1, 1), 0),
|
||||||
|
team158Ring(particle(Designs.CIRCLE, ParticleType.DUST, 0.01, 0.1, 1).with(COLORS, List.of(Color.BLUE)), 180)));
|
||||||
|
|
||||||
|
private ParticlePreset register(ParticlePreset preset) {
|
||||||
|
if (PRESETS.put(preset.getId(), preset) != null) {
|
||||||
|
throw new IllegalStateException("Duplicate particle preset " + preset.getId());
|
||||||
|
}
|
||||||
|
return preset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticlePreset byId(String id) {
|
||||||
|
return PRESETS.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ParticlePreset> all() {
|
||||||
|
return List.copyOf(PRESETS.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry particle(ParticleDesign design, ParticleType type, double spread, double speed, int count) {
|
||||||
|
return new ParticleEntry(design)
|
||||||
|
.with(PARTICLE, type)
|
||||||
|
.with(SPREAD_X, spread)
|
||||||
|
.with(SPREAD_Y, spread)
|
||||||
|
.with(SPREAD_Z, spread)
|
||||||
|
.withUnchecked(SPEED, speed)
|
||||||
|
.with(COUNT, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry wing(ParticleType type, double size, WingDesign image, int interval) {
|
||||||
|
return particle(Designs.WING, type, 0, 0, 1)
|
||||||
|
.with(Designs.WING_SHAPE, Designs.wingShape(image))
|
||||||
|
.with(Designs.WING_SIZE, size)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS)
|
||||||
|
.with(INTERVAL, interval)
|
||||||
|
.with(GLIDING, Gliding.NOT_GLIDING)
|
||||||
|
.with(PLACEMENT, Placement.WORLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry dustWing(Color color, WingDesign image) {
|
||||||
|
return wing(ParticleType.DUST, 0.15, image, 10)
|
||||||
|
.with(COLORS, List.of(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry pulse(ParticleType type, int count) {
|
||||||
|
return particle(InternalDesigns.PULSE_SHIMMER, type, 0, 0, count)
|
||||||
|
.with(InternalDesigns.PULSE_SPEED, 80)
|
||||||
|
.with(InternalDesigns.PULSE_ROTATION, 2)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS)
|
||||||
|
.with(MOVEMENT, Movement.STANDING_STILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry haylimRing(double phase, double speed, boolean inverted) {
|
||||||
|
return particle(Designs.CIRCLE, ParticleType.ENCHANT, 0, speed, 5)
|
||||||
|
.with(Designs.CIRCLE_PHASE, phase)
|
||||||
|
.with(Designs.CIRCLE_RISE, 2.0)
|
||||||
|
.with(Designs.CIRCLE_RISE_SPEED, 40.0)
|
||||||
|
.with(Designs.CIRCLE_BOUNCE, false)
|
||||||
|
.with(Designs.CIRCLE_INVERTED, inverted)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS)
|
||||||
|
.with(MOVEMENT, Movement.STANDING_STILL)
|
||||||
|
.with(GLIDING, Gliding.NOT_GLIDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry orbit(double orbitPhase, double phase, boolean inverted) {
|
||||||
|
return particle(InternalDesigns.ORBIT, ParticleType.CRIT, 0, 0.01, 1)
|
||||||
|
.with(InternalDesigns.ORBIT_RADIUS, 1.5)
|
||||||
|
.with(InternalDesigns.ORBIT_SPEED, 1.0)
|
||||||
|
.with(InternalDesigns.ORBIT_PHASE, orbitPhase)
|
||||||
|
.with(Designs.CIRCLE_RADIUS, 0.5)
|
||||||
|
.with(Designs.CIRCLE_SPEED, 4.0)
|
||||||
|
.with(Designs.CIRCLE_PHASE, phase)
|
||||||
|
.with(Designs.CIRCLE_RISE, 2.0)
|
||||||
|
.with(Designs.CIRCLE_RISE_SPEED, 20.0)
|
||||||
|
.with(Designs.CIRCLE_BOUNCE, true)
|
||||||
|
.with(Designs.CIRCLE_INVERTED, inverted)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two opposite red rings on each of 19 heights, widening towards the top
|
||||||
|
private List<ParticleEntry> redRingTower() {
|
||||||
|
double[] radii = {0.5, 0.5, 0.55, 0.55, 0.65, 0.65, 0.7, 0.7, 0.7, 0.75, 0.75, 0.75, 0.8, 0.8, 0.8, 0.85, 0.85, 0.85, 0.9};
|
||||||
|
List<ParticleEntry> entries = new ArrayList<>();
|
||||||
|
for (int i = 0; i < radii.length; i++) {
|
||||||
|
for (double phase : new double[]{0, 180}) {
|
||||||
|
entries.add(particle(Designs.CIRCLE, ParticleType.DUST, 0, 0, 1)
|
||||||
|
.with(COLORS, List.of(Color.RED))
|
||||||
|
.with(Designs.CIRCLE_RADIUS, radii[i])
|
||||||
|
.with(Designs.CIRCLE_SPEED, 10.0)
|
||||||
|
.with(Designs.CIRCLE_PHASE, phase)
|
||||||
|
.with(OFFSET_Y, 0.2 + i * 0.1)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS)
|
||||||
|
.with(MOVEMENT, Movement.STANDING_STILL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry explosion(Visibility visibility, int interval) {
|
||||||
|
return particle(Designs.SIMPLE, ParticleType.EXPLOSION, 0, 0.01, 1)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS)
|
||||||
|
.with(SNEAK, Sneak.SNEAKING)
|
||||||
|
.with(VISIBILITY, visibility)
|
||||||
|
.with(INTERVAL, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry rainbowRing(double phase) {
|
||||||
|
return particle(Designs.CIRCLE, ParticleType.DUST, 0.01, 0.01, 5)
|
||||||
|
.with(COLORS, List.of(Color.CYAN, Color.BLUE, Color.MAGENTA.darker(), Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN))
|
||||||
|
.with(Designs.CIRCLE_RADIUS, 0.7)
|
||||||
|
.with(Designs.CIRCLE_SPEED, 0.5)
|
||||||
|
.with(Designs.CIRCLE_PHASE, phase)
|
||||||
|
.with(OFFSET_Y, 0.6)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS)
|
||||||
|
.with(GLIDING, Gliding.NOT_GLIDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEntry team158Ring(ParticleEntry entry, double phase) {
|
||||||
|
return entry
|
||||||
|
.with(Designs.CIRCLE_RADIUS, 0.7)
|
||||||
|
.with(Designs.CIRCLE_SPEED, 1.0)
|
||||||
|
.with(Designs.CIRCLE_PHASE, phase)
|
||||||
|
.with(Designs.CIRCLE_RISE, 2.0)
|
||||||
|
.with(Designs.CIRCLE_RISE_SPEED, 40.0)
|
||||||
|
.with(Designs.CIRCLE_BOUNCE, false)
|
||||||
|
.with(Designs.CIRCLE_INVERTED, false)
|
||||||
|
.with(TICK_MODE, TickMode.ALWAYS)
|
||||||
|
.with(GLIDING, Gliding.NOT_GLIDING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,8 +19,12 @@
|
|||||||
|
|
||||||
package de.steamwar.lobby.util;
|
package de.steamwar.lobby.util;
|
||||||
|
|
||||||
import de.steamwar.lobby.particle.ParticleEnum;
|
import de.steamwar.lobby.particle.CompiledSet;
|
||||||
import de.steamwar.lobby.particle.ParticleInventory;
|
import de.steamwar.lobby.particle.UnlockContext;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticlePreset;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlot;
|
||||||
|
import de.steamwar.lobby.particle.slot.ParticleSlots;
|
||||||
|
import de.steamwar.lobby.particle.slot.Presets;
|
||||||
import de.steamwar.sql.SteamwarUser;
|
import de.steamwar.sql.SteamwarUser;
|
||||||
import de.steamwar.sql.UserConfig;
|
import de.steamwar.sql.UserConfig;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@@ -31,21 +35,41 @@ import java.util.UUID;
|
|||||||
|
|
||||||
public class LobbyPlayer {
|
public class LobbyPlayer {
|
||||||
|
|
||||||
|
private static final String ACTIVE_KEY = "lobby-particle-active";
|
||||||
|
private static final String ACTIVE_SLOT_PREFIX = "slot:";
|
||||||
|
private static final String ACTIVE_PRESET_PREFIX = "preset:";
|
||||||
|
|
||||||
private static Map<UUID, LobbyPlayer> cache = new HashMap<>();
|
private static Map<UUID, LobbyPlayer> cache = new HashMap<>();
|
||||||
|
|
||||||
private ParticleEnum particle;
|
private final UUID uuid;
|
||||||
|
private final ParticleSlot[] slots = new ParticleSlot[ParticleSlots.COUNT];
|
||||||
|
private final boolean[] loadedSlots = new boolean[ParticleSlots.COUNT];
|
||||||
|
// At most one of activeSlot and activePreset is set
|
||||||
|
private int activeSlot = -1;
|
||||||
|
private ParticlePreset activePreset;
|
||||||
|
private CompiledSet compiledSet;
|
||||||
private int userId;
|
private int userId;
|
||||||
|
|
||||||
private boolean fly;
|
private boolean fly;
|
||||||
|
|
||||||
private LobbyPlayer(UUID uuid) {
|
private LobbyPlayer(UUID uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
userId = SteamwarUser.get(uuid).getId();
|
userId = SteamwarUser.get(uuid).getId();
|
||||||
cache.put(uuid, this);
|
cache.put(uuid, this);
|
||||||
particle = null;
|
String active = UserConfig.getConfig(userId, ACTIVE_KEY);
|
||||||
String saved = UserConfig.getConfig(userId, "lobby-particle");
|
if (active != null && active.startsWith(ACTIVE_SLOT_PREFIX)) {
|
||||||
if (saved != null) {
|
try {
|
||||||
particle = ParticleInventory.convertFromString(saved);
|
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 particle selected
|
||||||
|
}
|
||||||
|
} else if (active != null && active.startsWith(ACTIVE_PRESET_PREFIX)) {
|
||||||
|
activePreset = Presets.byId(active.substring(ACTIVE_PRESET_PREFIX.length()));
|
||||||
|
}
|
||||||
|
recompile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFlying() {
|
public boolean isFlying() {
|
||||||
@@ -56,18 +80,92 @@ public class LobbyPlayer {
|
|||||||
this.fly = fly;
|
this.fly = fly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParticleEnum getParticle() {
|
public ParticleSlot getSlot(int index) {
|
||||||
return particle;
|
if (!loadedSlots[index]) {
|
||||||
|
slots[index] = ParticleSlots.load(userId, index);
|
||||||
|
loadedSlots[index] = true;
|
||||||
|
}
|
||||||
|
return slots[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParticle(ParticleEnum particle) {
|
// Null deletes the slot
|
||||||
if (particle == null) {
|
public void setSlot(int index, ParticleSlot slot) {
|
||||||
UserConfig.removePlayerConfig(userId, "lobby-particle");
|
ParticleSlots.save(userId, index, slot);
|
||||||
} else {
|
slots[index] = slot;
|
||||||
String saved = ParticleInventory.convertToString(particle);
|
loadedSlots[index] = true;
|
||||||
UserConfig.updatePlayerConfig(userId, "lobby-particle", saved);
|
if (index == activeSlot) {
|
||||||
|
recompile();
|
||||||
}
|
}
|
||||||
this.particle = particle;
|
}
|
||||||
|
|
||||||
|
public int getActiveSlot() {
|
||||||
|
return activeSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticlePreset getActivePreset() {
|
||||||
|
return activePreset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
activeSlot = index;
|
||||||
|
activePreset = null;
|
||||||
|
saveSelection();
|
||||||
|
recompile();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns false when the preset is locked for this player
|
||||||
|
public boolean selectPreset(ParticlePreset preset) {
|
||||||
|
if (!preset.getRequirement().test(unlockContext())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
activeSlot = -1;
|
||||||
|
activePreset = preset;
|
||||||
|
saveSelection();
|
||||||
|
recompile();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deselect() {
|
||||||
|
activeSlot = -1;
|
||||||
|
activePreset = null;
|
||||||
|
saveSelection();
|
||||||
|
recompile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompiledSet getCompiledSet() {
|
||||||
|
return compiledSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A locked selection shows nothing but stays selected, so it comes back once the rank does
|
||||||
|
public void recompile() {
|
||||||
|
UnlockContext context = unlockContext();
|
||||||
|
if (activeSlot >= 0) {
|
||||||
|
ParticleSlot slot = getSlot(activeSlot);
|
||||||
|
compiledSet = slot != null && ParticleSlots.requirement(activeSlot).test(context) ? CompiledSet.of(slot, context) : null;
|
||||||
|
} else if (activePreset != null) {
|
||||||
|
compiledSet = activePreset.getRequirement().test(context) ? CompiledSet.of(activePreset, context) : null;
|
||||||
|
} else {
|
||||||
|
compiledSet = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveSelection() {
|
||||||
|
if (activeSlot >= 0) {
|
||||||
|
UserConfig.updatePlayerConfig(userId, ACTIVE_KEY, ACTIVE_SLOT_PREFIX + (activeSlot + 1));
|
||||||
|
} else if (activePreset != null) {
|
||||||
|
UserConfig.updatePlayerConfig(userId, ACTIVE_KEY, ACTIVE_PRESET_PREFIX + activePreset.getId());
|
||||||
|
} else {
|
||||||
|
UserConfig.removePlayerConfig(userId, ACTIVE_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnlockContext unlockContext() {
|
||||||
|
return new UnlockContext(SteamwarUser.get(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LobbyPlayer getLobbyPlayer(UUID uuid) {
|
public static LobbyPlayer getLobbyPlayer(UUID uuid) {
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ public class SchematicSelector {
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SchematicNode> getSchematicList(SchematicNode parent) {
|
protected List<SchematicNode> getSchematicList(SchematicNode parent) {
|
||||||
List<SchematicNode> nodes = new ArrayList<>();
|
List<SchematicNode> nodes = new ArrayList<>();
|
||||||
switch (target.target) {
|
switch (target.target) {
|
||||||
case DIRECTORY:
|
case DIRECTORY:
|
||||||
|
|||||||
@@ -156,8 +156,7 @@ public class CheckCommand extends SWCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SchematicNode schem = SchematicNode.getSchematicNode(Integer.parseInt(schemID));
|
SchematicNode schem = SchematicNode.getSchematicNode(Integer.parseInt(schemID));
|
||||||
GameModeConfig<String, String> gameModeConfig = ArenaMode.getBySchemType(schem.getSchemtype());
|
if (!mayCheck(sender.user(), schem)) {
|
||||||
if (!gameModeConfig.Checkers.isEmpty() && !gameModeConfig.Checkers.contains(sender.user().getId())) {
|
|
||||||
sender.system("CHECK_SCHEMATIC_EVENT");
|
sender.system("CHECK_SCHEMATIC_EVENT");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -313,60 +312,6 @@ public class CheckCommand extends SWCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void accept() {
|
private void accept() {
|
||||||
// TODO: This Code is only for the WGS and because YoyoNow is not available this can be removed or changed after the WGS!
|
|
||||||
if (schematic.getSchemtype().toDB().equals("cwargearseason26")) {
|
|
||||||
int userId = schematic.getOwner();
|
|
||||||
SteamwarUser user = SteamwarUser.byId(userId);
|
|
||||||
int teamId = user.getTeam();
|
|
||||||
|
|
||||||
SchematicNode teamFolder = SchematicNode.getSchematicNodeInNode(172325)
|
|
||||||
.stream()
|
|
||||||
.filter(schematicNode -> schematicNode.getName().startsWith(teamId + "_"))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
if (teamFolder == null) {
|
|
||||||
internalAccept();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any added players from the schematic in the folder
|
|
||||||
for (SchematicNode schematicNode : SchematicNode.getSchematicNodeInNode(teamFolder.getNodeId())) {
|
|
||||||
for (NodeMember nodeMember : NodeMember.getNodeMembers(schematicNode.getNodeId())) {
|
|
||||||
nodeMember.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy Schem into team folder of -1 user
|
|
||||||
String name = DateTimeFormatter.ofPattern("yyyy.MM.dd_HH:mm:ss").format(schematic.getLastUpdate().toLocalDateTime());
|
|
||||||
NodeData data = NodeData.getLatest(schematic);
|
|
||||||
SchematicNode node = SchematicNode.createSchematic(-1, name, teamFolder.getNodeId());
|
|
||||||
NodeData.saveFromStream(node, data.schemData(false), data.getNodeFormat());
|
|
||||||
|
|
||||||
// Accept the team folder schematic and set other to Normal as well as adding the original owner on the schematic
|
|
||||||
node.setSchemtype(GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type);
|
|
||||||
NodeMember.createNodeMember(node.getNodeId(), schematic.getOwner());
|
|
||||||
for (NodeMember nodeMember : NodeMember.getNodeMembers(schematic.getId())) {
|
|
||||||
NodeMember.createNodeMember(node.getNodeId(), nodeMember.getMember());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conclude by setting send in schematic to normal and broadcast
|
|
||||||
concludeCheckSession("freigegeben", SchematicType.Normal, () -> {
|
|
||||||
Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID());
|
|
||||||
owner.withPlayerOrOffline(
|
|
||||||
player -> owner.system("CHECK_ACCEPTED", schematic.getSchemtype().name(), schematic.getName()),
|
|
||||||
() -> DiscordAlert.send(owner, Color.GREEN, new Message("DC_TITLE_SCHEMINFO"), new Message("DC_SCHEM_ACCEPT", schematic.getName()), true)
|
|
||||||
);
|
|
||||||
notifyTeam(new Message("CHECK_ACCEPTED_TEAM", schematic.getName(), owner.user().getUserName()));
|
|
||||||
|
|
||||||
return owner.getPlayer() != null;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
internalAccept();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void internalAccept() {
|
|
||||||
concludeCheckSession("freigegeben", GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type, () -> {
|
concludeCheckSession("freigegeben", GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type, () -> {
|
||||||
Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID());
|
Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID());
|
||||||
owner.withPlayerOrOffline(
|
owner.withPlayerOrOffline(
|
||||||
|
|||||||
Reference in New Issue
Block a user