Skip to main content

๐Ÿงฐ Advanced Relic Hunt Example

The Advanced Relic Hunt addon is a larger developer example showing how another plugin can add new EventForge mechanics.

It registers:

RELIC_HUNT objective
RELIC_LOG action

Use this example when you want to learn custom objectives, custom actions, dialogue integration, milestones, text effects, and public API services.


Requirements

Java 21
Spigot, Paper, or Purpur 1.21 - 1.21.11
EventForge v1.0.2+
EventForge API 1.0.2-release

What it demonstrates

The addon demonstrates:

custom objective registration
custom action registration
custom objective config loading
multi-objective compatibility
weighted scoring
player-facing multiplier text
public VariableService usage
public TextEffectService usage
DialogueService usage
event milestones
animated title/actionbar/bossbar config
custom trigger actions
leaderboard access
chance rewards
cooldowns

For a smaller API-only example, use the Simple Addon instead.


Maven dependency

The addon uses the EventForge API as a provided dependency.

<dependency>
<groupId>dev.hxze</groupId>
<artifactId>eventforge-api</artifactId>
<version>1.0.2-release</version>
<scope>provided</scope>
</dependency>

For Spigot compatibility, it also uses the Spigot API:

<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.21.11-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

plugin.yml

name: EventForgeAdvancedExample
version: '1.0.2'
main: dev.hxze.eventforgeadvancedexample.EventForgeAdvancedExamplePlugin
api-version: '1.21'
author: HxZe
description: Advanced example addon demonstrating custom objectives, custom actions, variables, text effects, milestones, dialogues, and EventForge v1.0.2 API features.
depend:
- EventForge

Folder structure

src/main/java/dev/hxze/eventforgeadvancedexample/
โ”œโ”€ EventForgeAdvancedExamplePlugin.java
โ”œโ”€ action/
โ”‚ โ””โ”€ RelicLogAction.java
โ”œโ”€ objective/
โ”‚ โ”œโ”€ RelicHuntObjectiveData.java
โ”‚ โ””โ”€ RelicHuntObjectiveHandler.java
โ”œโ”€ model/
โ”‚ โ””โ”€ RelicLocation.java
โ””โ”€ util/
โ””โ”€ TextUtil.java

This keeps startup, actions, objective logic, data, and formatting helpers separate.


Main plugin class

The addon checks that EventForge is available before registering anything.

if (!EventForgeAPI.isAvailable()) {
getLogger().severe("EventForgeAPI is not available. Is EventForge installed?");
getServer().getPluginManager().disablePlugin(this);
return;
}

Then it registers the custom objective and custom action.

EventForgeAPI.getObjectiveRegistry().register(
new RelicHuntObjectiveHandler(this)
);

EventForgeAPI.getActionRegistry().register(
"RELIC_LOG",
new RelicLogAction(this)
);

On disable, it unregisters both.

EventForgeAPI.getObjectiveRegistry().unregister("RELIC_HUNT");
EventForgeAPI.getActionRegistry().unregister("RELIC_LOG");

Custom objective

The addon registers a custom objective type:

RELIC_HUNT

Server owners can use it inside an event:

objectives:
relic_discovery:
type: RELIC_HUNT
weight: 1.5

How Relic Hunt works

Relic Hunt is an exploration objective.

Players earn points by:

moving near relic zones
interacting with configured relic blocks
breaking configured relic fragment blocks

The example config includes two relics:

spawn_relic
forest_relic

Each relic has:

clue
world
x/y/z location
radius
interact block
break block
discovery points
interact points
break points

Objective config loading

The objective reads its config inside:

load(EventObjectiveLoadContext context)

It validates:

relics section exists
world is set
radius is greater than 0
interact block is a valid block
break block is a valid block
point values are greater than 0
hint interval is greater than 0

If something is wrong, it returns a failed load result.

return ObjectiveLoadResult.failure("RELIC_HUNT requires a relics section.");

