Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>Attributes must be derived from authoritative state (content/mod owned), not client claims.</p>
*/
public final class ActivationInput {

private final UUID activatorPlayerId;
private final ResourceId activationGateKey;
private final Map<String, String> attributes;
private final Optional<String> targetRef;

public ActivationInput(
UUID activatorPlayerId,
ResourceId activationGateKey,
Map<String, String> attributes,
Optional<String> 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<String, String> attributes() {
return attributes;
}

public Optional<String> targetRef() {
return targetRef;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ record ActivationAttemptRequest(
EncounterDefinition definition,
EncounterContext context,
Optional<ResourceId> activationGateKey,
Optional<ActivationAuthority> authorityOverride) {}
Optional<ActivationAuthority> authorityOverride,
Optional<String> targetRef) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.
*
* <p>This provides a platform-agnostic reference implementation that platform adapters may
* construct and delegate to.</p>
*/
public final class DefaultCoreRuntime implements CoreRuntime {

private final RegistryAccess registries;
Expand All @@ -59,9 +56,6 @@ public final class DefaultCoreRuntime implements CoreRuntime {
private final Optional<PlayerDirectory> players;
private final Optional<PartyDirectory> parties;

/**
* Platform-agnostic default constructor using the internal reference EncounterManager.
*/
public DefaultCoreRuntime() {
this(Optional.empty(), Optional.empty(), Clock.systemUTC());
}
Expand Down Expand Up @@ -121,7 +115,10 @@ public DefaultCoreRuntime(Optional<PlayerDirectory> players, Optional<PartyDirec
ActivationSessionService sessions = new DefaultActivationSessionService();
this.services.register(ActivationSessionService.class, sessions);

ActivationService activations = new DefaultActivationService(gates, sessions);
ActivationInputResolver activationInputs = request -> 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(
Expand All @@ -141,33 +138,6 @@ public DefaultCoreRuntime(Optional<PlayerDirectory> players, Optional<PartyDirec
this.parties = Objects.requireNonNull(parties, "parties");
}

public DefaultCoreRuntime(EncounterManager encounters) {
this(encounters, Optional.empty(), Optional.empty());
}

private DefaultCoreRuntime(
EncounterManager encounters,
EventBus events,
Optional<PlayerDirectory> players,
Optional<PartyDirectory> 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<PlayerDirectory> players, Optional<PartyDirectory> parties) {
this(encounters, new SimpleEventBus(), players, parties);
}

@Override
public RegistryAccess registries() {
return registries;
Expand Down