Full Ecosystem Example
Full Ecosystem Example
Section titled “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
What this example builds
Section titled “What this example builds”A profile-style plugin where:
/profileopens 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.
3. Runtime bootstrap with DaisyCore
Section titled “3. Runtime bootstrap with DaisyCore”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_swordfeedback_sound: entity_player_levelupflags: - hide_enchantsenchantment: sharpnesseffect: slow_fallingbiome: cherry_groveparticle: totemstatistic: player_killssidebar_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”@DaisyCommandSetobject 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)) }, ) } }})6. The safety boundary
Section titled “6. The safety boundary”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
Why this full stack works well
Section titled “Why this full stack works well”- 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.
Next steps
Section titled “Next steps”- Follow the intended stack path: Build the Normal Stack
- Start with the runtime platform: DaisyCore
- See parser modules and values: DaisySeries
- Learn the typed config layer: DaisyConfig