First commit
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
# Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
core-minigames extends Hytale's existing asset, trigger-volume, and event systems. It does not run a separate minigame logic registry beside Hytale's trigger-volume registry.
|
||||
|
||||
```text
|
||||
Server/Minigames/*.json
|
||||
|
|
||||
v
|
||||
MinigameDefinition asset store
|
||||
|
|
||||
v
|
||||
MinigameRuntimeService
|
||||
|
|
||||
+--> Hytale event bus: MinigameEvent
|
||||
|
|
||||
+--> TriggerEffect.CODEC entries
|
||||
|
|
||||
`--> TriggerCondition.CODEC entries
|
||||
```
|
||||
|
||||
## Asset Store
|
||||
|
||||
`MinigameDefinition` is registered as a Hytale asset store in `MinigameCorePlugin.setup()`.
|
||||
|
||||
Important details:
|
||||
|
||||
- Path: `Server/Minigames/`
|
||||
- Codec: `MinigameDefinition.CODEC`
|
||||
- Key function: `MinigameDefinition::getId`
|
||||
- Editor dropdown source: `MinigameDefinition`
|
||||
|
||||
The trigger-volume editor can use this asset source for fields named `MinigameId`, so minigame trigger effects and conditions can pick from loaded minigame assets instead of free-typing IDs.
|
||||
|
||||
## Runtime
|
||||
|
||||
`DefaultMinigameRuntimeService` owns active `MinigameRuntime` instances keyed by runtime ID. A single minigame ID can have multiple active runtimes when multiple discovered maps or variants are started.
|
||||
|
||||
Runtime state includes:
|
||||
|
||||
- Current `MinigamePhase`
|
||||
- Player state, score, lives, team, tags, and status
|
||||
- Team membership and team score
|
||||
- Objective state
|
||||
- Counter and variable state
|
||||
- Spectator and eliminated-player sets
|
||||
- Runtime ID, map ID, and variant ID
|
||||
|
||||
`DefaultMinigameService` handles definition-level actions and delegates live actions to the runtime service.
|
||||
|
||||
## Events
|
||||
|
||||
`MinigameEvent` implements Hytale's `IEvent<String>`.
|
||||
|
||||
Runtime events dispatch through:
|
||||
|
||||
```java
|
||||
HytaleServer.get()
|
||||
.getEventBus()
|
||||
.dispatchFor(MinigameEvent.class, event.minigameId())
|
||||
.dispatch(event);
|
||||
```
|
||||
|
||||
This keeps minigame events available to Hytale's event system instead of routing them through a custom `EventRegistry`.
|
||||
|
||||
## Trigger Conditions
|
||||
|
||||
Minigame trigger conditions extend Hytale's `TriggerCondition` and are registered directly into `TriggerCondition.CODEC`.
|
||||
|
||||
Current condition type IDs:
|
||||
|
||||
- `MinigamePhase`
|
||||
- `PlayerInMinigame`
|
||||
- `PlayerStatus`
|
||||
- `PlayerScore`
|
||||
- `TeamScore`
|
||||
- `Counter`
|
||||
- `CurrentRound`
|
||||
- `PlayerLives`
|
||||
- `TeamCondition`
|
||||
- `TimerElapsed`
|
||||
- `ObjectiveStatus`
|
||||
- `WinConditionMet`
|
||||
|
||||
## Trigger Effects
|
||||
|
||||
Minigame trigger effects extend Hytale's `TriggerEffect` and are registered directly into `TriggerEffect.CODEC`.
|
||||
|
||||
Current effect type IDs:
|
||||
|
||||
- `StartMinigame`
|
||||
- `EndMinigame`
|
||||
- `ResetMinigame`
|
||||
- `SetMinigamePhase`
|
||||
- `ResetScores`
|
||||
- `ModifyPlayerScore`
|
||||
- `ModifyTeamScore`
|
||||
- `ModifyCounter`
|
||||
- `ModifyPlayerLives`
|
||||
- `SetPlayerStatus`
|
||||
- `JoinMinigameQueue`
|
||||
- `LeaveMinigameQueue`
|
||||
- `VoteForMap`
|
||||
- `StartQueuedMinigame`
|
||||
- `SpawnItem`
|
||||
- `SetRound`
|
||||
- `StartTimer`
|
||||
- `StopTimer`
|
||||
- `SetSpawnPoint`
|
||||
- `RespawnPlayer`
|
||||
- `SendMinigameMessage`
|
||||
- `AssignTeam`
|
||||
- `SaveInventory`
|
||||
- `RestoreInventory`
|
||||
- `ClearInventory`
|
||||
- `SetObjectiveStatus`
|
||||
- `ModifyObjectiveProgress`
|
||||
- `AwardKillScore`
|
||||
|
||||
## Volumes
|
||||
|
||||
There are no custom minigame volume classes after the refactor. Hytale trigger volumes provide the region shape, enter/exit behavior, tags, and trigger effect assets.
|
||||
|
||||
To define a minigame area, create a normal Hytale trigger volume and tag it with `MinigameId=<id>`, `MapId=<map>`, optional `VariantId=<variant>`, and optionally `MapRole=MainArena`. Then attach minigame trigger conditions and effects to that trigger volume or to reusable Hytale trigger effect assets.
|
||||
|
||||
## Removed Layer
|
||||
|
||||
The following old architecture pieces were removed because they duplicated Hytale systems:
|
||||
|
||||
- `BuiltinRegistries`
|
||||
- `ConditionRegistry`
|
||||
- `EventRegistry`
|
||||
- `MinigameRegistries`
|
||||
- `TemplateRegistry`
|
||||
- `VolumeRegistry`
|
||||
- `VolumeTypeDefinition`
|
||||
|
||||
Future minigame features should keep following the same pattern: use Hytale asset stores, Hytale trigger codec registration, and Hytale event dispatch unless there is a concrete engine gap that requires a local adapter.
|
||||
@@ -0,0 +1,100 @@
|
||||
# Commands Reference
|
||||
|
||||
All commands are subcommands of `/minigame`.
|
||||
|
||||
## Definition Management
|
||||
|
||||
### `/minigame list`
|
||||
|
||||
Lists registered minigames and basic runtime state.
|
||||
|
||||
### `/minigame create <id> <name>`
|
||||
|
||||
Creates a new minigame definition with default settings.
|
||||
|
||||
```text
|
||||
/minigame create Beach_Race "Beach Race"
|
||||
```
|
||||
|
||||
Definitions created by command start disabled. Enable them when the asset is configured.
|
||||
|
||||
### `/minigame info <id>`
|
||||
|
||||
Shows the loaded definition and runtime status for a minigame.
|
||||
|
||||
### `/minigame edit <id> <field> <value>`
|
||||
|
||||
Edits a single definition field and saves the definition.
|
||||
|
||||
```text
|
||||
/minigame edit Beach_Race MaxPlayers 16
|
||||
/minigame edit Beach_Race RoundLengthSeconds 600
|
||||
```
|
||||
|
||||
### `/minigame enable <id>`
|
||||
|
||||
Sets `Enabled = true`.
|
||||
|
||||
### `/minigame disable <id>`
|
||||
|
||||
Sets `Enabled = false`. This prevents new starts but does not necessarily stop an already running runtime.
|
||||
|
||||
### `/minigame validate <id>`
|
||||
|
||||
Runs definition validation. Current validation focuses on definition fields. The old custom-volume requirement for `MinigameAreaVolume` no longer applies.
|
||||
|
||||
### `/minigame delete <id>`
|
||||
|
||||
Deletes the minigame definition.
|
||||
|
||||
### `/minigame export <id>`
|
||||
|
||||
Exports the current definition JSON.
|
||||
|
||||
### `/minigame import <file>`
|
||||
|
||||
Imports a definition from a JSON file path relative to the data directory.
|
||||
|
||||
## Runtime Management
|
||||
|
||||
### `/minigame start <id>`
|
||||
|
||||
Starts a queued runtime for the given minigame. The selected map comes from `MapSelectionMode`: highest vote for `VOTE`, random discovered map for `RANDOM`.
|
||||
|
||||
### `/minigame stop <id>`
|
||||
|
||||
Stops all active runtimes for the minigame ID. An optional greedy `reason` argument can override the default admin-stop reason.
|
||||
|
||||
### `/minigame reset <id>`
|
||||
|
||||
Resets all active runtimes for the minigame ID.
|
||||
|
||||
## Map Commands
|
||||
|
||||
### `/minigame maps <id>`
|
||||
|
||||
Lists maps discovered from trigger volumes in the sender's current world. Run this command as a player in the world containing the tagged volumes.
|
||||
|
||||
### `/minigame vote <id> <mapId>`
|
||||
|
||||
Casts or updates the sender's vote in the minigame waiting session.
|
||||
|
||||
## Player Commands
|
||||
|
||||
### `/minigame join <id> [player]`
|
||||
|
||||
Adds the command sender, or the optional player target, to the minigame waiting queue.
|
||||
|
||||
### `/minigame leave [player]`
|
||||
|
||||
Currently registered but not implemented. Use the `LeaveMinigameQueue` trigger effect for in-world queue leaving until the command is wired.
|
||||
|
||||
### `/minigame spectate <id>`
|
||||
|
||||
Currently registered but not implemented. The command validates that the minigame exists and has a running runtime, then returns a not-implemented message.
|
||||
|
||||
## Volume Commands
|
||||
|
||||
The old custom minigame volume system was removed. Use Hytale's trigger-volume editor for real volume placement and attach the minigame trigger effects and conditions there.
|
||||
|
||||
`/minigame volume create <type>` currently remains as a command stub for future Hytale trigger-volume integration. It should not be used as the primary way to define arenas.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Events, Conditions & Effects
|
||||
|
||||
Minigame logic is implemented as Hytale-native trigger conditions and trigger effects. These are registered into `TriggerCondition.CODEC` and `TriggerEffect.CODEC`, so they appear beside Hytale's built-in trigger-volume logic.
|
||||
|
||||
## Events
|
||||
|
||||
`MinigameEvent` implements `IEvent<String>` and is dispatched through Hytale's event bus, keyed by minigame ID.
|
||||
|
||||
Current runtime events emitted by the service layer:
|
||||
|
||||
| Event ID | Fired When |
|
||||
|----------|------------|
|
||||
| `on_game_start` | A minigame runtime starts. |
|
||||
| `on_game_end` | A minigame runtime ends. |
|
||||
| `on_arena_reset` | A minigame runtime is reset. |
|
||||
| `on_phase_change` | `SetMinigamePhase` changes the runtime phase. |
|
||||
| `on_round_start` | `SetRound` starts a round, or the round timer advances to the next round. |
|
||||
| `on_round_end` | The internal round timer expires. |
|
||||
| `on_game_rounds_complete` | The final configured round ends. |
|
||||
| `on_timer_expired` | A named signal timer expires. The internal `$round` timer is consumed by runtime round progression and is not re-dispatched. |
|
||||
| `on_player_eliminated` | `SetPlayerStatus` sets `ELIMINATED`, or `ModifyPlayerLives` reduces lives to zero. |
|
||||
| `on_objective_complete` | An objective status becomes `COMPLETE` or objective progress reaches completion. |
|
||||
| `on_kill_score` | The kill scoring system awards points for a configured kill. |
|
||||
|
||||
The event payload is a string-object map. Runtime events include `runtime_id`, `minigame_id`, `map_id`, and `variant_id`. End events also include `reason`.
|
||||
|
||||
## Conditions
|
||||
|
||||
All minigame conditions share a `MinigameId` field. In the editor this is registered as an asset picker backed by `MinigameDefinition` assets.
|
||||
|
||||
| Type ID | Fields | Description |
|
||||
|---------|--------|-------------|
|
||||
| `MinigamePhase` | `MinigameId`, `Phase` | Passes when the runtime phase matches. |
|
||||
| `PlayerInMinigame` | `MinigameId`, optional `PlayerId` | Passes when the player is in the runtime. If `PlayerId` is blank, the trigger context player is used. |
|
||||
| `PlayerStatus` | `MinigameId`, optional `PlayerId`, `Status` | Passes when the player's minigame status matches. |
|
||||
| `PlayerScore` | `MinigameId`, optional `PlayerId`, `Compare`, `Value` | Compares a player's score. |
|
||||
| `TeamScore` | `MinigameId`, `TeamId`, `Compare`, `Value` | Compares a team's score. |
|
||||
| `Counter` | `MinigameId`, `CounterId`, `Compare`, `Value` | Compares a named runtime counter. |
|
||||
| `CurrentRound` | `MinigameId`, `Compare`, `Round` | Compares the current runtime round number. |
|
||||
| `PlayerLives` | `MinigameId`, optional `PlayerId`, `Compare`, `Value` | Compares a player's remaining lives. |
|
||||
| `TeamCondition` | `MinigameId`, optional `PlayerId`, `TeamId` | Passes when the player is assigned to the expected team. |
|
||||
| `TimerElapsed` | `MinigameId`, `TimerName` | Passes when a named runtime timer exists and has elapsed. |
|
||||
| `ObjectiveStatus` | `MinigameId`, `ObjectiveId`, `Status` | Passes when an objective has the expected status. |
|
||||
| `WinConditionMet` | `MinigameId`, optional `TargetScore` | Evaluates the runtime definition's win condition for `LAST_ALIVE`, `FIRST_TO_SCORE`, and `OBJECTIVE_COMPLETE`. |
|
||||
|
||||
### Compare Values
|
||||
|
||||
`PlayerScore`, `TeamScore`, `Counter`, `CurrentRound`, and `PlayerLives` use the `NumberCompare` enum:
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| `EQUAL` | Actual value equals `Value`. |
|
||||
| `NOT_EQUAL` | Actual value does not equal `Value`. |
|
||||
| `GREATER_THAN` | Actual value is greater than `Value`. |
|
||||
| `GREATER_THAN_OR_EQUAL` | Actual value is greater than or equal to `Value`. |
|
||||
| `LESS_THAN` | Actual value is less than `Value`. |
|
||||
| `LESS_THAN_OR_EQUAL` | Actual value is less than or equal to `Value`. |
|
||||
|
||||
## Effects
|
||||
|
||||
All minigame effects that operate on a runtime include a `MinigameId` field. In the editor this is registered as an asset picker backed by `MinigameDefinition` assets.
|
||||
|
||||
| Type ID | Fields | Description |
|
||||
|---------|--------|-------------|
|
||||
| `StartMinigame` | `MinigameId` | Starts a minigame runtime using the triggering volume's map tags when present. |
|
||||
| `JoinMinigameQueue` | `MinigameId` | Adds the triggering player to the minigame waiting session. |
|
||||
| `LeaveMinigameQueue` | `MinigameId` | Removes the triggering player from the minigame waiting session and clears their vote. |
|
||||
| `VoteForMap` | `MinigameId`, optional `MapId` | Votes for a map in the waiting session. If `MapId` is blank, the triggering volume's `MapId` tag is used. |
|
||||
| `StartQueuedMinigame` | `MinigameId` | Starts a queued match using `MapSelectionMode` and discovered map tags. |
|
||||
| `SpawnItem` | optional `MinigameId`, `ItemId`, optional `Amount`, `RespawnDelaySeconds`, `SpawnKey`, `OffsetX`, `OffsetY`, `OffsetZ` | Spawns an item entity on the ground at the trigger volume position. When attached to a `TICK` volume, it respawns after the previous item is picked up or despawns and the delay has elapsed. |
|
||||
| `AwardKillScore` | optional `MinigameId`, `TargetIds`, `Points`, `AwardTarget` | Configures kill scoring for deaths inside this volume. `TargetIds` accepts `Player`, NPC role ids such as `fox`, or `*`. `AwardTarget` is `PLAYER`, `TEAM`, or `BOTH`. |
|
||||
| `EndMinigame` | `MinigameId`, optional `Reason` | Ends the selected minigame runtime. |
|
||||
| `ResetMinigame` | `MinigameId` | Resets the selected minigame runtime. |
|
||||
| `SetMinigamePhase` | `MinigameId`, `Phase` | Sets the runtime phase directly. |
|
||||
| `ResetScores` | `MinigameId` | Resets all player and team scores for the runtime. |
|
||||
| `ModifyPlayerScore` | `MinigameId`, optional `PlayerId`, `Operation`, `Amount` | Sets, adds, or subtracts a player's score. |
|
||||
| `ModifyTeamScore` | `MinigameId`, `TeamId`, `Operation`, `Amount` | Sets, adds, or subtracts a team's score. |
|
||||
| `ModifyCounter` | `MinigameId`, `CounterId`, `Operation`, `Amount` | Sets, adds, or subtracts a named runtime counter. |
|
||||
| `ModifyPlayerLives` | `MinigameId`, optional `PlayerId`, `Operation`, `Amount` | Sets, adds, or subtracts a player's remaining lives. |
|
||||
| `SetPlayerStatus` | `MinigameId`, optional `PlayerId`, `Status` | Sets a player's minigame status. |
|
||||
| `SetRound` | `MinigameId`, `Operation`, `Amount` | Sets, adds, or subtracts the runtime round number and dispatches `on_round_start`. |
|
||||
| `StartTimer` | `MinigameId`, `TimerName`, `DurationSeconds` | Starts a named runtime timer and signal timer. |
|
||||
| `StopTimer` | `MinigameId`, `TimerName` | Stops a named runtime timer and cancels the signal timer. |
|
||||
| `SetSpawnPoint` | `MinigameId`, optional `PlayerId`, `DestinationId` | Stores a player's respawn destination/checkpoint. |
|
||||
| `RespawnPlayer` | `MinigameId`, optional `PlayerId`, optional `DestinationId` | Respawns a player using explicit destination, checkpoint, then team spawn fallback. |
|
||||
| `SendMinigameMessage` | `MinigameId`, `MessageKey`, `Target` | Sends a translated message to `PLAYER`, `TEAM`, or `ALL`. |
|
||||
| `AssignTeam` | `MinigameId`, optional `PlayerId`, `TeamId` | Assigns a player to a team in the runtime. |
|
||||
| `SaveInventory` | `MinigameId`, optional `PlayerId` | Saves the player's current inventory into runtime state. |
|
||||
| `RestoreInventory` | `MinigameId`, optional `PlayerId` | Restores the player's saved runtime inventory. |
|
||||
| `ClearInventory` | `MinigameId`, optional `PlayerId` | Clears the player's inventory. |
|
||||
| `SetObjectiveStatus` | `MinigameId`, `ObjectiveId`, `Status` | Creates or updates an objective status and dispatches `on_objective_complete` when complete. |
|
||||
| `ModifyObjectiveProgress` | `MinigameId`, `ObjectiveId`, `Operation`, `Amount` | Sets, adds, or subtracts objective progress and completes an active objective when progress reaches the required threshold. |
|
||||
|
||||
### Operation Values
|
||||
|
||||
Score, counter, and lives mutation effects use:
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| `SET` | Replace the current value with `Amount`. |
|
||||
| `ADD` | Add `Amount` to the current value. |
|
||||
| `SUBTRACT` | Subtract `Amount` from the current value. |
|
||||
|
||||
## Player Resolution
|
||||
|
||||
Several conditions and effects allow `PlayerId` to be omitted. When it is blank, the implementation attempts to resolve the triggering player from Hytale's trigger context. This is the intended setup for normal enter/interact trigger volumes.
|
||||
|
||||
Use an explicit `PlayerId` only when the trigger is not naturally tied to the player who caused it.
|
||||
|
||||
## Respawn Resolution
|
||||
|
||||
`RespawnPlayer` resolves where to send the player in this order:
|
||||
|
||||
1. The effect's explicit `DestinationId`.
|
||||
2. The player's stored checkpoint from `SetSpawnPoint`.
|
||||
3. A random team spawn volume matching the player's `TeamId`.
|
||||
|
||||
Team spawn volumes are normal Hytale trigger volumes tagged with `MinigameId`, `MapId`, optional `VariantId`, `SpawnRole=TeamSpawn`, and `TeamId=<team id>`.
|
||||
|
||||
## Item Spawners
|
||||
|
||||
`SpawnItem` creates a normal Hytale item drop, not a direct inventory grant. Use it for health packs, ammo, weapons, armor, and other pickups placed in the arena.
|
||||
|
||||
For automatic respawns, attach it to a trigger volume that fires on `TICK`. The effect keeps one active item per volume plus `SpawnKey`; once that item reference is invalid, it waits `RespawnDelaySeconds` before spawning the next copy. If one volume hosts several item spawners, give each effect a different `SpawnKey`.
|
||||
|
||||
## Kill Scoring
|
||||
|
||||
`AwardKillScore` is configured on a normal trigger volume, but scoring is applied by the minigame death system rather than by volume enter/exit/tick. The volume must cover the area where deaths should count and should use the normal `MinigameId`, `MapId`, and optional `VariantId` tags for runtime routing.
|
||||
|
||||
Use `TargetIds = ["Player"]` for player kills, NPC role ids such as `fox` for specific NPC kills, or `["*"]` for any supported target. Friendly kills use `FriendlyFireKillScoreMode` on the `MinigameDefinition`.
|
||||
|
||||
## Runtime Resolution
|
||||
|
||||
Effects and conditions resolve a runtime in this order:
|
||||
|
||||
1. The triggering player's active runtime.
|
||||
2. A runtime matching the triggering volume's `MinigameId`, `MapId`, and `VariantId` tags.
|
||||
3. The configured `MinigameId` as a fallback.
|
||||
@@ -0,0 +1,101 @@
|
||||
# Examples
|
||||
|
||||
These examples describe the current Hytale-native setup: minigame definitions are assets, and world interaction is done with normal Hytale trigger volumes using the minigame trigger condition/effect types.
|
||||
|
||||
## Simple Start Pad
|
||||
|
||||
Definition file: `Server/Minigames/Quick_Brawl.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"Id": "Quick_Brawl",
|
||||
"Name": "Quick Brawl",
|
||||
"Description": "A short solo score test.",
|
||||
"Enabled": true,
|
||||
"MinPlayers": 1,
|
||||
"MaxPlayers": 8,
|
||||
"TeamMode": "SOLO",
|
||||
"WinCondition": "HIGHEST_SCORE",
|
||||
"RoundLengthSeconds": 120,
|
||||
"CountdownSeconds": 5,
|
||||
"AllowPvp": true
|
||||
}
|
||||
```
|
||||
|
||||
Editor setup:
|
||||
|
||||
1. Create a normal Hytale trigger volume at the start pad.
|
||||
2. Add the `StartMinigame` effect.
|
||||
3. Set `MinigameId` to `Quick_Brawl` using the asset picker.
|
||||
|
||||
## Score Zone
|
||||
|
||||
Definition file: `Server/Minigames/Hill_Points.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"Id": "Hill_Points",
|
||||
"Name": "Hill Points",
|
||||
"Enabled": true,
|
||||
"MinPlayers": 1,
|
||||
"MaxPlayers": 12,
|
||||
"TeamMode": "SOLO",
|
||||
"WinCondition": "FIRST_TO_SCORE",
|
||||
"RoundLengthSeconds": 300
|
||||
}
|
||||
```
|
||||
|
||||
Editor setup:
|
||||
|
||||
1. Create a normal Hytale trigger volume around the scoring area.
|
||||
2. Add `PlayerInMinigame` as a condition.
|
||||
3. Add `ModifyPlayerScore` as an effect.
|
||||
4. Set `MinigameId = Hill_Points`, `Operation = ADD`, and `Amount = 1`.
|
||||
|
||||
If the trigger context already contains the player who entered the volume, leave `PlayerId` blank.
|
||||
|
||||
## End When Score Reaches a Target
|
||||
|
||||
Condition:
|
||||
|
||||
- Type: `PlayerScore`
|
||||
- `MinigameId = Hill_Points`
|
||||
- `Compare = GREATER_THAN_OR_EQUAL`
|
||||
- `Value = 100`
|
||||
|
||||
Effect:
|
||||
|
||||
- Type: `EndMinigame`
|
||||
- `MinigameId = Hill_Points`
|
||||
- `Reason = score_reached`
|
||||
|
||||
## Team Score Zone
|
||||
|
||||
Use `ModifyTeamScore` when the score belongs to a team instead of a player.
|
||||
|
||||
Required fields:
|
||||
|
||||
- `MinigameId`
|
||||
- `TeamId`
|
||||
- `Operation`
|
||||
- `Amount`
|
||||
|
||||
The current implementation expects an explicit `TeamId`.
|
||||
|
||||
## Counter Gate
|
||||
|
||||
Increment:
|
||||
|
||||
- Effect: `ModifyCounter`
|
||||
- `CounterId = captured_points`
|
||||
- `Operation = ADD`
|
||||
- `Amount = 1`
|
||||
|
||||
Gate:
|
||||
|
||||
- Condition: `Counter`
|
||||
- `CounterId = captured_points`
|
||||
- `Compare = GREATER_THAN_OR_EQUAL`
|
||||
- `Value = 3`
|
||||
|
||||
Then attach a follow-up effect, such as `SetMinigamePhase` or `EndMinigame`.
|
||||
@@ -0,0 +1,147 @@
|
||||
# Minigame Definition Reference
|
||||
|
||||
Minigame definitions live in `src/main/resources/Server/Minigames/<Id>.json`. JSON keys use PascalCase. The filename should match the `Id` field.
|
||||
|
||||
## Identity
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `Id` | String | Yes | none | Unique asset ID. Use underscores instead of spaces. |
|
||||
| `Name` | String | Yes | none | Display name shown in commands and UI. |
|
||||
| `Description` | String | No | `""` | Short display description. |
|
||||
| `Enabled` | Boolean | No | `true` | Whether the minigame can be started. |
|
||||
| `Debug` | Boolean | No | `false` | Enables verbose minigame debug messages when supported by runtime logic. |
|
||||
| `GameType` | Enum | No | `GENERIC` | `GENERIC`, `BATTLE`, `PUZZLE`, `RACE`, `CAPTURE`, or `SURVIVAL`. |
|
||||
|
||||
## World Binding
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `WorldId` | String | No | `""` | Optional world identifier. |
|
||||
| `RequiredGameMode` | String | No | `"adventure"` | Game mode expected for players. |
|
||||
|
||||
## Players
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `MinPlayers` | Integer | No | `1` | Minimum player count. |
|
||||
| `MaxPlayers` | Integer | No | `16` | Maximum player count. |
|
||||
| `AllowJoinMidgame` | Boolean | No | `false` | Whether players can join after the game starts. |
|
||||
| `AllowSpectators` | Boolean | No | `true` | Whether spectators are allowed. |
|
||||
|
||||
## Teams
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `TeamMode` | Enum | No | `SOLO` | `SOLO`, `TEAMS`, or `FREE_FOR_ALL`. |
|
||||
| `TeamCount` | Integer | No | `0` | Number of teams when team mode is enabled. `0` means automatic. |
|
||||
| `PlayersPerTeam` | Integer | No | `0` | Maximum players per team. `0` means no explicit limit. |
|
||||
|
||||
## Timing
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `RoundLengthSeconds` | Integer | No | `300` | Active round duration. |
|
||||
| `TotalRounds` | Integer | No | `1` | Number of rounds to run. Values less than or equal to `0` allow open-ended round progression. |
|
||||
| `CountdownSeconds` | Integer | No | `10` | Pre-game countdown duration. |
|
||||
| `OvertimeEnabled` | Boolean | No | `false` | Enables overtime handling. |
|
||||
| `SuddenDeathEnabled` | Boolean | No | `false` | Enables sudden-death handling. |
|
||||
|
||||
## Win Condition
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `WinCondition` | Enum | No | `HIGHEST_SCORE` | How the winner is determined. |
|
||||
| `MapSelectionMode` | Enum | No | `VOTE` | How a waiting session chooses a discovered map: `VOTE` or `RANDOM`. |
|
||||
| `FriendlyFireKillScoreMode` | Enum | No | `NO_POINTS` | How same-team player kills affect kill scoring: `OFF`, `LOSES_POINTS`, or `NO_POINTS`. |
|
||||
|
||||
Supported values:
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `HIGHEST_SCORE` | Highest score at the end wins. |
|
||||
| `FIRST_TO_SCORE` | First player or team to a target score wins. |
|
||||
| `LAST_ALIVE` | Last active player or team wins. |
|
||||
| `OBJECTIVE_COMPLETE` | Objective completion determines the winner. |
|
||||
| `CUSTOM` | Trigger logic determines the winner. |
|
||||
|
||||
## World Rules
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `AllowPvp` | Boolean | No | `false` | Whether players can damage each other. |
|
||||
| `AllowBlockBreaking` | Boolean | No | `false` | Whether blocks can be broken. |
|
||||
| `AllowBlockPlacing` | Boolean | No | `false` | Whether blocks can be placed. |
|
||||
|
||||
## Inventory
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `SavePlayerInventory` | Boolean | No | `true` | Save player inventory on join. |
|
||||
| `RestorePlayerInventory` | Boolean | No | `true` | Restore player inventory when the game ends. |
|
||||
| `ResetOnEnd` | Boolean | No | `true` | Reset runtime state when the game ends. |
|
||||
|
||||
## Items
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `StartItems` | `ItemStackConfig[]` | No | `[]` | Items given to players at game start. |
|
||||
| `Rewards` | `RewardConfig[]` | No | `[]` | Rewards distributed at game end. |
|
||||
|
||||
## ItemStackConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"ItemId": "event_fishing_rod",
|
||||
"Amount": 1
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `ItemId` | String | Yes | Hytale item ID. |
|
||||
| `Amount` | Integer | Yes | Stack amount. |
|
||||
|
||||
## RewardConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"Target": "winner",
|
||||
"Items": [
|
||||
{ "ItemId": "gold_coin", "Amount": 25 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `Target` | String | Yes | `winner`, `participant`, `team_winner`, or a team ID. |
|
||||
| `Items` | `ItemStackConfig[]` | Yes | Items to grant. |
|
||||
|
||||
## Minimal Example
|
||||
|
||||
```json
|
||||
{
|
||||
"Id": "Quick_Brawl",
|
||||
"Name": "Quick Brawl",
|
||||
"Enabled": true,
|
||||
"MinPlayers": 2,
|
||||
"MaxPlayers": 8,
|
||||
"TeamMode": "SOLO",
|
||||
"WinCondition": "LAST_ALIVE",
|
||||
"RoundLengthSeconds": 120,
|
||||
"AllowPvp": true
|
||||
}
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
Current validation is definition-focused:
|
||||
|
||||
- `Name` must not be blank.
|
||||
- `MinPlayers` must be greater than or equal to 1.
|
||||
- `MaxPlayers` must be greater than or equal to `MinPlayers`.
|
||||
- `RoundLengthSeconds` must be greater than 0.
|
||||
- `CountdownSeconds` must be greater than or equal to 0.
|
||||
|
||||
The old custom `MinigameAreaVolume` validation requirement no longer applies. Minigame areas are normal Hytale trigger volumes configured in the editor.
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# Trigger Volume Tags
|
||||
|
||||
core-minigames uses normal Hytale trigger-volume tags for map discovery, runtime routing, team spawns, kill scoring, and runtime signals. Tag keys and values are case-sensitive.
|
||||
|
||||
## Map And Runtime Tags
|
||||
|
||||
These tags identify which minigame map or variant a volume belongs to.
|
||||
|
||||
| Tag | Required | Values | Used By |
|
||||
|-----|----------|--------|---------|
|
||||
| `MinigameId` | Yes for discovered map volumes | A loaded `MinigameDefinition` ID, such as `KOTH` | Map discovery, runtime routing, queue start, kill scoring, respawns, signals |
|
||||
| `MapId` | Yes for discovered map volumes | Map or arena ID, such as `Castle` | Map discovery, voting, runtime routing |
|
||||
| `VariantId` | No | Variant ID, such as `Night` or `AltA` | Runtime routing and random variant selection |
|
||||
| `MapRole` | No | `MainArena` | Marks the primary arena volume for a discovered map candidate |
|
||||
|
||||
Minimum map candidate:
|
||||
|
||||
```text
|
||||
MinigameId=KOTH
|
||||
MapId=Castle
|
||||
MapRole=MainArena
|
||||
```
|
||||
|
||||
Variant candidate:
|
||||
|
||||
```text
|
||||
MinigameId=KOTH
|
||||
MapId=Castle
|
||||
VariantId=Night
|
||||
MapRole=MainArena
|
||||
```
|
||||
|
||||
Overlapping volumes are allowed. Volumes with the same `MinigameId`, `MapId`, and `VariantId` are grouped into one discovered map candidate.
|
||||
|
||||
## Spawn Tags
|
||||
|
||||
Team spawn volumes are normal trigger volumes used as teleport destinations by `RespawnPlayer`.
|
||||
|
||||
| Tag | Required | Values | Used By |
|
||||
|-----|----------|--------|---------|
|
||||
| `MinigameId` | Recommended | Minigame ID | Filters spawns to the current runtime's minigame |
|
||||
| `MapId` | Recommended | Map ID | Filters spawns to the current runtime's map |
|
||||
| `VariantId` | No | Variant ID | Filters spawns to the current runtime's variant when present |
|
||||
| `SpawnRole` | Yes | `TeamSpawn` | Marks the volume as a team spawn candidate |
|
||||
| `TeamId` | Yes | Team ID, such as `red` or `blue` | Selects spawns for the player's assigned team |
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
MinigameId=KOTH
|
||||
MapId=Castle
|
||||
SpawnRole=TeamSpawn
|
||||
TeamId=red
|
||||
```
|
||||
|
||||
`RespawnPlayer` chooses a random matching team spawn when no explicit `DestinationId` and no player checkpoint are available.
|
||||
|
||||
## Signal Tags
|
||||
|
||||
Signal tags let runtime state changes fire normal Hytale trigger-volume logic by toggling a tag on matching volumes.
|
||||
|
||||
| Tag | Required | Values | Behavior |
|
||||
|-----|----------|--------|----------|
|
||||
| `SignalRoundStart` | No | Round number, such as `1` | Fires once when that round starts. |
|
||||
| `SignalRoundTick` | No | Round number, such as `1` | Fires repeatedly while that round is active. |
|
||||
| `SignalPhaseTick` | No | `MinigamePhase` enum name, such as `ACTIVE` | Fires repeatedly while the runtime is in that phase. |
|
||||
| `SignalTickDelay` | No | Seconds, such as `1` or `0.5` | Delay between repeated signal fires. Minimum effective delay is 50ms. |
|
||||
| `SignalOnTimerExpire` | No | Timer name from `StartTimer` | Fires once when the named timer expires. |
|
||||
| `MinigameSignal` | Internal | `0` or `1` | Written by the runtime signal service to trigger Hytale `TAG_ADDED` behavior. Do not configure manually. |
|
||||
|
||||
Signal volumes must also have `MinigameId=<id>`. Timer names starting with `$` are internal runtime timers and do not fire trigger-volume signal tags.
|
||||
|
||||
Round-start example:
|
||||
|
||||
```text
|
||||
MinigameId=KOTH
|
||||
MapId=Castle
|
||||
SignalRoundStart=1
|
||||
```
|
||||
|
||||
Active-phase tick example:
|
||||
|
||||
```text
|
||||
MinigameId=KOTH
|
||||
MapId=Castle
|
||||
SignalPhaseTick=ACTIVE
|
||||
SignalTickDelay=1
|
||||
```
|
||||
|
||||
Timer-expire example:
|
||||
|
||||
```text
|
||||
MinigameId=KOTH
|
||||
MapId=Castle
|
||||
SignalOnTimerExpire=overtime_warning
|
||||
```
|
||||
|
||||
## Validation Notes
|
||||
|
||||
- A volume with `MapId` but no `MinigameId` produces a warning and is ignored for map discovery.
|
||||
- A discovered map candidate without `MapRole=MainArena` produces a warning but does not stop the minigame from loading.
|
||||
- Missing map discovery is a warning, not a startup failure.
|
||||
- Duplicate or overlapping variants are allowed when they intentionally represent alternate zones for the same map.
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
# Volumes
|
||||
|
||||
Minigame areas now use Hytale's built-in trigger volumes. The mod no longer defines custom volume types such as `MinigameAreaVolume`, `JoinQueueVolume`, or `ScoreVolume`.
|
||||
|
||||
This is intentional: the area, shape, enter/exit behavior, tags, and trigger-effect asset wiring belong to Hytale's trigger-volume system. core-minigames adds minigame-specific conditions and effects that can be attached to those trigger volumes.
|
||||
|
||||
## Defining Minigame Maps
|
||||
|
||||
1. Create a normal Hytale trigger volume in the editor.
|
||||
2. Shape it around the arena or interaction area.
|
||||
3. Tag it with `MinigameId=<MinigameId>` and `MapId=<MapId>`.
|
||||
4. Add `VariantId=<VariantId>` when the same map can randomly use overlapping alternate zones.
|
||||
5. Add `MapRole=MainArena` to the primary arena volume for each map or variant.
|
||||
6. Attach trigger effects and conditions for the relevant minigame.
|
||||
7. Use the `MinigameId` dropdown to select the `MinigameDefinition` asset.
|
||||
|
||||
There is no required root `MinigameAreaVolume` type. If validation needs an arena boundary later, it should inspect Hytale trigger-volume metadata instead of requiring a custom minigame volume class.
|
||||
|
||||
Map candidates are discovered from trigger-volume tags. See [Trigger Volume Tags](tags.md) for the full tag reference.
|
||||
|
||||
Overlapping volumes are valid. Runtime routing uses the selected `MinigameId`, `MapId`, and `VariantId`, so the same Hytale build can host multiple arena definitions.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Start Zone
|
||||
|
||||
Use a normal trigger volume at an entrance, sign, NPC, or lobby pad.
|
||||
|
||||
Attach:
|
||||
|
||||
- Effect: `JoinMinigameQueue`
|
||||
- Field: `MinigameId = <your minigame>`
|
||||
|
||||
For exits or cancel pads:
|
||||
|
||||
- Effect: `LeaveMinigameQueue`
|
||||
- Field: `MinigameId = <your minigame>`
|
||||
|
||||
For voting buttons or pads:
|
||||
|
||||
- Effect: `VoteForMap`
|
||||
- Field: `MapId = <map id>`, or leave blank to use the volume's `MapId` tag.
|
||||
|
||||
To start the queued match:
|
||||
|
||||
- Effect: `StartQueuedMinigame`
|
||||
- Field: `MinigameId = <your minigame>`
|
||||
|
||||
Optionally guard it with:
|
||||
|
||||
- Condition: `MinigamePhase`
|
||||
- Field: `Phase = WAITING_FOR_PLAYERS`
|
||||
|
||||
### Score Zone
|
||||
|
||||
Use a normal trigger volume around the scoring area.
|
||||
|
||||
Attach:
|
||||
|
||||
- Condition: `PlayerInMinigame`
|
||||
- Effect: `ModifyPlayerScore`
|
||||
- Fields: `Operation = ADD`, `Amount = <points>`
|
||||
|
||||
If the score is team-based, use `ModifyTeamScore` instead.
|
||||
|
||||
### Phase Gate
|
||||
|
||||
Use Hytale's normal volume behavior for the area and guard attached effects with:
|
||||
|
||||
- Condition: `MinigamePhase`
|
||||
- Field: `Phase = ACTIVE`
|
||||
|
||||
### Team Spawns
|
||||
|
||||
Use normal trigger volumes as spawn markers. The volume position is the teleport destination.
|
||||
|
||||
Tag each team spawn volume with:
|
||||
|
||||
- `MinigameId=<your minigame>`
|
||||
- `MapId=<map id>`
|
||||
- optional `VariantId=<variant id>`
|
||||
- `SpawnRole=TeamSpawn`
|
||||
- `TeamId=<team id>`
|
||||
|
||||
`RespawnPlayer` resolves destinations in this order:
|
||||
|
||||
1. The effect's explicit `DestinationId`.
|
||||
2. The player's stored checkpoint from `SetSpawnPoint`.
|
||||
3. A random matching team spawn volume for the player's `TeamId`.
|
||||
|
||||
This means checkpoint-based games can still override spawns per player, while team arena games can rely on tagged team spawn volumes.
|
||||
|
||||
### Counters
|
||||
|
||||
Use a trigger volume to increment a named runtime counter:
|
||||
|
||||
- Effect: `ModifyCounter`
|
||||
- Fields: `CounterId = <name>`, `Operation = ADD`, `Amount = 1`
|
||||
|
||||
Then use the `Counter` condition to branch once a threshold is reached.
|
||||
|
||||
### Kill Scoring
|
||||
|
||||
Use a trigger volume covering the arena or scoring zone and attach:
|
||||
|
||||
- Effect: `AwardKillScore`
|
||||
- Fields: `TargetIds = ["Player"]` or NPC role ids such as `["fox"]`, `Points = <points>`, `AwardTarget = PLAYER`
|
||||
|
||||
The death system checks this volume when an entity dies inside it. It routes by the volume's `MinigameId`, `MapId`, and optional `VariantId` tags, so overlapping arenas can have different kill scoring rules.
|
||||
|
||||
### Pickup Spawners
|
||||
|
||||
Use a small trigger volume at the pickup location and attach:
|
||||
|
||||
- Effect: `SpawnItem`
|
||||
- Fields: `ItemId = <item id>`, `Amount = <stack size>`, `RespawnDelaySeconds = <seconds>`
|
||||
|
||||
The item spawns at the volume position plus optional `OffsetX`, `OffsetY`, and `OffsetZ`. Attach the effect to a `TICK` trigger when the pickup should automatically respawn after being collected or despawning. If the same volume has multiple item spawners, set a different `SpawnKey` for each one.
|
||||
|
||||
## Reusable Effect Assets
|
||||
|
||||
For repeated logic, define a Hytale trigger effect asset and attach it to multiple trigger volumes. The minigame trigger condition/effect entries are normal codec types, so they can be reused the same way Hytale's built-in trigger-volume effects are reused.
|
||||
Reference in New Issue
Block a user