This allows EventForge validation to show a clear reason instead of silently failing.


Relic objective config

objectives:
relic_discovery:
type: RELIC_HUNT
display-name: "{var:event_color}Relic Discovery"
display-items:
- "{var:accent_color}Follow clues to relic sites"
- "{var:accent_color}Move near relic zones"
- "{var:accent_color}Interact with ancient blocks"
- "{var:accent_color}Break relic fragments"
weight: 1.5

hint-interval: 30s
announce-leaderboard-on-stop: true

relics:
spawn_relic:
clue: "The first relic rests near {var:arena_name}."
world: world
x: 0
y: 64
z: 0
radius: 6
interact-block: CHISELED_STONE_BRICKS
break-block: GOLD_BLOCK
discovery-points: 2
interact-points: 5
break-points: 10

Multi-objective support

RELIC_HUNT works inside the normal v1.0.2 objectives: section.

objectives:
relic_discovery:
type: RELIC_HUNT
weight: 1.5

mobs:
type: KILL_MOBS
weight: 1.0

Both objectives contribute to the same event score and leaderboard.


Weighted scoring

The addon adds raw score through the objective session.

session.addScore(player, relic.getDiscoveryPoints());

EventForge applies the objective multiplier internally.

When showing points to the player, the addon uses:

int points = session.getWeightedScore(relic.getDiscoveryPoints());

This keeps player messages in sync with the score that EventForge actually adds.


Player-facing multiplier text

The addon displays objective weight as:

Multiplier: x1.5

instead of:

Weight: 1.5

The helper lives in:

util/TextUtil.java

VariableService usage

The addon uses the public VariableService when sending objective messages.

String parsed = EventForgeAPI.getVariableService()
.parse(session.getEventId(), player, message);

This lets the custom objective use the same variables as normal EventForge configs.


TextEffectService usage

The addon also parses EventForge text effects before sending messages.

parsed = EventForgeAPI.getTextEffectService()
.parse(parsed, (int) Math.floorMod(session.getElapsedSeconds() * 20L, Integer.MAX_VALUE));

Example messages in the addon use effects such as:

<stack:rainbow,wobble>Relic Hunt has started!</stack>
<pulse:#ffd166:#ffffff>[Relic Hunt]</pulse>

Custom action

The addon registers a custom action type:

RELIC_LOG

Server owners can use it in event triggers:

triggers:
event-start:
actions:
- type: RELIC_LOG
message: "{event_display} started. Arena={var:arena_name}"

The action parses placeholders and text effects, then logs the result to console.

String message = action.getString("message", "{event_display} triggered RELIC_LOG.");
String parsedMessage = context.parsePlaceholders(message);
parsedMessage = EventForgeAPI.getTextEffectService().parse(parsedMessage);

plugin.getLogger().info("[RELIC_LOG] "
+ "trigger="
+ context.getTriggerId()
+ " | event="
+ context.getEventInfo().getId()
+ " | message="
+ parsedMessage);

DialogueService usage

The addon starts a dialogue when a player discovers a relic area.

if (!EventForgeAPI.getDialogueService().hasDialogue(eventId, "guide_intro")) {
return;
}

EventForgeAPI.getDialogueService()
.startEventDialogue(player, eventId, "guide_intro");

This keeps the addon safe if the event does not configure that dialogue.


Dialogue config

dialogues:
guide_intro:
npc-id: "relic_guide"
display-name: "{var:guide_name} Introduction"
cancel-existing: true
lines:
- speaker: "{var:guide_name}"
text: "&fWelcome to {var:arena_name}, {player}. Ancient relics are hidden nearby."
title: "{var:event_color}{var:guide_name}"
subtitle: "&fSearch the ruins for clues."
sound: ENTITY_VILLAGER_AMBIENT
delay: 3
- speaker: "{var:guide_name}"
text: "&fInteract with ancient blocks and recover fragments to earn points."
sound: ENTITY_EXPERIENCE_ORB_PICKUP
delay: 3
complete-commands:
- "say {player} has spoken to the {dialogue_display}."

