9
0
mirror of https://github.com/Xiao-MoMi/Custom-Nameplates.git synced 2025-12-29 11:49:14 +00:00
This commit is contained in:
XiaoMoMi
2024-10-06 01:25:48 +08:00
parent 7209b9772b
commit 844e53ff1a
178 changed files with 3460 additions and 546 deletions

View File

@@ -22,6 +22,12 @@ dependencies {
compileOnly("org.mongodb:mongodb-driver-sync:${rootProject.properties["mongodb_driver_version"]}")
compileOnly("com.zaxxer:HikariCP:${rootProject.properties["hikari_version"]}")
compileOnly("redis.clients:jedis:${rootProject.properties["jedis_version"]}")
// Cache
compileOnly("com.github.ben-manes.caffeine:caffeine:${rootProject.properties["caffeine_version"]}")
// COMMONS IO
compileOnly("commons-io:commons-io:${rootProject.properties["commons_io_version"]}")
// FOP
compileOnly("org.apache.pdfbox:fontbox:${rootProject.properties["fontbox_version"]}")
}
tasks {

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.actionbar;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.CarouselText;
import net.momirealms.customnameplates.api.feature.JoinQuitListener;
import net.momirealms.customnameplates.api.feature.actionbar.ActionBarConfig;
import net.momirealms.customnameplates.api.feature.actionbar.ActionBarManager;
import net.momirealms.customnameplates.api.requirement.Requirement;
import net.momirealms.customnameplates.api.util.ConfigUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class ActionBarManagerImpl implements ActionBarManager, JoinQuitListener {
protected final CustomNameplates plugin;
private final LinkedHashMap<String, ActionBarConfig> configs = new LinkedHashMap<>();
private final ConcurrentHashMap<UUID, ActionBarSender> senders = new ConcurrentHashMap<>();
private ActionBarConfig[] configArray = new ActionBarConfig[0];
public ActionBarManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void load() {
// ignore disabled modules
if (!ConfigManager.actionbarModule()) return;
this.loadConfig();
this.resetArray();
for (CNPlayer online : plugin.getOnlinePlayers()) {
onPlayerJoin(online);
}
}
@Override
public void unload() {
for (ActionBarSender sender : senders.values()) {
sender.destroy();
}
this.senders.clear();
this.configs.clear();
this.resetArray();
}
@Override
public void refreshConditions() {
for (ActionBarSender sender : senders.values()) {
sender.onConditionTimerCheck();
}
}
@Override
public void checkHeartBeats() {
for (ActionBarSender sender : senders.values()) {
sender.onHeartBeatTimer();
}
}
private void resetArray() {
configArray = configs.values().toArray(new ActionBarConfig[0]);
}
@Override
public ActionBarConfig configById(String name) {
return configs.get(name);
}
@Override
public ActionBarConfig[] actionBarConfigs() {
return configArray;
}
public void handleActionBarPacket(CNPlayer player, String miniMessage) {
ActionBarSender sender = senders.get(player.uuid());
if (sender != null) {
sender.externalActionBar(miniMessage);
}
}
@Override
public @Nullable String getExternalActionBar(CNPlayer player) {
ActionBarSender sender = senders.get(player.uuid());
if (sender != null) {
return sender.externalActionBar();
}
return null;
}
@Override
public void onPlayerJoin(CNPlayer player) {
if (!ConfigManager.actionbarModule()) return;
plugin.getScheduler().asyncLater(() -> {
if (!player.isOnline()) return;
ActionBarSender sender = new ActionBarSender(this, player);
ActionBarSender previous = senders.put(player.uuid(), sender);
if (previous != null) {
previous.destroy();
}
}, ConfigManager.delaySend() * 50L, TimeUnit.MILLISECONDS);
}
@Override
public void onPlayerQuit(CNPlayer player) {
ActionBarSender sender = senders.remove(player.uuid());
if (sender != null) {
sender.destroy();
}
}
private void loadConfig() {
plugin.getConfigManager().saveResource("configs" + File.separator + "actionbar.yml");
YamlDocument document = plugin.getConfigManager().loadData(new File(plugin.getDataDirectory().toFile(), "configs" + File.separator + "actionbar.yml"));
for (Map.Entry<String, Object> entry : document.getStringRouteMappedValues(false).entrySet()) {
if (!(entry.getValue() instanceof Section section))
return;
this.configs.put(entry.getKey(),
ActionBarConfig.builder()
.id(entry.getKey())
.requirement(plugin.getRequirementManager().parseRequirements(section.getSection("conditions")))
.carouselText(
section.contains("text") ?
new CarouselText[]{new CarouselText(-1, new Requirement[0], section.getString("text"), false)} :
ConfigUtils.carouselTexts(section.getSection("text-display-order"))
)
.build()
);
}
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.actionbar;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.CarouselText;
import net.momirealms.customnameplates.api.feature.DynamicText;
import net.momirealms.customnameplates.api.feature.Feature;
import net.momirealms.customnameplates.api.feature.actionbar.ActionBarConfig;
import net.momirealms.customnameplates.api.feature.actionbar.ActionBarManager;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.api.placeholder.Placeholder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static java.util.Objects.requireNonNull;
public class ActionBarSender implements Feature {
private final ActionBarManager manager;
private final CNPlayer owner;
private long lastUpdateTime;
private ActionBarConfig currentConfig;
private int order;
private int timeLeft;
private DynamicText currentActionBar;
private String externalActionBar;
private long externalExpireTime;
private String latestContent;
private boolean textChangeFlag = false;
@Override
public String name() {
return "ActionBarSender";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ActionBarSender sender = (ActionBarSender) o;
return owner == sender.owner;
}
@Override
public int hashCode() {
return owner.name().hashCode();
}
public ActionBarSender(ActionBarManager manager, CNPlayer owner) {
this.owner = owner;
this.manager = manager;
this.owner.addFeature(this);
this.onConditionTimerCheck();
}
public void onConditionTimerCheck() {
ActionBarConfig[] configs = manager.actionBarConfigs();
outer: {
for (ActionBarConfig config : configs) {
if (owner.isMet(config.requirements())) {
if (config != currentConfig) {
currentConfig = config;
order = config.carouselTexts().length - 1;
timeLeft = 0;
}
break outer;
}
}
// no actionbar available
currentConfig = null;
currentActionBar = null;
latestContent = null;
}
if (currentConfig != null) {
if (timeLeft > 0)
timeLeft--;
if (timeLeft == 0) {
int triedTimes = 0;
do {
if (triedTimes == currentConfig.carouselTexts().length) {
timeLeft = 20;
latestContent = null;
CustomNameplates.getInstance().getPluginLogger().warn("No text in order is available for player " + owner.name() + ". Please check your actionbar's conditions.");
return;
}
order++;
if (order >= currentConfig.carouselTexts().length) {
order = 0;
}
triedTimes++;
} while (
!owner.isMet(currentConfig.carouselTexts()[order].requirements())
);
CarouselText carouselText = currentConfig.carouselTexts()[order];
timeLeft = carouselText.duration();
currentActionBar = carouselText.preParsedDynamicText().fastCreate(owner);
if (carouselText.updateOnDisplay()) {
owner.forceUpdate(currentActionBar.placeholders(), Collections.emptySet());
}
textChangeFlag = true;
}
}
}
public void onHeartBeatTimer() {
if (textChangeFlag) {
refresh();
sendLatestActionBar();
return;
}
if (shouldSendBeatPacket()) {
sendLatestActionBar();
}
}
public void refresh() {
latestContent = this.currentActionBar.render(owner);
textChangeFlag = false;
}
public void sendLatestActionBar() {
if (latestContent != null) {
updateLastUpdateTime();
Object packet = CustomNameplates.getInstance().getPlatform().setActionBarTextPacket(AdventureHelper.miniMessageToMinecraftComponent(latestContent, "np", "ab"));
CustomNameplates.getInstance().getPacketSender().sendPacket(owner, packet);
}
}
public void destroy() {
this.owner.removeFeature(this);
}
@Override
public Set<Placeholder> activePlaceholders() {
if (currentActionBar == null) return Collections.emptySet();
return currentActionBar.placeholders();
}
@Override
public Set<Placeholder> allPlaceholders() {
HashSet<Placeholder> placeholders = new HashSet<>();
for (ActionBarConfig config : manager.actionBarConfigs()) {
for (CarouselText text : config.carouselTexts()) {
placeholders.addAll(text.preParsedDynamicText().placeholders());
}
}
return placeholders;
}
@Override
public void notifyPlaceholderUpdates(CNPlayer player, boolean force) {
refresh();
sendLatestActionBar();
}
public void updateLastUpdateTime() {
lastUpdateTime = System.currentTimeMillis();
}
public boolean shouldSendBeatPacket() {
return System.currentTimeMillis() - lastUpdateTime > 1500;
}
@Nullable
public String externalActionBar() {
if (System.currentTimeMillis() > externalExpireTime) {
externalActionBar = null;
}
return externalActionBar;
}
public void externalActionBar(@NotNull String externalActionBar) {
requireNonNull(externalActionBar);
this.externalActionBar = externalActionBar;
this.externalExpireTime = System.currentTimeMillis() + 3000;
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.advance;
public record SizeOverrides(int startCodePoint, int endCodePoint, int left, int right) {
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.background;
import dev.dejvokep.boostedyaml.YamlDocument;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.ConfiguredCharacter;
import net.momirealms.customnameplates.api.feature.background.Background;
import net.momirealms.customnameplates.api.feature.background.BackgroundManager;
import net.momirealms.customnameplates.api.util.ConfigUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
public class BackgroundManagerImpl implements BackgroundManager {
private final CustomNameplates plugin;
private final Map<String, Background> backgrounds = new HashMap<>();
public BackgroundManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void unload() {
this.backgrounds.clear();
}
@Override
public void load() {
this.loadConfigs();
}
@Nullable
@Override
public Background backgroundById(String id) {
return this.backgrounds.get(id);
}
@Override
public Collection<Background> getBackgrounds() {
return new HashSet<>(backgrounds.values());
}
private void loadConfigs() {
File backgroundFolder = new File(plugin.getDataDirectory().toFile(), "contents" + File.separator + "backgrounds");
if (!backgroundFolder.exists() && backgroundFolder.mkdirs()) {
saveDefaultBackgrounds();
}
List<File> configFiles = ConfigUtils.getConfigsDeeply(backgroundFolder);
for (File configFile : configFiles) {
YamlDocument config = plugin.getConfigManager().loadData(configFile);
String id = configFile.getName().substring(0, configFile.getName().lastIndexOf("."));
int height = config.getInt("middle.height", 14);
int ascent = config.getInt("middle.ascent", 8);
Background background = Background.builder()
.id(id)
.left(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("left.image") + ".png"),
config.getInt("left.ascent", 8),
config.getInt("left.height", 14)
))
.right(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("right.image") + ".png"),
config.getInt("right.ascent", 8),
config.getInt("right.height", 14)
))
.width_1(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.1") + ".png"),
ascent, height
))
.width_2(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.2") + ".png"),
ascent, height
))
.width_4(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.4") + ".png"),
ascent, height
))
.width_8(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.8") + ".png"),
ascent, height
))
.width_16(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.16") + ".png"),
ascent, height
))
.width_32(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.32") + ".png"),
ascent, height
))
.width_64(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.64") + ".png"),
ascent, height
))
.width_128(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.128") + ".png"),
ascent, height
))
.build();
this.backgrounds.put(id, background);
}
}
private void saveDefaultBackgrounds() {
String[] bg_list = new String[]{"b0", "b1", "b2", "b4", "b8", "b16","b32","b64","b128"};
for (String bg : bg_list) {
plugin.getConfigManager().saveResource("contents" + File.separator + "backgrounds" + File.separator + bg + ".png");
}
String[] config_list = new String[]{"bedrock_1", "bedrock_2"};
for (String config : config_list) {
plugin.getConfigManager().saveResource("contents" + File.separator + "backgrounds" + File.separator + config + ".yml");
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.bossbar;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.feature.bossbar.BossBarConfig;
import net.momirealms.customnameplates.api.feature.bossbar.BossBarManager;
import java.util.ArrayList;
import java.util.List;
public class BossBarDisplayController {
private final CNPlayer owner;
private final BossBarManager manager;
private final BossBarSender[] senders;
public BossBarDisplayController(BossBarManager manager, CNPlayer owner) {
this.owner = owner;
this.manager = manager;
List<BossBarSender> senderList = new ArrayList<>();
for (BossBarConfig config : manager.bossBarConfigs()) {
BossBarSender sender = new BossBarSender(owner, config);
senderList.add(sender);
this.owner.addFeature(sender);
}
this.senders = senderList.toArray(new BossBarSender[0]);
}
public void onTick() {
int size = senders.length;
int[] states = new int[size];
int index = size;
for (int i = 0; i < size; i++) {
BossBarSender sender = senders[i];
boolean canShow = sender.checkConditions();
if (canShow) {
if (!sender.isShown()) {
states[i] = 1;
sender.init();
sender.tick();
if (index == size) {
index = i;
}
} else {
states[i] = 2;
if (i > index) {
sender.hide();
}
sender.tick();
}
} else {
if (sender.isShown()) {
sender.hide();
}
states[i] = 0;
}
}
if (index != size) {
for (int i = index; i < size; i++) {
if (states[i] != 0) {
senders[i].show();
}
}
}
}
public void destroy() {
for (BossBarSender sender : this.senders) {
sender.hide();
this.owner.removeFeature(sender);
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.bossbar;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.CarouselText;
import net.momirealms.customnameplates.api.feature.JoinQuitListener;
import net.momirealms.customnameplates.api.feature.bossbar.BossBar;
import net.momirealms.customnameplates.api.feature.bossbar.BossBarConfig;
import net.momirealms.customnameplates.api.feature.bossbar.BossBarManager;
import net.momirealms.customnameplates.api.requirement.Requirement;
import net.momirealms.customnameplates.api.util.ConfigUtils;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class BossBarManagerImpl implements BossBarManager, JoinQuitListener {
private final CustomNameplates plugin;
private final LinkedHashMap<String, BossBarConfig> configs = new LinkedHashMap<>();
private final ConcurrentHashMap<UUID, BossBarDisplayController> senders = new ConcurrentHashMap<>();
private BossBarConfig[] configArray = new BossBarConfig[0];
public BossBarManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void load() {
// ignore disabled modules
if (!ConfigManager.bossbarModule()) return;
this.loadConfig();
this.resetArray();
for (CNPlayer online : plugin.getOnlinePlayers()) {
onPlayerJoin(online);
}
}
@Override
public void unload() {
for (BossBarDisplayController sender : senders.values()) {
sender.destroy();
}
this.senders.clear();
this.configs.clear();
this.resetArray();
}
@Override
public void onTick() {
for (BossBarDisplayController sender : senders.values()) {
sender.onTick();
}
}
private void resetArray() {
configArray = configs.values().toArray(new BossBarConfig[0]);
}
@Override
public void onPlayerJoin(CNPlayer player) {
if (!ConfigManager.bossbarModule()) return;
plugin.getScheduler().asyncLater(() -> {
if (!player.isOnline()) return;
BossBarDisplayController sender = new BossBarDisplayController(this, player);
BossBarDisplayController previous = senders.put(player.uuid(), sender);
if (previous != null) {
previous.destroy();
}
}, ConfigManager.delaySend() * 50L, TimeUnit.MILLISECONDS);
}
@Override
public void onPlayerQuit(CNPlayer player) {
BossBarDisplayController sender = senders.remove(player.uuid());
if (sender != null) {
sender.destroy();
}
}
@Override
public BossBarConfig configById(String name) {
return configs.get(name);
}
@Override
public BossBarConfig[] bossBarConfigs() {
return configArray;
}
private void loadConfig() {
plugin.getConfigManager().saveResource("configs" + File.separator + "bossbar.yml");
YamlDocument document = plugin.getConfigManager().loadData(new File(plugin.getDataDirectory().toFile(), "configs" + File.separator + "bossbar.yml"));
for (Map.Entry<String, Object> entry : document.getStringRouteMappedValues(false).entrySet()) {
if (!(entry.getValue() instanceof Section section))
return;
this.configs.put(entry.getKey(),
BossBarConfig.builder()
.id(entry.getKey())
.overlay(BossBar.Overlay.valueOf(section.getString("overlay", "PROGRESS").toUpperCase(Locale.ENGLISH)))
.color(BossBar.Color.valueOf(section.getString("color", "YELLOW").toUpperCase(Locale.ENGLISH)))
.requirement(plugin.getRequirementManager().parseRequirements(section.getSection("conditions")))
.carouselText(
section.contains("text") ?
new CarouselText[]{new CarouselText(-1, new Requirement[0], section.getString("text"), false)} :
ConfigUtils.carouselTexts(section.getSection("text-display-order"))
)
.progress(section.getFloat("progress", 0f))
.build()
);
}
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.bossbar;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.CarouselText;
import net.momirealms.customnameplates.api.feature.DynamicText;
import net.momirealms.customnameplates.api.feature.Feature;
import net.momirealms.customnameplates.api.feature.bossbar.BossBar;
import net.momirealms.customnameplates.api.feature.bossbar.BossBarConfig;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.api.placeholder.Placeholder;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class BossBarSender implements Feature, BossBar {
private final UUID uuid = UUID.randomUUID();
private final CNPlayer owner;
private boolean isShown = false;
private final BossBarConfig config;
private int order;
private int timeLeft;
private DynamicText currentBossBar;
private String latestContent;
public BossBarSender(CNPlayer owner, BossBarConfig config) {
this.owner = owner;
this.config = config;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BossBarSender that = (BossBarSender) o;
return owner == that.owner && uuid == that.uuid;
}
@Override
public int hashCode() {
return uuid.hashCode();
}
@Override
public UUID uuid() {
return uuid;
}
@Override
public String name() {
return "BossBarSender";
}
@Override
public float progress() {
return config.progress();
}
@Override
public Color color() {
return config.color();
}
@Override
public Overlay overlay() {
return config.overlay();
}
public boolean checkConditions() {
return owner.isMet(config.requirements());
}
public void init() {
order = config.carouselTexts().length - 1;
timeLeft = 0;
}
public void tick() {
if (timeLeft > 0)
timeLeft--;
if (timeLeft == 0) {
int triedTimes = 0;
do {
if (triedTimes == config.carouselTexts().length) {
timeLeft = 20;
currentBossBar = null;
CustomNameplates.getInstance().getPluginLogger().warn("No text in order is available for player " + owner.name() + ". Please check your bossbar's conditions.");
return;
}
order++;
if (order >= config.carouselTexts().length) {
order = 0;
}
triedTimes++;
} while (
!owner.isMet(config.carouselTexts()[order].requirements())
);
CarouselText carouselText = config.carouselTexts()[order];
timeLeft = carouselText.duration();
currentBossBar = carouselText.preParsedDynamicText().fastCreate(owner);
if (carouselText.updateOnDisplay()) {
owner.forceUpdate(currentBossBar.placeholders(), Collections.emptySet());
}
refresh();
if (isShown()) {
sendLatestBossBarName();
}
}
}
public void hide() {
isShown = false;
Object packet = CustomNameplates.getInstance().getPlatform().removeBossBarPacket(uuid);
CustomNameplates.getInstance().getPacketSender().sendPacket(owner, packet);
}
public void show() {
isShown = true;
if (latestContent == null) {
refresh();
}
Object packet = CustomNameplates.getInstance().getPlatform().createBossBarPacket(uuid, AdventureHelper.miniMessageToMinecraftComponent(latestContent), progress(), overlay(), color());
CustomNameplates.getInstance().getPacketSender().sendPacket(owner, packet);
}
public boolean isShown() {
return isShown;
}
@Override
public Set<Placeholder> activePlaceholders() {
if (currentBossBar == null || !isShown()) return Collections.emptySet();
return currentBossBar.placeholders();
}
@Override
public Set<Placeholder> allPlaceholders() {
HashSet<Placeholder> placeholders = new HashSet<>();
for (CarouselText text : config.carouselTexts()) {
placeholders.addAll(text.preParsedDynamicText().placeholders());
}
return placeholders;
}
@Override
public void notifyPlaceholderUpdates(CNPlayer p1, boolean force) {
refresh();
if (isShown()) {
sendLatestBossBarName();
}
}
public void refresh() {
latestContent = this.currentBossBar.render(owner);
}
public void sendLatestBossBarName() {
if (latestContent != null) {
Object packet = CustomNameplates.getInstance().getPlatform().updateBossBarNamePacket(uuid, AdventureHelper.miniMessageToMinecraftComponent(latestContent));
CustomNameplates.getInstance().getPacketSender().sendPacket(owner, packet);
}
}
}

View File

@@ -0,0 +1,295 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.bubble;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.ChatListener;
import net.momirealms.customnameplates.api.feature.ConfiguredCharacter;
import net.momirealms.customnameplates.api.feature.bubble.Bubble;
import net.momirealms.customnameplates.api.feature.bubble.BubbleConfig;
import net.momirealms.customnameplates.api.feature.bubble.BubbleManager;
import net.momirealms.customnameplates.api.feature.bubble.ChannelMode;
import net.momirealms.customnameplates.api.feature.tag.TagRenderer;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.api.requirement.Requirement;
import net.momirealms.customnameplates.api.util.ConfigUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class BubbleManagerImpl implements BubbleManager, ChatListener {
private final CustomNameplates plugin;
private final Map<String, Bubble> bubbles = new HashMap<>();
private Requirement[] sendBubbleRequirements;
private Requirement[] viewBubbleRequirements;
private String defaultBubbleId;
private double yOffset;
private int stayDuration;
private int appearDuration;
private int disappearDuration;
private float viewRange;
private Set<String> blacklistChannels;
private ChannelMode channelMode;
private final HashMap<String, BubbleConfig> bubbleConfigs = new HashMap<>();
public BubbleManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void unload() {
this.bubbles.clear();
this.bubbleConfigs.clear();
}
@Override
public void load() {
if (!ConfigManager.bubbleModule()) return;
this.loadConfigs();
this.loadConfig();
}
@Nullable
@Override
public Bubble bubbleById(String id) {
return this.bubbles.get(id);
}
@Override
public @Nullable BubbleConfig bubbleConfigById(String id) {
return this.bubbleConfigs.get(id);
}
@Override
public Collection<Bubble> bubbles() {
return new HashSet<>(bubbles.values());
}
@Override
public Collection<BubbleConfig> bubbleConfigs() {
return new HashSet<>(bubbleConfigs.values());
}
@Override
public boolean hasBubble(CNPlayer player, String id) {
if (!this.bubbleConfigs.containsKey(id)) {
return false;
}
return player.hasPermission("bubbles.equip." + id);
}
@Override
public Collection<BubbleConfig> availableBubbles(CNPlayer player) {
ArrayList<BubbleConfig> available = new ArrayList<>();
for (BubbleConfig bubble : bubbleConfigs.values()) {
if (player.hasPermission("bubbles.equip." + bubble.id())) {
available.add(bubble);
}
}
return available;
}
@Override
public Set<String> blacklistChannels() {
return blacklistChannels;
}
@Override
public String defaultBubbleId() {
return defaultBubbleId;
}
@Override
public Requirement[] sendBubbleRequirements() {
return sendBubbleRequirements;
}
@Override
public Requirement[] viewBubbleRequirements() {
return viewBubbleRequirements;
}
@Override
public double verticalOffset() {
return yOffset;
}
@Override
public int stayDuration() {
return stayDuration;
}
@Override
public int appearDuration() {
return appearDuration;
}
@Override
public int disappearDuration() {
return disappearDuration;
}
@Override
public float viewRange() {
return viewRange;
}
@Override
public ChannelMode channelMode() {
return channelMode;
}
private void loadConfig() {
plugin.getConfigManager().saveResource("configs" + File.separator + "bubble.yml");
YamlDocument document = plugin.getConfigManager().loadData(new File(plugin.getDataDirectory().toFile(), "configs" + File.separator + "bubble.yml"));
sendBubbleRequirements = plugin.getRequirementManager().parseRequirements(document.getSection("sender-requirements"));
viewBubbleRequirements = plugin.getRequirementManager().parseRequirements(document.getSection("viewer-requirements"));
defaultBubbleId = document.getString("default-bubble", "chat");
yOffset = document.getDouble("y-offset", 0.2);
stayDuration = document.getInt("stay-duration", 160);
appearDuration = document.getInt("appear-duration", 20);
disappearDuration = document.getInt("disappear-duration", 10);
viewRange = document.getFloat("view-range", 0.5f);
blacklistChannels = new HashSet<>(document.getStringList("blacklist-channels"));
channelMode = ChannelMode.valueOf(document.getString("channel-mode", "ALL").toUpperCase(Locale.ENGLISH));
Section bubbleSettings = document.getSection("bubble-settings");
if (bubbleSettings != null) {
for (Map.Entry<String, Object> entry : bubbleSettings.getStringRouteMappedValues(false).entrySet()) {
String key = entry.getKey();
if (entry.getValue() instanceof Section inner) {
int maxLines = inner.getInt("max-lines", 1);
Bubble[] bubbleArray = new Bubble[maxLines];
for (int i = 0; i < maxLines; i++) {
bubbleArray[i] = bubbleById(inner.getString("lines." + (i+1)));
}
this.bubbleConfigs.put(key, BubbleConfig.builder()
.id(key)
.maxLines(maxLines)
.bubbles(bubbleArray)
.displayName(inner.getString("display-name", key))
.lineWidth(inner.getInt("line-width", 100))
.backgroundColor(ConfigUtils.argb(inner.getString("background-color", "0,0,0,0")))
.textPrefix(inner.getString("text-prefix", ""))
.textSuffix(inner.getString("text-suffix", ""))
.scale(ConfigUtils.vector3(inner.getString("scale", "1,1,1")))
.build());
}
}
}
}
private void loadConfigs() {
File bubbleFolder = new File(plugin.getDataDirectory().toFile(), "contents" + File.separator + "bubbles");
if (!bubbleFolder.exists() && bubbleFolder.mkdirs()) {
saveDefaultBubbles();
}
List<File> configFiles = ConfigUtils.getConfigsDeeply(bubbleFolder);
for (File configFile : configFiles) {
YamlDocument config = plugin.getConfigManager().loadData(configFile);
String id = configFile.getName().substring(0, configFile.getName().lastIndexOf("."));
Bubble bubble = Bubble.builder()
.id(id)
.left(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("left.image") + ".png"),
config.getInt("left.ascent", 12),
config.getInt("left.height", 16)
))
.middle(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.image") + ".png"),
config.getInt("middle.ascent", 12),
config.getInt("middle.height", 16)
))
.tail(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("tail.image") + ".png"),
config.getInt("tail.ascent", 12),
config.getInt("tail.height", 16)
))
.right(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("right.image") + ".png"),
config.getInt("right.ascent", 12),
config.getInt("right.height", 16)
))
.build();
this.bubbles.put(id, bubble);
}
}
private void saveDefaultBubbles() {
String[] png_list = new String[]{"chat_1", "chat_2", "chat_3"};
String[] part_list = new String[]{"_left.png", "_middle.png", "_right.png", "_tail.png", ".yml"};
for (String name : png_list) {
for (String part : part_list) {
plugin.getConfigManager().saveResource("contents" + File.separator + "bubbles" + File.separatorChar + name + part);
}
}
}
@Override
public void onPlayerChat(CNPlayer player, String message, String channel) {
if (!ConfigManager.bubbleModule()) return;
// ignore blacklist channels
if (blacklistChannels().contains(channel)) return;
// check requirements
if (!player.isMet(sendBubbleRequirements())) return;
String equippedBubble = player.equippedBubble();
if (equippedBubble.equals("none")) equippedBubble = defaultBubbleId;
BubbleConfig config = bubbleConfigs.get(equippedBubble);
if (config == null) {
return;
}
String fullText = config.textPrefix() + AdventureHelper.stripTags(message.replace("\\", "\\\\")) + config.textSuffix();
int lines = plugin.getAdvanceManager().getLines(fullText, config.lineWidth());
if (lines > config.maxLines()) return;
TagRenderer renderer = plugin.getUnlimitedTagManager().getTagRender(player);
if (renderer == null) return;
int removed = renderer.removeTagIf(tag -> tag.id().equals("bubble"));
int delay = 0;
if (removed != 0) {
delay += disappearDuration;
}
Bubble bubble = config.bubbles()[lines - 1];
float advance;
if (lines == 1) {
advance = plugin.getAdvanceManager().getLineAdvance(fullText);
} else {
advance = config.lineWidth();
}
BubbleTag bubbleTagText = new BubbleTag(player, renderer, channel, config,
AdventureHelper.miniMessageToMinecraftComponent(fullText),
bubble == null ? null : AdventureHelper.miniMessageToMinecraftComponent(AdventureHelper.surroundWithNameplatesFont(bubble.createImage(advance, 1,1))), this);
renderer.addTag(bubbleTagText);
if (delay != 0) {
plugin.getScheduler().asyncLater(() -> bubbleTagText.setCanShow(true), delay * 50L, TimeUnit.MILLISECONDS);
} else {
bubbleTagText.setCanShow(true);
}
}
}

