XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
package com.xfestudio.xfeservermanager.core.trigger;

import com.xfestudio.xfeservermanager.api.trigger.TriggerExtension;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.LinkedHashMap;

/** Type-driven six-part catalog consumed by the V2 editor and extension registry. */
public final class TriggerDescriptorCatalog {
    public static final Set<String> FOUR_FORGE_TARGETS = Set.of(
            "forge-1.20.1", "forge-1.20.6", "forge-1.21.1", "forge-1.21.11");
    private static final Set<String> OWNER_ACTIONS = Set.of(
            "server_command", "player_command", "set_time", "set_weather",
            "spawn_entity", "damage_entity", "teleport_entity", "remove_entity",
            "set_entity_attribute", "tag_entity", "set_block", "fill_blocks", "explosion",
            "lightning", "set_difficulty", "set_gamerule", "world_border");
    private static final Set<String> ADMIN_ACTIONS = Set.of(
            "kick", "teleport", "give_item", "clear_inventory", "set_gamemode", "add_effect",
            "remove_effects", "heal", "feed", "whitelist_add", "whitelist_remove", "ban", "pardon",
            "economy_deposit", "economy_withdraw", "economy_set_balance", "economy_transfer",
            "economy_deposit_player", "economy_withdraw_player", "economy_set_player_balance",
            "economy_transfer_players", "set_health", "set_food", "set_experience",
            "set_spawnpoint", "scoreboard_set", "scoreboard_add", "team_join", "team_leave",
            "advancement_grant", "advancement_revoke");
    public static final List<TriggerExtension.Descriptor> DESCRIPTORS = build();
    public static final TriggerExtensionRegistry.Snapshot AVAILABLE =
            TriggerExtensionRegistry.discover(DESCRIPTORS, TriggerDescriptorCatalog.class.getClassLoader());

    private TriggerDescriptorCatalog() { }

    public static TriggerExtension.Descriptor functionDescriptor(String id) {
        if (id == null) return null;
        String normalized = id.toLowerCase(java.util.Locale.ROOT);
        return AVAILABLE.descriptors().stream().filter(descriptor -> descriptor.id().equals(normalized)
                && (descriptor.kind() == TriggerExtension.Kind.VALUE_FUNCTION
                || descriptor.kind() == TriggerExtension.Kind.CONDITION_FUNCTION))
                .findFirst().orElse(null);
    }

    public static boolean hasFunctionHandler(String id) {
        return id != null && AVAILABLE.functionHandlers().containsKey(id.toLowerCase(java.util.Locale.ROOT));
    }

    public static Object invokeFunction(String id, List<Object> arguments, Map<String, ?> event,
                                        Map<String, Object> variables, long seed, java.time.Instant instant) {
        String normalized = id.toLowerCase(java.util.Locale.ROOT);
        TriggerExtension.FunctionHandler handler = AVAILABLE.functionHandlers().get(normalized);
        if (handler == null) throw new IllegalArgumentException("unknown extension function: " + id);
        Map<String, Object> safeEvent = transportMap(event);
        Map<String, Object> safeVariables = transportMap(variables);
        TriggerExtensionRegistry.validateTransportValue(arguments, 0);
        try {
            Object result = handler.evaluate(new TriggerExtension.FunctionInvocation(
                    normalized, arguments, safeEvent, safeVariables, seed, instant.toString()));
            TriggerExtensionRegistry.validateTransportValue(result, 0);
            return result;
        } catch (RuntimeException failure) {
            throw new IllegalStateException("trigger extension function failed: " + normalized, failure);
        } catch (Exception failure) {
            throw new IllegalStateException("trigger extension function failed: " + normalized, failure);
        }
    }

    public static boolean hasActionHandler(String id) {
        return id != null && AVAILABLE.actionHandlers().containsKey(id.toLowerCase(java.util.Locale.ROOT));
    }

