๐งฐ 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