Skip to content

Developer Architecture

Atlasroar edited this page Oct 3, 2026 · 6 revisions

Architecture & Internal Jar Breakdown

WandererZ v2.3.0+ ships as a single jar (wandererz-<version>+<mc>.jar) under Fabric mod id wandererz, but internally it is composed of six previously-separate codebases that still live in their own Java packages and resource namespaces. This page documents how the jar is actually laid out.

Top-level package split

net.wandererz.*        – the original WandererZ mod (player levels, skills, stats)
net.jobsaddon.*     – the former JobsAddon mod (job system), merged in v1.5.0
net.rpgdifficulty.* – the former RpgDifficulty mod (mob difficulty scaling), merged in v1.6.0
draylar.tiered.*    – the former Tiered mod (item rarity tiers + anvil reforging), merged in v2.1.0
net.partyaddon.*    – the former PartyAddon mod (player parties/groups, shared XP), merged in v2.2.0
net.spoiledz.*      – the former SpoiledZ mod (seasons-based food spoilage), merged in v2.3.0

These are fully disjoint namespaces — including several intentionally-duplicated class names (ConfigInit, EventInit, RenderInit, TagInit, CriteriaInit, CommandInit, JsonReaderInit, MobEntityMixin, LivingEntityMixin, ServerWorldMixin exist once under each relevant package). This is legal in Java and was a deliberate choice during every merge: each mod's own classes stay self-contained rather than being combined into one, which kept the merges mechanical/low-risk (see JobsAddon Integration, RpgDifficulty Integration, Tiered Integration, PartyAddon Integration, and SpoiledZ Integration). Note Tiered kept its upstream draylar package root (matching its own maven_group) rather than moving under net — the only merged addon not rooted there. Note also SpoiledZ is the only merged addon with zero LevelZ/WandererZ coupling — it's a general-purpose mod by the same author, not a LevelZ-specific addon.

net.wandererz subpackages

Package Responsibility
access Mixin-implemented interfaces exposed as a public API (PlayerSyncAccess, PlayerStatsManagerAccess, PlayerListAccess, PlayerBreakBlockAccess, PlayerDropAccess, MobEntityAccess)
compat REI/EMI recipe plugin integration
config WandererzConfig (Cloth Config @Config) + the combined ModMenuIntegration
criteria Custom advancement criteria/predicates for skill-based advancements
data LevelLoader/LevelLists — data-driven XP tables
entity / entity.render LevelExperienceOrbEntity and its renderer
init Registration classes (commands, compat hooks, config, criteria, entities, events, items, JSON readers, keybinds, rendering, tags)
item Custom items (RareCandyItem, StrangePotionItem)
mixin.* ~90 mixins across block, compat, entity, item, misc, player — see below
network PlayerStatsClientPacket / PlayerStatsServerPacket custom payloads
screen Skill list/info screens + widgets
stats PlayerStatsManager, Skill
util TooltipUtil
waila Jade/WAILA plugin integration

net.jobsaddon subpackages

