feat: Add new effects for player interactions and NPC management

- Introduced DismountPlayerEffect to handle player dismounting from NPCs.
- Added MountPlayerEffect for mounting players onto NPCs with configurable parameters.
- Implemented PlaceBlocksEffect to allow block placement in specified regions.
- Created RespawnPlayerEffect with improved destination resolution for player respawns.
- Added SetPlayerLoadoutEffect to manage player loadouts dynamically.
- Introduced SetPlayerStatusEffect to manage player statuses with additional activation logic.
- Created SpawnNpcEffect for spawning and managing NPCs with respawn capabilities.
- Updated language files to include tooltips and descriptions for new effects.
This commit is contained in:
2026-06-07 21:38:30 -07:00
parent 479d010e60
commit 67e70a71bd
30 changed files with 1750 additions and 85 deletions
+3
View File
@@ -104,6 +104,9 @@ Current effect type IDs:
- `VoteForMap`
- `StartQueuedMinigame`
- `SpawnItem`
- `SpawnNpc`
- `MountPlayer`
- `PlaceBlocks`
- `SetRound`
- `StartTimer`
- `StopTimer`
+31 -4
View File
@@ -12,11 +12,13 @@ Current runtime events emitted by the service layer:
|----------|------------|
| `on_game_start` | A minigame runtime starts. |
| `on_game_end` | A minigame runtime ends. |
| `on_arena_reset` | A minigame runtime is reset. |
| `on_arena_reset` | A minigame runtime is reset via `ResetMinigame`, or a game ends with `ResetOnEnd=true`. |
| `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_overtime_start` | The final round ends and `OvertimeEnabled=true`. Phase is set to `OVERTIME`. Fired before `on_game_rounds_complete`. |
| `on_sudden_death_start` | The final round ends and `SuddenDeathEnabled=true`. Phase is set to `SUDDEN_DEATH`. Fired before `on_game_rounds_complete`. |
| `on_game_rounds_complete` | The final configured round ends (after any overtime/sudden-death events). |
| `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. |
@@ -68,6 +70,10 @@ All minigame effects that operate on a runtime include a `MinigameId` field. In
| `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. |
| `SpawnNpc` | optional `MinigameId`, `RoleId`, optional `Count`, `RespawnDelaySeconds`, `SpawnKey`, `OffsetX`, `OffsetY`, `OffsetZ`, `Yaw`, `Pitch`, `Roll`, `SpreadRadius` | Spawns one or more NPCs at the trigger volume position. Spawned NPCs are tracked as temporary runtime entities and can respawn after all active NPCs for the spawn key are gone. |
| `MountPlayer` | optional `MinigameId`, `PlayerId`, `NpcId`, `NpcRoleId`, `MaxDistance`, `AnchorX`, `AnchorY`, `AnchorZ`, `MovementConfig`, `EmptyRoleId`, optional `PreventManualDismount` | Mounts the triggering or configured player onto the nearest matching tracked NPC. When `PreventManualDismount` is `true`, the player cannot dismount manually until a `DismountPlayer` effect runs. |
| `DismountPlayer` | optional `MinigameId`, optional `PlayerId`, optional `NpcRoleId`, optional `RemoveNpc` | Dismounts the player from their NPC mount. `RemoveNpc` defaults to `true` and removes the mount entity from the world. Clears any manual-dismount lock set by `MountPlayer`. |
| `PlaceBlocks` | optional `MinigameId`, `BlockId`, `X1`, `Y1`, `Z1`, `X2`, `Y2`, `Z2`, optional `MaxBlocks` | Places `BlockId` in the inclusive cuboid between the two world-coordinate corners. `MaxBlocks` defaults to `32768` as a guard against accidental large fills. |
| `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. |
@@ -77,14 +83,15 @@ All minigame effects that operate on a runtime include a `MinigameId` field. In
| `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. |
| `SetPlayerStatus` | `MinigameId`, optional `PlayerId`, `Status` | Sets a player's minigame status. Setting `ACTIVE` triggers the activation lifecycle: saves inventory (if `SavePlayerInventory`), gives loadout or start items, and applies `RequiredGameMode`. |
| `SetPlayerLoadout` | `MinigameId`, optional `PlayerId`, `LoadoutId` | Assigns a named loadout to a player. The loadout must exist in `StartLoadouts`. Takes effect the next time the player becomes `ACTIVE`. Pass an empty `LoadoutId` to clear the assignment. |
| `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. |
| `AssignTeam` | `MinigameId`, optional `PlayerId`, `TeamId`, optional `BalanceTeams` | Assigns a player to a team in the runtime. If `BalanceTeams` is true, evenly distributes all runtime players across existing teams or auto-creates `team1..teamN` using `TeamCount`, or `PlayersPerTeam` when `TeamCount` is `0`. |
| `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. |
@@ -117,12 +124,32 @@ Use an explicit `PlayerId` only when the trigger is not naturally tied to the pl
Team spawn volumes are normal Hytale trigger volumes tagged with `MinigameId`, `MapId`, optional `VariantId`, `SpawnRole=TeamSpawn`, and `TeamId=<team id>`.
Player deaths during a minigame use the same resolution order automatically. `DestinationId` and `SetSpawnPoint` checkpoint values are coordinate strings, such as `100,64,200` or `world_name,100,64,200`.
## 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`.
## NPC Spawners
`SpawnNpc` uses Hytale NPC role ids. `RoleId` is registered as a spawnable NPC role asset picker in the editor. Use `Count` plus `SpreadRadius` for small packs, and use `RespawnDelaySeconds` on a repeatedly firing volume when the encounter should come back after all NPCs for the same `SpawnKey` are gone.
## Player Mounts
`MountPlayer` uses Hytale's built-in NPC mount component. It mounts the player onto the nearest tracked NPC in the trigger volume, optionally filtered by `NpcRoleId` or exact `NpcId`. The trigger volume must track the NPC as well as the player, so include NPC target types on the volume when using nearest-NPC mounting.
`AnchorX`, `AnchorY`, and `AnchorZ` define the rider anchor passed to the client. `EmptyRoleId` defaults to `Empty_Role`, matching Hytale's built-in mount action. `MovementConfig` is optional; set it when the mounted player should use horse-style movement settings.
Set `PreventManualDismount=true` to lock the player onto the mount. The client's dismount packet is silently dropped until `DismountPlayer` is called. `DismountPlayer` always clears the lock, dismounts the player, and removes the NPC entity from the world by default (`RemoveNpc=true`). Filter by `NpcRoleId` if multiple mount types are active at the same time.
## Block Placement
`PlaceBlocks` fills an inclusive world-coordinate cuboid with one block type. Configure `BlockId`, `X1`, `Y1`, `Z1`, `X2`, `Y2`, and `Z2`; coordinates can be provided in either corner order.
The effect clamps Y to the valid world range `0..319`. Use `MaxBlocks` when a fill intentionally exceeds the default `32768` block guard.
## 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.
+1 -1
View File
@@ -80,7 +80,7 @@ Required fields:
- `Operation`
- `Amount`
The current implementation expects an explicit `TeamId`.
Use `AssignTeam` with `BalanceTeams=true` to split all players in the started runtime evenly across teams. If no teams already exist, the effect creates `team1..teamN` from the minigame's `TeamCount`.
## Counter Gate
+49 -16
View File
@@ -18,7 +18,7 @@ Minigame definitions live in `src/main/resources/Server/Minigames/<Id>.json`. JS
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `WorldId` | String | No | `""` | Optional world identifier. |
| `RequiredGameMode` | String | No | `"adventure"` | Game mode expected for players. |
| `RequiredGameMode` | Enum | No | `Adventure` | Game mode applied to each player when they become active. |
## Players
@@ -26,6 +26,7 @@ Minigame definitions live in `src/main/resources/Server/Minigames/<Id>.json`. JS
|-------|------|----------|---------|-------------|
| `MinPlayers` | Integer | No | `1` | Minimum player count. |
| `MaxPlayers` | Integer | No | `16` | Maximum player count. |
| `DefaultPlayerLives` | Integer | No | `0` | Initial lives assigned to each player when a queued runtime starts. |
| `AllowJoinMidgame` | Boolean | No | `false` | Whether players can join after the game starts. |
| `AllowSpectators` | Boolean | No | `true` | Whether spectators are allowed. |
@@ -34,8 +35,8 @@ Minigame definitions live in `src/main/resources/Server/Minigames/<Id>.json`. JS
| 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. |
| `TeamCount` | Integer | No | `0` | Explicit number of teams when using `BalanceTeams`. `0` defers to `PlayersPerTeam`. |
| `PlayersPerTeam` | Integer | No | `0` | When `TeamCount` is `0`, `BalanceTeams` divides total players by this to determine team count. `0` means no automatic splitting. |
## Timing
@@ -44,14 +45,14 @@ Minigame definitions live in `src/main/resources/Server/Minigames/<Id>.json`. JS
| `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. |
| `OvertimeEnabled` | Boolean | No | `false` | When the final round ends, sets the phase to `OVERTIME` and dispatches `on_overtime_start` before `on_game_rounds_complete`. |
| `SuddenDeathEnabled` | Boolean | No | `false` | When the final round ends (after overtime if also enabled), sets the phase to `SUDDEN_DEATH` and dispatches `on_sudden_death_start`. |
## Win Condition
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `WinCondition` | Enum | No | `HIGHEST_SCORE` | How the winner is determined. |
| `WinCondition` | Enum | No | `HIGHEST_SCORE` | How the winner is determined. Affects end-score display order and reward distribution. |
| `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`. |
@@ -59,7 +60,8 @@ Supported values:
| Value | Description |
|-------|-------------|
| `HIGHEST_SCORE` | Highest score at the end wins. |
| `HIGHEST_SCORE` | Highest score at the end wins. Players ranked descending. |
| `LOWEST_SCORE` | Lowest score at the end wins. Players ranked ascending. |
| `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. |
@@ -70,23 +72,24 @@ Supported values:
| 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. |
| `AllowBlockBreaking` | Boolean | No | `false` | Whether blocks can be broken. Readable via definition; enforcement relies on `RequiredGameMode` (`Adventure` blocks both break and place by default). |
| `AllowBlockPlacing` | Boolean | No | `false` | Whether blocks can be placed. Same enforcement note as above. |
## Inventory
## Lifecycle
| 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. |
| `ResetOnEnd` | Boolean | No | `true` | After clearing runtime state at game end, dispatches `on_arena_reset` so trigger volumes can reset world state. |
| `SavePlayerInventory` | Boolean | No | `true` | Saves each player's inventory the first time they become `ACTIVE`. Restored on game end if `RestorePlayerInventory` is true. |
| `RestorePlayerInventory` | Boolean | No | `true` | Restores each player's saved inventory 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. |
| `StartItems` | `ItemStackConfig[]` | No | `[]` | Items given to each player when they become `ACTIVE`, if they have no assigned loadout. |
| `StartLoadouts` | `LoadoutConfig[]` | No | `[]` | Named item loadouts. A player's assigned loadout (set via `SetPlayerLoadout`) takes priority over `StartItems`. |
| `Rewards` | `RewardConfig[]` | No | `[]` | Rewards distributed at game end, before `on_game_end` fires. |
## ItemStackConfig
@@ -102,6 +105,28 @@ Supported values:
| `ItemId` | String | Yes | Hytale item ID. |
| `Amount` | Integer | Yes | Stack amount. |
## LoadoutConfig
```json
{
"Id": "archer",
"DisplayName": "Archer",
"Items": [
{ "ItemId": "hunting_bow", "Amount": 1 },
{ "ItemId": "arrow", "Amount": 32 }
]
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `Id` | String | Yes | Loadout ID referenced by `SetPlayerLoadout`. |
| `DisplayName` | String | No | Player-facing name for UI display. |
| `Items` | `ItemStackConfig[]` | No | Items given when this loadout is applied. |
| `PlayerCustomizable` | Boolean | No | Reserved for a future loadout-selection UI. When `true`, item giving is skipped at activation. Defaults to `false`. |
Loadouts are selected by a `SetPlayerLoadout` effect before the player becomes `ACTIVE`. If no loadout is assigned, `StartItems` is used as a fallback.
## RewardConfig
```json
@@ -115,9 +140,17 @@ Supported values:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `Target` | String | Yes | `winner`, `participant`, `team_winner`, or a team ID. |
| `Target` | String | Yes | Who receives the reward. See target values below. |
| `Items` | `ItemStackConfig[]` | Yes | Items to grant. |
Reward target values:
| Value | Recipients |
|-------|------------|
| `all` | Every player in the runtime. |
| `winner` | The player ranked first (rank determined by `WinCondition`). |
| `top_N` | Top N players, e.g. `top_3`. |
## Minimal Example
```json
+9
View File
@@ -63,6 +63,7 @@ Signal tags let runtime state changes fire normal Hytale trigger-volume logic by
|-----|----------|--------|----------|
| `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. |
| `SignalPhaseStart` | No | `MinigamePhase` enum name, such as `ACTIVE` or `ENDING` | Fires once when the runtime enters that phase. |
| `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. |
@@ -87,6 +88,14 @@ SignalPhaseTick=ACTIVE
SignalTickDelay=1
```
Game-end phase example:
```text
MinigameId=KOTH
MapId=Castle
SignalPhaseStart=ENDING
```
Timer-expire example:
```text
+11
View File
@@ -90,6 +90,8 @@ Tag each team spawn volume with:
This means checkpoint-based games can still override spawns per player, while team arena games can rely on tagged team spawn volumes.
Player deaths during a minigame automatically use the same destination order. `DestinationId` and `SetSpawnPoint` values are coordinate strings, such as `100,64,200` or `world_name,100,64,200`; team-spawn fallback is resolved from tagged spawn volumes.
### Counters
Use a trigger volume to increment a named runtime counter:
@@ -117,6 +119,15 @@ Use a small trigger volume at the pickup location and attach:
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.
### Block Fill Effects
Attach `PlaceBlocks` when a trigger should place or replace a cuboid of blocks:
- Effect: `PlaceBlocks`
- Fields: `BlockId = <block id>`, `X1/Y1/Z1 = <first world corner>`, `X2/Y2/Z2 = <second world corner>`
The region is inclusive and uses absolute world block coordinates. `MaxBlocks` defaults to `32768`.
## 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.