Essential concepts and terminology for understanding Argonath Systems.
Argonath Systems is built around several core concepts that form the foundation of the framework. Understanding these concepts is crucial for effective development.
A design pattern that separates game-specific code from framework logic, allowing the same framework code to work across different game engines or platforms.
Benefits:
Example:
// Platform-agnostic code
Player player = platform.getPlayer(uuid);
player.sendMessage("Hello!");
// Hytale adapter translates to:
// hytalePlayer.sendChatMessage(Text.of("Hello!"));
┌──────────────────────────────────────┐
│ Your Mod/Framework │ (Platform-agnostic)
├──────────────────────────────────────┤
│ Platform SDK │ (Abstractions)
├──────────────────────────────────────┤
│ Platform Core │ (Interfaces)
├──────────────────────────────────────┤
│ Adapter (Hytale) │ (Game-specific)
├──────────────────────────────────────┤
│ Hytale Game Engine │ (Game runtime)
└──────────────────────────────────────┘
A centralized storage and lookup system for game objects (quests, NPCs, items, etc.).
// Register a quest
QuestRegistry.register(myQuest);
// Retrieve by ID
Quest quest = QuestRegistry.get("my_quest_id");
// List all quests
Collection<Quest> allQuests = QuestRegistry.getAll();
// Check existence
boolean exists = QuestRegistry.contains("my_quest_id");
QuestRegistry - QuestsNPCRegistry - NPCsObjectiveTypeRegistry - Objective typesConditionTypeRegistry - Condition typesUIRegistry - UI componentsA fluent API for constructing complex objects step-by-step with readable, chainable method calls.
Quest quest = QuestBuilder.create("epic_quest")
.name("Epic Adventure")
.description("An amazing journey!")
.startCondition(Condition.level(10))
.addObjective(
Objective.builder()
.type(ObjectiveType.KILL_ENTITY)
.target("dragon")
.count(1)
.build()
)
.addReward(reward -> reward
.gold(1000)
.item("legendary_sword", 1)
)
.build();
vs Traditional Constructor:
// Hard to read, easy to make mistakes
Quest quest = new Quest(
"epic_quest",
"Epic Adventure",
"An amazing journey!",
new LevelCondition(10),
Arrays.asList(new KillObjective("dragon", 1)),
new Reward(1000, Arrays.asList(new ItemStack("legendary_sword", 1))),
null, // no prerequisites
false, // not repeatable
QuestCategory.MAIN // category
);
Modular, reusable pieces of functionality that can be attached to entities or objects.
Create → Initialize → Active → Dispose
public class HealthComponent implements Component {
private int health;
private int maxHealth;
@Override
public void initialize() {
health = maxHealth;
}
@Override
public void update() {
// Update logic each tick
}
@Override
public void dispose() {
// Cleanup
}
public void damage(int amount) {
health = Math.max(0, health - amount);
}
}
// Attach to entity
entity.addComponent(new HealthComponent(100));
// Retrieve component
HealthComponent health = entity.getComponent(HealthComponent.class);
health.damage(20);
Notifications that something happened, allowing decoupled communication between systems.
Event Source → Event Bus → Event Listeners
// Define event
public class QuestCompletedEvent extends Event {
private final Quest quest;
private final Player player;
// ... constructor, getters
}
// Listen for event
EventBus.subscribe(QuestCompletedEvent.class, event -> {
Quest quest = event.getQuest();
Player player = event.getPlayer();
player.sendMessage("Quest completed: " + quest.getName());
// Give rewards, update stats, etc.
});
// Fire event
EventBus.fire(new QuestCompletedEvent(quest, player));
QuestStartedEventQuestCompletedEventObjectiveProgressEventNPCInteractEventPlayerJoinEventEvent base classPredicates that evaluate to true/false based on game state, used for gating content.
// Simple condition
Condition.level(10)
// Composite conditions
Condition.and(
Condition.level(5),
Condition.hasQuest("tutorial")
)
Condition.or(
Condition.hasItem("key_red"),
Condition.hasItem("key_blue")
)
Condition.not(
Condition.questCompleted("forbidden_quest")
)
public class CustomCondition implements Condition {
@Override
public boolean test(Player player) {
// Your custom logic
return player.getPlaytime() > 3600; // 1 hour
}
}
Trackable goals with progress that players must complete.
Create → Track Progress → Check Completion → Reward
| Type | Description | Example |
|---|---|---|
KILL_ENTITY |
Kill specific entities | Kill 10 goblins |
COLLECT_ITEM |
Gather items | Collect 5 apples |
TALK_TO_NPC |
Interact with NPC | Talk to Elder |
REACH_LOCATION |
Travel to area | Reach the castle |
CRAFT_ITEM |
Craft specific item | Craft iron sword |
USE_ITEM |
Use item X times | Use potion 3 times |
CUSTOM |
Custom logic | Any condition |
Objective objective = Objective.builder()
.type(ObjectiveType.KILL_ENTITY)
.target("goblin")
.count(10)
.build();
// Track progress
ObjectiveProgress progress = new ObjectiveProgress(objective);
progress.increment(); // 1/10
progress.setProgress(5); // 5/10
boolean complete = progress.isComplete(); // false
// Get percentage
float percent = progress.getPercentage(); // 0.5 (50%)
Mechanisms for saving and loading data across game sessions.
Per-player data (quest progress, settings)
PlayerStorage<QuestProgress> storage =
PlayerStorage.create("quests", QuestProgress.class);
// Save
storage.save(player, questProgress);
// Load
QuestProgress progress = storage.load(player);
Per-world data (spawns, state)
WorldStorage<SpawnPoint> storage =
WorldStorage.create("spawns", SpawnPoint.class);
Server-wide data (leaderboards, economy)
GlobalStorage<Economy> storage =
GlobalStorage.create("economy", Economy.class);
Data is automatically serialized to JSON:
{
"questId": "my_quest",
"progress": 50,
"completed": false,
"objectives": [
{"id": "obj1", "progress": 5}
]
}
Safe wrappers for accessing potentially unavailable objects.
Handle cases where objects may not exist:
// Instead of direct access (risky):
Player player = getPlayer(); // might be null
player.sendMessage("Hi"); // NullPointerException!
// Use accessor:
Accessor<Player> playerAccessor = Accessor.of(() -> getPlayer());
if (playerAccessor.isPresent()) {
playerAccessor.get().sendMessage("Hi");
}
// Or with ifPresent:
playerAccessor.ifPresent(p -> p.sendMessage("Hi"));
// Or with default:
Player player = playerAccessor.orElse(defaultPlayer);
Accessor<T> - Basic accessorLazyAccessor<T> - Lazy initializationCachedAccessor<T> - Cached valueMutableAccessor<T> - Mutable valueExternal settings loaded from files that control mod behavior without code changes.
Loaded once at startup
@ConfigClass("my-mod")
public class MyConfig {
@ConfigProperty("feature.enabled")
private boolean featureEnabled = true;
@ConfigProperty("max.quests")
private int maxQuests = 10;
}
Reloadable at runtime
Config config = ConfigLoader.load("my-mod.json");
config.watch(); // Auto-reload on file change
Location: config/argonath/<mod-id>.json
{
"feature": {
"enabled": true
},
"max": {
"quests": 10
}
}
Automatic provisioning of dependencies instead of manual creation.
// Register service
ServiceProvider.register(QuestService.class, new QuestServiceImpl());
// Inject automatically
@Inject
private QuestService questService;
// Or get manually
QuestService service = ServiceProvider.get(QuestService.class);
Multi-language support via translation keys.
// Define key
Text.translatable("quest.complete")
.with("questName", quest.getName())
.send(player);
Translation files:
lang/en_US.json:
{
"quest.complete": "Quest {questName} completed!"
}
lang/es_ES.json:
{
"quest.complete": "¡Misión {questName} completada!"
}
Load data only when needed:
LazyAccessor<ExpensiveData> data = LazyAccessor.of(() -> loadData());
// Data not loaded yet
data.get(); // Loads now
Store computed results:
CachedAccessor<List<Quest>> quests = CachedAccessor.of(
() -> database.loadQuests(),
Duration.ofMinutes(5) // Cache for 5 minutes
);
Non-blocking operations:
CompletableFuture<Quest> futureQuest =
QuestLoader.loadAsync("my_quest");
futureQuest.thenAccept(quest -> {
// Process quest when loaded
});
Framework code never directly uses Hytale APIs
Use components, not deep class hierarchies
Build immutable objects when possible
Graceful degradation when things go wrong
Each module has one responsibility
| Pattern | Purpose | Example |
|---|---|---|
| Registry | Centralized lookup | QuestRegistry.get() |
| Builder | Fluent construction | QuestBuilder.create() |
| Component | Modular functionality | entity.addComponent() |
| Event | Decoupled communication | EventBus.fire() |
| Accessor | Safe object access | accessor.ifPresent() |
| Service | Dependency injection | @Inject |
| Next: Module Reference | Dependencies |