A Cobblemon addon that adds smartphone items with an extensible action system. Built with Architectury for Fabric and NeoForge (Minecraft 1.21.1).
├── common/ # Shared code (Kotlin + Java mixins)
│ └── src/main/kotlin/com/nbp/cobblemon_smartphone/
│ ├── actions/ # Built-in action implementations
│ ├── api/ # Public API (SmartphoneAction, DatapackAction, registry)
│ ├── client/ # Client-side code (GUI, keybinds, scanner)
│ ├── config/ # JSON config loader
│ ├── item/ # SmartphoneItem, SmartphoneColor enum (16 variants)
│ ├── network/ # Packets + handlers (packet/ and handler/ subpackages)
│ ├── registry/ # Item registration via RegistryProvider
│ ├── upgrade/ # Upgrade system (registry, helpers, SimulatedItemUse)
│ └── util/ # Utilities (SmartphoneHelper, Utils)
├── fabric/ # Fabric loader implementation
│ └── src/main/kotlin/com/nbp/cobblemon_smartphone/
│ ├── compat/ # Trinkets + Accessories integration
│ ├── client/ # Fabric client init + keybind handler
│ ├── CobblemonSmartphoneFabric.kt # ModInitializer + Implementation
│ └── DatapackActionReloadListenerWrapper.kt
├── neoforge/ # NeoForge loader implementation
│ └── src/main/kotlin/com/nbp/neoforge/
│ ├── compat/ # Curios + Accessories integration
│ ├── CobblemonSmartphoneNeoForge.kt # @Mod class + Implementation
│ └── ...
├── wiki/ # User-facing API docs (GitBook synced)
└── build.gradle # Root build (Architectury Loom)
All logic lives in common/. Platform modules (fabric/, neoforge/) contain only loader-specific code:
| Concern | Location |
|---|---|
| Actions, upgrades, recipes, network packets | common/ |
| Item registration | common/registry/ via RegistryProvider |
| Platform event wiring | fabric/ / neoforge/ |
| Mod compat (Trinkets, Curios, Accessories) | fabric/compat/ / neoforge/compat/ |
| Mixins | common/src/main/java/.../mixin/ |
- Gradle 8.11 with Architectury Loom 1.11
- Kotlin 2.0 for common + platform code
- Java 21 for mixins
- Targets:
fabric(Fabric Loader 0.16+) andneoforge(NeoForge 21.1+)
All registries follow the same singleton + register pattern:
SmartphoneActionRegistry → actions shown in the smartphone GUI
SmartphoneUpgradeRegistry → known upgrade types (NBT keys → metadata)
CobblemonSmartphoneItems → items (extends RegistryProvider)
Example flow for the upgrade registry:
// Registration (in CobblemonSmartphone.init)
SmartphoneUpgradeRegistry.register(
SmartphoneUpgrade(id = "upgrade_pokenav", nbtKey = "upgrade_pokenav", ...)
)
// Consumption (in action isEnabled)
smartphone.hasUpgrade("upgrade_pokenav")
// Query all installed
SmartphoneUpgradeRegistry.getInstalledUpgrades(stack)Common code can't reference platform-specific compat managers (Trinkets vs Curios). Solution:
common/util/SmartphoneHelper.kt → getSmartphoneImpl: ((Player) -> ItemStack?)?
↓ sets
fabric/CobblemonSmartphoneFabric.kt SmartphoneHelper.getSmartphoneImpl = { SmartphoneCompatManager.getSmartphone(it) }
neoforge/CobblemonSmartphoneNeoForge.kt SmartphoneHelper.getSmartphoneImpl = { SmartphoneCompatManager.getSmartphone(it) }
All actions and handlers call SmartphoneHelper.getSmartphone(player) regardless of platform.
The Implementation interface defines platform-specific setup steps that CobblemonSmartphone.init() delegates to:
interface Implementation {
val networkManager: NetworkManager
fun registerItems()
fun registerCommands()
fun registerReloadListeners()
}Fabric and NeoForge provide their own implementations, wiring events appropriately (e.g., Fabric uses ResourceManagerHelper, NeoForge uses AddReloadListenerEvent).
The core extensibility point. Actions implement a simple interface:
interface SmartphoneAction {
val id: String
val texture: ResourceLocation
val hoverTexture: ResourceLocation
fun onClick() // Client-side
fun isEnabled(): Boolean = true // Client-side visibility check
}SmartphoneActionRegistry.getEnabledActions() filters by isEnabled() before rendering in the GUI. This is how upgrade-locked and mod-conditional actions are hidden.
Packets extend CobblemonSmartphoneNetworkPacket<T> which wraps Cobblemon's NetworkPacket<T>:
Packet (common/network/packet/) → sendToServer() / sendToPlayer()
Handler (common/network/handler/) → ServerNetworkPacketHandler<T>
Registration (CobblemonSmartphoneNetwork.kt) → c2s and s2c PacketRegisterInfo lists
Datapack actions use a generic ExecuteDatapackActionPacket that carries an action ID string. Built-in actions have dedicated packet classes.
Upgrades are stored as boolean flags in DataComponents.CUSTOM_DATA:
minecraft:custom_data
└── cobblemon_smartphone:upgrades
├── upgrade_pokenav: 1b
└── upgrade_waystone: 1b
fun ItemStack.hasUpgrade(nbtKey: String): Boolean // Read check
fun ItemStack.addUpgrade(nbtKey: String) // Write (smithing)
fun ItemStack.isSmartphone(): Boolean // Type checkRecipes use vanilla minecraft:smithing_transform with the cobblemon_smartphone:smartphones tag as the base. A Mixin on SmithingTransformRecipe.assemble() (MixinSmithingTransformRecipe.java) intercepts crafting:
- Checks if
input.base()is aSmartphoneItem - Identifies the upgrade from the addition item's registry key
- Copies the base (preserving smartphone color) + adds upgrade NBT
- Returns the colored result instead of the fixed recipe result
This means one recipe JSON handles all 16 smartphone colors.
For addon actions that need to "use" items from optional mods without class references:
SimulatedItemUse.simulate(player, itemPredicate, useAction)Temporarily places a synthetic ItemStack in the player's main hand, executes the use action, and restores the original hand in a finally block.
Recipes use dual-condition format for cross-platform conditional loading:
{
"fabric:load_conditions": [{ "condition": "fabric:all_mods_loaded", "values": ["cobblenav"] }],
"neoforge:conditions": [{ "type": "neoforge:mod_loaded", "modid": "cobblenav" }]
}Each loader strips its own field before the codec runs; the other field is ignored as an unknown JSON key.
| Mixin | Target | Purpose |
|---|---|---|
MixinSmithingTransformRecipe |
SmithingTransformRecipe.assemble() |
Base-to-result copy for smartphone upgrades |
EntityMixin |
Entity |
Persist player preferences across death |
ItemRendererMixin |
ItemRenderer |
Custom smartphone model rendering |
ModelLoaderMixin |
ModelLoader |
Register smartphone hand models |
MixinItemInHandRenderer |
ItemInHandRenderer |
Smartphone in-hand rendering |
MixinPlayerExtensionsKt |
Cobblemon player extensions | Extend player capabilities |
MixinPokedexUsageContext |
Cobblemon pokedex context | Scanner integration |
All mixins are declared in common/src/main/resources/cobblemonsmartphone.mixins.json.
SmartphoneConfig.kt loads/saves JSON from config/cobblemon_smartphone.json. Structure:
SmartphoneConfig
├── ignoreUpgrades: String[] // Action IDs that ignore required smartphone upgrades
├── cooldowns: Cooldowns // Per-action cooldowns in seconds
│ ├── healButton: Int
│ ├── pcButton: Int
│ ├── cloudButton: Int
│ ├── waystoneButton: Int
│ └── pokedexButton: Int
└── features: Features // Enable/disable toggle per feature
├── enableHeal: Boolean
├── enablePC: Boolean
├── enableCloud: Boolean
├── enablePokenav: Boolean
├── enableCobbleDollars: Boolean
├── enableWaystone: Boolean
├── enablePokedex: Boolean
└── enableScanner: BooleanDatapacks can add actions via JSON files in data/<namespace>/smartphone_actions/. Actions may execute commands and built-in functions such as open_pc, heal_party, open_pokedex, and open_ender_chest. See wiki/datapack-api.md for the full API reference.
Loading flow:
DatapackActionLoader(common) implementsPreparableReloadListener- Fabric: wrapped by
DatapackActionReloadListenerWrapperforIdentifiableResourceReloadListener - NeoForge: registered directly via
AddReloadListenerEvent - On reload: scans all namespaces → filters by
require_mod→ buildsDatapackActioninstances - Actions synced to clients via
SyncDatapackActionsPacketon player join
- Create action class in
common/.../actions/implementingSmartphoneAction - Create packet in
common/.../network/packet/extendingCobblemonSmartphoneNetworkPacket - Create handler in
common/.../network/handler/implementingServerNetworkPacketHandler - Register packet + handler in
CobblemonSmartphoneNetwork.kt - Register action in
CobblemonSmartphone.registerDefaultActions() - Add config flags in
SmartphoneConfig.ktif feature should be toggleable - Add localization in
en_us.json
- Register
SmartphoneUpgradeinCobblemonSmartphone.registerDefaultUpgrades() - Create smithing recipe JSON in
common/src/main/resources/data/cobblemon_smartphone/recipe/ - Add recipe conditions for optional mods
- In the action's handler, check upgrade NBT + add
SimulatedItemUsefallback - Add localization messages
If the upgrade's addition item comes from another mod, no code changes are needed in MixinSmithingTransformRecipe — the mixin identifies upgrades by the addition item's registry key.
| Dependency | Version | Notes |
|---|---|---|
| Cobblemon | 1.6+ | Required. Provides sounds, network API, Pokedex integration |
| Architectury | 13+ | Multi-loader abstraction |
| Fabric API | 0.100+ | Fabric loader (Fabric target only) |
| Kotlin for Forge | 4.11+ | Kotlin runtime on NeoForge |
| Trinkets | 3.10+ | Optional (Fabric) — smartphone accessory slot |
| Curios | 9.0+ | Optional (NeoForge) — smartphone accessory slot |
| Accessories | 1.1.0-beta+ | Optional (Fabric & NeoForge) — smartphone accessory slot |
- Datapack API — Add actions via JSON
- Mod API — Add actions programmatically
- Overview & Upgrade Recipes — Index and smithing recipe format