Skip to content

Full Ecosystem Example

This page shows the intended full-stack DaisySuite story:

  • DaisyCore owns runtime systems
  • DaisySeries owns parser-safe value decoding
  • DaisyConfig owns typed YAML, managed config lifecycle, and config-backed text

A profile-style plugin where:

  • /profile opens a menu
  • the menu uses a configured icon and item flags
  • a sidebar and tablist use config-backed text
  • a managed module stores sound, enchantment, potion, particle, and statistic values
  • all user-facing rendering still flows through DaisyCore

1. Typed config with DaisyConfig and DaisySeries-backed codecs

Section titled “1. Typed config with DaisyConfig and DaisySeries-backed codecs”
data class ProfileUiConfig(
val icon: Material,
val feedbackSound: Sound,
val flags: Set<ItemFlag>,
val enchantment: Enchantment,
val effect: PotionEffectType,
val biome: Biome,
val particle: Particle,
val statistic: Statistic,
val sidebarTitle: String,
)
val profileUiCodec =
objectCodec {
ProfileUiConfig(
icon = required("icon", materialCodec()),
feedbackSound = required("feedback_sound", soundCodec()),
flags = defaulted("flags", itemFlagsCodec(), emptySet()),
enchantment = required("enchantment", enchantmentCodec()),
effect = required("effect", potionEffectCodec()),
biome = required("biome", biomeCodec()),
particle = required("particle", particleCodec()),
statistic = required("statistic", statisticCodec()),
sidebarTitle = defaulted(
"sidebar_title",
stringCodec(),
"<gradient:#7dd3fc:#c4b5fd>Profile</gradient>",
),
)
}

2. Managed module ownership with DaisyConfig

Section titled “2. Managed module ownership with DaisyConfig”
val profileModuleDefinition =
DaisyModuleDefinition(
category = "commands",
module = "profile",
settings =
DaisyManagedYamlFile(
id = "commands/profile/settings",
path = "modules/commands/profile/settings.yml",
codec = profileUiCodec,
currentVersion = 2,
),
lang =
DaisyManagedYamlFile(
id = "commands/profile/lang",
path = "modules/commands/profile/lang.yml",
codec = daisyTextConfigCodec(),
),
)

Managed module files are the preferred path once config lifecycle, shared text, and reload safety are real plugin concerns.

class ProfilePlugin : JavaPlugin() {
lateinit var daisy: DaisyPlatform
private set
lateinit var modules: DaisyModuleRegistry
private set
override fun onEnable() {
modules =
DaisyModules.load(this) {
module(profileModuleDefinition)
}
val profile = modules.require<ProfileUiConfig>("commands", "profile")
daisy =
DaisyPlatform.create(this) {
messages(profile.textSource)
commands()
menus()
scoreboards()
tablists()
}
}
}

4. Values come from DaisySeries-backed codecs

Section titled “4. Values come from DaisySeries-backed codecs”
icon: diamond_sword
feedback_sound: entity_player_levelup
flags:
- hide_enchants
enchantment: sharpness
effect: slow_falling
biome: cherry_grove
particle: totem
statistic: player_kills
sidebar_title: "<gradient:#7dd3fc:#c4b5fd>Profile</gradient>"

Those values are clean because DaisyConfig is decoding them through DaisySeries codecs underneath.

That means:

  • canonical config keys stay stable
  • aliases remain accepted where supported
  • value parsing logic is not rebuilt in your plugin

5. Command, menu, sidebar, and tablist in one flow

Section titled “5. Command, menu, sidebar, and tablist in one flow”
@DaisyCommandSet
object ProfileCommands : DaisyCommandGroup({
command("profile") {
description("Open your profile tools")
player {
val profile = plugin.modules.require<ProfileUiConfig>("commands", "profile")
val config = profile.current.settings
replyLang("profile.messages.opening", "player" to player.name)
player.openMenu("Profile", rows = 3) {
slot(13) {
item(config.icon) {
nameLang("profile.menu.card.name", viewer = player, "player" to player.name)
lore(
"<gray>Enchant: <white>${DaisyEnchantments.displayName(config.enchantment)}</white>",
"<gray>Effect: <white>${DaisyPotions.displayName(config.effect)}</white>",
"<gray>Biome: <white>${DaisyBiomes.displayName(config.biome)}</white>",
"<gray>Statistic: <white>${DaisyStatistics.displayName(config.statistic)}</white>",
)
}
message("<gray>Clicked the profile card.</gray>")
closeOnClick()
}
}
player.spawnParticle(config.particle, player.location, 4)
daisy.scoreboards?.show(
player,
sidebar {
title(config.sidebarTitle)
line("coins") {
textLang("profile.sidebar.coins", viewer = player, "coins" to "1,250")
}
},
)
daisy.tablists?.show(
player,
tablist {
headerLang("profile.tablist.header", viewer = player, "player" to player.name)
footerLang("profile.tablist.footer", viewer = player, "stat" to DaisyStatistics.displayName(config.statistic))
},
)
}
}
})

This is the important production rule:

  • DaisyConfig stores and loads text plus config data
  • DaisyCore renders it

That means:

  • DaisyConfig does not expand PlaceholderAPI directly
  • DaisySeries does not render text
  • DaisyCore remains the viewer-aware rendering owner
  • DaisyCore keeps runtime systems coherent
  • DaisySeries keeps value parsing clean and config-safe
  • DaisyConfig keeps YAML, reloads, and text storage explicit

The result is one believable plugin architecture instead of three disconnected libraries pretending not to know about each other.