diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index 54b446877..3fb06992d 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -101,6 +101,7 @@ warning.config.condition.expression.missing_expression: "Issue found in warning.config.condition.is_null.missing_argument: "Issue found in file - The config '' is missing the required 'argument' argument for 'is_null' condition." warning.config.condition.hand.missing_hand: "Issue found in file - The config '' is missing the required 'hand' argument for 'hand' condition." warning.config.condition.hand.invalid_hand: "Issue found in file - The config '' is using an invalid 'hand' argument '' for 'hand' condition. Allowed hand types: []" +warning.config.condition.cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'cooldown' condition." warning.config.structure.not_section: "Issue found in file - The config '' is expected to be a config section while it's actually a(n) ''." warning.config.image.duplicate: "Issue found in file - Duplicated image ''. Please check if there is the same configuration in other files." warning.config.image.missing_height: "Issue found in file - The image '' is missing the required 'height' argument." @@ -339,6 +340,9 @@ warning.config.function.leveler_exp.missing_leveler: "Issue found in fil warning.config.function.leveler_exp.missing_plugin: "Issue found in file - The config '' is missing the required 'plugin' argument for 'leveler_exp' function." warning.config.function.remove_potion_effect.missing_potion_effect: "Issue found in file - The config '' is missing the required 'potion-effect' argument for 'remove_potion_effect' function." warning.config.function.potion_effect.missing_potion_effect: "Issue found in file - The config '' is missing the required 'potion-effect' argument for 'potion_effect' function." +warning.config.function.set_cooldown.missing_time: "Issue found in file - The config '' is missing the required 'time' argument for 'set_cooldown' function." +warning.config.function.set_cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'set_cooldown' function." +warning.config.function.remove_cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'remove_cooldown' function." warning.config.selector.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for selector." warning.config.selector.invalid_type: "Issue found in file - The config '' is using an invalid selector type ''." warning.config.selector.invalid_target: "Issue found in file - The config '' is using an invalid selector target ''." diff --git a/bukkit/loader/src/main/resources/translations/zh_cn.yml b/bukkit/loader/src/main/resources/translations/zh_cn.yml index 7ecef97c7..804f0414b 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -100,8 +100,9 @@ warning.config.condition.string_regex.missing_regex: "在文件 warning.config.condition.expression.missing_expression: "在文件 中发现问题 - 配置项 '' 缺少 'expression' 条件必需的 'expression' 参数" warning.config.condition.is_null.missing_argument: "在文件 发现问题 - 配置项 '' 缺少 'is_null' 条件的必需的 'argument' 参数." warning.config.structure.not_section: "在文件 发现问题 - 配置项 '' 应为配置段落 但实际类型为 ''" -warning.config.condition.hand.missing_hand: "Issue found in file - The config '' is missing the required 'hand' argument for 'hand' condition." -warning.config.condition.hand.invalid_hand: "Issue found in file - The config '' is using an invalid 'hand' argument '' for 'hand' condition. Allowed hand types: []" +warning.config.condition.hand.missing_hand: "在文件 发现问题 - 配置项 '' 缺少 'hand' 条件必需的 'hand' 参数" +warning.config.condition.hand.invalid_hand: "在文件 发现问题 - 配置项 '' 使用了无效的 'hand' 参数 ''('hand' 条件)。允许的手部类型: []" +warning.config.condition.cooldown.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'cooldown' 条件必需的 'id' 参数" warning.config.image.duplicate: "在文件 发现问题 - 重复的图片配置 '' 请检查其他文件中是否存在相同配置" warning.config.image.missing_height: "在文件 发现问题 - 图片 '' 缺少必需的 'height' 参数" warning.config.image.height_ascent_conflict: "在文件 发现问题 - 图片 '' 违反位图规则: 'height' 参数 '' 必须不小于 'ascent' 参数 ''" @@ -337,8 +338,11 @@ warning.config.function.particle.missing_block_state: "在文件 warning.config.function.leveler_exp.missing_count: "在文件 中发现问题 - 配置项 '' 缺少 'leveler_exp' 函数必需的 'count' 参数" warning.config.function.leveler_exp.missing_leveler: "在文件 中发现问题 - 配置项 '' 缺少 'leveler_exp' 函数必需的 'leveler' 参数" warning.config.function.leveler_exp.missing_plugin: "在文件 中发现问题 - 配置项 '' 缺少 'leveler_exp' 函数必需的 'plugin' 参数" -warning.config.function.remove_potion_effect.missing_potion_effect: "Issue found in file - The config '' is missing the required 'potion-effect' argument for 'remove_potion_effect' function." -warning.config.function.potion_effect.missing_potion_effect: "Issue found in file - The config '' is missing the required 'potion-effect' argument for 'potion_effect' function." +warning.config.function.remove_potion_effect.missing_potion_effect: "在文件 中发现问题 - 配置项 '' 缺少 'remove_potion_effect' 函数必需的 'potion-effect' 参数" +warning.config.function.potion_effect.missing_potion_effect: "在文件 中发现问题 - 配置项 '' 缺少 'potion_effect' 函数必需的 'potion-effect' 参数" +warning.config.function.set_cooldown.missing_time: "在文件 中发现问题 - 配置项 '' 缺少 'set_cooldown' 函数必需的 'time' 参数" +warning.config.function.set_cooldown.missing_id: "在文件 中发现问题 - 配置项 '' 缺少 'set_cooldown' 函数必需的 'id' 参数" +warning.config.function.remove_cooldown.missing_id: "在文件 中发现问题 - 配置项 '' 缺少 'remove_cooldown' 函数必需的 'id' 参数" warning.config.selector.missing_type: "在文件 中发现问题 - 配置项 '' 缺少选择器必需的 'type' 参数" warning.config.selector.invalid_type: "在文件 中发现问题 - 配置项 '' 使用了无效的选择器类型 ''" warning.config.selector.invalid_target: "在文件 中发现问题 - 配置项 '' 使用了无效的选择器目标 ''" diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index d5998aed6..82b8ea92a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -12,8 +12,10 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20_5; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.*; import net.momirealms.craftengine.core.util.*; import org.bukkit.Bukkit; @@ -24,10 +26,12 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; @@ -191,17 +195,33 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } - @EventHandler(priority = EventPriority.HIGHEST) + @EventHandler(priority = EventPriority.LOWEST) public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); Channel channel = getChannel(player); NetWorkUser user = removeUser(channel); if (user == null) return; + saveCooldown(player, user); handleDisconnection(channel); this.onlineUsers.remove(player.getUniqueId()); this.resetUserArray(); } + private void saveCooldown(Player player, NetWorkUser user) { + if (user instanceof BukkitServerPlayer serverPlayer) { + CooldownData cd = serverPlayer.cooldown(); + if (cd != null) { + try { + byte[] data = CooldownData.toBytes(cd); + player.getPersistentDataContainer().set(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY, data); + } catch (IOException e) { + player.getPersistentDataContainer().remove(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY)); + this.plugin.logger().warn("Failed to save cooldown for player " + player.getName(), e); + } + } + } + } + private void resetUserArray() { this.onlineUserArray = this.onlineUsers.values().toArray(new BukkitServerPlayer[0]); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 40fac266a..ad1375138 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -19,6 +19,7 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; import net.momirealms.craftengine.core.plugin.network.ProtocolVersion; @@ -35,11 +36,13 @@ import org.bukkit.block.Block; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.bukkit.persistence.PersistentDataType; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.RayTraceResult; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.*; @@ -94,6 +97,8 @@ public class BukkitServerPlayer extends Player { // cache interaction range here private int lastUpdateInteractionRangeTick; private double cachedInteractionRange; + // cooldown data + private CooldownData cooldownData; private final Map entityTypeView = new ConcurrentHashMap<>(); @@ -107,6 +112,13 @@ public class BukkitServerPlayer extends Player { this.serverPlayerRef = new WeakReference<>(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)); this.uuid = player.getUniqueId(); this.name = player.getName(); + byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY); + try { + this.cooldownData = CooldownData.fromBytes(bytes); + } catch (IOException e) { + this.cooldownData = new CooldownData(); + this.plugin.logger().warn("Failed to parse cooldown data", e); + } } @Override @@ -871,4 +883,14 @@ public class BukkitServerPlayer extends Player { if (type == null) return; this.platformPlayer().removePotionEffect(type); } + + @Override + public void clearPotionEffects() { + this.platformPlayer().clearActivePotionEffects(); + } + + @Override + public CooldownData cooldown() { + return this.cooldownData; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 4a5f8f7a8..5e0309c97 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.entity.player; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.BlockPos; @@ -128,4 +129,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void addPotionEffect(Key potionEffectType, int duration, int amplifier, boolean ambient, boolean particles); public abstract void removePotionEffect(Key potionEffectType); + + public abstract void clearPotionEffects(); + + public abstract CooldownData cooldown(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/CooldownData.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/CooldownData.java new file mode 100644 index 000000000..f693e573f --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/CooldownData.java @@ -0,0 +1,75 @@ +package net.momirealms.craftengine.core.plugin.context; + +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.LongTag; +import net.momirealms.sparrow.nbt.NBT; +import net.momirealms.sparrow.nbt.Tag; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class CooldownData { + public static final Key COOLDOWN_KEY = Key.of("craftengine:cooldown"); + private final Map cooldownMap = Collections.synchronizedMap(new HashMap<>()); + + public boolean isOnCooldown(String key) { + long currentTime = System.currentTimeMillis(); + if (this.cooldownMap.containsKey(key)) { + long expirationTime = this.cooldownMap.get(key); + return currentTime < expirationTime; + } + return false; + } + + public void setCooldown(String key, long duration) { + this.cooldownMap.put(key, System.currentTimeMillis() + duration); + } + + public void addCooldown(String key, long duration) { + if (this.cooldownMap.containsKey(key)) { + this.cooldownMap.put(key, this.cooldownMap.get(key) + duration); + } else { + setCooldown(key, duration); + } + } + + public void removeCooldown(String key) { + this.cooldownMap.remove(key); + } + + public void clearCooldowns() { + this.cooldownMap.clear(); + } + + public static byte[] toBytes(CooldownData data) throws IOException { + CompoundTag tag = new CompoundTag(); + long currentTime = System.currentTimeMillis(); + for (Map.Entry entry : data.cooldownMap.entrySet()) { + if (currentTime < entry.getValue()) { + tag.putLong(entry.getKey(), entry.getValue()); + } + } + return NBT.toBytes(tag); + } + + public static CooldownData fromBytes(byte[] data) throws IOException { + if (data == null || data.length == 0) return new CooldownData(); + CooldownData cd = new CooldownData(); + long currentTime = System.currentTimeMillis(); + CompoundTag tag = NBT.fromBytes(data); + if (tag != null) { + for (Map.Entry entry : tag.tags.entrySet()) { + if (entry.getValue() instanceof LongTag longTag) { + long expire = longTag.getAsLong(); + if (currentTime < expire) { + cd.setCooldown(entry.getKey(), expire); + } + } + } + } + return cd; + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java index 3279302ad..43ac2cb0d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java @@ -18,6 +18,7 @@ public final class CommonConditions { public static final Key FALLING_BLOCK = Key.from("craftengine:falling_block"); public static final Key DISTANCE = Key.from("craftengine:distance"); public static final Key PERMISSION = Key.from("craftengine:permission"); + public static final Key COOLDOWN = Key.from("craftengine:cooldown"); public static final Key EQUALS = Key.from("craftengine:equals"); public static final Key STRING_EQUALS = Key.from("craftengine:string_equals"); public static final Key STRING_CONTAINS = Key.from("craftengine:string_contains"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CooldownCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CooldownCondition.java new file mode 100644 index 000000000..785866de7 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CooldownCondition.java @@ -0,0 +1,43 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; +import java.util.Optional; + +public class CooldownCondition implements Condition { + private final String key; + + public CooldownCondition(String key) { + this.key = key; + } + + @Override + public Key type() { + return CommonConditions.COOLDOWN; + } + + @Override + public boolean test(CTX ctx) { + Optional player = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + if (player.isPresent()) { + Player p = player.get(); + return p.cooldown().isOnCooldown(this.key); + } + return false; + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("id"), "warning.config.condition.cooldown.missing_id"); + return new CooldownCondition<>(id); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java index 4d5d5ebcf..a46d7a09b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java @@ -36,6 +36,7 @@ public class EventConditions { register(CommonConditions.EXPRESSION, new ExpressionCondition.FactoryImpl<>()); register(CommonConditions.IS_NULL, new IsNullCondition.FactoryImpl<>()); register(CommonConditions.HAND, new HandCondition.FactoryImpl<>()); + register(CommonConditions.COOLDOWN, new CooldownCondition.FactoryImpl<>()); } public static void register(Key key, ConditionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java index 1392d6f6a..8613eb5c4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java @@ -37,6 +37,8 @@ public class EventFunctions { register(CommonFunctions.POTION_EFFECT, new PotionEffectFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.REMOVE_POTION_EFFECT, new RemovePotionEffectFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.LEVELER_EXP, new LevelerExpFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SET_COOLDOWN, new SetCooldownFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.REMOVE_COOLDOWN, new RemoveCooldownFunction.FactoryImpl<>(EventConditions::fromMap)); } public static void register(Key key, FunctionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java index 3431302fe..7f490b6d9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java @@ -21,6 +21,8 @@ public final class CommonFunctions { public static final Key SET_COUNT = Key.of("craftengine:set_count"); public static final Key PLACE_BLOCK = Key.of("craftengine:place_block"); public static final Key SET_FOOD = Key.of("craftengine:food"); + public static final Key SET_COOLDOWN = Key.of("craftengine:set_cooldown"); + public static final Key REMOVE_COOLDOWN = Key.of("craftengine:remove_cooldown"); public static final Key SET_SATURATION = Key.of("craftengine:saturation"); public static final Key DROP_LOOT = Key.of("craftengine:drop_loot"); public static final Key SWING_HAND = Key.of("craftengine:swing_hand"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java new file mode 100644 index 000000000..7463d8bf2 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java @@ -0,0 +1,69 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.CooldownData; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class RemoveCooldownFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final String id; + private final boolean all; + + public RemoveCooldownFunction(String id, boolean all, PlayerSelector selector, List> predicates) { + super(predicates); + this.selector = selector; + this.id = id; + this.all = all; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + optionalPlayer.ifPresent(player -> { + CooldownData data = player.cooldown(); + if (this.all) data.clearCooldowns(); + else data.removeCooldown(this.id); + }); + } else { + for (Player target : this.selector.get(ctx)) { + CooldownData data = target.cooldown(); + if (this.all) data.clearCooldowns(); + else data.removeCooldown(this.id); + } + } + } + + @Override + public Key type() { + return CommonFunctions.REMOVE_COOLDOWN; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + boolean all = (boolean) arguments.getOrDefault("all", false); + if (all) { + return new RemoveCooldownFunction<>(null, true, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } else { + String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("id"), "warning.config.function.remove_cooldown.missing_id"); + return new RemoveCooldownFunction<>(id, false, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java index df290c3c3..cc0148996 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java @@ -15,22 +15,26 @@ import java.util.Map; public class RemovePotionEffectFunction extends AbstractConditionalFunction { private final PlayerSelector selector; private final Key potionEffectType; + private final boolean all; - public RemovePotionEffectFunction(Key potionEffectType, PlayerSelector selector, List> predicates) { + public RemovePotionEffectFunction(Key potionEffectType, boolean all, PlayerSelector selector, List> predicates) { super(predicates); this.potionEffectType = potionEffectType; this.selector = selector; + this.all = all; } @Override public void runInternal(CTX ctx) { if (this.selector == null) { ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { - it.removePotionEffect(this.potionEffectType); + if (this.all) it.clearPotionEffects(); + else it.removePotionEffect(this.potionEffectType); }); } else { for (Player target : this.selector.get(ctx)) { - target.removePotionEffect(this.potionEffectType); + if (this.all) target.clearPotionEffects(); + else target.removePotionEffect(this.potionEffectType); } } } @@ -48,8 +52,13 @@ public class RemovePotionEffectFunction extends AbstractCon @Override public Function create(Map arguments) { - Key effectType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("potion-effect"), "warning.config.function.remove_potion_effect.missing_potion_effect")); - return new RemovePotionEffectFunction<>(effectType, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + boolean all = (boolean) arguments.getOrDefault("all", false); + if (all) { + return new RemovePotionEffectFunction<>(null, true, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } else { + Key effectType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("potion-effect"), "warning.config.function.remove_potion_effect.missing_potion_effect")); + return new RemovePotionEffectFunction<>(effectType, false, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java new file mode 100644 index 000000000..68eb002d6 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java @@ -0,0 +1,72 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.plugin.context.text.TextProviders; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.TimeUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class SetCooldownFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final TextProvider time; + private final String id; + private final boolean add; + + public SetCooldownFunction(TextProvider time, String id, boolean add, PlayerSelector selector, List> predicates) { + super(predicates); + this.time = time; + this.add = add; + this.selector = selector; + this.id = id; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + optionalPlayer.ifPresent(player -> { + long millis = TimeUtils.parseToMillis(this.time.get(ctx)); + CooldownData data = player.cooldown(); + if (this.add) data.addCooldown(this.id, millis); + else data.setCooldown(this.id, millis); + }); + } else { + for (Player target : this.selector.get(ctx)) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + long millis = TimeUtils.parseToMillis(this.time.get(relationalContext)); + CooldownData data = target.cooldown(); + if (this.add) data.addCooldown(this.id, millis); + else data.setCooldown(this.id, millis); + } + } + } + + @Override + public Key type() { + return CommonFunctions.SET_COOLDOWN; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("id"), "warning.config.function.set_cooldown.missing_id"); + String time = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("time"), "warning.config.function.set_cooldown.missing_time"); + boolean add = (boolean) arguments.getOrDefault("add", false); + return new SetCooldownFunction<>(TextProviders.fromString(time), id, add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/TimeUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/TimeUtils.java new file mode 100644 index 000000000..072e4d74a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/TimeUtils.java @@ -0,0 +1,62 @@ +package net.momirealms.craftengine.core.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class TimeUtils { + private TimeUtils() {} + + private static final Map TIME_UNITS = new HashMap<>(Map.of( + 'w', 604800000L, + 'd', 86400000L, + 'h', 3600000L, + 'm', 60000L, + 's', 1000L + )); + + private static final Pattern TIME_PATTERN = Pattern.compile("(\\d+)([dhmsDwHMSW])", Pattern.CASE_INSENSITIVE); + + public static long parseToMillis(String timeStr) { + if (timeStr == null || timeStr.trim().isEmpty()) { + throw new IllegalArgumentException("Time string cannot be null or empty"); + } + String trimmedStr = timeStr.trim(); + if (trimmedStr.matches("^\\d+$")) { + return Long.parseLong(trimmedStr); + } + long totalMillis = 0; + Matcher matcher = TIME_PATTERN.matcher(trimmedStr); + int lastEnd = 0; + while (matcher.find()) { + if (matcher.start() != lastEnd) { + throw new IllegalArgumentException("Invalid characters in time string: " + + trimmedStr.substring(lastEnd, matcher.start())); + } + lastEnd = matcher.end(); + long value = Long.parseLong(matcher.group(1)); + if (value < 0) { + throw new IllegalArgumentException("Time value cannot be negative: " + value); + } + char unit = Character.toLowerCase(matcher.group(2).charAt(0)); + if (!TIME_UNITS.containsKey(unit)) { + throw new IllegalArgumentException("Unknown time unit: " + unit); + } + try { + totalMillis = Math.addExact(totalMillis, Math.multiplyExact(value, TIME_UNITS.get(unit))); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Time value too large, would overflow long: " + timeStr); + } + } + + if (lastEnd != trimmedStr.length()) { + throw new IllegalArgumentException("Invalid time format at position " + lastEnd + + ": " + trimmedStr.substring(lastEnd)); + } + if (totalMillis < 0) { + throw new IllegalArgumentException("Resulting time cannot be negative"); + } + return totalMillis; + } +}