Milestones

The example event includes v1.0.2 milestones.

milestones:
enabled: true
thresholds:
10:
display-name: "{var:accent_color}10 Relic Points"
once-per-player: true
actions:
- type: MESSAGE
message: "{var:event_color}You reached &f{milestone_display}&6!"
- type: SOUND
sound: ENTITY_PLAYER_LEVELUP
volume: 1.0
pitch: 1.2

25:
display-name: "{var:event_color}25 Relic Points"
once-per-player: true
actions:
- type: BROADCAST
message: "{var:event_color}{player} reached &f{milestone_display} &6in &f{event_display}&6!"
- type: RELIC_LOG
message: "{player} crossed milestone {milestone_display} with score {new_score}"

This shows that custom addon actions can also be used inside milestone actions.


Animated actions

The example config uses the v1.0.2 animated actions.

triggers:
event-start:
actions:
- type: ANIMATED_TITLE
frames:
- title: "<stack:rainbow,wobble>{event_display}</stack>"
subtitle: "&eSearch &f{var:arena_name} &efor ancient relics!"
fade-in: 5
stay: 50
fade-out: 10
interval: 5

player-score:
actions:
- type: ANIMATED_ACTIONBAR
frames:
- message: "<pulse:#ffd166:#ffffff>+{score_change} points</pulse> &8| &7Total: &f{new_score}"
duration: 30
interval: 5

Animated bossbar text

The example event also uses animated bossbar text.

bossbar:
enabled: true
text: "{var:event_color}Relic Hunt &8| &f{time_left} &8| &eScore: {score} &8| &7Rank: #{rank}"
color: YELLOW
style: SOLID
animated-text:
enabled: true
interval: 20
frames:
- "<gradient:#ffd166:#f97316>Relic Hunt</gradient> &8| &f{time_left}"
- "&eScore: &f{score} &8| &7Rank: &e#{rank}"
- "<pulse:#ffd166:#ffffff>Follow the clues!</pulse>"

Full relic_hunt.yml example

id: relic_hunt
enabled: true
display-name: "{var:event_color}Relic Hunt"
duration: 5m

metadata:
category: "Adventure"
tags:
- relic
- hunt
- exploration
- custom-objective
- multi-objective
- v1-0-2
difficulty: "Medium"
author: "HxZe"
version: "1.0.2"
description:
- "Follow clues and discover hidden relic locations."
- "Demonstrates variables, text effects, milestones, triggers, custom actions, dialogues, and custom objective scoring."

variables:
event_color: "&6"
accent_color: "&e"
arena_name: "Spawn Ruins"
reward_name: "Ancient Relic Cache"
guide_name: "Relic Guide"

cooldown:
enabled: true
duration: 1h

milestones:
enabled: true
thresholds:
10:
display-name: "{var:accent_color}10 Relic Points"
once-per-player: true
actions:
- type: MESSAGE
message: "{var:event_color}You reached &f{milestone_display}&6!"
- type: SOUND
sound: ENTITY_PLAYER_LEVELUP
volume: 1.0
pitch: 1.2

25:
display-name: "{var:event_color}25 Relic Points"
once-per-player: true
actions:
- type: BROADCAST
message: "{var:event_color}{player} reached &f{milestone_display} &6in &f{event_display}&6!"
- type: RELIC_LOG
message: "{player} crossed milestone {milestone_display} with score {new_score}"

schedule:
enabled: false
type: INTERVAL
every: 30m
announce-before:
- 1m
- 30s

conditions:
minimum-players: 1
worlds:
- world