    public static void invokeAction(String id, Map<String, Object> parameters, Map<String, ?> event,
                                    String executionId, String nodeId, String idempotencyKey) {
        String normalized = id.toLowerCase(java.util.Locale.ROOT);
        TriggerExtension.ActionHandler handler = AVAILABLE.actionHandlers().get(normalized);
        if (handler == null) throw new IllegalArgumentException("unknown extension action: " + id);
        Map<String, Object> safeParameters = transportMap(parameters);
        Map<String, Object> safeEvent = transportMap(event);
        try {
            handler.execute(new TriggerExtension.ActionInvocation(normalized, safeParameters, safeEvent,
                    executionId, nodeId, idempotencyKey));
        } catch (RuntimeException failure) {
            throw new IllegalStateException("trigger extension action failed: " + normalized, failure);
        } catch (Exception failure) {
            throw new IllegalStateException("trigger extension action failed: " + normalized, failure);
        }
    }

    private static Map<String, Object> transportMap(Map<String, ?> values) {
        Map<String, Object> copy = new LinkedHashMap<>();
        if (values != null) values.forEach(copy::put);
        TriggerExtensionRegistry.validateTransportValue(copy, 0);
        return java.util.Collections.unmodifiableMap(copy);
    }

    /** Rejects unknown executable/event ids before a document can be persisted. */
    public static void validateProgram(TriggerProgramV2 program) {
        Map<String, TriggerExtension.Descriptor> events = new HashMap<>();
        Map<String, TriggerExtension.Descriptor> actions = new HashMap<>();
        for (TriggerExtension.Descriptor descriptor : AVAILABLE.descriptors()) {
            if (descriptor.kind() == TriggerExtension.Kind.EVENT) events.put(descriptor.id(), descriptor);
            if (descriptor.kind() == TriggerExtension.Kind.ACTION) actions.put(descriptor.id(), descriptor);
        }
        for (TriggerProgramV2.EventBinding event : program.events()) {
            if (!events.containsKey(event.type()) && !event.type().startsWith("custom.")) {
                throw new IllegalArgumentException("unknown trigger event: " + event.type());
            }
        }
        program.statements().forEach(statement -> validateStatement(statement, actions));
        program.functions().forEach(function ->
                function.statements().forEach(statement -> validateStatement(statement, actions)));
    }

    private static void validateStatement(
            TriggerProgramV2.Statement statement, Map<String, TriggerExtension.Descriptor> actions) {
        if (statement.kind() == TriggerProgramV2.StatementKind.ACTION) {
            TriggerExtension.Descriptor descriptor = actions.get(statement.name());
            if (descriptor == null) throw new IllegalArgumentException("unknown trigger action: " + statement.name());
            Set<String> parameterNames = descriptor.parameters().stream()
                    .map(TriggerExtension.Parameter::name).collect(java.util.stream.Collectors.toSet());
            if (!parameterNames.containsAll(statement.inputs().keySet())) {
                throw new IllegalArgumentException("action " + statement.name() + " has an unknown parameter");
            }
            if (!builtInAction(descriptor.id())) {
                for (TriggerExtension.Parameter parameter : descriptor.parameters()) {
                    if (parameter.required() && !statement.inputs().containsKey(parameter.name())) {
                        throw new IllegalArgumentException("action " + statement.name()
                                + " is missing parameter " + parameter.name());
                    }
                }
                if (!AVAILABLE.actionHandlers().containsKey(descriptor.id())) {
                    throw new IllegalArgumentException("extension action has no runtime handler: " + descriptor.id());
                }
            }
        }
        statement.statements().forEach(child -> validateStatement(child, actions));
        statement.elseStatements().forEach(child -> validateStatement(child, actions));
        statement.cases().forEach(branch ->
                branch.statements().forEach(child -> validateStatement(child, actions)));
    }

    private static boolean builtInAction(String id) {
        return DESCRIPTORS.stream().anyMatch(descriptor -> descriptor.kind() == TriggerExtension.Kind.ACTION
                && descriptor.id().equals(id));
    }

