/* * GNU Lesser General Public License v3 * Copyright (C) 2024 Tschipp * mrtschipp@gmail.com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package tschipp.carryon.common.scripting; import com.mojang.serialization.Codec; import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.minecraft.advancements.Advancement; import net.minecraft.advancements.AdvancementHolder; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.ServerAdvancementManager; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.phys.Vec3; import net.minecraft.world.scores.Objective; import net.minecraft.world.scores.Score; import net.minecraft.world.scores.Scoreboard; import tschipp.carryon.platform.Services; import java.util.*; public final class Matchables { public interface Matchable { boolean matches(T elem); } private static float getValueFromStringOrDefault(String toGetFrom, String key, float defaultVal) { Optional val = getValueFromString(toGetFrom, key); return val.orElse(defaultVal); } private static Optional getValueFromString(String toGetFrom, String key) { if (toGetFrom == null || toGetFrom.isEmpty()) return Optional.empty(); String[] s = toGetFrom.split(","); for (String string : s) { if (string.contains(key) && string.contains("=")) { float numb = 0; string = string.replace(key + "=", ""); try { numb = Float.parseFloat(string); } catch (Exception e) { } return Optional.of(numb); } } return Optional.empty(); } public record NumberBoundCondition(String bounds) implements Matchable { public static final Codec CODEC = Codec.STRING.xmap(NumberBoundCondition::new, NumberBoundCondition::bounds); public static final NumberBoundCondition NONE = new NumberBoundCondition(""); @Override public boolean matches(Number num) { double number = num.doubleValue(); if (bounds == null || bounds.isEmpty()) return true; try { if (bounds.contains("<=")) return number <= Double.parseDouble(bounds.replace("<=", "")); if (bounds.contains(">=")) return number >= Double.parseDouble(bounds.replace(">=", "")); if (bounds.contains("<")) return number < Double.parseDouble(bounds.replace("<", "")); if (bounds.contains(">")) return number > Double.parseDouble(bounds.replace(">", "")); if (bounds.contains("=")) return number == Double.parseDouble(bounds.replace("=", "")); else return number == Double.parseDouble(bounds); } catch (Exception e) { throw new RuntimeException("Error while parsing Number bound for string: "+ bounds + ". Error: " + e.getMessage()); } } } public record AdvancementCondition(String advancement) implements Matchable { public static final Codec CODEC = Codec.STRING.xmap(AdvancementCondition::new, AdvancementCondition::advancement); public static final AdvancementCondition NONE = new AdvancementCondition(""); @Override public boolean matches(ServerPlayer player) { ServerAdvancementManager manager = player.server.getAdvancements(); AdvancementHolder adv = manager.get(ResourceLocation.parse(advancement.isEmpty() ? "" : advancement)); boolean achievement = adv == null ? true : player.getAdvancements().getOrStartProgress(adv).isDone(); return achievement; } } public record GamestageCondition(String gamestage) implements Matchable { public static final Codec CODEC = Codec.STRING.xmap(GamestageCondition::new, GamestageCondition::gamestage); public static final GamestageCondition NONE = new GamestageCondition(""); @Override public boolean matches(ServerPlayer player) { if(!Services.PLATFORM.isModLoaded("gamestages")) return true; if(gamestage == null || gamestage.isEmpty()) return true; return Services.GAMESTAGES.hasStage(player, gamestage); } } public record ScoreboardCondition(String cond) implements Matchable { public static final Codec CODEC = Codec.STRING.xmap(ScoreboardCondition::new, ScoreboardCondition::cond); public static final ScoreboardCondition NONE = new ScoreboardCondition(""); @Override public boolean matches(ServerPlayer player) { if (cond == null || cond.isEmpty()) return true; Scoreboard score = player.getScoreboard(); String numb; String scorename; int iE = cond.indexOf("="); int iG = cond.indexOf(">"); int iL = cond.indexOf("<"); if (iG == -1 || (iE < iG && iL == -1 || iE < iL && iE != -1)) numb = cond.substring(iE); else if (iE == -1 || (iG < iE && iL == -1 || iG < iL && iG != -1)) numb = cond.substring(iG); else numb = cond.substring(iL); scorename = cond.replace(numb, ""); Object2IntMap scores = score.listPlayerScores(player); int scoreVal = scores.getInt(score.getObjective(scorename)); return new NumberBoundCondition(numb).matches(scoreVal); } } public record PositionCondition(String cond) implements Matchable { public static final Codec CODEC = Codec.STRING.xmap(PositionCondition::new, PositionCondition::cond); public static final PositionCondition NONE = new PositionCondition(""); @Override public boolean matches(ServerPlayer elem) { if (cond == null || cond.isEmpty()) return true; BlockPos blockpos = new BlockPos((int) getValueFromStringOrDefault(cond, "x", 0), (int) getValueFromStringOrDefault(cond, "y", 0), (int) getValueFromStringOrDefault(cond, "z", 0)); BlockPos expand = new BlockPos((int) getValueFromStringOrDefault(cond, "dx", 0), (int) getValueFromStringOrDefault(cond, "dy", 0), (int) getValueFromStringOrDefault(cond, "dz", 0)); BlockPos expanded = blockpos.offset(expand); BlockPos pos = elem.blockPosition(); boolean x = pos.getX() >= blockpos.getX() && pos.getX() <= expanded.getX() || blockpos.getX() == 0; boolean y = pos.getY() >= blockpos.getY() && pos.getY() <= expanded.getY() || blockpos.getY() == 0; boolean z = pos.getZ() >= blockpos.getZ() && pos.getZ() <= expanded.getZ() || blockpos.getZ() == 0; return x && y && z; } } public record EffectsCondition(String effects) implements Matchable { public static final Codec CODEC = Codec.STRING.xmap(EffectsCondition::new, EffectsCondition::effects); public static final EffectsCondition NONE = new EffectsCondition(""); @Override public boolean matches(ServerPlayer player) { if (effects == null || effects.isEmpty()) return true; Collection fx = player.getActiveEffects(); String[] potions = effects.split(","); List names = new ArrayList<>(); List levels = new ArrayList<>(); for (String pot : potions) { if (pot.contains("#")) { String level = pot.substring(pot.indexOf("#")); String name = pot.substring(0, pot.indexOf("#")); level = level.replace("#", ""); int lev = 0; try { lev = Integer.parseInt(level); } catch (Exception e) { } levels.add(lev); names.add(name); } else { levels.add(0); names.add(pot); } } int matches = 0; for (MobEffectInstance effect : fx) { int amp = effect.getAmplifier(); String name = effect.getEffect().getRegisteredName(); if (names.contains(name)) { int idx = names.indexOf(name); int lev = levels.get(idx); if (lev <= amp) matches++; } } return matches == potions.length; } } public record NBTCondition(CompoundTag tag) implements Matchable { public static final Codec CODEC = CompoundTag.CODEC.xmap(NBTCondition::new, NBTCondition::tag); public static final NBTCondition NONE = new NBTCondition(new CompoundTag()); @Override public boolean matches(CompoundTag other) { if(other == null) return true; return NbtUtils.compareNbt(tag, other, true); } } public static class OptionalVec3 { public static final Codec CODEC = Codec.STRING.xmap(OptionalVec3::new, OptionalVec3::source); public static final OptionalVec3 NONE = new OptionalVec3(""); String source; Vec3 vec; boolean x, y, z; public OptionalVec3(String source) { this.source = source; Optional xOpt = getValueFromString(source, "x"); Optional yOpt = getValueFromString(source, "y"); Optional zOpt = getValueFromString(source, "z"); float x = 0, y = 0, z = 0; if(xOpt.isPresent()) { x = xOpt.get(); this.x = true; } if(yOpt.isPresent()) { y = yOpt.get(); this.y = true; } if(zOpt.isPresent()) { z = zOpt.get(); this.z = true; } vec = new Vec3(x, y, z); } private String source() { return source; } /** * Gets the contained optional vector. Nonexisting numbers are set to 0. */ public Vec3 getVec() { return vec; } public Vec3 getVec(double dX, double dY, double dZ) { double x = !this.x ? dX : vec.x; double y = !this.y ? dY : vec.y; double z = !this.z ? dZ : vec.z; return new Vec3(x, y, z); } } }