objectives:
relic_discovery:
type: RELIC_HUNT
display-name: "{var:event_color}Relic Discovery"
display-items:
- "{var:accent_color}Follow clues to relic sites"
- "{var:accent_color}Move near relic zones"
- "{var:accent_color}Interact with ancient blocks"
- "{var:accent_color}Break relic fragments"
weight: 1.5

hint-interval: 30s
announce-leaderboard-on-stop: true

relics:
spawn_relic:
clue: "The first relic rests near {var:arena_name}."
world: world
x: 0
y: 64
z: 0
radius: 6
interact-block: CHISELED_STONE_BRICKS
break-block: GOLD_BLOCK
discovery-points: 2
interact-points: 5
break-points: 10

forest_relic:
clue: "The second relic is hidden beyond the trees."
world: world
x: 30
y: 64
z: 30
radius: 6
interact-block: MOSSY_COBBLESTONE
break-block: EMERALD_BLOCK
discovery-points: 3
interact-points: 6
break-points: 12

dialogues:
guide_intro:
npc-id: "relic_guide"
display-name: "{var:guide_name} Introduction"
cancel-existing: true
lines:
- speaker: "{var:guide_name}"
text: "&fWelcome to {var:arena_name}, {player}. Ancient relics are hidden nearby."
title: "{var:event_color}{var:guide_name}"
subtitle: "&fSearch the ruins for clues."
sound: ENTITY_VILLAGER_AMBIENT
delay: 3
- speaker: "{var:guide_name}"
text: "&fInteract with ancient blocks and recover fragments to earn points."
sound: ENTITY_EXPERIENCE_ORB_PICKUP
delay: 3
complete-commands:
- "say {player} has spoken to the {dialogue_display}."

triggers:
event-start:
actions:
- type: BROADCAST
message: "<gradient:#ffd166:#f97316>{event_display}</gradient> &ehas started at &f{var:arena_name}&e!"
- type: ANIMATED_TITLE
frames:
- title: "<stack:rainbow,wobble>{event_display}</stack>"
subtitle: "&eSearch &f{var:arena_name} &efor ancient relics!"
fade-in: 5
stay: 50
fade-out: 10
interval: 5
- type: RELIC_LOG
message: "<pulse:#ffd166:#ffffff>{event_display}</pulse> started. Arena={var:arena_name}"

player-score:
actions:
- type: ANIMATED_ACTIONBAR
frames:
- message: "<pulse:#ffd166:#ffffff>+{score_change} points</pulse> &8| &7Total: &f{new_score}"
duration: 30
interval: 5
- type: RELIC_LOG
message: "{player} gained {score_change} points in {event_display}. New score: {new_score}"

event-finish:
actions:
- type: BROADCAST
message: "{var:event_color}&l{event_display} &ehas finished!"
- type: RELIC_LOG
message: "{event_display} finished naturally."

event-stop:
actions:
- type: RELIC_LOG
message: "{event_display} was force-stopped."

bossbar:
enabled: true
text: "{var:event_color}Relic Hunt &8| &f{time_left} &8| &eScore: {score} &8| &7Rank: #{rank}"
color: YELLOW
style: SOLID
animated-text:
enabled: true
interval: 20
frames:
- "<gradient:#ffd166:#f97316>Relic Hunt</gradient> &8| &f{time_left}"
- "&eScore: &f{score} &8| &7Rank: &e#{rank}"
- "<pulse:#ffd166:#ffffff>Follow the clues!</pulse>"

sidebar:
enabled: true
title: "{var:event_color}&l{event_display}"
max-objective-lines: 6
lines:
- "{var:accent_color}Objectives:"
- "{objective_lines}"
- ""
- "&7Arena: &f{var:arena_name}"
- "&7Reward: &f{var:reward_name}"

leaderboard:
announce-results: true
max-shown: 3
no-participants-message: "&7No relic hunters scored points."
header:
- ""
- "{var:event_color}&lRelic Hunt Results"
line: "&e#{position} &f{player} &8- &a{score} points"
footer:
- ""