View File

@@ -0,0 +1,305 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.bubble;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.bubble.BubbleConfig;
import net.momirealms.customnameplates.api.feature.bubble.BubbleManager;
import net.momirealms.customnameplates.api.feature.tag.AbstractTag;
import net.momirealms.customnameplates.api.feature.tag.Tag;
import net.momirealms.customnameplates.api.feature.tag.TagRenderer;
import net.momirealms.customnameplates.api.network.Tracker;
import net.momirealms.customnameplates.api.util.Alignment;
import net.momirealms.customnameplates.api.util.SelfIncreaseEntityID;
import net.momirealms.customnameplates.api.util.Vector3;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class BubbleTag extends AbstractTag {
private final Object text;
@Nullable
private final Object background;
private final BubbleManager manager;
private final BubbleConfig bubbleConfig;
private int ticker;
private boolean canShow;
private final String channel;
private final HashMap<UUID, Boolean> cachedVisibility = new HashMap<>();
protected final int subEntityID = SelfIncreaseEntityID.getAndIncrease();
protected final UUID subEntityUUID = UUID.randomUUID();
public BubbleTag(CNPlayer owner, TagRenderer renderer, String channel, BubbleConfig bubbleConfig, Object text, @Nullable Object background, BubbleManager bubbleManager) {
super(owner, renderer);
this.text = text;
this.manager = bubbleManager;
this.bubbleConfig = bubbleConfig;
this.channel = channel;
this.background = background;
}
@Override
protected List<Object> spawnPacket(CNPlayer viewer) {
Tracker tracker = owner.getTracker(viewer);
Vector3 translation = translation(viewer);
List<Object> packets = new ArrayList<>(CustomNameplates.getInstance().getPlatform().createTextDisplayPacket(
entityID, uuid,
owner.position().add(0, (1.8 + (affectedByCrouching() && tracker.isCrouching() && !owner.isFlying() ? -0.3 : 0) + renderer.hatOffset()) * (affectedByScaling() ? tracker.getScale() : 1), 0),
0f, 0f, 0d,
-1, 0, 0,
text, bubbleConfig.backgroundColor(), (byte) -1, false, false, false,
Alignment.CENTER, manager.viewRange(), 0.0f, 1.0f,
new Vector3(0.001, 0.001, 0.001),
affectedByScaling() ? translation.multiply(tracker.getScale()).add(0.01, 0, 0.01) : translation,
bubbleConfig.lineWidth(),
(affectedByCrouching() && tracker.isCrouching())
));
if (background != null) {
packets.addAll(CustomNameplates.getInstance().getPlatform().createTextDisplayPacket(
subEntityID, subEntityUUID,
owner.position().add(0,(1.8 + (affectedByCrouching() && tracker.isCrouching() && !owner.isFlying() ? -0.3 : 0) + renderer.hatOffset()) * (affectedByScaling() ? tracker.getScale() : 1),0),
0f, 0f, 0d,
-1, 0, 0,
background, 0, (byte) -1, false, false, false,
Alignment.CENTER, manager.viewRange(), 0.0f, 1.0f,
new Vector3(0.001, 0.001, 0.001),
affectedByScaling() ? translation.multiply(tracker.getScale()) : translation,
2048,
(affectedByCrouching() && tracker.isCrouching())
));
}
return packets;
}
public void setCanShow(boolean canShow) {
this.canShow = canShow;
}
@Override
public boolean canShow() {
return canShow;
}
@Override
public boolean canShow(CNPlayer viewer) {
if (!viewer.isMet(owner, manager.viewBubbleRequirements())) {
return false;
}
switch (manager.channelMode()) {
case ALL -> {
return true;
}
case JOINED -> {
Boolean previous = cachedVisibility.get(viewer.uuid());
if (previous != null) return previous;
boolean can = CustomNameplates.getInstance().getChatManager().chatProvider().hasJoinedChannel(viewer, channel);
cachedVisibility.put(viewer.uuid(), can);
return can;
}
case CAN_JOIN -> {
Boolean previous = cachedVisibility.get(viewer.uuid());
if (previous != null) return previous;
boolean can = CustomNameplates.getInstance().getChatManager().chatProvider().canJoinChannel(viewer, channel);
cachedVisibility.put(viewer.uuid(), can);
return can;
}
}
return true;
}
@Override
public void show(CNPlayer viewer) {
if (!isShown()) return;
viewers.add(viewer);
resetViewerArray();
owner.trackPassengers(viewer, entityID);
if (background != null)
owner.trackPassengers(viewer, subEntityID);
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, spawnPacket(viewer));
Tracker tracker = owner.getTracker(viewer);
CustomNameplates.getInstance().getScheduler().asyncLater(() -> {
Consumer<List<Object>> modifier0 = CustomNameplates.getInstance().getPlatform().createInterpolationDelayModifier(-1);
Consumer<List<Object>> modifier1 = CustomNameplates.getInstance().getPlatform().createTransformationInterpolationDurationModifier(manager.appearDuration());
Consumer<List<Object>> modifier2 = CustomNameplates.getInstance().getPlatform().createScaleModifier(affectedByScaling() ? bubbleConfig.scale().multiply(tracker.getScale()) : bubbleConfig.scale());
Object packet1 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(modifier0, modifier1, modifier2));
if (background != null) {
Object packet2 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(subEntityID, List.of(modifier0, modifier1, modifier2));
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, List.of(packet1, packet2));
} else {
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, packet1);
}
}, 100, TimeUnit.MILLISECONDS);
}
@Override
public void tick() {
if (!canShow) return;
if (ticker >= manager.stayDuration()) {
renderer.removeTag(this);
return;
}
ticker++;
}
@Override
public void onPlayerScaleUpdate(CNPlayer viewer, double scale) {
Consumer<List<Object>> modifier1 = CustomNameplates.getInstance().getPlatform().createScaleModifier(scale(viewer).multiply(scale));
Vector3 translation = translation(viewer);
Consumer<List<Object>> modifier2 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(scale).add(0.01,0,0.01));
Object packet1 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(modifier1, modifier2));
if (background != null) {
Consumer<List<Object>> modifier3 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(scale));
Object packet2 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(subEntityID, List.of(modifier1, modifier3));
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, List.of(packet1, packet2));
} else {
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, packet1);
}
}
@Override
public void updateTranslation() {
for (CNPlayer player : viewerArray) {
Tracker tracker = owner.getTracker(player);
if (tracker != null) {
Vector3 translation = translation(player);
Consumer<List<Object>> modifier1 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(tracker.getScale()).add(0.01,0,0.01));
Object packet1 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(modifier1));
if (background != null) {
Consumer<List<Object>> modifier2 = CustomNameplates.getInstance().getPlatform().createTranslationModifier(translation.multiply(tracker.getScale()));
Object packet2 = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(subEntityID, List.of(modifier2));
CustomNameplates.getInstance().getPacketSender().sendPacket(player, List.of(packet1, packet2));
} else {
CustomNameplates.getInstance().getPacketSender().sendPacket(player, packet1);
}
}
}
}
@Override
public void hide() {
if (!isShown()) return;
CNPlayer[] viewers = viewerArray.clone();
CustomNameplates.getInstance().getScheduler().asyncLater(() -> {
Object removePacket = createRemovePacket();
for (CNPlayer viewer : viewers) {
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, removePacket);
}
}, manager.disappearDuration() * 50L, TimeUnit.MILLISECONDS);
List<Object> disappearPacket = createDisappearPacket();
for (CNPlayer viewer : viewers) {
if (background != null) {
owner.untrackPassengers(viewer, entityID, subEntityID);
} else {
owner.untrackPassengers(viewer, entityID);
}
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, disappearPacket);
}
this.cachedVisibility.clear();
this.isShown = false;
this.viewers.clear();
resetViewerArray();
}
@Override
public void hide(CNPlayer viewer) {
if (!isShown()) return;
viewers.remove(viewer);
resetViewerArray();
if (background != null) {
owner.untrackPassengers(viewer, entityID, subEntityID);
} else {
owner.untrackPassengers(viewer, entityID);
}
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, createDisappearPacket());
}
private Object createRemovePacket() {
Object packet;
if (background != null) {
packet = CustomNameplates.getInstance().getPlatform().removeEntityPacket(entityID, subEntityID);
} else {
packet = CustomNameplates.getInstance().getPlatform().removeEntityPacket(entityID);
}
return packet;
}
private List<Object> createDisappearPacket() {
Consumer<List<Object>> modifier0 = CustomNameplates.getInstance().getPlatform().createInterpolationDelayModifier(-1);
Consumer<List<Object>> modifier1 = CustomNameplates.getInstance().getPlatform().createTransformationInterpolationDurationModifier(manager.disappearDuration());
Consumer<List<Object>> modifier2 = CustomNameplates.getInstance().getPlatform().createScaleModifier(new Vector3(0.001,0.001,0.001));
if (background != null) {
return List.of(CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(modifier0, modifier1, modifier2)), CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(subEntityID, List.of(modifier0, modifier1, modifier2)));
} else {
return List.of(CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(modifier0, modifier1, modifier2)));
}
}
@Override
public double getTextHeight(CNPlayer viewer) {
return -Double.MAX_VALUE;
}
@Override
public Vector3 scale(CNPlayer viewer) {
return new Vector3(1,1,1);
}
@Override
public Vector3 translation(CNPlayer viewer) {
return new Vector3(0, manager.verticalOffset() + maxY(viewer), 0);
}
public double maxY(CNPlayer viewer) {
double y = 0;
for (Tag tag : renderer.tags()) {
if (tag.isShown() && tag.isShown(viewer) && !tag.id().equals(id())) {
double currentY = tag.translation(viewer).y() + tag.getTextHeight(viewer);
if (currentY > y) {
y = currentY;
}
}
}
return y;
}
@Override
public String id() {
return "bubble";
}
@Override
public boolean affectedByCrouching() {
return false;
}
@Override
public boolean affectedByScaling() {
return true;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.chat;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.ChatListener;
import net.momirealms.customnameplates.api.feature.chat.AbstractChatMessageProvider;
import net.momirealms.customnameplates.api.feature.chat.ChatManager;
import net.momirealms.customnameplates.api.feature.chat.ChatMessageProvider;
import net.momirealms.customnameplates.api.feature.chat.emoji.EmojiProvider;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractChatManager implements ChatManager {
protected final CustomNameplates plugin;
protected final List<EmojiProvider> emojiProviders = new ArrayList<>();
protected final List<ChatListener> listeners = new ArrayList<>();
protected ChatMessageProvider chatProvider;
protected ChatMessageProvider customProvider;
public AbstractChatManager(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void unload() {
this.emojiProviders.clear();
if (chatProvider instanceof AbstractChatMessageProvider chatMessageProvider) {
chatMessageProvider.unregister();
}
chatProvider = null;
}
@Override
public void load() {
setUpPlatformEmojiProviders();
if (customProvider == null) {
setUpPlatformProvider();
} else {
chatProvider = customProvider;
}
if (chatProvider instanceof AbstractChatMessageProvider chatMessageProvider) {
chatMessageProvider.register();
}
}
protected abstract void setUpPlatformProvider();
protected abstract void setUpPlatformEmojiProviders();
@Override
public boolean setCustomChatProvider(ChatMessageProvider provider) {
if (this.customProvider != null)
return false;
this.customProvider = provider;
this.reload();
return true;
}
@Override
public boolean removeCustomChatProvider() {
if (this.customProvider != null) {
this.customProvider = null;
this.reload();
return true;
}
return false;
}
@Override
public void disable() {
unload();
this.listeners.clear();
}
@Override
public void registerListener(final ChatListener listener) {
this.listeners.add(listener);
}
@Override
public void unregisterListener(final ChatListener listener) {
this.listeners.remove(listener);
}
@Override
public ChatMessageProvider chatProvider() {
return chatProvider;
}
@Override
public void onChat(CNPlayer player, String message, String channel) {
String text = message;
for (EmojiProvider provider : emojiProviders) {
text = provider.replace(player, text);
}
text = AdventureHelper.legacyToMiniMessage(text);
for (ChatListener listener : listeners) {
listener.onPlayerChat(player, text, channel);
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.image;
import dev.dejvokep.boostedyaml.YamlDocument;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.ConfiguredCharacter;
import net.momirealms.customnameplates.api.feature.image.Image;
import net.momirealms.customnameplates.api.feature.image.ImageManager;
import net.momirealms.customnameplates.api.util.ConfigUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
public class ImageManagerImpl implements ImageManager {
private final CustomNameplates plugin;
private final HashMap<String, Image> images = new HashMap<>();
public ImageManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void unload() {
this.images.clear();
}
@Override
public void load() {
this.loadConfigs();
}
@Override
public @Nullable Image imageById(String id) {
return images.get(id);
}
@Override
public Collection<Image> images() {
return new HashSet<>(images.values());
}
private void loadConfigs() {
File imageFolder = new File(plugin.getDataDirectory().toFile(), "contents" + File.separator + "images");
if (!imageFolder.exists() && imageFolder.mkdirs()) {
saveDefaultImages();
}
List<File> configFiles = ConfigUtils.getConfigsDeeply(imageFolder);
for (File configFile : configFiles) {
YamlDocument config = plugin.getConfigManager().loadData(configFile);
String id = configFile.getName().substring(0, configFile.getName().lastIndexOf("."));
Image image = Image.builder()
.id(id)
.hasShadow(!config.getBoolean("shadow.remove", false))
.opacity(config.getInt("shadow.opacity", 254))
.character(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("image") + ".png"),
config.getInt("ascent", 8),
config.getInt("height", 10)
))
.build();
this.images.put(id, image);
}
}
private void saveDefaultImages() {
String[] png_list = new String[]{"bell", "bubble", "clock", "coin", "compass", "weather", "stamina_0", "stamina_1", "stamina_2"};
String[] part_list = new String[]{".png", ".yml"};
for (String name : png_list) {
for (String part : part_list) {
plugin.getConfigManager().saveResource("contents" + File.separator + "images" + File.separator + name + part);
}
}
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.nameplate;
import dev.dejvokep.boostedyaml.YamlDocument;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.ConfiguredCharacter;
import net.momirealms.customnameplates.api.feature.nameplate.Nameplate;
import net.momirealms.customnameplates.api.feature.nameplate.NameplateManager;
import net.momirealms.customnameplates.api.util.ConfigUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
public class NameplateManagerImpl implements NameplateManager {
private final CustomNameplates plugin;
private final Map<String, Nameplate> nameplates = new HashMap<>();
private String defaultNameplateId;
private String nameTag;
public NameplateManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void unload() {
this.nameplates.clear();
}
@Override
public void load() {
this.loadConfig();
this.loadConfigs();
}
@Nullable
@Override
public Nameplate nameplateById(String id) {
return this.nameplates.get(id);
}
@Override
public Collection<Nameplate> nameplates() {
return new HashSet<>(nameplates.values());
}
@Override
public boolean hasNameplate(CNPlayer player, String id) {
if (!this.nameplates.containsKey(id)) {
return false;
}
return player.hasPermission("nameplates.equip." + id);
}
@Override
public Collection<Nameplate> availableNameplates(CNPlayer player) {
ArrayList<Nameplate> available = new ArrayList<>();
for (Nameplate nameplate : nameplates.values()) {
if (player.hasPermission("nameplates.equip." + nameplate.id())) {
available.add(nameplate);
}
}
return available;
}
@Override
public String defaultNameplateId() {
return defaultNameplateId;
}
@Override
public String playerNameTag() {
return nameTag;
}
private void loadConfig() {
plugin.getConfigManager().saveResource("configs" + File.separator + "nameplate.yml");
YamlDocument document = plugin.getConfigManager().loadData(new File(plugin.getDataDirectory().toFile(), "configs" + File.separator + "nameplate.yml"));
defaultNameplateId = document.getString("default-nameplate", "none");
String prefix = document.getString("nameplate.prefix", "");
String name = document.getString("nameplate.player-name", "%player_name%");
String suffix = document.getString("nameplate.suffix", "");
nameTag = prefix + name + suffix;
}
private void loadConfigs() {
File nameplateFolder = new File(plugin.getDataDirectory().toFile(), "contents" + File.separator + "nameplates");
if (!nameplateFolder.exists() && nameplateFolder.mkdirs()) {
saveDefaultNameplates();
}
List<File> configFiles = ConfigUtils.getConfigsDeeply(nameplateFolder);
for (File configFile : configFiles) {
YamlDocument config = plugin.getConfigManager().loadData(configFile);
String id = configFile.getName().substring(0, configFile.getName().lastIndexOf("."));
Nameplate nameplate = Nameplate.builder()
.id(id)
.displayName(config.getString("display-name", id))
.left(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("left.image") + ".png"),
config.getInt("left.ascent", 12),
config.getInt("left.height", 16)
))
.middle(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("middle.image") + ".png"),
config.getInt("middle.ascent", 12),
config.getInt("middle.height", 16)
))
.right(ConfiguredCharacter.create(
ConfigUtils.getFileInTheSameFolder(configFile, config.getString("right.image") + ".png"),
config.getInt("right.ascent", 12),
config.getInt("right.height", 16)
))
.build();
this.nameplates.put(id, nameplate);
}
}
private void saveDefaultNameplates() {
String[] png_list = new String[]{"cat", "egg", "cheems", "wither", "xmas", "halloween", "hutao", "starsky", "trident", "rabbit"};
String[] part_list = new String[]{"_left.png", "_middle.png", "_right.png", ".yml"};
for (String name : png_list) {
for (String part : part_list) {
plugin.getConfigManager().saveResource("contents" + File.separator + "nameplates" + File.separator + name + part);
}
}
}
}

View File

@@ -0,0 +1,616 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.pack;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.ConfiguredCharacter;
import net.momirealms.customnameplates.api.feature.OffsetFont;
import net.momirealms.customnameplates.api.feature.advance.CharacterFontAdvanceData;
import net.momirealms.customnameplates.api.feature.background.Background;
import net.momirealms.customnameplates.api.feature.bubble.Bubble;
import net.momirealms.customnameplates.api.feature.image.Image;
import net.momirealms.customnameplates.api.feature.nameplate.Nameplate;
import net.momirealms.customnameplates.api.feature.pack.ResourcePackManager;
import net.momirealms.customnameplates.api.helper.VersionHelper;
import net.momirealms.customnameplates.api.util.CharacterUtils;
import org.apache.commons.io.FileUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class ResourcePackManagerImpl implements ResourcePackManager {
private final CustomNameplates plugin;
public ResourcePackManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void generate() {
File resourcePackFolder = new File(plugin.getDataFolder() + File.separator + "ResourcePack");
// delete the old one
this.deleteDirectory(resourcePackFolder);
// create folders
File fontFolder = new File(plugin.getDataFolder(), "ResourcePack" + File.separator + "assets" + File.separator + ConfigManager.namespace() + File.separatorChar + "font");
File texturesFolder = new File(plugin.getDataFolder(), "ResourcePack" + File.separator+ "assets" + File.separator + ConfigManager.namespace() + File.separatorChar + "textures");
if (!fontFolder.mkdirs() || !texturesFolder.mkdirs()) {
plugin.getPluginLogger().severe("Failed to generate resource pack folders");
return;
}
// save BossBars
this.saveBossBar();
// save unicodes
this.saveLegacyUnicodes();
if (!VersionHelper.isVersionNewerThan1_20_5()) {
this.generateShaders("ResourcePack" + File.separator + "assets" + File.separator + "minecraft" + File.separator + "shaders" + File.separator + "core" + File.separator, false);
this.generateShaders("ResourcePack" + File.separator + "overlay_1_20_5" + File.separator + "assets" + File.separator + "minecraft" + File.separator + "shaders" + File.separator + "core" + File.separator, true);
} else {
this.generateShaders("ResourcePack" + File.separator + "overlay_1_20_5" + File.separator + "assets" + File.separator + "minecraft" + File.separator + "shaders" + File.separator + "core" + File.separator, true);
try {
FileUtils.copyDirectory(
new File(plugin.getDataFolder(), "ResourcePack" + File.separator + "overlay_1_20_5"),
new File(plugin.getDataFolder(), "ResourcePack")
);
FileUtils.deleteDirectory(new File(plugin.getDataFolder(), "ResourcePack" + File.separator + "overlay_1_20_5"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// create json object
JsonObject fontJson = new JsonObject();
JsonArray providers = new JsonArray();
fontJson.add("providers", providers);
// add offset characters
this.getOffsets(texturesFolder).forEach(providers::add);
// add nameplate characters
this.getNameplates(texturesFolder).forEach(providers::add);
// add bubble characters
this.getBubbles(texturesFolder).forEach(providers::add);
// add background characters
this.getBackgrounds(texturesFolder).forEach(providers::add);
// add image characters
this.getImages(texturesFolder).forEach(providers::add);
// save json object to file
this.saveFont(fontJson);
// generate shift fonts
this.generateFont();
// set pack.mcmeta/pack.png
this.setPackFormat();
// copy the resource pack to hooked plugins
this.copyResourcePackToHookedPlugins(resourcePackFolder);
}
private void saveFont(JsonObject fontJson) {
try (FileWriter fileWriter = new FileWriter(
plugin.getDataFolder() +
File.separator + "ResourcePack" +
File.separator + "assets" +
File.separator + ConfigManager.namespace() +
File.separator + "font" +
File.separator + ConfigManager.font() + ".json")
) {
fileWriter.write(fontJson.toString().replace("\\\\", "\\"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void generateFont() {
YamlDocument document = ConfigManager.getMainConfig();
Section section = document.getSection("other-settings.shift-fonts");
if (section != null) {
for (Object key : section.getKeys()) {
if (key instanceof String font) {
JsonObject jo = new JsonObject();
JsonArray providers = new JsonArray();
jo.add("providers", providers);
List<String> order = section.getStringList(font);
for (String f : order) {
String[] split = f.split(":", 2);
Map<String, Object> properties = new HashMap<>();
if (split.length == 2) {
properties.put("shift_y", Integer.parseInt(split[1]));
}
CharacterFontAdvanceData data = plugin.getAdvanceManager().templateFontDataById(split[0]);
if (data == null) {
plugin.getPluginLogger().warn("Font template [" + split[0] + "] not found");
continue;
}
List<JsonObject> jsonObject = data.fontProvider(properties);
if (jsonObject == null) {
plugin.getPluginLogger().warn("Font template [" + split[0] + "] doesn't support shift");
continue;
}
for (JsonObject o : jsonObject) {
providers.add(o);
}
}
try (FileWriter file = new FileWriter(new File(plugin.getDataFolder(),
"ResourcePack" +
File.separator + "assets" +
File.separator + ConfigManager.namespace() +
File.separator + "font" +
File.separator + font + ".json"))) {
file.write(jo.toString().replace("\\\\", "\\"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void setPackFormat() {
if (VersionHelper.isVersionNewerThan1_20_5()) {
plugin.getConfigManager().saveResource("ResourcePack" + File.separator + "pack_1_20_5.mcmeta");
File file = new File(plugin.getDataFolder(), "ResourcePack" + File.separator + "pack_1_20_5.mcmeta");
file.renameTo(new File(plugin.getDataFolder(), "ResourcePack" + File.separator + "pack.mcmeta"));
} else {
plugin.getConfigManager().saveResource("ResourcePack" + File.separator + "pack.mcmeta");
}
plugin.getConfigManager().saveResource("ResourcePack" + File.separator + "pack.png");
}
private void copyResourcePackToHookedPlugins(File resourcePackFolder) {
File pluginsFolder = plugin.getDataFolder().getParentFile();
if (ConfigManager.packItemsAdder()) {
File file = new File(pluginsFolder, "ItemsAdder" + File.separator + "config.yml");
YamlDocument iaConfig = plugin.getConfigManager().loadData(file);
List<String> folders = iaConfig.getStringList("resource-pack.zip.merge_other_plugins_resourcepacks_folders");
boolean changed = false;
if (!folders.contains("CustomNameplates/ResourcePack")) {
folders.add("CustomNameplates/ResourcePack");
iaConfig.set("resource-pack.zip.merge_other_plugins_resourcepacks_folders", folders);
changed = true;
}
if (changed) {
try {
iaConfig.save(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
if (ConfigManager.packItemsAdderLegacy()){
try {
FileUtils.copyDirectory(new File(resourcePackFolder, "assets"), new File(pluginsFolder, "ItemsAdder" + File.separator + "contents" + File.separator + "nameplates" + File.separator + "resourcepack" + File.separator + "assets") );
} catch (IOException e){
plugin.getPluginLogger().warn("Failed to copy files to ItemsAdder", e);
}
}
if (ConfigManager.packOraxen()){
try {
FileUtils.copyDirectory(new File(resourcePackFolder, "assets"), new File(pluginsFolder, "Oraxen" + File.separator + "pack" + File.separator + "assets"));
} catch (IOException e){
plugin.getPluginLogger().warn("Failed to copy files to Oraxen", e);
}
}
}
private List<JsonObject> getBubbles(File texturesFolder) {
ArrayList<JsonObject> list = new ArrayList<>();
if (!ConfigManager.bubbleModule()) return list;
for (Bubble bubble : plugin.getBubbleManager().bubbles()) {
for (ConfiguredCharacter configuredChar : new ConfiguredCharacter[]{bubble.left(), bubble.middle(), bubble.right(), bubble.tail()}) {
JsonObject jo = new JsonObject();
jo.add("type", new JsonPrimitive("bitmap"));
jo.add("file", new JsonPrimitive(ConfigManager.namespace() + ":" + ConfigManager.bubblePath().replace("\\", "/") + configuredChar.imageFile().getName()));
jo.add("ascent", new JsonPrimitive(configuredChar.ascent()));
jo.add("height", new JsonPrimitive(configuredChar.height()));
JsonArray ja = new JsonArray();
ja.add(CharacterUtils.char2Unicode(configuredChar.character()));
jo.add("chars", ja);
list.add(jo);
try {
FileUtils.copyFile(
new File(plugin.getDataFolder(),
"contents" + File.separator + "bubbles" + File.separator + configuredChar.imageFile().getName()),
new File(texturesFolder,
ConfigManager.bubblePath().replace("\\", File.separator) + configuredChar.imageFile().getName()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return list;
}
private List<JsonObject> getBackgrounds(File texturesFolder) {
ArrayList<JsonObject> list = new ArrayList<>();
if (!ConfigManager.backgroundModule()) return list;
for (Background backGround : plugin.getBackgroundManager().getBackgrounds()) {
for (ConfiguredCharacter configuredChar : new ConfiguredCharacter[]{
backGround.left(), backGround.width_1(),
backGround.width_2(), backGround.width_4(),
backGround.width_8(), backGround.width_16(),
backGround.width_32(), backGround.width_64(),
backGround.width_128(), backGround.right()}
) {
JsonObject jo = new JsonObject();
jo.add("type", new JsonPrimitive("bitmap"));
jo.add("file", new JsonPrimitive(ConfigManager.namespace() + ":" + ConfigManager.backgroundPath().replace("\\", "/") + configuredChar.imageFile().getName()));
jo.add("ascent", new JsonPrimitive(configuredChar.ascent()));
jo.add("height", new JsonPrimitive(configuredChar.height()));
JsonArray ja = new JsonArray();
ja.add(CharacterUtils.char2Unicode(configuredChar.character()));
jo.add("chars", ja);
list.add(jo);
try {
FileUtils.copyFile(
new File(plugin.getDataFolder(),
"contents" + File.separator + "backgrounds" + File.separator + configuredChar.imageFile().getName()),
new File(texturesFolder,
ConfigManager.backgroundPath().replace("\\", File.separator) + configuredChar.imageFile().getName()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return list;
}
private List<JsonObject> getImages(File texturesFolder) {
ArrayList<JsonObject> list = new ArrayList<>();
if (!ConfigManager.imageModule()) return list;
for (Image image : plugin.getImageManager().images()) {
ConfiguredCharacter character = image.character();
JsonObject jo = new JsonObject();
jo.add("type", new JsonPrimitive("bitmap"));
jo.add("file", new JsonPrimitive(ConfigManager.namespace() + ":" + ConfigManager.imagePath().replace("\\", "/") + character.imageFile().getName()));
jo.add("ascent", new JsonPrimitive(character.ascent()));
jo.add("height", new JsonPrimitive(character.height()));
JsonArray ja = new JsonArray();
ja.add(CharacterUtils.char2Unicode(character.character()));
jo.add("chars", ja);
list.add(jo);
try {
FileUtils.copyFile(
new File(plugin.getDataFolder(),
"contents" + File.separator + "images" + File.separator + character.imageFile().getName()),
new File(texturesFolder,
ConfigManager.imagePath().replace("\\", File.separator) + character.imageFile().getName()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return list;
}
private List<JsonObject> getNameplates(File texturesFolder) {
ArrayList<JsonObject> list = new ArrayList<>();
if (!ConfigManager.nameplateModule()) return list;
for (Nameplate nameplate : plugin.getNameplateManager().nameplates()) {
for (ConfiguredCharacter configuredChar : new ConfiguredCharacter[]{nameplate.left(), nameplate.middle(), nameplate.right()}) {
JsonObject jo = new JsonObject();
jo.add("type", new JsonPrimitive("bitmap"));
jo.add("file", new JsonPrimitive(ConfigManager.namespace() + ":" + ConfigManager.nameplatePath().replace("\\", "/") + configuredChar.imageFile().getName()));
jo.add("ascent", new JsonPrimitive(configuredChar.ascent()));
jo.add("height", new JsonPrimitive(configuredChar.height()));
JsonArray ja = new JsonArray();
ja.add(CharacterUtils.char2Unicode(configuredChar.character()));
jo.add("chars", ja);
list.add(jo);
try {
FileUtils.copyFile(
new File(plugin.getDataFolder(),
"contents" + File.separator + "nameplates" + File.separator + configuredChar.imageFile().getName()),
new File(texturesFolder,
ConfigManager.nameplatePath().replace("\\", File.separator) + configuredChar.imageFile().getName()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return list;
}
private List<JsonObject> getOffsets(File texturesFolder) {
this.saveSplit(texturesFolder);
ArrayList<JsonObject> list = new ArrayList<>();
for (OffsetFont offsetFont : OffsetFont.values()) {
JsonObject jsonObject = new JsonObject();
jsonObject.add("type", new JsonPrimitive("bitmap"));
jsonObject.add("file", new JsonPrimitive(ConfigManager.namespace() + ":" + ConfigManager.spaceSplitPath().replace("\\","/") + "space_split.png"));
jsonObject.add("ascent", new JsonPrimitive(-5000));
jsonObject.add("height", new JsonPrimitive(offsetFont.height()));
final JsonArray jsonArray = new JsonArray();
jsonArray.add(CharacterUtils.char2Unicode(offsetFont.character()));
jsonObject.add("chars", jsonArray);
list.add(jsonObject);
}
return list;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void saveSplit(File texturesFolder) {
try {
plugin.getConfigManager().saveResource("space_split.png");
FileUtils.copyFile(new File(plugin.getDataFolder(),"space_split.png"), new File(texturesFolder, ConfigManager.spaceSplitPath().replace("\\", File.separator) + "space_split.png"));
File file = new File(plugin.getDataFolder(),"space_split.png");
if (file.exists()) {
file.delete();
}
} catch (IOException e){
throw new RuntimeException(e);
}
}
public void deleteDirectory(File file){
if (file.exists()) {
try {
FileUtils.deleteDirectory(file);
} catch (IOException e){
throw new RuntimeException(e);
}
}
}
private void saveBossBar() {
if (ConfigManager.bossBar1_20_2()) {
String color = ConfigManager.removedBarColor().name().toLowerCase(Locale.ENGLISH);
String path = "ResourcePack" + File.separator + "assets" + File.separator + "minecraft" + File.separator + "textures" + File.separator + "gui" + File.separator + "sprites" + File.separator + "boss_bar" + File.separator;
plugin.getConfigManager().saveResource(path + color + "_background.png");
plugin.getConfigManager().saveResource(path + color + "_progress.png");
}
if (ConfigManager.bossBar1_17()) {
String path = "ResourcePack" + File.separator + "assets" + File.separator + "minecraft" + File.separator + "textures" + File.separator + "gui" + File.separator + "bars.png";
plugin.getConfigManager().saveResource(path);
try {
File inputFile = new File(plugin.getDataFolder(), path);
BufferedImage image = ImageIO.read(inputFile);
int y;
switch (ConfigManager.removedBarColor()) {
case PINK -> y = 0;
case BLUE -> y = 10;
case RED -> y = 20;
case GREEN -> y = 30;
case PURPLE -> y = 50;
case WHITE -> y = 60;
default -> y = 40;
}
int width = 182;
int height = 10;
for (int i = 0; i < width; i++) {
for (int j = y; j < y + height; j++) {
image.setRGB(i, j, 0);
}
}
ImageIO.write(image, "png", inputFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private void saveLegacyUnicodes() {
if (ConfigManager.legacyUnicodes()) {
for (int i = 0; i < 256; i++) {
var path = "font" + File.separator + "unicode_page_" + String.format("%02x", i) + ".png";
var destination = "ResourcePack" + File.separator + "assets" + File.separator + "minecraft" + File.separator + "textures" + File.separator + "font" + File.separator + "unicode_page_" + String.format("%02x", i) + ".png";
File imageFile = new File(plugin.getDataFolder(), path);
File destinationFile = new File(plugin.getDataFolder(), destination);
if (imageFile.exists()) {
try {
FileUtils.copyFile(imageFile, destinationFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
private void generateShaders(String path, boolean v1_20_5) {
if (!ConfigManager.enableShader()) return;
plugin.getConfigManager().saveResource(path + "rendertype_text.fsh");
plugin.getConfigManager().saveResource(path + "rendertype_text.json");
plugin.getConfigManager().saveResource(path + "rendertype_text.vsh");
String line;
StringBuilder sb1 = new StringBuilder();
File shader1 = new File(plugin.getDataFolder(), path + "rendertype_text.vsh");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(shader1), StandardCharsets.UTF_8))) {
while ((line = reader.readLine()) != null) {
sb1.append(line).append(System.lineSeparator());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
String mainShader = v1_20_5 ? ShaderConstants.Nameplates_Shader_1_20_5 : ShaderConstants.Nameplates_Shader_1_20_4;
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(shader1), StandardCharsets.UTF_8))) {
writer.write(sb1.toString()
.replace("%SHADER_0%", !ConfigManager.animatedText() ? "" : ShaderConstants.Animated_Text_Out)
.replace("%SHADER_1%", !ConfigManager.itemsAdderEffect() ? mainShader : ShaderConstants.ItemsAdder_Text_Effects + mainShader)
.replace("%SHADER_2%", !ConfigManager.animatedText() ? "" : ShaderConstants.Animated_Text_VSH)
.replace("%SHADER_3%", !ConfigManager.hideScoreBoardNumber() ? "" : ShaderConstants.Hide_ScoreBoard_Numbers)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
File shader2 = new File(plugin.getDataFolder(), path + "rendertype_text.fsh");
StringBuilder sb2 = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(shader2), StandardCharsets.UTF_8))) {
while ((line = reader.readLine()) != null) {
sb2.append(line).append(System.lineSeparator());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(shader2), StandardCharsets.UTF_8))) {
writer.write(sb2.toString()
.replace("%SHADER_0%", !ConfigManager.animatedText() ? "" : ShaderConstants.Animated_Text_In)
.replace("%SHADER_1%", !ConfigManager.animatedText() ? "" : ShaderConstants.Animated_Text_FSH)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static class ShaderConstants {
public static final String Nameplates_Shader_1_20_5 =
"if (Color.xyz == vec3(255., 254., 253.) / 255.) {\n" +
" vertexColor = Color*texelFetch(Sampler2, UV2 / 16, 0);\n" +
" vertex.y += 1;\n" +
" vertex.x += 1;\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" } else if (Color.xyz == vec3(254., 254., 254.) / 255.) {\n" +
" vertexColor = Color*texelFetch(Sampler2, UV2 / 16, 0);\n" +
" vertex.z *= 1.001;\n" +
" vertex.x *= 1.001;\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" } else if (Color.xyz == vec3(253., 254., 254.) / 255.) {\n" +
" vertexColor = Color*texelFetch(Sampler2, UV2 / 16, 0);\n" +
" vertex.z *= 1.001001;\n" +
" vertex.x *= 1.001001;\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" } else {\n" +
" vertexColor = Color*texelFetch(Sampler2, UV2 / 16, 0);\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" }";
public static final String Nameplates_Shader_1_20_4 =
"if (Color.xyz == vec3(255., 254., 253.) / 255.) {\n" +
" vertexColor = Color*texelFetch(Sampler2, UV2 / 16, 0);\n" +
" vertex.y += 1;\n" +
" vertex.x += 1;\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" } else if (Color.xyz == vec3(254., 254., 254.) / 255.) {\n" +
" vertexColor = Color*texelFetch(Sampler2, UV2 / 16, 0);\n" +
" vertex.z -= 0.001;\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" } else if (Color.xyz == vec3(253., 254., 254.) / 255.) {\n" +
" vertexColor = Color*texelFetch(Sampler2, UV2 / 16, 0);\n" +
" vertex.z -= 0.0011;\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" } else {\n" +
" vertexColor = Color*texelFetch(Sampler2, UV2 / 16, 0);\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" }";
public static final String ItemsAdder_Text_Effects =
"if (Color.xyz == vec3(255., 255., 254.) / 255.) {\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" vertexColor = ((.6 + .6 * cos(6. * (gl_Position.x + GameTime * 1000.) + vec4(0, 23, 21, 1))) + vec4(0., 0., 0., 1.)) * texelFetch(Sampler2, UV2 / 16, 0);\n" +
" } else if (Color.xyz == vec3(255., 255., 253.) / 255.) {\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" vertexColor = Color * texelFetch(Sampler2, UV2 / 16, 0);\n" +
" gl_Position.y = gl_Position.y + sin(GameTime * 12000. + (gl_Position.x * 6)) / 150.;\n" +
" } else if (Color.xyz == vec3(255., 255., 252.) / 255.) {\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" vertexColor = ((.6 + .6 * cos(6. * (gl_Position.x + GameTime * 1000.) + vec4(0, 23, 21, 1))) + vec4(0., 0., 0., 1.)) * texelFetch(Sampler2, UV2 / 16, 0);\n" +
" gl_Position.y = gl_Position.y + sin(GameTime*12000. + (gl_Position.x*6)) / 150.;\n" +
" } else if (Color.xyz == vec3(255., 255., 251.) / 255.) {\n" +
" vertexColor = Color * texelFetch(Sampler2, UV2 / 16, 0);\n" +
" float vertexId = mod(gl_VertexID, 4.0);\n" +
" if (vertex.z <= 0.) {\n" +
" if (vertexId == 3. || vertexId == 0.) vertex.y += cos(GameTime * 12000. / 4) * 0.1;\n" +
" vertex.y += max(cos(GameTime*12000. / 4) * 0.1, 0.);\n" +
" } else {\n" +
" if (vertexId == 3. || vertexId == 0.) vertex.y -= cos(GameTime * 12000. / 4) * 3;\n" +
" vertex.y -= max(cos(GameTime*12000. / 4) * 4, 0.);\n" +
" }\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" } else if (Color.xyz == vec3(255., 254., 254.) / 255.) {\n" +
" float vertexId = mod(gl_VertexID, 4.0);\n" +
" if (vertex.z <= 0.) {\n" +
" if (vertexId == 3. || vertexId == 0.) vertex.y += cos(GameTime * 12000. / 4) * 0.1;\n" +
" vertex.y += max(cos(GameTime*12000. / 4) * 0.1, 0.);\n" +
" } else {\n" +
" if (vertexId == 3. || vertexId == 0.) vertex.y -= cos(GameTime * 12000. / 4) * 3;\n" +
" vertex.y -= max(cos(GameTime*12000. / 4) * 4, 0.);\n" +
" }\n" +
" vertexColor = ((.6 + .6 * cos(6. * (gl_Position.x + GameTime * 1000.) + vec4(0, 23, 21, 1))) + vec4(0., 0., 0., 1.)) * texelFetch(Sampler2, UV2 / 16, 0);\n" +
" gl_Position = ProjMat * ModelViewMat * vertex;\n" +
" } else ";
public static final String Hide_ScoreBoard_Numbers =
"\n" +
" if (Position.z == 0.0\n" +
" && gl_Position.x >= 0.94\n" +
" && gl_Position.y >= -0.35\n" +
" && vertexColor.g == 84.0/255.0\n" +
" && vertexColor.g == 84.0/255.0\n" +
" && vertexColor.r == 252.0/255.0\n" +
" && gl_VertexID <= 7\n" +
" ) {\n" +
" gl_Position = ProjMat * ModelViewMat * vec4(ScreenSize + 100.0, 0.0, ScreenSize + 100.0);\n" +
" }";
public static final String Animated_Text_FSH =
"\n" +
" vec2 p1 = round(pos1 / (posID == 0 ? 1 - coord.x : 1 - coord.y));\n" +
" vec2 p2 = round(pos2 / (posID == 0 ? coord.y : coord.x));\n" +
" ivec2 resolution = ivec2(abs(p1 - p2));\n" +
" ivec2 corner = ivec2(min(p1, p2));\n" +
" vec4 pixel = texture(Sampler0, corner / 256.0) * 255;\n" +
" if (pixel.a == 1) {\n" +
" ivec2 frames = ivec2(resolution / pixel.gb);\n" +
" vec2 uv = (texCoord0 * 256 - corner) / frames.x;\n" +
" if (uv.x > pixel.y || uv.y > pixel.z)\n" +
" discard;\n" +
" int time = int(GameTime * pixel.r * 10 * pixel.x) % int(frames.x * frames.y);\n" +
" uv = corner + mod(uv, pixel.yz) + vec2(time % frames.x, time / frames.x % frames.y) * pixel.yz;\n" +
" color = texture(Sampler0, uv / 256.0) * vertexColor * ColorModulator;\n" +
" }";
public static final String Animated_Text_VSH =
"\n" +
" pos1 = pos2 = vec2(0);\n" +
" posID = gl_VertexID % 4;\n" +
" const vec2[4] corners = vec2[4](vec2(0), vec2(0, 1), vec2(1), vec2(1, 0));\n" +
" coord = corners[posID];\n" +
" if (posID == 0) pos1 = UV0 * 256;\n" +
" if (posID == 2) pos2 = UV0 * 256;";
public static final String Animated_Text_Out =
"\n" +
"out vec2 pos1;\n" +
"out vec2 pos2;\n" +
"out vec2 coord;\n" +
"flat out int posID;\n";
public static final String Animated_Text_In =
"\n" +
"in vec2 pos1;\n" +
"in vec2 pos2;\n" +
"in vec2 coord;\n" +
"flat in int posID;\n";
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.tag;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.CarouselText;
import net.momirealms.customnameplates.api.feature.DynamicText;
import net.momirealms.customnameplates.api.feature.RelationalFeature;
import net.momirealms.customnameplates.api.feature.tag.AbstractTag;
import net.momirealms.customnameplates.api.feature.tag.NameTagConfig;
import net.momirealms.customnameplates.api.feature.tag.TagRenderer;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.api.network.Tracker;
import net.momirealms.customnameplates.api.placeholder.Placeholder;
import net.momirealms.customnameplates.api.util.Vector3;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class NameTag extends AbstractTag implements RelationalFeature {
private final NameTagConfig config;
private int order;
private int timeLeft;
private DynamicText currentText;
public NameTag(CNPlayer owner, NameTagConfig config, TagRenderer renderer) {
super(owner, renderer);
this.config = config;
}
@Override
public void init() {
order = config.carouselTexts().length - 1;
timeLeft = 0;
}
@Override
protected List<Object> spawnPacket(CNPlayer viewer) {
String newName = currentText.render(viewer);
Object component = AdventureHelper.miniMessageToMinecraftComponent(newName);
Tracker tracker = owner.getTracker(viewer);
return CustomNameplates.getInstance().getPlatform().createTextDisplayPacket(
entityID, uuid,
owner.position().add(0,(1.8 + (affectedByCrouching() && tracker.isCrouching() && !owner.isFlying() ? -0.3 : 0) + renderer.hatOffset()) * (affectedByScaling() ? tracker.getScale() : 1),0),
0f, 0f, 0d,
0, 0, 0,
component, config.backgroundColor(), config.opacity(), config.hasShadow(), config.isSeeThrough(), config.useDefaultBackgroundColor(),
config.alignment(), config.viewRange(), config.shadowRadius(), config.shadowStrength(),
(affectedByScaling() ? scale(viewer).multiply(tracker.getScale()) : scale(viewer)),
(affectedByScaling() ? translation(viewer).multiply(tracker.getScale()) : translation(viewer)),
config.lineWidth(),
(affectedByCrouching() && tracker.isCrouching())
);
}
@Override
public void tick() {
if (timeLeft > 0)
timeLeft--;
if (timeLeft == 0) {
int triedTimes = 0;
do {
if (triedTimes == config.carouselTexts().length) {
timeLeft = 20;
currentText = null;
CustomNameplates.getInstance().getPluginLogger().warn("No text in order is available for player " + owner.name() + ". Please check your tag's conditions.");
return;
}
order++;
if (order >= config.carouselTexts().length) {
order = 0;
}
triedTimes++;
} while (
!owner.isMet(config.carouselTexts()[order].requirements())
);
CarouselText carouselText = config.carouselTexts()[order];
timeLeft = carouselText.duration();
currentText = carouselText.preParsedDynamicText().fastCreate(owner);
if (carouselText.updateOnDisplay()) {
owner.forceUpdate(currentText.placeholders(), owner.nearbyPlayers());
}
refresh();
}
}
@Override
public void notifyPlaceholderUpdates(CNPlayer p1, CNPlayer p2, boolean force) {
refresh(p2);
}
@Override
public void notifyPlaceholderUpdates(CNPlayer p1, boolean force) {
refresh();
}
@Override
public boolean canShow() {
return owner.isMet(config.ownerRequirements());
}
@Override
public boolean canShow(CNPlayer viewer) {
return viewer.isMet(owner, config.viewerRequirements());
}
public void refresh() {
for (CNPlayer viewer : viewerArray) {
refresh(viewer);
}
}
public void refresh(CNPlayer viewer) {
String newName = currentText.render(viewer);
Object component = AdventureHelper.miniMessageToMinecraftComponent(newName);
Object packet = CustomNameplates.getInstance().getPlatform().updateTextDisplayPacket(entityID, List.of(CustomNameplates.getInstance().getPlatform().createTextComponentModifier(component)));
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, packet);
}
@Override
public double getTextHeight(CNPlayer viewer) {
String current = currentText.render(viewer);
Tracker tracker = viewer.getTracker(owner);
int lines = CustomNameplates.getInstance().getAdvanceManager().getLines(current, config.lineWidth());
return ((lines * (9+1) + config.translation().y()) * config.scale().y() * (config.affectedByScaling() ? tracker.getScale() : 1)) / 40;
}
@Override
public void hide() {
if (!isShown()) return;
Object packet = CustomNameplates.getInstance().getPlatform().removeEntityPacket(entityID);
for (CNPlayer viewer : viewerArray) {
owner.untrackPassengers(viewer, entityID);
CustomNameplates.getInstance().getPacketSender().sendPacket(viewer, packet);
}
this.isShown = false;
this.viewers.clear();
resetViewerArray();
}
@Override
public Vector3 scale(CNPlayer viewer) {
return config.scale();
}
@Override
public Vector3 translation(CNPlayer viewer) {
return config.translation().add(0, renderer.hatOffset(), 0);
}
@Override
public String name() {
return "UnlimitedTag";
}
@Override
public Set<Placeholder> activePlaceholders() {
if (currentText == null || !isShown()) return Collections.emptySet();
return currentText.placeholders();
}
@Override
public Set<Placeholder> allPlaceholders() {
HashSet<Placeholder> placeholders = new HashSet<>();
for (CarouselText text : config.carouselTexts()) {
placeholders.addAll(text.preParsedDynamicText().placeholders());
}
return placeholders;
}
@Override
public byte opacity() {
return config.opacity();
}
@Override
public String id() {
return "nametag";
}
@Override
public boolean affectedByCrouching() {
return config.affectedByScaling();
}
@Override
public boolean affectedByScaling() {
return affectedByCrouching();
}
}

View File

@@ -0,0 +1,298 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.tag;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.Feature;
import net.momirealms.customnameplates.api.feature.tag.NameTagConfig;
import net.momirealms.customnameplates.api.feature.tag.Tag;
import net.momirealms.customnameplates.api.feature.tag.TagRenderer;
import net.momirealms.customnameplates.api.feature.tag.UnlimitedTagManager;
import net.momirealms.customnameplates.api.network.Tracker;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
public class TagRendererImpl implements TagRenderer {
private final CNPlayer owner;
private final UnlimitedTagManager manager;
private Tag[] tags;
private double hatOffset;
public TagRendererImpl(UnlimitedTagManager manager, CNPlayer owner) {
this.owner = owner;
this.manager = manager;
List<NameTag> senderList = new ArrayList<>();
for (NameTagConfig config : manager.nameTagConfigs()) {
NameTag sender = new NameTag(owner, config, this);
senderList.add(sender);
this.owner.addFeature(sender);
}
this.tags = senderList.toArray(new Tag[0]);
}
@Override
public double hatOffset() {
return hatOffset;
}
@Override
public void hatOffset(double hatOffset) {
if (hatOffset != this.hatOffset) {
this.hatOffset = hatOffset;
for (Tag tag : tags) {
tag.updateTranslation();
}
}
}
@Override
public void onTick() {
HashSet<CNPlayer> playersToUpdatePassengers = new HashSet<>();
for (Tag display : tags) {
boolean canShow = display.canShow();
// 能大众显示
if (canShow) {
// 当前大众显示
if (display.isShown()) {
for (CNPlayer nearby : owner.nearbyPlayers()) {
// 如果已经展示了
if (display.isShown(nearby)) {
// 不满足条件就撤掉
if (!display.canShow(nearby)) {
display.hide(nearby);
}
} else {
// 未展示,则检测条件,可以就上
if (display.canShow(nearby)) {
display.show(nearby);
playersToUpdatePassengers.add(nearby);
}
}
}
// 更新一下文字顺序放在后面是为了防止已经被hide的玩家多收一个包
display.tick();
} else {
// 之前隐藏,现在开始大众显示
// 需要重置文字顺序
display.init();
// 更新一下文字顺序
display.tick();
display.show();
for (CNPlayer nearby : owner.nearbyPlayers()) {
if (display.canShow(nearby) && !display.isShown(nearby)) {
display.show(nearby);
playersToUpdatePassengers.add(nearby);
}
}
}
} else {
// 不能展示的情况
// 如果已经展示了,就咔掉所有玩家
if (display.isShown()) {
display.hide();
}
}
}
// Update passengers
Set<Integer> realPassengers = owner.passengers();
for (CNPlayer nearby : playersToUpdatePassengers) {
updatePassengers(nearby, realPassengers);
}
}
@Override
public void destroy() {
for (Tag tag : this.tags) {
tag.hide();
if (tag instanceof Feature feature) {
this.owner.removeFeature(feature);
}
}
}
public void handlePlayerRemove(CNPlayer another) {
for (Tag display : this.tags) {
if (display.isShown()) {
if (display.isShown(another)) {
display.hide(another);
}
}
}
}
@Override
public void addTag(Tag tag) {
Tag[] newTags = new Tag[this.tags.length + 1];
System.arraycopy(this.tags, 0, newTags, 0, this.tags.length);
newTags[this.tags.length] = tag;
this.tags = newTags;
if (tag instanceof Feature feature) {
this.owner.addFeature(feature);
}
}
@Override
public Tag[] tags() {
return this.tags;
}
@Override
public int removeTagIf(Predicate<Tag> predicate) {
Set<Integer> removedIndexes = new HashSet<>();
for (int i = 0; i < this.tags.length; i++) {
if (predicate.test(this.tags[i])) {
removedIndexes.add(i);
this.tags[i].hide();
if (this.tags[i] instanceof Feature feature) {
this.owner.removeFeature(feature);
}
}
}
if (removedIndexes.isEmpty()) {
return 0;
}
Tag[] newTags = new Tag[this.tags.length - removedIndexes.size()];
int newIndex = 0;
for (int i = 0; i < this.tags.length; i++) {
if (!removedIndexes.contains(i)) {
newTags[newIndex++] = this.tags[i];
}
}
this.tags = newTags;
return removedIndexes.size();
}
@Override
public int tagIndex(Tag tag) {
for (int i = 0; i < this.tags.length; i++) {
if (this.tags[i].equals(tag)) {
return i;
}
}
return -1;
}
@Override
public void addTag(Tag tag, int index) {
if (index < 0 || index > this.tags.length) {
throw new IndexOutOfBoundsException("Index out of bounds: " + index);
}
Tag[] newTags = new Tag[this.tags.length + 1];
System.arraycopy(this.tags, 0, newTags, 0, index);
newTags[index] = tag;
System.arraycopy(this.tags, index, newTags, index + 1, this.tags.length - index);
this.tags = newTags;
tag.show();
if (tag instanceof Feature feature) {
this.owner.addFeature(feature);
}
}
@Override
public void removeTag(Tag tag) {
int i = 0;
boolean has = false;
for (Tag display : this.tags) {
if (display == tag) {
has = true;
break;
}
i++;
}
if (has) {
Tag[] newTags = new Tag[this.tags.length - 1];
System.arraycopy(this.tags, 0, newTags, 0, i);
System.arraycopy(this.tags, i + 1, newTags, i, (this.tags.length - i) - 1);
this.tags = newTags;
tag.hide();
if (tag instanceof Feature feature) {
this.owner.removeFeature(feature);
}
}
}
public void handlePlayerAdd(CNPlayer another) {
boolean updatePassengers = false;
for (Tag display : this.tags) {
if (display.isShown()) {
if (!display.isShown(another)) {
if (display.canShow(another)) {
display.show(another);
updatePassengers = true;
}
}
}
}
if (updatePassengers) {
Set<Integer> realPassengers = owner.passengers();
updatePassengers(another, realPassengers);
}
}
private void updatePassengers(CNPlayer another, Set<Integer> realPassengers) {
Set<Integer> fakePassengers = owner.getTrackedPassengerIds(another);
fakePassengers.addAll(realPassengers);
int[] passengers = new int[fakePassengers.size()];
int index = 0;
for (int passenger : fakePassengers) {
passengers[index++] = passenger;
}
Object packet = CustomNameplates.getInstance().getPlatform().setPassengersPacket(owner.entityID(), passengers);
CustomNameplates.getInstance().getPacketSender().sendPacket(another, packet);
}
public void handleEntityDataChange(CNPlayer another, boolean isCrouching) {
Tracker properties = owner.getTracker(another);
// should never be null
if (properties == null) return;
properties.setCrouching(isCrouching);
for (Tag display : this.tags) {
if (display.affectedByCrouching()) {
if (display.isShown()) {
if (display.isShown(another)) {
display.onPlayerCrouching(another, isCrouching);
}
}
}
}
}
public void handleAttributeChange(CNPlayer another, double scale) {
boolean updatePassengers = false;
Tracker properties = owner.getTracker(another);
// should never be null
if (properties == null) return;
properties.setScale(scale);
for (Tag display : this.tags) {
if (display.affectedByScaling()) {
if (display.isShown()) {
if (display.isShown(another)) {
display.onPlayerScaleUpdate(another, scale);
}
}
}
}
}
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.feature.tag;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import net.momirealms.customnameplates.api.AbstractCNPlayer;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.CarouselText;
import net.momirealms.customnameplates.api.feature.JoinQuitListener;
import net.momirealms.customnameplates.api.feature.tag.NameTagConfig;
import net.momirealms.customnameplates.api.feature.tag.TagRenderer;
import net.momirealms.customnameplates.api.feature.tag.UnlimitedTagManager;
import net.momirealms.customnameplates.api.helper.VersionHelper;
import net.momirealms.customnameplates.api.network.Tracker;
import net.momirealms.customnameplates.api.requirement.Requirement;
import net.momirealms.customnameplates.api.util.Alignment;
import net.momirealms.customnameplates.api.util.ConfigUtils;
import net.momirealms.customnameplates.api.util.Vector3;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class UnlimitedTagManagerImpl implements UnlimitedTagManager, JoinQuitListener {
private final CustomNameplates plugin;
private final LinkedHashMap<String, NameTagConfig> configs = new LinkedHashMap<>();
private final ConcurrentHashMap<UUID, TagRendererImpl> tagRenderers = new ConcurrentHashMap<>();
private NameTagConfig[] configArray = new NameTagConfig[0];
private int previewDuration;
private boolean alwaysShow;
public UnlimitedTagManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void setPreviewing(CNPlayer player, boolean preview) {
boolean isPreviewing = player.isPreviewing();
if (isPreviewing) {
if (preview) return;
plugin.getUnlimitedTagManager().onRemovePlayer(player, player);
player.removePlayerFromTracker(player);
((AbstractCNPlayer) player).setPreviewing(false);
} else {
if (!preview) return;
Tracker tracker = player.addPlayerToTracker(player);
tracker.setScale(player.scale());
tracker.setCrouching(player.isCrouching());
plugin.getUnlimitedTagManager().onAddPlayer(player, player);
((AbstractCNPlayer) player).setPreviewing(true);
}
}
@Override
public int previewDuration() {
return previewDuration;
}
@Override
public void onPlayerJoin(CNPlayer player) {
TagRendererImpl sender = new TagRendererImpl(this, player);
sender.onTick();
TagRendererImpl previous = tagRenderers.put(player.uuid(), sender);
if (previous != null) {
previous.destroy();
}
setPreviewing(player, isAlwaysShow());
}
@Override
public void onPlayerQuit(CNPlayer player) {
TagRendererImpl sender = tagRenderers.remove(player.uuid());
if (sender != null) {
sender.destroy();
}
}
@Override
public void load() {
if (!ConfigManager.nameplateModule()) return;
this.loadConfig();
this.resetArray();
for (CNPlayer online : plugin.getOnlinePlayers()) {
onPlayerJoin(online);
}
}
@Override
public void unload() {
for (TagRendererImpl sender : tagRenderers.values()) {
sender.destroy();
}
this.tagRenderers.clear();
this.configs.clear();
this.resetArray();
}
@Override
public void onTick() {
for (TagRendererImpl sender : tagRenderers.values()) {
sender.onTick();
}
}
@Override
public boolean isAlwaysShow() {
return alwaysShow;
}
private void resetArray() {
configArray = configs.values().toArray(new NameTagConfig[0]);
}
@Override
public NameTagConfig configById(String name) {
return configs.get(name);
}
@Override
public NameTagConfig[] nameTagConfigs() {
return configArray;
}
@Override
public void onAddPlayer(CNPlayer owner, CNPlayer added) {
TagRendererImpl controller = tagRenderers.get(owner.uuid());
if (controller != null) {
controller.handlePlayerAdd(added);
}
}
@Override
public TagRenderer getTagRender(CNPlayer owner) {
return tagRenderers.get(owner.uuid());
}
@Override
public void onRemovePlayer(CNPlayer owner, CNPlayer removed) {
TagRendererImpl controller = tagRenderers.get(owner.uuid());
if (controller != null) {
controller.handlePlayerRemove(removed);
}
}
@Override
public void onPlayerDataSet(CNPlayer owner, CNPlayer viewer, boolean isCrouching) {
TagRendererImpl controller = tagRenderers.get(owner.uuid());
if (controller != null) {
controller.handleEntityDataChange(viewer, isCrouching);
}
}
@Override
public void onPlayerAttributeSet(CNPlayer owner, CNPlayer viewer, double scale) {
TagRendererImpl controller = tagRenderers.get(owner.uuid());
if (controller != null) {
controller.handleAttributeChange(viewer, scale);
}
}
private void loadConfig() {
plugin.getConfigManager().saveResource("configs" + File.separator + "nameplate.yml");
YamlDocument document = plugin.getConfigManager().loadData(new File(plugin.getDataDirectory().toFile(), "configs" + File.separator + "nameplate.yml"));
previewDuration = document.getInt("preview-duration", 5);
alwaysShow = document.getBoolean("always-show", false);
Section unlimitedSection = document.getSection("unlimited");
if (unlimitedSection == null) return;
for (Map.Entry<String, Object> entry : unlimitedSection.getStringRouteMappedValues(false).entrySet()) {
if (!(entry.getValue() instanceof Section section))
return;
Vector3 translation = ConfigUtils.vector3(section.getString("translation", "0,0,0"));
this.configs.put(entry.getKey(),
NameTagConfig.builder()
.id(entry.getKey())
.ownerRequirement(plugin.getRequirementManager().parseRequirements(section.getSection("owner-conditions")))
.viewerRequirement(plugin.getRequirementManager().parseRequirements(section.getSection("viewer-conditions")))
.translation(VersionHelper.isVersionNewerThan1_20_2() ? translation : translation.add(0,0.5,0))
.scale(ConfigUtils.vector3(section.getString("scale", "1,1,1")))
.alignment(Alignment.valueOf(section.getString("alignment", "CENTER")))
.viewRange(section.getFloat("view-range", 1f))
.shadowRadius(section.getFloat("shadow-radius", 0f))
.shadowStrength(section.getFloat("shadow-strength", 1f))
.lineWidth(section.getInt("line-width", 200))
.hasShadow(section.getBoolean("has-shadow", false))
.seeThrough(section.getBoolean("is-see-through", false))
.opacity(section.getByte("opacity", (byte) -1))
.useDefaultBackgroundColor(section.getBoolean("use-default-background-color", false))
.backgroundColor(ConfigUtils.argb(section.getString("background-color", "64,0,0,0")))
.affectedByCrouching(section.getBoolean("affected-by-crouching", true))
.affectedByScaling(section.getBoolean("affected-by-scale-attribute", true))
.carouselText(
section.contains("text") ?
new CarouselText[]{new CarouselText(-1, new Requirement[0], section.getString("text"), false)} :
ConfigUtils.carouselTexts(section.getSection("text-display-order"))
)
.build()
);
}
}
}

View File

@@ -0,0 +1,708 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.placeholder;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.ConfigManager;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.MainTask;
import net.momirealms.customnameplates.api.feature.*;
import net.momirealms.customnameplates.api.feature.background.Background;
import net.momirealms.customnameplates.api.feature.bubble.Bubble;
import net.momirealms.customnameplates.api.feature.bubble.BubbleConfig;
import net.momirealms.customnameplates.api.feature.image.Image;
import net.momirealms.customnameplates.api.feature.nameplate.Nameplate;
import net.momirealms.customnameplates.api.helper.AdventureHelper;
import net.momirealms.customnameplates.api.placeholder.*;
import net.momirealms.customnameplates.api.placeholder.internal.*;
import net.momirealms.customnameplates.api.requirement.Requirement;
import net.momirealms.customnameplates.api.util.Vector3;
import net.momirealms.customnameplates.common.util.Pair;
import java.io.File;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.Objects.requireNonNull;
@SuppressWarnings("DuplicatedCode")
public class PlaceholderManagerImpl implements PlaceholderManager {
private static final Pattern PATTERN = Pattern.compile("%([^%]*)%");
private final CustomNameplates plugin;
private final HashMap<String, Integer> refreshIntervals = new HashMap<>();
private final Map<String, Placeholder> registeredPlaceholders = new HashMap<>();
private final HashMap<Placeholder, List<PreParsedDynamicText>> childrenText = new HashMap<>();
private final List<PreParsedDynamicText> delayedInitTexts = new ArrayList<>();
private final HashMap<String, Placeholder> nestedPlaceholders = new HashMap<>();
public PlaceholderManagerImpl(CustomNameplates plugin) {
this.plugin = plugin;
}
@Override
public void load() {
YamlDocument document = ConfigManager.getMainConfig();
Section section = document.getSection("other-settings.placeholder-refresh-interval");
if (section != null) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
Object value = entry.getValue();
if (value instanceof Integer) {
refreshIntervals.put(id, (Integer) value);
}
}
}
this.loadInternalPlaceholders();
this.loadNestNameplatePlaceholders();
this.loadCustomPlaceholders();
for (Map.Entry<Placeholder, List<PreParsedDynamicText>> entry : this.childrenText.entrySet()) {
Placeholder placeholder = entry.getKey();
for (PreParsedDynamicText preParsedText : entry.getValue()) {
preParsedText.init();
placeholder.addChildren(preParsedText.placeholders());
}
this.nestedPlaceholders.put(placeholder.id().replace("%np_", "%nameplates_").replace("%rel_np_", "%rel_nameplates_"), placeholder);
}
this.childrenText.clear();
for (PreParsedDynamicText preParsedText : this.delayedInitTexts) {
preParsedText.init();
}
this.delayedInitTexts.clear();
}
@Override
public void unload() {
this.refreshIntervals.clear();
this.registeredPlaceholders.clear();
this.delayedInitTexts.clear();
this.childrenText.clear();
this.nestedPlaceholders.clear();
PlaceholderCounter.reset();
}
private void loadNestNameplatePlaceholders() {
PreParsedDynamicText nameTag = new PreParsedDynamicText(plugin.getNameplateManager().playerNameTag());
Placeholder placeholder1 = this.registerPlayerPlaceholder("%np_tag-image%", (player -> {
String equippedNameplate = player.equippedNameplate();
if (equippedNameplate.equals("none")) equippedNameplate = plugin.getNameplateManager().defaultNameplateId();
Nameplate nameplate = plugin.getNameplateManager().nameplateById(equippedNameplate);
if (nameplate == null) return "";
String tag = nameTag.fastCreate(player).render(player);
float advance = plugin.getAdvanceManager().getLineAdvance(tag);
return AdventureHelper.surroundWithNameplatesFont(nameplate.createImage(advance, 1, 0));
}));
Placeholder placeholder2 = this.registerRelationalPlaceholder("%rel_np_tag-image%", (p1, p2) -> {
String equippedNameplate = p1.equippedNameplate();
if (equippedNameplate.equals("none")) equippedNameplate = plugin.getNameplateManager().defaultNameplateId();
Nameplate nameplate = plugin.getNameplateManager().nameplateById(equippedNameplate);
if (nameplate == null) return "";
String tag = nameTag.fastCreate(p1).render(p2);
float advance = plugin.getAdvanceManager().getLineAdvance(tag);
return AdventureHelper.surroundWithNameplatesFont(nameplate.createImage(advance, 1, 0));
});
Placeholder placeholder3 = this.registerPlayerPlaceholder("%np_tag-text%", (player -> nameTag.fastCreate(player).render(player)));
Placeholder placeholder4 = this.registerRelationalPlaceholder("%rel_np_tag-text%", (p1, p2) -> nameTag.fastCreate(p1).render(p2));
Placeholder placeholder5 = this.registerPlayerPlaceholder("%np_tag%", (player -> {
String equippedNameplate = player.equippedNameplate();
if (equippedNameplate.equals("none")) equippedNameplate = plugin.getNameplateManager().defaultNameplateId();
Nameplate nameplate = plugin.getNameplateManager().nameplateById(equippedNameplate);
String tag = nameTag.fastCreate(player).render(player);
if (nameplate == null) return tag;
float advance = plugin.getAdvanceManager().getLineAdvance(tag);
return AdventureHelper.surroundWithNameplatesFont(nameplate.createImagePrefix(advance, 1, 0))
+ tag + AdventureHelper.surroundWithNameplatesFont(nameplate.createImageSuffix(advance, 1, 0));
}));
Placeholder placeholder6 = this.registerRelationalPlaceholder("%rel_np_tag%", (p1, p2) -> {
String equippedNameplate = p1.equippedNameplate();
Nameplate nameplate = plugin.getNameplateManager().nameplateById(equippedNameplate);
if (nameplate == null) return p1.name();
String tag = nameTag.fastCreate(p1).render(p2);
float advance = plugin.getAdvanceManager().getLineAdvance(tag);
return AdventureHelper.surroundWithNameplatesFont(nameplate.createImagePrefix(advance, 1, 0))
+ tag + AdventureHelper.surroundWithNameplatesFont(nameplate.createImageSuffix(advance, 1, 0));
});
List<PreParsedDynamicText> list = List.of(nameTag);
childrenText.put(placeholder1, list);
childrenText.put(placeholder2, list);
childrenText.put(placeholder3, list);
childrenText.put(placeholder4, list);
childrenText.put(placeholder5, list);
childrenText.put(placeholder6, list);
}
private void loadInternalPlaceholders() {
this.registerPlayerPlaceholder("%player_name%", CNPlayer::name);
this.registerPlayerPlaceholder("%player_x%", (player -> String.valueOf((int) Math.floor(player.position().x()))));
this.registerPlayerPlaceholder("%player_y%", (player -> String.valueOf((int) Math.floor(player.position().y()))));
this.registerPlayerPlaceholder("%player_z%", (player -> String.valueOf((int) Math.floor(player.position().z()))));
this.registerPlayerPlaceholder("%player_world%", (CNPlayer::world));
this.registerPlayerPlaceholder("%player_remaining_air%", (player -> String.valueOf(player.remainingAir())));
for (int i = -256; i <= 256; i++) {
String characters = OffsetFont.createOffsets(i);
this.registerPlayerPlaceholder("%np_offset_" + i + "%", (p) -> AdventureHelper.surroundWithNameplatesFont(characters));
}
this.registerPlayerPlaceholder("%np_equipped_nameplate%", CNPlayer::equippedNameplate);
this.registerPlayerPlaceholder("%np_equipped_bubble%", CNPlayer::equippedBubble);
this.registerPlayerPlaceholder("%np_equipped_nameplate-name%", (player) -> {
Nameplate nameplate = plugin.getNameplateManager().nameplateById(player.equippedNameplate());
return Optional.ofNullable(nameplate).map(Nameplate::displayName).orElse("");
});
this.registerPlayerPlaceholder("%np_equipped_bubble-name%", (player) -> {
BubbleConfig bubble = plugin.getBubbleManager().bubbleConfigById(player.equippedBubble());
return Optional.ofNullable(bubble).map(BubbleConfig::displayName).orElse("");
});
this.registerSharedPlaceholder("%shared_np_is-latest%", () -> String.valueOf(plugin.isUpToDate()));
this.registerPlayerPlaceholder("%np_is-latest%", (player) -> String.valueOf(plugin.isUpToDate()));
this.registerPlayerPlaceholder("%np_time%", (player) -> {
long time = player.playerTime() % 24_000;
String ap = time >= 6000 && time < 18000 ? " PM" : " AM";
int hours = (int) (time / 1000) ;
int minutes = (int) ((time - hours * 1000 ) * 0.06);
hours += 6;
while (hours >= 12) hours -= 12;
if (minutes < 10) return hours + ":0" + minutes + ap;
else return hours + ":" + minutes + ap;
});
this.registerPlayerPlaceholder("%np_actionbar%", (player) -> Optional.ofNullable(plugin.getActionBarManager().getExternalActionBar(player)).orElse(""));
for (Image image : plugin.getImageManager().images()) {
this.registerSharedPlaceholder("%shared_np_image_" + image.id() + "%",
() -> AdventureHelper.surroundWithNameplatesFont(String.valueOf(image.character().character())));
this.registerPlayerPlaceholder("%np_image_" + image.id() + "%",
(player) -> AdventureHelper.surroundWithNameplatesFont(String.valueOf(image.character().character())));
this.registerSharedPlaceholder("%shared_np_img_" + image.id() + "%",
() -> String.valueOf(image.character().character()));
this.registerPlayerPlaceholder("%np_img_" + image.id() + "%",
(player) -> String.valueOf(image.character().character()));
}
this.registerRelationalPlaceholder("%rel_np_distance%", (p1, p2) -> {
Vector3 loc1 = p1.position();
Vector3 loc2 = p2.position();
double distance = Math.sqrt(Math.pow(loc1.x() - loc2.x(), 2) + Math.pow(loc1.y() - loc2.y(), 2) + Math.pow(loc1.z() - loc2.z(), 2));
return String.format("%.2f", distance);
});
}
private void loadCustomPlaceholders() {
plugin.getConfigManager().saveResource("configs" + File.separator + "custom-placeholders.yml");
YamlDocument document = plugin.getConfigManager().loadData(new File(plugin.getDataDirectory().toFile(), "configs" + File.separator + "custom-placeholders.yml"));
Section section1 = document.getSection("conditional-text");
if (section1 != null) loadConditionTextSection(section1);
Section section2 = document.getSection("background-text");
if (section2 != null) loadBackgroundTextSection(section2);
Section section3 = document.getSection("nameplate-text");
if (section3 != null) loadNameplateTextSection(section3);
Section section4 = document.getSection("bubble-text");
if (section4 != null) loadBubbleTextSection(section4);
Section section5 = document.getSection("switch-text");
if (section5 != null) loadSwitchTextSection(section5);
Section section6 = document.getSection("vanilla-hud");
if (section6 != null) loadVanillaHud(section6);
Section section7 = document.getSection("static-text");
if (section7 != null) loadStaticText(section7);
Section section8 = document.getSection("shift-text");
if (section8 != null) loadShiftText(section8);
}
private void loadShiftText(Section section) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
if (entry.getValue() instanceof Section inner) {
String text = inner.getString("text");
String font = inner.getString("font");
ShiftText shiftText = new ShiftText(font, text);
Placeholder placeholder1 = registerSharedPlaceholder("%shared_np_shift_" + id + "%", () -> {
String parsed = shiftText.getText().fastCreate(null).render(null);
return AdventureHelper.surroundWithMiniMessageFont(parsed, ConfigManager.namespace() + ":" + font);
});
Placeholder placeholder2 = registerPlayerPlaceholder("%np_shift_" + id + "%", (player) -> {
String parsed = shiftText.getText().fastCreate(player).render(player);
return AdventureHelper.surroundWithMiniMessageFont(parsed, ConfigManager.namespace() + ":" + font);
});
Placeholder placeholder3 = registerRelationalPlaceholder("%rel_np_shift_" + id + "%", (p1, p2) -> {
String parsed = shiftText.getText().fastCreate(p1).render(p2);
return AdventureHelper.surroundWithMiniMessageFont(parsed, ConfigManager.namespace() + ":" + font);
});
List<PreParsedDynamicText> list = List.of(shiftText.getText());
childrenText.put(placeholder1, list);
childrenText.put(placeholder2, list);
childrenText.put(placeholder3, list);
}
}
}
private void loadStaticText(Section section) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
if (entry.getValue() instanceof Section inner) {
StaticPosition position = StaticPosition.valueOf(inner.getString("position", "middle").toUpperCase(Locale.ENGLISH));
int totalWidth = inner.getInt("value", 100);
StaticText staticText = new StaticText(totalWidth, position, inner.getString("text"));
Placeholder placeholder1 = registerSharedPlaceholder("%shared_np_static_" + id + "%", staticText::create);
Placeholder placeholder2 = registerPlayerPlaceholder("%np_static_" + id + "%", staticText::create);
Placeholder placeholder3 = registerRelationalPlaceholder("%rel_np_static_" + id + "%", staticText::create);
List<PreParsedDynamicText> list = List.of(staticText.getText());
childrenText.put(placeholder1, list);
childrenText.put(placeholder2, list);
childrenText.put(placeholder3, list);
}
}
}
private void loadVanillaHud(Section section) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
if (entry.getValue() instanceof Section inner) {
boolean reverse = inner.getBoolean("reverse", true);
Image empty = requireNonNull(plugin.getImageManager().imageById(inner.getString("images.empty")), "image.empty should not be null");
Image half = requireNonNull(plugin.getImageManager().imageById(inner.getString("images.half")), "image.half should not be null");
Image full = requireNonNull(plugin.getImageManager().imageById(inner.getString("images.full")), "image.full should not be null");
String currentValue = section.getString("placeholder.value", "1");
String maxValue = section.getString("placeholder.max-value", currentValue);
boolean removeShadow = section.getBoolean("remove-shadow", false);
VanillaHud vanillaHud = new VanillaHud(empty, half, full, reverse, currentValue, maxValue, removeShadow);
List<PreParsedDynamicText> list = List.of(vanillaHud.getCurrent(), vanillaHud.getMax());
Placeholder placeholder1 = registerSharedPlaceholder("%shared_np_vanilla_" + id + "%", vanillaHud::create);
Placeholder placeholder2 = registerPlayerPlaceholder("%np_vanilla_" + id + "%", vanillaHud::create);
Placeholder placeholder3 = registerRelationalPlaceholder("%np_vanilla_" + id + "%", vanillaHud::create);
childrenText.put(placeholder1, list);
childrenText.put(placeholder2, list);
childrenText.put(placeholder3, list);
}
}
}
private void loadSwitchTextSection(Section section) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
if (entry.getValue() instanceof Section inner) {
PreParsedDynamicText placeholderToSwitch = new PreParsedDynamicText(inner.getString("switch"));
PreParsedDynamicText defaultValue = new PreParsedDynamicText(inner.getString("default", ""));
ArrayList<PreParsedDynamicText> list = new ArrayList<>();
list.add(placeholderToSwitch);
this.delayedInitTexts.add(defaultValue);
Map<String, PreParsedDynamicText> valueMap = new HashMap<>();
Section results = inner.getSection("case");
if (results != null) {
for (Map.Entry<String, Object> strEntry : results.getStringRouteMappedValues(false).entrySet()) {
if (strEntry.getValue() instanceof String string) {
PreParsedDynamicText preParsedDynamicText = new PreParsedDynamicText(string);
valueMap.put(strEntry.getKey(), preParsedDynamicText);
this.delayedInitTexts.add(preParsedDynamicText);
}
}
}
Placeholder placeholder1 = registerSharedPlaceholder("%shared_np_switch_" + id + "%", () -> {
String value = placeholderToSwitch.fastCreate(null).render(null);
PreParsedDynamicText text = valueMap.getOrDefault(value, defaultValue);
return text.fastCreate(null).render(null);
});
Placeholder placeholder2 = registerPlayerPlaceholder("%np_switch_" + id + "%", (player) -> {
String value = placeholderToSwitch.fastCreate(player).render(player);
PreParsedDynamicText text = valueMap.getOrDefault(value, defaultValue);
player.forceUpdate(text.placeholders(), Collections.emptySet());
return text.fastCreate(player).render(player);
});
Placeholder placeholder3 = registerRelationalPlaceholder("%rel_np_switch_" + id + "%", (p1, p2) -> {
String value = placeholderToSwitch.fastCreate(p1).render(p2);
PreParsedDynamicText text = valueMap.getOrDefault(value, defaultValue);
p1.forceUpdate(text.placeholders(), Set.of(p2));
return text.fastCreate(p1).render(p2);
});
childrenText.put(placeholder1, list);
childrenText.put(placeholder2, list);
childrenText.put(placeholder3, list);
}
}
}
private void loadBubbleTextSection(Section section) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
if (entry.getValue() instanceof Section inner) {
String bbID = inner.getString("bubble");
Bubble bubble = plugin.getBubbleManager().bubbleById(bbID);
if (bubble != null) {
AdaptiveImageText<Bubble> adaptiveImageText = AdaptiveImageText.create(
id, inner.getString("text"), bubble, inner.getBoolean("remove-shadow"),
inner.getInt("left-margin", 1), inner.getInt("right-margin", 1)
);
List<PreParsedDynamicText> list = new ArrayList<>();
list.add(adaptiveImageText.getPreParsedDynamicText());
Placeholder placeholder1 = this.registerSharedPlaceholder("%shared_np_bubble_" + id + "%", () -> adaptiveImageText.getFull(null, null));
Placeholder placeholder2 = this.registerPlayerPlaceholder("%np_bubble_" + id + "%", (p) -> adaptiveImageText.getFull(p, p));
Placeholder placeholder3 = this.registerRelationalPlaceholder("%rel_np_bubble_" + id + "%", adaptiveImageText::getFull);
Placeholder placeholder4 = this.registerSharedPlaceholder("%shared_np_bubble-text_" + id + "%", () -> adaptiveImageText.getText(null, null));
Placeholder placeholder5 = this.registerPlayerPlaceholder("%np_bubble-text_" + id + "%", (p) -> adaptiveImageText.getText(p, p));
Placeholder placeholder6 = this.registerRelationalPlaceholder("%rel_np_bubble-text_" + id + "%", adaptiveImageText::getText);
Placeholder placeholder7 = this.registerSharedPlaceholder("%shared_np_bubble-image_" + id + "%", () -> adaptiveImageText.getImage(null, null));
Placeholder placeholder8 = this.registerPlayerPlaceholder("%np_bubble-image_" + id + "%", (p) -> adaptiveImageText.getImage(p, p));
Placeholder placeholder9 = this.registerRelationalPlaceholder("%rel_np_bubble-image_" + id + "%", adaptiveImageText::getImage);
childrenText.put(placeholder1, list);
childrenText.put(placeholder2, list);
childrenText.put(placeholder3, list);
childrenText.put(placeholder4, list);
childrenText.put(placeholder5, list);
childrenText.put(placeholder6, list);
childrenText.put(placeholder7, list);
childrenText.put(placeholder8, list);
childrenText.put(placeholder9, list);
} else {
plugin.getPluginLogger().warn("Nameplate [" + bbID + "] not exists");
}
}
}
}
private void loadNameplateTextSection(Section section) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
if (entry.getValue() instanceof Section inner) {
String npID = inner.getString("nameplate");
Nameplate nameplate = plugin.getNameplateManager().nameplateById(npID);
if (nameplate != null) {
AdaptiveImageText<Nameplate> adaptiveImageText = AdaptiveImageText.create(
id, inner.getString("text"), nameplate, inner.getBoolean("remove-shadow"),
inner.getInt("left-margin", 1), inner.getInt("right-margin", 1)
);
List<PreParsedDynamicText> list = new ArrayList<>();
list.add(adaptiveImageText.getPreParsedDynamicText());
Placeholder placeholder1 = this.registerSharedPlaceholder("%shared_np_nameplate_" + id + "%", () -> adaptiveImageText.getFull(null, null));
Placeholder placeholder2 = this.registerPlayerPlaceholder("%np_nameplate_" + id + "%", (p) -> adaptiveImageText.getFull(p, p));
Placeholder placeholder3 = this.registerRelationalPlaceholder("%rel_np_nameplate_" + id + "%", adaptiveImageText::getFull);
Placeholder placeholder4 = this.registerSharedPlaceholder("%shared_np_nameplate-text_" + id + "%", () -> adaptiveImageText.getText(null, null));
Placeholder placeholder5 = this.registerPlayerPlaceholder("%np_nameplate-text_" + id + "%", (p) -> adaptiveImageText.getText(p, p));
Placeholder placeholder6 = this.registerRelationalPlaceholder("%rel_np_nameplate-text_" + id + "%", adaptiveImageText::getText);
Placeholder placeholder7 = this.registerSharedPlaceholder("%shared_np_nameplate-image_" + id + "%", () -> adaptiveImageText.getImage(null, null));
Placeholder placeholder8 = this.registerPlayerPlaceholder("%np_nameplate-image_" + id + "%", (p) -> adaptiveImageText.getImage(p, p));
Placeholder placeholder9 = this.registerRelationalPlaceholder("%rel_np_nameplate-image_" + id + "%", adaptiveImageText::getImage);
childrenText.put(placeholder1, list);
childrenText.put(placeholder2, list);
childrenText.put(placeholder3, list);
childrenText.put(placeholder4, list);
childrenText.put(placeholder5, list);
childrenText.put(placeholder6, list);
childrenText.put(placeholder7, list);
childrenText.put(placeholder8, list);
childrenText.put(placeholder9, list);
} else {
plugin.getPluginLogger().warn("Nameplate [" + npID + "] not exists");
}
}
}
}
private void loadBackgroundTextSection(Section section) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
if (entry.getValue() instanceof Section inner) {
String bgID = inner.getString("background");
Background background = plugin.getBackgroundManager().backgroundById(bgID);
if (background != null) {
AdaptiveImageText<Background> adaptiveImageText = AdaptiveImageText.create(
id, inner.getString("text"), background, inner.getBoolean("remove-shadow"),
inner.getInt("left-margin", 2), inner.getInt("right-margin", 0)
);
List<PreParsedDynamicText> list = new ArrayList<>();
list.add(adaptiveImageText.getPreParsedDynamicText());
Placeholder placeholder1 = this.registerSharedPlaceholder("%shared_np_background_" + id + "%", () -> adaptiveImageText.getFull(null, null));
Placeholder placeholder2 = this.registerPlayerPlaceholder("%np_background_" + id + "%", (p) -> adaptiveImageText.getFull(p, p));
Placeholder placeholder3 = this.registerRelationalPlaceholder("%rel_np_background_" + id + "%", adaptiveImageText::getFull);
Placeholder placeholder4 = this.registerSharedPlaceholder("%shared_np_background-text_" + id + "%", () -> adaptiveImageText.getText(null, null));
Placeholder placeholder5 = this.registerPlayerPlaceholder("%np_background-text_" + id + "%", (p) -> adaptiveImageText.getText(p, p));
Placeholder placeholder6 = this.registerRelationalPlaceholder("%rel_np_background-text_" + id + "%", adaptiveImageText::getText);
Placeholder placeholder7 = this.registerSharedPlaceholder("%shared_np_background-image_" + id + "%", () -> adaptiveImageText.getImage(null, null));
Placeholder placeholder8 = this.registerPlayerPlaceholder("%np_background-image_" + id + "%", (p) -> adaptiveImageText.getImage(p, p));
Placeholder placeholder9 = this.registerRelationalPlaceholder("%rel_np_background-image_" + id + "%", adaptiveImageText::getImage);
childrenText.put(placeholder1, list);
childrenText.put(placeholder2, list);
childrenText.put(placeholder3, list);
childrenText.put(placeholder4, list);
childrenText.put(placeholder5, list);
childrenText.put(placeholder6, list);
childrenText.put(placeholder7, list);
childrenText.put(placeholder8, list);
childrenText.put(placeholder9, list);
} else {
plugin.getPluginLogger().warn("Background [" + bgID + "] not exists");
}
}
}
}
private void loadConditionTextSection(Section section) {
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String id = entry.getKey();
if (entry.getValue() instanceof Section placeholderSection) {
ArrayList<Pair<PreParsedDynamicText, Requirement[]>> orderedTexts = new ArrayList<>();
for (Map.Entry<String, Object> conditionEntry : placeholderSection.getStringRouteMappedValues(false).entrySet()) {
if (conditionEntry.getValue() instanceof Section inner) {
String text = inner.getString("text");
Requirement[] requirements = plugin.getRequirementManager().parseRequirements(inner.getSection("conditions"));
PreParsedDynamicText preParsedDynamicText = new PreParsedDynamicText(text);
orderedTexts.add(Pair.of(preParsedDynamicText, requirements));
this.delayedInitTexts.add(preParsedDynamicText);
}
}
this.registerSharedPlaceholder("%shared_np_conditional_" + id + "%", () -> {
outer:
for (Pair<PreParsedDynamicText, Requirement[]> orderedText : orderedTexts) {
for (Requirement requirement : orderedText.right()) {
if (!requirement.isSatisfied(null, null)) {
continue outer;
}
}
return orderedText.left().fastCreate(null).render(null);
}
return "";
});
this.registerPlayerPlaceholder("%np_conditional_" + id + "%", (player) -> {
for (Pair<PreParsedDynamicText, Requirement[]> orderedText : orderedTexts) {
if (!player.isMet(orderedText.right())) {
continue;
}
PreParsedDynamicText text = orderedText.left();
player.forceUpdate(text.placeholders(), Collections.emptySet());
return text.fastCreate(player).render(player);
}
return "";
});
this.registerRelationalPlaceholder("%rel_np_conditional_" + id + "%", (p1, p2) -> {
for (Pair<PreParsedDynamicText, Requirement[]> orderedText : orderedTexts) {
if (!p1.isMet(p2, orderedText.right())) {
continue;
}
PreParsedDynamicText text = orderedText.left();
p1.forceUpdate(text.placeholders(), Set.of(p2));
return text.fastCreate(p1).render(p2);
}
return "";
});
}
}
}
@Override
public void refreshPlaceholders() {
for (CNPlayer player : plugin.getOnlinePlayers()) {
if (!player.isOnline()) continue;
Set<Feature> featuresToNotifyUpdates = new HashSet<>();
Map<Feature, List<CNPlayer>> relationalFeaturesToNotifyUpdates = new HashMap<>();
List<Placeholder> placeholdersToUpdate = player.activePlaceholdersToRefresh();
List<RelationalPlaceholder> delayedPlaceholdersToUpdate = new ArrayList<>();
for (Placeholder placeholder : placeholdersToUpdate) {
if (placeholder instanceof PlayerPlaceholder playerPlaceholder) {
TickStampData<String> previous = player.getValue(placeholder);
if (previous == null) {
String value = playerPlaceholder.request(player);
player.setValue(placeholder, new TickStampData<>(value, MainTask.getTicks(), true));
featuresToNotifyUpdates.addAll(player.activeFeatures(placeholder));
} else {
if (previous.ticks() == MainTask.getTicks()) {
if (previous.hasValueChanged()) {
featuresToNotifyUpdates.addAll(player.activeFeatures(placeholder));
}
continue;
}
String value = playerPlaceholder.request(player);
if (!previous.data().equals(value)) {
previous.data(value);
previous.updateTicks(true);
featuresToNotifyUpdates.addAll(player.activeFeatures(placeholder));
} else {
previous.updateTicks(false);
}
}
} else if (placeholder instanceof RelationalPlaceholder relationalPlaceholder) {
delayedPlaceholdersToUpdate.add(relationalPlaceholder);
} else if (placeholder instanceof SharedPlaceholder sharedPlaceholder) {
TickStampData<String> previous = player.getValue(placeholder);
if (previous == null) {
String value;
// if the shared placeholder has been updated by other players
if (MainTask.hasRequested(sharedPlaceholder.countId())) {
value = sharedPlaceholder.getLatestValue();
} else {
value = sharedPlaceholder.request();
}
player.setValue(placeholder, new TickStampData<>(value, MainTask.getTicks(), true));
featuresToNotifyUpdates.addAll(player.activeFeatures(placeholder));
} else {
// The placeholder has been refreshed by other codes
if (previous.ticks() == MainTask.getTicks()) {
if (previous.hasValueChanged()) {
featuresToNotifyUpdates.addAll(player.activeFeatures(placeholder));
}
continue;
}
String value;
// if the shared placeholder has been updated by other players
if (MainTask.hasRequested(sharedPlaceholder.countId())) {
value = sharedPlaceholder.getLatestValue();
} else {
value = sharedPlaceholder.request();
}
if (!previous.data().equals(value)) {
previous.data(value);
previous.updateTicks(true);
featuresToNotifyUpdates.addAll(player.activeFeatures(placeholder));
} else {
previous.updateTicks(false);
}
}
}
}
for (RelationalPlaceholder placeholder : delayedPlaceholdersToUpdate) {
for (CNPlayer nearby : player.nearbyPlayers()) {
TickStampData<String> previous = player.getRelationalValue(placeholder, nearby);
if (previous == null) {
String value = placeholder.request(player, nearby);
player.setRelationalValue(placeholder, nearby, new TickStampData<>(value, MainTask.getTicks(), true));
for (Feature feature : player.activeFeatures(placeholder)) {
// Filter features that will not be updated for all players
if (!featuresToNotifyUpdates.contains(feature)) {
List<CNPlayer> players = relationalFeaturesToNotifyUpdates.computeIfAbsent(feature, k -> new ArrayList<>());
players.add(nearby);
}
}
} else {
if (previous.ticks() == MainTask.getTicks()) {
if (previous.hasValueChanged()) {
for (Feature feature : player.activeFeatures(placeholder)) {
// Filter features that will not be updated for all players
if (!featuresToNotifyUpdates.contains(feature)) {
List<CNPlayer> players = relationalFeaturesToNotifyUpdates.computeIfAbsent(feature, k -> new ArrayList<>());
players.add(nearby);
}
}
}
continue;
}
String value = placeholder.request(player, nearby);
if (!previous.data().equals(value)) {
previous.data(value);
previous.updateTicks(true);
for (Feature feature : player.activeFeatures(placeholder)) {
// Filter features that will not be updated for all players
if (!featuresToNotifyUpdates.contains(feature)) {
List<CNPlayer> players = relationalFeaturesToNotifyUpdates.computeIfAbsent(feature, k -> new ArrayList<>());
players.add(nearby);
}
}
} else {
previous.updateTicks(false);
}
}
}
}
// Switch to another thread for updating
plugin.getScheduler().async().execute(() -> {
// Async task takes time and the player might have been offline
if (!player.isOnline()) return;
for (Feature feature : featuresToNotifyUpdates) {
feature.notifyPlaceholderUpdates(player, false);
}
for (Map.Entry<Feature, List<CNPlayer>> innerEntry : relationalFeaturesToNotifyUpdates.entrySet()) {
Feature feature = innerEntry.getKey();
if (feature instanceof RelationalFeature relationalFeature) {
for (CNPlayer other : innerEntry.getValue()) {
relationalFeature.notifyPlaceholderUpdates(player, other, false);
}
}
}
});
}
}
@Override
public int getRefreshInterval(String id) {
return refreshIntervals.getOrDefault(id, ConfigManager.defaultRefreshInterval());
}
@Override
public <T extends Placeholder> T registerPlaceholder(T placeholder) {
Placeholder nested = nestedPlaceholders.get(placeholder.id());
if (nested != null) {
placeholder.addChildren(nested.children());
}
registeredPlaceholders.put(placeholder.id(), placeholder);
return placeholder;
}
@Override
public PlayerPlaceholder registerPlayerPlaceholder(String id, int refreshInterval, Function<CNPlayer, String> function) {
PlayerPlaceholderImpl impl = new PlayerPlaceholderImpl(this, id, refreshInterval, function);
return registerPlaceholder(impl);
}
@Override
public RelationalPlaceholder registerRelationalPlaceholder(String id, int refreshInterval, BiFunction<CNPlayer, CNPlayer, String> function) {
RelationalPlaceholderImpl impl = new RelationalPlaceholderImpl(this, id, refreshInterval, function);
return registerPlaceholder(impl);
}
@Override
public SharedPlaceholder registerSharedPlaceholder(String id, int refreshInterval, Supplier<String> contextSupplier) {
SharedPlaceholderImpl impl = new SharedPlaceholderImpl(this, id, refreshInterval, contextSupplier);
return registerPlaceholder(impl);
}
@Override
public Placeholder getPlaceholder(String id) {
Placeholder placeholder = registeredPlaceholders.get(id);
if (placeholder != null) return placeholder;
return plugin.getPlatform().registerPlatformPlaceholder(id);
}
@Override
public Placeholder getRegisteredPlaceholder(String id) {
return registeredPlaceholders.get(id);
}
@Override
public void unregisterPlaceholder(String id) {
registeredPlaceholders.remove(id);
}
@Override
public List<String> detectPlaceholders(String text) {
int firstPercent = text.indexOf('%');
if (firstPercent == -1) return Collections.emptyList();
if (firstPercent == 0 && text.charAt(text.length() - 1) == '%' && text.indexOf('%', 1) == text.length() - 1) {
return Collections.singletonList(text);
}
List<String> placeholders = new ArrayList<>();
Matcher m = PATTERN.matcher(text);
while (m.find()) {
placeholders.add(m.group());
}
return placeholders;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.placeholder;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.placeholder.AbstractPlaceholder;
import net.momirealms.customnameplates.api.placeholder.PlaceholderManager;
import net.momirealms.customnameplates.api.placeholder.PlayerPlaceholder;
import java.util.function.Function;
public class PlayerPlaceholderImpl extends AbstractPlaceholder implements PlayerPlaceholder {
private final Function<CNPlayer, String> function;
protected PlayerPlaceholderImpl(PlaceholderManager manager, String id, int refreshInterval, Function<CNPlayer, String> function) {
super(manager, id, refreshInterval);
this.function = function;
}
@Override
public String request(CNPlayer player) {
return function.apply(player);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.placeholder;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.placeholder.AbstractPlaceholder;
import net.momirealms.customnameplates.api.placeholder.PlaceholderManager;
import net.momirealms.customnameplates.api.placeholder.RelationalPlaceholder;
import java.util.function.BiFunction;
public class RelationalPlaceholderImpl extends AbstractPlaceholder implements RelationalPlaceholder {
private final BiFunction<CNPlayer, CNPlayer, String> function;
protected RelationalPlaceholderImpl(PlaceholderManager manager, String id, int refreshInterval, BiFunction<CNPlayer, CNPlayer, String> function) {
super(manager, id, refreshInterval);
this.function = function;
}
@Override
public String request(CNPlayer p1, CNPlayer p2) {
return function.apply(p1, p2);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.placeholder;
import net.momirealms.customnameplates.api.placeholder.AbstractPlaceholder;
import net.momirealms.customnameplates.api.placeholder.PlaceholderManager;
import net.momirealms.customnameplates.api.placeholder.SharedPlaceholder;
import java.util.function.Supplier;
public class SharedPlaceholderImpl extends AbstractPlaceholder implements SharedPlaceholder {
private final Supplier<String> supplier;
private String latestValue;
protected SharedPlaceholderImpl(PlaceholderManager manager, String id, int refreshInterval, Supplier<String> supplier) {
super(manager, id, refreshInterval);
this.supplier = supplier;
}
@Override
public String request() {
return supplier.get();
}
@Override
public void update() {
latestValue = request();
}
@Override
public String getLatestValue() {
return latestValue;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement;
import net.momirealms.customnameplates.api.requirement.Requirement;
public abstract class AbstractRequirement implements Requirement {
protected final int refreshInterval;
public AbstractRequirement(int refreshInterval) {
this.refreshInterval = refreshInterval;
}
@Override
public int refreshInterval() {
return refreshInterval;
}
}

View File

@@ -0,0 +1,263 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
import net.momirealms.customnameplates.api.requirement.Requirement;
import net.momirealms.customnameplates.api.requirement.RequirementFactory;
import net.momirealms.customnameplates.api.requirement.RequirementManager;
import net.momirealms.customnameplates.api.util.ConfigUtils;
import net.momirealms.customnameplates.backend.requirement.builtin.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public abstract class AbstractRequirementManager implements RequirementManager {
protected final CustomNameplates plugin;
private final HashMap<String, RequirementFactory> requirementFactoryMap = new HashMap<>();
public AbstractRequirementManager(CustomNameplates plugin) {
this.plugin = plugin;
this.registerInternalRequirements();
this.registerPlatformRequirements();
}
private void registerInternalRequirements() {
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("papi", ""), true);
return new NotInListRequirement(interval, dynamicText1, new HashSet<>(section.getStringList("values")));
}, "!in-list");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("papi", ""), true);
return new InListRequirement(interval, dynamicText1, new HashSet<>(section.getStringList("values")));
}, "in-list");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
String value2 = section.getString("value2", "");
return new NotEndWithRequirement(interval, dynamicText1, value2);
}, "!ends-with", "!endsWith", "!end-with", "!endWith");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
String value2 = section.getString("value2", "");
return new EndWithRequirement(interval, dynamicText1, value2);
}, "ends-with", "endsWith", "end-with", "endWith");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
String value2 = section.getString("value2", "");
return new NotStartWithRequirement(interval, dynamicText1, value2);
}, "!starts-with", "!startsWith", "!start-with", "!startWith");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
String value2 = section.getString("value2", "");
return new StartWithRequirement(interval, dynamicText1, value2);
}, "starts-with", "startsWith", "start-with", "startWith");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new NotContainsRequirement(interval, dynamicText1, dynamicText2);
}, "!contains", "!contain");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new ContainsRequirement(interval, dynamicText1, dynamicText2);
}, "contains", "contain");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
String regex = section.getString("value2", "");
return new RegexRequirement(interval, dynamicText1, regex);
}, "regex");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new NumberLessRequirement(interval, dynamicText1, dynamicText2);
}, "<");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new NumberNoGreaterRequirement(interval, dynamicText1, dynamicText2);
}, "<=");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new NumberGreaterRequirement(interval, dynamicText1, dynamicText2);
}, ">");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new NumberNoLessRequirement(interval, dynamicText1, dynamicText2);
}, ">=");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new NumberEqualsRequirement(interval, dynamicText1, dynamicText2);
}, "=");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new NumberNotEqualsRequirement(interval, dynamicText1, dynamicText2);
}, "!=");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new EqualsRequirement(interval, dynamicText1, dynamicText2);
}, "equals", "equal");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
if (section == null) return Requirement.empty();
PreParsedDynamicText dynamicText1 = new PreParsedDynamicText(section.getString("value1", ""), true);
PreParsedDynamicText dynamicText2 = new PreParsedDynamicText(section.getString("value2", ""), true);
return new NotEqualsRequirement(interval, dynamicText1, dynamicText2);
}, "!equals", "!equal");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
Requirement[] requirements = parseRequirements(section);
return new OrRequirement(interval, requirements);
}, "||", "or");
this.registerRequirement((args, interval) -> {
Section section = ConfigUtils.safeCast(args, Section.class);
Requirement[] requirements = parseRequirements(section);
return new AndRequirement(interval, requirements);
}, "&&", "and");
this.registerRequirement((args, interval) -> {
boolean has = (boolean) args;
return new HasNameplateRequirement(interval, has);
}, "has-nameplate");
this.registerRequirement((args, interval) -> {
boolean has = (boolean) args;
return new HasBubbleRequirement(interval, has);
}, "has-bubble");
}
protected abstract void registerPlatformRequirements();
@Override
public void disable() {
this.requirementFactoryMap.clear();
}
@Override
public boolean registerRequirement(@NotNull RequirementFactory requirementFactory, @NotNull String... types) {
for (String type : types) {
if (this.requirementFactoryMap.containsKey(type)) return false;
}
for (String type : types) {
this.requirementFactoryMap.put(type, requirementFactory);
}
return true;
}
@Override
public boolean unregisterRequirement(@NotNull String type) {
return this.requirementFactoryMap.remove(type) != null;
}
@Override
public @Nullable RequirementFactory getRequirementFactory(@NotNull String type) {
return requirementFactoryMap.get(type);
}
@Override
public boolean hasRequirement(@NotNull String type) {
return requirementFactoryMap.containsKey(type);
}
@NotNull
@Override
public Requirement[] parseRequirements(Section section) {
List<Requirement> requirements = new ArrayList<>();
if (section != null)
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
String typeOrName = entry.getKey();
if (hasRequirement(typeOrName)) {
requirements.add(parseSimpleRequirement(typeOrName, entry.getValue()));
} else {
Section inner = section.getSection(typeOrName);
if (inner != null) {
requirements.add(parseRichRequirement(inner));
} else {
plugin.getPluginLogger().warn("Invalid requirement type found at " + section.getRouteAsString() + "." + typeOrName);
}
}
}
return requirements.toArray(new Requirement[0]);
}
@Override
public @NotNull Requirement parseRichRequirement(@Nullable Section section) {
if (section == null) {
return Requirement.empty();
}
String type = section.getString("type");
if (type == null) {
plugin.getPluginLogger().warn("No requirement type found at " + section.getRouteAsString());
return Requirement.empty();
}
var factory = getRequirementFactory(type);
if (factory == null) {
plugin.getPluginLogger().warn("Requirement type: " + type + " not exists");
return Requirement.empty();
}
return factory.process(section.get("value"), section.getInt("refresh-interval", 10));
}
@Override
public @NotNull Requirement parseSimpleRequirement(@NotNull String type, @NotNull Object value) {
RequirementFactory factory = getRequirementFactory(type);
if (factory == null) {
plugin.getPluginLogger().warn("Requirement type: " + type + " doesn't exist.");
return Requirement.empty();
}
return factory.process(value, 10);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.requirement.Requirement;
import net.momirealms.customnameplates.backend.requirement.AbstractRequirement;
public class AndRequirement extends AbstractRequirement {
private final Requirement[] requirements;
public AndRequirement(int refreshInterval, Requirement[] requirements) {
super(refreshInterval);
this.requirements = requirements;
}
@Override
public boolean isSatisfied(CNPlayer p1, CNPlayer p2) {
for (Requirement requirement : requirements) {
if (!requirement.isSatisfied(p1, p2)) return false;
}
return true;
}
@Override
public String type() {
return "&&";
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
AndRequirement that = (AndRequirement) object;
int length = requirements.length;
if (that.requirements.length != length)
return false;
for (int i = 0; i < length; i++) {
Object e1 = requirements[i];
Object e2 = that.requirements[i];
if (e1 == e2)
continue;
if (!e1.equals(e2)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int hash = 281;
for (Requirement requirement : requirements) {
hash += requirement.hashCode();
}
return hash;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class ContainsRequirement extends PlaceholdersRequirement {
public ContainsRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return a1.contains(a2);
}
@Override
public String type() {
return "contains";
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class EndWithRequirement extends PlaceholderRequirement<String> {
public EndWithRequirement(int refreshInterval, PreParsedDynamicText text, String any) {
super(refreshInterval, text, any);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return a1.endsWith(a2);
}
@Override
public String type() {
return "ends-with";
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class EqualsRequirement extends PlaceholdersRequirement {
public EqualsRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return a1.equals(a2);
}
@Override
public String type() {
return "equals";
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.backend.requirement.AbstractRequirement;
public class HasBubbleRequirement extends AbstractRequirement {
private final boolean has;
public HasBubbleRequirement(int refreshInterval, boolean has) {
super(refreshInterval);
this.has = has;
}
@Override
public boolean isSatisfied(CNPlayer p1, CNPlayer p2) {
String bubble = p1.equippedBubble();
if (bubble.equals("none")) bubble = CustomNameplates.getInstance().getBubbleManager().defaultBubbleId();
if (has) {
return !bubble.equals("none");
} else {
return bubble.equals("none");
}
}
@Override
public String type() {
return "has-bubble";
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
HasBubbleRequirement that = (HasBubbleRequirement) object;
return has == that.has;
}
@Override
public int hashCode() {
return (has ? 781 : 3);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.backend.requirement.AbstractRequirement;
public class HasNameplateRequirement extends AbstractRequirement {
private final boolean has;
public HasNameplateRequirement(int refreshInterval, boolean has) {
super(refreshInterval);
this.has = has;
}
@Override
public boolean isSatisfied(CNPlayer p1, CNPlayer p2) {
String nameplate = p1.equippedNameplate();
if (nameplate.equals("none")) nameplate = CustomNameplates.getInstance().getNameplateManager().defaultNameplateId();
if (has) {
return !nameplate.equals("none");
} else {
return nameplate.equals("none");
}
}
@Override
public String type() {
return "has-nameplate";
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
HasNameplateRequirement that = (HasNameplateRequirement) object;
return has == that.has;
}
@Override
public int hashCode() {
return (has ? 211 : 73);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
import java.util.Set;
public class InListRequirement extends PlaceholderRequirement<Set<String>> {
public InListRequirement(int refreshInterval, PreParsedDynamicText text, Set<String> any) {
super(refreshInterval, text, any);
}
@Override
protected boolean checkArgument(String a1, Set<String> a2) {
return a2.contains(a1);
}
@Override
public String type() {
return "in-list";
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NotContainsRequirement extends PlaceholdersRequirement {
public NotContainsRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return !a1.contains(a2);
}
@Override
public String type() {
return "!contains";
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NotEndWithRequirement extends PlaceholderRequirement<String> {
public NotEndWithRequirement(int refreshInterval, PreParsedDynamicText text, String any) {
super(refreshInterval, text, any);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return !a1.endsWith(a2);
}
@Override
public String type() {
return "!ends-with";
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NotEqualsRequirement extends PlaceholdersRequirement {
public NotEqualsRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return !a1.equals(a2);
}
@Override
public String type() {
return "!equals";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
import java.util.Set;
public class NotInListRequirement extends PlaceholderRequirement<Set<String>> {
public NotInListRequirement(int refreshInterval, PreParsedDynamicText text, Set<String> any) {
super(refreshInterval, text, any);
}
@Override
protected boolean checkArgument(String a1, Set<String> a2) {
return !a2.contains(a1);
}
@Override
public String type() {
return "!in-list";
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NotStartWithRequirement extends PlaceholderRequirement<String> {
public NotStartWithRequirement(int refreshInterval, PreParsedDynamicText text, String any) {
super(refreshInterval, text, any);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return !a1.startsWith(a2);
}
@Override
public String type() {
return "!starts-with";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NumberEqualsRequirement extends PlaceholdersRequirement {
public NumberEqualsRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
double d1 = Double.parseDouble(a1);
double d2 = Double.parseDouble(a2);
return d1 == d2;
}
@Override
public String type() {
return "=";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NumberGreaterRequirement extends PlaceholdersRequirement {
public NumberGreaterRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
double d1 = Double.parseDouble(a1);
double d2 = Double.parseDouble(a2);
return d1 > d2;
}
@Override
public String type() {
return ">";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NumberLessRequirement extends PlaceholdersRequirement {
public NumberLessRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
double d1 = Double.parseDouble(a1);
double d2 = Double.parseDouble(a2);
return d1 < d2;
}
@Override
public String type() {
return "<";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NumberNoGreaterRequirement extends PlaceholdersRequirement {
public NumberNoGreaterRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
double d1 = Double.parseDouble(a1);
double d2 = Double.parseDouble(a2);
return d1 <= d2;
}
@Override
public String type() {
return "<=";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NumberNoLessRequirement extends PlaceholdersRequirement {
public NumberNoLessRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
double d1 = Double.parseDouble(a1);
double d2 = Double.parseDouble(a2);
return d1 >= d2;
}
@Override
public String type() {
return ">=";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class NumberNotEqualsRequirement extends PlaceholdersRequirement {
public NumberNotEqualsRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval, t1, t2);
}
@Override
protected boolean checkArgument(String a1, String a2) {
double d1 = Double.parseDouble(a1);
double d2 = Double.parseDouble(a2);
return d1 != d2;
}
@Override
public String type() {
return "!=";
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.requirement.Requirement;
import net.momirealms.customnameplates.backend.requirement.AbstractRequirement;
public class OrRequirement extends AbstractRequirement {
private final Requirement[] requirements;
public OrRequirement(int refreshInterval, Requirement[] requirements) {
super(refreshInterval);
this.requirements = requirements;
}
@Override
public boolean isSatisfied(CNPlayer p1, CNPlayer p2) {
if (requirements.length == 0) return true;
for (Requirement requirement : requirements) {
if (requirement.isSatisfied(p1, p2)) return true;
}
return false;
}
@Override
public String type() {
return "||";
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
OrRequirement that = (OrRequirement) object;
int length = requirements.length;
if (that.requirements.length != length)
return false;
for (int i = 0; i < length; i++) {
Object e1 = requirements[i];
Object e2 = that.requirements[i];
if (e1 == e2)
continue;
if (!e1.equals(e2)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int hash = 271;
for (Requirement requirement : requirements) {
hash += requirement.hashCode();
}
return hash;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
import net.momirealms.customnameplates.backend.requirement.AbstractRequirement;
import java.util.Objects;
import java.util.Set;
public abstract class PlaceholderRequirement<T> extends AbstractRequirement {
private final PreParsedDynamicText text;
private final T any;
public PlaceholderRequirement(int refreshInterval, PreParsedDynamicText text, T any) {
super(refreshInterval);
this.text = text;
this.any = any;
}
@Override
public boolean isSatisfied(CNPlayer p1, CNPlayer p2) {
p1.forceUpdate(text.placeholders(), Set.of(p2));
String a1 = text.fastCreate(p1).render(p2);
return checkArgument(a1, any);
}
protected abstract boolean checkArgument(String a1, T a2);
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
PlaceholderRequirement that = (PlaceholderRequirement) object;
return Objects.equals(text, that.text) && Objects.equals(any, that.any);
}
@Override
public int hashCode() {
return 283 + text.hashCode() * 53 + any.hashCode() * 457 + type().hashCode() * 3;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
import net.momirealms.customnameplates.backend.requirement.AbstractRequirement;
import java.util.Objects;
import java.util.Set;
public abstract class PlaceholdersRequirement extends AbstractRequirement {
private final PreParsedDynamicText t1;
private final PreParsedDynamicText t2;
public PlaceholdersRequirement(int refreshInterval, PreParsedDynamicText t1, PreParsedDynamicText t2) {
super(refreshInterval);
this.t1 = t1;
this.t2 = t2;
}
@Override
public boolean isSatisfied(CNPlayer p1, CNPlayer p2) {
p1.forceUpdate(t1.placeholders(), Set.of(p2));
p1.forceUpdate(t2.placeholders(), Set.of(p2));
String a1 = t1.fastCreate(p1).render(p2);
String a2 = t2.fastCreate(p1).render(p2);
return checkArgument(a1, a2);
}
protected abstract boolean checkArgument(String a1, String a2);
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
PlaceholdersRequirement that = (PlaceholdersRequirement) object;
return Objects.equals(t1, that.t1) && Objects.equals(t2, that.t2);
}
@Override
public int hashCode() {
return 73 + t1.hashCode() * 509 + t2.hashCode() * 211 + type().hashCode() * 311;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class RegexRequirement extends PlaceholderRequirement<String> {
public RegexRequirement(int refreshInterval, PreParsedDynamicText text, String any) {
super(refreshInterval, text, any);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return a1.matches(a2);
}
@Override
public String type() {
return "regex";
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customnameplates.backend.requirement.builtin;
import net.momirealms.customnameplates.api.feature.PreParsedDynamicText;
public class StartWithRequirement extends PlaceholderRequirement<String> {
public StartWithRequirement(int refreshInterval, PreParsedDynamicText text, String any) {
super(refreshInterval, text, any);
}
@Override
protected boolean checkArgument(String a1, String a2) {
return a1.startsWith(a2);
}
@Override
public String type() {
return "starts-with";
}
}

View File

@@ -5,6 +5,7 @@ import dev.dejvokep.boostedyaml.YamlDocument;
import net.momirealms.customnameplates.api.AbstractCNPlayer;
import net.momirealms.customnameplates.api.CNPlayer;
import net.momirealms.customnameplates.api.CustomNameplates;
import net.momirealms.customnameplates.api.event.DataLoadEvent;
import net.momirealms.customnameplates.api.feature.JoinQuitListener;
import net.momirealms.customnameplates.api.helper.GsonHelper;
import net.momirealms.customnameplates.api.storage.DataStorageProvider;
@@ -53,17 +54,19 @@ public class StorageManagerImpl implements StorageManager, JoinQuitListener {
PlayerData data = playerData1.get();
handleDataLoad(player, data);
((AbstractCNPlayer) player).setLoaded(true);
plugin.getEventManager().dispatch(DataLoadEvent.class, data);
this.redisManager.updatePlayerData(data, async).thenAccept(result -> {
if (!result) {
plugin.getPluginLogger().warn("Failed to refresh redis player data for " + player.name());
}
});
} else {
this.getDataSource().getPlayerData(uuid, async).thenAccept(playerData2 -> {
this.dataSource().getPlayerData(uuid, async).thenAccept(playerData2 -> {
if (playerData2.isPresent()) {
PlayerData data = playerData2.get();
handleDataLoad(player, data);
((AbstractCNPlayer) player).setLoaded(true);
plugin.getEventManager().dispatch(DataLoadEvent.class, data);
this.redisManager.updatePlayerData(data, async).thenAccept(result -> {
if (!result) {
plugin.getPluginLogger().warn("Failed to refresh redis player data for " + player.name());
@@ -76,7 +79,7 @@ public class StorageManagerImpl implements StorageManager, JoinQuitListener {
}
});
} else {
this.getDataSource().getPlayerData(uuid, async).thenAccept(playerData -> {
this.dataSource().getPlayerData(uuid, async).thenAccept(playerData -> {
if (playerData.isPresent()) {
PlayerData data = playerData.get();
handleDataLoad(player, data);
@@ -151,13 +154,13 @@ public class StorageManagerImpl implements StorageManager, JoinQuitListener {
@NotNull
@Override
public String getServerID() {
public String serverID() {
return serverID;
}
@NotNull
@Override
public DataStorageProvider getDataSource() {
public DataStorageProvider dataSource() {
return dataSource;
}
@@ -174,7 +177,7 @@ public class StorageManagerImpl implements StorageManager, JoinQuitListener {
@NotNull
@Override
public String toJson(@NotNull PlayerData data) {
return GsonHelper.get().toJson(data.toGsonData());
return GsonHelper.get().toJson(data.toJsonData());
}
@NotNull

View File

@@ -17,7 +17,7 @@ public class DummyStorage extends AbstractStorage {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.NONE;
}

View File

@@ -93,7 +93,7 @@ public class MongoDBProvider extends AbstractStorage {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.MongoDB;
}

View File

@@ -103,7 +103,7 @@ public class RedisManager extends AbstractStorage {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.Redis;
}

View File

@@ -21,14 +21,14 @@ public abstract class AbstractHikariDatabase extends AbstractSQLDatabase {
public AbstractHikariDatabase(CustomNameplates plugin) {
super(plugin);
this.driverClass = getStorageType() == StorageType.MariaDB ? "org.mariadb.jdbc.Driver" : "com.mysql.cj.jdbc.Driver";
this.sqlBrand = getStorageType() == StorageType.MariaDB ? "MariaDB" : "MySQL";
this.driverClass = storageType() == StorageType.MariaDB ? "org.mariadb.jdbc.Driver" : "com.mysql.cj.jdbc.Driver";
this.sqlBrand = storageType() == StorageType.MariaDB ? "MariaDB" : "MySQL";
try {
Class.forName(this.driverClass);
} catch (ClassNotFoundException e1) {
if (getStorageType() == StorageType.MariaDB) {
if (storageType() == StorageType.MariaDB) {
plugin.getPluginLogger().warn("No MariaDB driver is found");
} else if (getStorageType() == StorageType.MySQL) {
} else if (storageType() == StorageType.MySQL) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e2) {

View File

@@ -37,7 +37,7 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
*/
public void createTableIfNotExist() {
try (Connection connection = getConnection()) {
final String[] databaseSchema = getSchema(getStorageType().name().toLowerCase(Locale.ENGLISH));
final String[] databaseSchema = getSchema(storageType().name().toLowerCase(Locale.ENGLISH));
try (Statement statement = connection.createStatement()) {
for (String tableCreationStatement : databaseSchema) {
statement.execute(tableCreationStatement);

View File

@@ -58,7 +58,7 @@ public class H2Provider extends AbstractSQLDatabase {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.H2;
}

View File

@@ -10,7 +10,7 @@ public class MariaDBProvider extends AbstractHikariDatabase {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.MariaDB;
}
}

View File

@@ -10,7 +10,7 @@ public class MySQLProvider extends AbstractHikariDatabase {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.MySQL;
}
}

View File

@@ -62,7 +62,7 @@ public class SQLiteProvider extends AbstractSQLDatabase {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.SQLite;
}

View File

@@ -32,7 +32,7 @@ public class JsonProvider extends AbstractStorage {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.JSON;
}
@@ -61,7 +61,7 @@ public class JsonProvider extends AbstractStorage {
if (executor == null) executor = plugin.getScheduler().async();
executor.execute(() -> {
try {
this.saveToJsonFile(playerData.toGsonData(), getPlayerDataFile(playerData.uuid()));
this.saveToJsonFile(playerData.toJsonData(), getPlayerDataFile(playerData.uuid()));
} catch (Exception e) {
future.complete(false);
}

View File

@@ -25,7 +25,7 @@ public class YAMLProvider extends AbstractStorage {
}
@Override
public StorageType getStorageType() {
public StorageType storageType() {
return StorageType.YAML;
}