    private static List<TriggerExtension.Descriptor> build() {
        List<TriggerExtension.Descriptor> result = new ArrayList<>();
        for (String event : TriggerCatalog.EVENTS) {
            result.add(descriptor(event, TriggerExtension.Kind.EVENT, event, "Trigger event", List.of(),
                    "event", TriggerExtension.Purity.SNAPSHOT_QUERY,
                    TriggerExtension.ThreadAffinity.SERVER, TriggerExtension.Risk.SAFE, Set.of("*")));
        }
        for (TriggerVariableCatalog.VariableDescriptor variable : TriggerVariableCatalog.VARIABLES) {
            String responseId = "response." + variable.key().toLowerCase(java.util.Locale.ROOT)
                    .replaceAll("[^a-z0-9_.:_-]", "_");
            result.add(descriptor(responseId, TriggerExtension.Kind.EVENT_RESPONSE,
                    variable.nameEn(), variable.descriptionEn(), List.of(), variable.type(),
                    TriggerExtension.Purity.PURE, TriggerExtension.ThreadAffinity.BACKGROUND,
                    TriggerExtension.Risk.SAFE, Set.copyOf(variable.events())));
        }
        Map<String, String> functions = Map.ofEntries(
                Map.entry("len", "integer"), Map.entry("contains", "bool"),
                Map.entry("starts_with", "bool"), Map.entry("ends_with", "bool"),
                Map.entry("lower", "string"), Map.entry("upper", "string"),
                Map.entry("abs", "float"), Map.entry("min", "float"), Map.entry("max", "float"),
                Map.entry("clamp", "float"), Map.entry("random_float", "float"),
                Map.entry("random_int", "integer"), Map.entry("instant", "instant"),
                Map.entry("is_null", "bool"), Map.entry("chance", "bool"),
                Map.entry("random_pick", "any"), Map.entry("distance", "float"),
                Map.entry("has_tag", "bool"));
        functions.forEach((name, type) -> result.add(descriptor(name,
                TriggerExtension.Kind.VALUE_FUNCTION, name, "Pure snapshot function", List.of(), type,
                TriggerExtension.Purity.PURE, TriggerExtension.ThreadAffinity.BACKGROUND,
                TriggerExtension.Risk.SAFE, Set.of("*"))));
        for (String operator : TriggerCatalog.OPERATORS) {
            result.add(descriptor("compare." + operator, TriggerExtension.Kind.CONDITION_FUNCTION,
                    operator, "Typed comparison", List.of(parameter("left", "any"), parameter("right", "any")),
                    "bool", TriggerExtension.Purity.PURE, TriggerExtension.ThreadAffinity.BACKGROUND,
                    TriggerExtension.Risk.SAFE, Set.of("*")));
        }
        for (String action : TriggerCatalog.ACTIONS) {
            List<TriggerExtension.Parameter> parameters = TriggerCatalog.ACTION_PARAMETERS.get(action).stream()
                    .map(name -> parameter(name, "expression<any>")).toList();
            TriggerExtension.Risk risk = OWNER_ACTIONS.contains(action) ? TriggerExtension.Risk.OWNER
                    : ADMIN_ACTIONS.contains(action) ? TriggerExtension.Risk.ADMINISTRATOR
                    : TriggerExtension.Risk.SAFE;
            result.add(descriptor(action, TriggerExtension.Kind.ACTION, action, "Trigger action", parameters,
                    "void", TriggerExtension.Purity.SIDE_EFFECT,
                    TriggerExtension.ThreadAffinity.SERVER, risk, Set.of("*")));
        }
        for (String type : TriggerValueTypes.VARIABLE_TYPES) {
            result.add(descriptor(type, TriggerExtension.Kind.TYPE, type, "Trigger value type", List.of(),
                    type, TriggerExtension.Purity.PURE, TriggerExtension.ThreadAffinity.BACKGROUND,
                    TriggerExtension.Risk.SAFE, Set.of("*")));
        }
        return List.copyOf(result);
    }

    private static TriggerExtension.Parameter parameter(String name, String type) {
        return new TriggerExtension.Parameter(name, type, true, null, "");
    }

    private static TriggerExtension.Descriptor descriptor(
            String id,
            TriggerExtension.Kind kind,
            String name,
            String description,
            List<TriggerExtension.Parameter> parameters,
            String returnType,
            TriggerExtension.Purity purity,
            TriggerExtension.ThreadAffinity affinity,
            TriggerExtension.Risk risk,
            Set<String> events) {
        return new TriggerExtension.Descriptor(id, kind, name, description, parameters, returnType,
                purity, affinity, risk, FOUR_FORGE_TARGETS, events, Map.of("builtIn", true));
    }
}