messages:
start:
- "{var:event_color}&lRelic Hunt has started!"
- "&eFollow the clues around &f{var:arena_name}&e!"
end:
- "{var:event_color}&lRelic Hunt has ended!"

announcements:
start-title:
enabled: true
title: "{var:event_color}&lRelic Hunt Started!"
subtitle: "&eFind ancient relics at &f{var:arena_name}&e!"
fade-in: 10
stay: 60
fade-out: 20

end-title:
enabled: true
title: "{var:event_color}&lRelic Hunt Ended!"
subtitle: "&eYou placed &f#{rank} &ewith &f{score} &epoints"
subtitle-no-score: "&7You did not participate."
fade-in: 10
stay: 80
fade-out: 20

countdown-title:
enabled: true
title: "{var:event_color}&lRelic Hunt"
subtitle: "&eStarting in &f{time}"
fade-in: 10
stay: 40
fade-out: 10

rewards:
participation:
enabled: true
minimum-score: 1
commands:
- "give {player} gold_ingot 2"
- "say {player} earned the {var:reward_name} participation reward."

chance-rewards:
bonus_relic_key:
chance: 10.0
commands:
- "give {player} emerald 1"
- "say {player} found a rare bonus from {var:reward_name}!"

leaderboard:
1:
commands:
- "give {player} diamond 3"
chance-rewards:
champion_bonus:
chance: 25.0
commands:
- "give {player} netherite_scrap 1"
2:
commands:
- "give {player} diamond 2"
3:
commands:
- "give {player} diamond 1"

Building the addon

From the advanced addon folder, run:

mvn clean package

Expected output:

target/EventForgeAdvancedExample-1.0.2.jar

Testing the addon

Install both plugins:

plugins/
โ”œโ”€ EventForge.jar
โ””โ”€ EventForgeAdvancedExample-1.0.2.jar

Copy the example event file:

EventForgeAdvancedExample/src/main/resources/relic_hunt.yml

into your EventForge events folder:

plugins/EventForge/events/relic_hunt.yml

Reload EventForge:

/eventforge reload

Start the event:

/events start relic_hunt

Then test by moving near the configured relic locations and interacting with or breaking the configured blocks.


Testing checklist

Check console for:

Registered EventForge objective: RELIC_HUNT
Registered EventForge action: RELIC_LOG
[RELIC_LOG] trigger=...

Check in-game for:

relic discovery messages
weighted score messages
animated actionbar score messages
guide_intro dialogue
milestone rewards
animated bossbar text
leaderboard results
chance rewards
cooldown after natural finish

Common issues

RELIC_HUNT is unknownโ€‹

Check:

the advanced addon jar is installed
the addon loaded successfully
the event was reloaded after installing the addon
the objective type is exactly RELIC_HUNT

RELIC_LOG is unknownโ€‹

Check:

the advanced addon jar is installed
the addon registered the action
the action type is exactly RELIC_LOG

Players do not scoreโ€‹

Check:

the event is active
the player is near the configured relic location
the world name is correct
the radius is large enough
the interact/break block matches the config
the point values are greater than 0

Dialogue does not startโ€‹

Check:

the event has a dialogues section
the dialogue ID is guide_intro
the player triggers relic discovery
the addon loaded successfully

Scores look higher than the raw config pointsโ€‹

This is expected when the objective uses a multiplier.

weight: 1.5

Player-facing messages show this as:

Multiplier: x1.5

Cooldown does not startโ€‹

Cooldowns start after a natural event finish.

They do not start after:

forced stop
reload
shutdown

Summary

The Advanced Relic Hunt addon shows how to build a full EventForge addon using v1.0.2 API features.

It includes:

RELIC_HUNT custom objective
RELIC_LOG custom action
VariableService
TextEffectService
DialogueService
event milestones
animated actions
animated bossbar text
custom objective scoring