Package Responsibility
access JobsManagerAccess (fetches the per-player JobsManager), PlayerAccess (block-break/recipe tracking helpers used to prevent XP farming exploits)
config JobsAddonConfig (Cloth Config @Config)
criteria JobUpCriterion / JobPredicate — advancement triggers for job level-ups
data JobLoader / JobLists — data-driven per-job XP tables loaded from data/jobsaddon/<job>/*.json
init Registration classes (commands /jobmanager, config, criteria, events, JSON readers, rendering, tags)
jobs JobsManager (core per-player job state machine) and JobHelper
mixin.block / mixin.compat / mixin.entity / mixin.misc / mixin.player 18 mixins, several gated behind optional-mod checks (see below)
network JobsClientPacket / JobsServerPacket custom payloads
screen JobScreen + JobTab inventory tab widget

net.rpgdifficulty subpackages

Package Responsibility
access EntityAccess / ZombieEntityAccess (mob-level NBT state), ScreenAccess (lets RpgDifficultyConfigScreen add the clock widget to the generic Screen)
api MobStrengthener — the core static methods that scale a mob's attributes, XP drop, and loot, invoked from mixins
config RpgDifficultyConfig (Cloth Config @Config), RpgDifficultyConfigScreen (factored out of the original ModMenuApi entrypoint so WandererZ's combined chooser can reuse it), ConfigClock (in-game-time widget)
data DifficultyLoader — data-driven per-dimension difficulty overrides
mixin.* 20 mixins across the root package, access, client, compat, and entity — see below

draylar.tiered subpackages

Package Responsibility
api ModifierUtils — core tier-roll/reforge-odds math, including the Smithing-skill/Luck weighted-reroll bonus
block / item Tiered-specific block/item classes
config TieredConfig (Cloth Config @Config, including wandererzReforgeModifier)
mixin.* ~15 mixins across the root package, client, and compat — see below
network TieredServerPacket custom payload
screen Reforging tab screen + widgets
util CommandInit (/tiered tier / /tiered untier), TieredItemTags, CustomEntityAttributes

net.partyaddon subpackages

Package Responsibility
access GroupLeaderAccess / GroupManagerAccess (per-player group state), WorldMapAccess (Xaero's World Map compat hook)
config PartyAddonConfig (Cloth Config @Config)
group GroupManager — core group/invite/membership state machine
init Registration classes (/party join//party leave commands, compat, config, events, rendering)
mixin.compat LevelExperienceOrbEntityMixin (WandererZ XP-orb share hook), GuiMapMixin/ControlsHandlerMixin (Xaero's World Map), LevelZServerPlayerEntityMixin (name kept unchanged, see PartyAddon Integration)
mixin.misc / mixin.player ExperienceOrbEntityMixin, PlayerEntityMixin, PlayerManagerMixin, ServerPlayerEntityMixin, ServerWorldMixin
network PartyAddonClientPacket / PartyAddonServerPacket custom payloads
screen PartyScreen, PartyTab inventory tab widget
util NameHelper, GroupHelper

net.spoiledz subpackages

Package Responsibility
access ServerWorldAccess (season-tracking state hook)
compat AppleSkinPlugin (fully commented-out dead code, not wired to any entrypoint, kept for upstream fidelity)
config SpoiledZConfig (Cloth Config @Config)
init Registration classes (config, food-eaten/food-properties event hooks via Capsaicin, model providers, tags)
mixin.* ~20 mixins across the root package, access, client, and compat — see below
util SpoiledUtil — core spoilage-time calculation and item-stack spoilage state helpers

Mixin configuration files

Four separate Mixin config JSONs ship in the jar and are all declared in fabric.mod.json's "mixins" array:

  • wandererz.mixins.json — package net.wandererz.mixin, plugin net.wandererz.mixin.WandererzMixinPlugin
  • jobsaddon.mixins.json — package net.jobsaddon.mixin, plugin net.jobsaddon.mixin.compat.JobsAddonMixinPlugin
  • rpgdifficulty.mixins.json — package net.rpgdifficulty.mixin, plugin net.rpgdifficulty.mixin.compat.RpgDifficultyMixinPlugin
  • tiered.mixins.json — package draylar.tiered.mixin, plugin draylar.tiered.mixin.compat.TieredMixinPlugin
  • partyaddon.mixins.json — package net.partyaddon.mixin, plugin net.partyaddon.mixin.PartyAddonMixinPlugin
  • spoiledz.mixins.json — package net.spoiledz.mixin, plugin net.spoiledz.mixin.SpoiledzMixinPlugin

All three plugins implement IMixinConfigPlugin.shouldApplyMixin(...) to conditionally skip mixins targeting classes from mods that may not be installed. For JobsAddon, four mixins are gated this way:

Mixin Only applied if loaded
TomatoBushCropBlockMixin, CookingPotResultSlotMixin farmersdelight
BlockWrapperMixin fallingtree
ScytheItemMixin, MacheteItemMixin harvest_scythes

For RpgDifficulty, two mixins are gated:

Mixin Only applied if loaded
compat.ChangeAttributeMixin wandererz — always true post-merge, see RpgDifficulty Integration
compat.PlayerExCompatMixin playerex

For Tiered, two mixins are gated:

Mixin Only applied if loaded
mixin.compat.SkillInfoScreenMixin wandererz — always true post-merge, see Tiered Integration
mixin.compat.ModAnvilScreenMixin easyanvils

For PartyAddon, three mixins are gated:

Mixin Only applied if loaded
mixin.compat.LevelExperienceOrbEntityMixin, mixin.compat.LevelZServerPlayerEntityMixin wandererz — always true post-merge, see PartyAddon Integration
mixin.compat.GuiMapMixin, mixin.compat.ControlsHandlerMixin xaeroworldmap

For SpoiledZ, several mixins are gated:

Mixin Only applied if loaded
mixin.compat.CookingPotBlockEntityMixin, mixin.compat.CookingPotEntityMixin, mixin.compat.CuttingBoardBlockEntityMixin (farmersdelight variants) farmersdelight
mixin.compat.ApplePressBlockEntityMixin, mixin.compat.JuicerBlockEntityMixin vinery
mixin.compat.SkilletBlockEntityMixin, mixin.compat.SkilletItemMixin, mixin.compat.StoveBlockEntityMixin, mixin.compat.WoodFiredOvenBlockEntityMixin expandeddelight

All other Jobs mixins (PlayerManagerMixin, PlayerEntityMixin, ServerPlayerEntityMixin, FishingBobberEntityMixin, the screen-handler mixins, BlockMixin, TntBlockMixin, SweetBerryBushBlockMixin, ComposterBlockMixin), all other RpgDifficulty mixins (the entity-attribute, zombie/creeper/guardian/wither/ender-dragon/projectile mixins, ServerWorldMixin, ChunkRegionMixin, ScreenMixin, ZombieEntityRendererMixin, SpawnHelperMixin), all other Tiered mixins (item/anvil/tooltip rendering mixins), all other PartyAddon mixins (ExperienceOrbEntityMixin, PlayerEntityMixin, PlayerManagerMixin, ServerPlayerEntityMixin, ServerWorldMixin), and all other SpoiledZ mixins (the block/item/inventory/furnace/campfire/cake/fishing-bobber/loot-table/item-scatterer mixins, ServerWorldMixin, the client-side ItemRendererMixin/ItemStackMixin) always apply.

Resource namespaces

Resource/data pack namespaces are independent of the Fabric mod id — they key off the assets/<namespace> / data/<namespace> folder name, not the owning mod. So assets/jobsaddon/*, data/jobsaddon/*, assets/rpgdifficulty/*, assets/partyaddon/*, and assets/spoiledz/*/data/spoiledz/* continue to work unmodified inside the wandererz-id jar; nothing needed renaming here. Lang files merge globally by key across every assets/*/lang/<locale>.json. RpgDifficulty's data/c/tags/entity_types/bosses.json uses Fabric's shared common-tag convention (c namespace, not rpgdifficulty) with "replace": false, so it merges additively with any other mod/datapack contributing to the same tag. PartyAddon ships no data/ folder at all — it has no datapack-configurable content. SpoiledZ is the first merged addon to ship vanilla resource overrides directly under assets/minecraft/* (a cake blockstate + 41 item-model predicate overrides adding "spoiled" variants) rather than only its own namespace — verified to have zero collision with any prior merge's resources before copying.

The jar also bundles resourcepacks/*_compat/ — eight small built-in resource packs (bakery, betterend, betternether, candlelight, earlystage, snuffles, supplementaries, vinery) that add job-restriction tag data for those mods' blocks/items when detected at runtime.

Entrypoints (fabric.mod.json)

"entrypoints": {
  "main":   ["net.wandererz.WandererzMain", "net.jobsaddon.JobsAddonMain", "net.rpgdifficulty.RpgDifficultyMain", "draylar.tiered.Tiered", "net.partyaddon.PartyAddonMain", "net.spoiledz.SpoiledZMain"],
  "client": ["net.wandererz.WandererzClient", "net.jobsaddon.JobsAddonClient", "net.rpgdifficulty.RpgDifficultyClient", "draylar.tiered.TieredClient", "net.partyaddon.PartyAddonClient", "net.spoiledz.SpoiledZClient"],
  "modmenu": ["net.wandererz.config.ModMenuIntegration"]
}

main/client are additive arrays — Fabric Loader calls onInitialize()/onInitializeClient() on every listed class, so JobsAddon's, RpgDifficulty's, Tiered's, PartyAddon's, and SpoiledZ's entrypoints were all simply appended without touching their initializer logic. modmenu is not additive (Mod Menu keys its config-screen map by mod id, one slot per id), which is why only one combined class is registered there, now with six buttons — see JobsAddon Integration, RpgDifficulty Integration, Tiered Integration, PartyAddon Integration, and SpoiledZ Integration for the reasoning.

Bundled vs. required-separate dependencies

Tiered introduced the jar's first example of both patterns side by side, and SpoiledZ added a third dependency category on top:

  • Bundled (shaded into the jar via Loom's include(...)): reach-entity-attributes (from Tiered) and capsaicin-1.20 (from SpoiledZ) — end users don't need to install them themselves; they show up as auto-generated META-INF/jars/*.jar entries in the built jar.
  • Required, separately-installed: AutoTag (Tiered) and Fabric Seasons (SpoiledZ) — declared only in depends ("autotag-convention": ">=2.0.2+1.20", "seasons": "*"), not bundled, matching each addon's own upstream non-include Gradle dependency declaration for that dependency.

One important runtime detail: FabricLoader.getModContainer(id)

Unlike resource namespaces, FabricLoader.getModContainer(id) is id-sensitive — it only resolves real registered Fabric mod ids. net.jobsaddon.init.EventInit uses this to register the 8 built-in compat resource packs via ResourceManagerHelper.registerBuiltinResourcePack(...). Because the merged mod's id is wandererz (not jobsaddon), all 8 call sites use FabricLoader.getInstance().getModContainer("wandererz") — using the old "jobsaddon" id here would throw NoSuchElementException via .orElseThrow(). RpgDifficulty's source has no equivalent getModContainer calls, so no analogous fix was needed there.

Clone this wiki locally