diff --git a/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInput.java b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInput.java new file mode 100644 index 0000000..e266d8b --- /dev/null +++ b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInput.java @@ -0,0 +1,47 @@ +package io.github.legendaryforge.legendary.core.api.activation; + +import io.github.legendaryforge.legendary.core.api.id.ResourceId; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** + * Server-side resolved activation input passed to gates. + * + *

Attributes must be derived from authoritative state (content/mod owned), not client claims.

+ */ +public final class ActivationInput { + + private final UUID activatorPlayerId; + private final ResourceId activationGateKey; + private final Map attributes; + private final Optional targetRef; + + public ActivationInput( + UUID activatorPlayerId, + ResourceId activationGateKey, + Map attributes, + Optional targetRef) { + this.activatorPlayerId = Objects.requireNonNull(activatorPlayerId, "activatorPlayerId"); + this.activationGateKey = Objects.requireNonNull(activationGateKey, "activationGateKey"); + this.attributes = Map.copyOf(Objects.requireNonNull(attributes, "attributes")); + this.targetRef = Objects.requireNonNull(targetRef, "targetRef"); + } + + public UUID activatorPlayerId() { + return activatorPlayerId; + } + + public ResourceId activationGateKey() { + return activationGateKey; + } + + public Map attributes() { + return attributes; + } + + public Optional targetRef() { + return targetRef; + } +} diff --git a/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInputResolver.java b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInputResolver.java new file mode 100644 index 0000000..9284d94 --- /dev/null +++ b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInputResolver.java @@ -0,0 +1,10 @@ +package io.github.legendaryforge.legendary.core.api.activation; + +/** + * Resolves authoritative activation inputs (attributes/target context) server-side. + */ +@FunctionalInterface +public interface ActivationInputResolver { + + ActivationInput resolve(ActivationService.ActivationAttemptRequest request); +} diff --git a/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationService.java b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationService.java index 115f0ac..eeedfc1 100644 --- a/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationService.java +++ b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationService.java @@ -15,5 +15,6 @@ record ActivationAttemptRequest( EncounterDefinition definition, EncounterContext context, Optional activationGateKey, - Optional authorityOverride) {} + Optional authorityOverride, + Optional targetRef) {} } diff --git a/src/main/java/io/github/legendaryforge/legendary/core/internal/activation/DefaultActivationService.java b/src/main/java/io/github/legendaryforge/legendary/core/internal/activation/DefaultActivationService.java index 77be004..a9146b9 100644 --- a/src/main/java/io/github/legendaryforge/legendary/core/internal/activation/DefaultActivationService.java +++ b/src/main/java/io/github/legendaryforge/legendary/core/internal/activation/DefaultActivationService.java @@ -3,6 +3,8 @@ import io.github.legendaryforge.legendary.core.api.activation.ActivationAttemptResult; import io.github.legendaryforge.legendary.core.api.activation.ActivationAttemptStatus; import io.github.legendaryforge.legendary.core.api.activation.ActivationDecision; +import io.github.legendaryforge.legendary.core.api.activation.ActivationInput; +import io.github.legendaryforge.legendary.core.api.activation.ActivationInputResolver; import io.github.legendaryforge.legendary.core.api.activation.ActivationService; import io.github.legendaryforge.legendary.core.api.activation.session.ActivationSessionBeginResult; import io.github.legendaryforge.legendary.core.api.activation.session.ActivationSessionBeginStatus; @@ -11,7 +13,6 @@ import io.github.legendaryforge.legendary.core.api.gate.ConditionGate; import io.github.legendaryforge.legendary.core.api.gate.GateDecision; import io.github.legendaryforge.legendary.core.api.gate.GateService; -import io.github.legendaryforge.legendary.core.api.id.ResourceId; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -20,10 +21,17 @@ public final class DefaultActivationService implements ActivationService { private final GateService gates; private final ActivationSessionService sessions; + private final ActivationInputResolver inputResolver; public DefaultActivationService(GateService gates, ActivationSessionService sessions) { + this(gates, sessions, DefaultActivationService::defaultResolve); + } + + public DefaultActivationService( + GateService gates, ActivationSessionService sessions, ActivationInputResolver inputResolver) { this.gates = Objects.requireNonNull(gates, "gates"); this.sessions = Objects.requireNonNull(sessions, "sessions"); + this.inputResolver = Objects.requireNonNull(inputResolver, "inputResolver"); } @Override @@ -34,59 +42,52 @@ public ActivationAttemptResult attemptActivation(ActivationAttemptRequest reques // Phase 2: evaluate activation gate (if present) ActivationDecision decision = request.activationGateKey() - .map(gateKey -> evaluateGate(gateKey, request, encounterKey)) + .map(ignored -> evaluateGate(inputResolver.resolve(request), encounterKey)) .orElseGet(ActivationDecision::allow); if (!decision.allowed()) { return new ActivationAttemptResult( - ActivationAttemptStatus.FAILED, - decision, - Optional.empty(), - Optional.empty()); + ActivationAttemptStatus.FAILED, decision, Optional.empty(), Optional.empty()); } // Phase 2: begin activation session - ActivationSessionBeginResult begin = sessions.begin( - new ActivationSessionService.ActivationSessionBeginRequest( - request.activatorId(), - encounterKey, - request.definition(), - request.context(), - request.activationGateKey(), - decision.attributes() - ) - ); + ActivationSessionBeginResult begin = sessions.begin(new ActivationSessionService.ActivationSessionBeginRequest( + request.activatorId(), + encounterKey, + request.definition(), + request.context(), + request.activationGateKey(), + decision.attributes())); - if (begin.status() == ActivationSessionBeginStatus.CREATED || begin.status() == ActivationSessionBeginStatus.EXISTING) { + if (begin.status() == ActivationSessionBeginStatus.CREATED + || begin.status() == ActivationSessionBeginStatus.EXISTING) { return new ActivationAttemptResult( - ActivationAttemptStatus.SUCCESS, - decision, - Optional.of(begin.sessionId()), - Optional.empty()); + ActivationAttemptStatus.SUCCESS, decision, Optional.of(begin.sessionId()), Optional.empty()); } // DENIED => FAILED ActivationDecision denied = ActivationDecision.deny(begin.reasonCode(), decision.attributes()); - return new ActivationAttemptResult( - ActivationAttemptStatus.FAILED, - denied, - Optional.empty(), - Optional.empty()); + return new ActivationAttemptResult(ActivationAttemptStatus.FAILED, denied, Optional.empty(), Optional.empty()); } - private ActivationDecision evaluateGate(ResourceId gateKey, ActivationAttemptRequest request, EncounterKey encounterKey) { + private ActivationDecision evaluateGate(ActivationInput input, EncounterKey encounterKey) { ConditionGate.GateRequest gateRequest = new ConditionGate.GateRequest( - gateKey, - request.activatorId(), + input.activationGateKey(), + input.activatorPlayerId(), Optional.of(encounterKey), Optional.empty(), Optional.empty(), - Map.of() - ); + input.attributes()); GateDecision gateDecision = gates.evaluate(gateRequest); return gateDecision.allowed() ? ActivationDecision.allow() : ActivationDecision.deny(gateDecision.reasonCode(), gateDecision.attributes()); } + + private static ActivationInput defaultResolve(ActivationAttemptRequest request) { + // Only invoked when request.activationGateKey() is present (see attemptActivation). + return new ActivationInput( + request.activatorId(), request.activationGateKey().orElseThrow(), Map.of(), request.targetRef()); + } } diff --git a/src/main/java/io/github/legendaryforge/legendary/core/internal/runtime/DefaultCoreRuntime.java b/src/main/java/io/github/legendaryforge/legendary/core/internal/runtime/DefaultCoreRuntime.java index 023b76b..c0e52ba 100644 --- a/src/main/java/io/github/legendaryforge/legendary/core/internal/runtime/DefaultCoreRuntime.java +++ b/src/main/java/io/github/legendaryforge/legendary/core/internal/runtime/DefaultCoreRuntime.java @@ -1,5 +1,7 @@ package io.github.legendaryforge.legendary.core.internal.runtime; +import io.github.legendaryforge.legendary.core.api.activation.ActivationInput; +import io.github.legendaryforge.legendary.core.api.activation.ActivationInputResolver; import io.github.legendaryforge.legendary.core.api.activation.ActivationService; import io.github.legendaryforge.legendary.core.api.activation.session.ActivationSessionService; import io.github.legendaryforge.legendary.core.api.encounter.EncounterManager; @@ -35,17 +37,12 @@ import io.github.legendaryforge.legendary.core.internal.lifecycle.DefaultServiceRegistry; import io.github.legendaryforge.legendary.core.internal.registry.DefaultRegistryAccess; import java.time.Clock; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -/** - * Default internal wiring of LegendaryCore runtime components. - * - *

This provides a platform-agnostic reference implementation that platform adapters may - * construct and delegate to.

- */ public final class DefaultCoreRuntime implements CoreRuntime { private final RegistryAccess registries; @@ -59,9 +56,6 @@ public final class DefaultCoreRuntime implements CoreRuntime { private final Optional players; private final Optional parties; - /** - * Platform-agnostic default constructor using the internal reference EncounterManager. - */ public DefaultCoreRuntime() { this(Optional.empty(), Optional.empty(), Clock.systemUTC()); } @@ -121,7 +115,10 @@ public DefaultCoreRuntime(Optional players, Optional new ActivationInput( + request.activatorId(), request.activationGateKey().orElseThrow(), Map.of(), request.targetRef()); + + ActivationService activations = new DefaultActivationService(gates, sessions, activationInputs); this.services.register(ActivationService.class, activations); bus.subscribe( @@ -141,33 +138,6 @@ public DefaultCoreRuntime(Optional players, Optional players, - Optional parties) { - this.registries = new DefaultRegistryAccess(); - - DefaultLifecycle lifecycle = new DefaultLifecycle(); - this.lifecycle = lifecycle; - - this.services = new DefaultServiceRegistry(lifecycle); - this.events = java.util.Objects.requireNonNull(events, "events"); - this.clock = Clock.systemUTC(); - this.encounters = java.util.Objects.requireNonNull(encounters, "encounters"); - this.players = java.util.Objects.requireNonNull(players, "players"); - this.parties = java.util.Objects.requireNonNull(parties, "parties"); - } - - private DefaultCoreRuntime( - EncounterManager encounters, Optional players, Optional parties) { - this(encounters, new SimpleEventBus(), players, parties); - } - @Override public RegistryAccess registries() { return registries;