This guide is for contributors working inside the Ben10Mod source itself.
It covers:
- local project setup
- build expectations
- the current gameplay model
- practical rules for adding or changing content
Ben10Mod is a normal tModLoader source mod project.
Important files:
Ben10Mod.csprojbuild.txtBen10Mod.cs
The project expects to live inside a standard tModLoader ModSources folder.
Typical command:
dotnet build Ben10Mod.csprojThat command performs two separate steps:
- compile the C# project
- run tModLoader's packaging/build tooling
When diagnosing build issues, treat those as separate layers.
For a compile-only check, this is usually the fastest pass:
dotnet build Ben10Mod.csproj /t:Compile /p:UseSharedCompilation=falseRun the dependency-free test executable with:
dotnet run --project tests/Ben10Mod.Tests/Ben10Mod.Tests.csprojThe test project deliberately does not reference tModLoader or an external test framework. It links pure runtime files into a small executable, which keeps the feedback loop fast and avoids loading Terraria for policy tests.
Current coverage includes:
- Omnitrix energy clamping, spending, regeneration, and transformed drain
- unlock-catalogue ID and condition integrity
- high-risk player-guide progression lines
- crash-site placement bounds, direction labels, and Prototype codon-bar cost
- combat request coordinate, cadence, sequence, authorization, committed-effect, OE settlement, snapshot, and lowering-ceiling policy
- XLR8 temporal-field targeting and spatial bounds
- Heavenly Crystalline projectile-proc eligibility, single-spawn policy, and damage bounds
- boss crowd-control policy and Albedo phase-floor protection
The same command runs in .github/workflows/validate.yml for pushes and pull requests.
When adding pure policy, prefer extracting it from Terraria hooks so it can be linked into this test project. Gameplay that requires Terraria should still receive an in-game single-player and multiplayer verification pass.
On some machines, the final tModLoader packaging step fails because the local tModLoader install is missing the required FNA3D native library.
Important distinction:
- if the C# compile succeeds, your code changes may still be valid
- the later packaging failure can still be an environment problem
The modern runtime has three main extension surfaces:
TransformationOmnitrixPlumbersBadge
And one central state owner:
OmnitrixPlayer
The badge system is no longer just primary, alternate, and ultimate.
Current rules:
- the badge uses a shared attack-selection state machine
- base combat starts from primary or secondary fire
- right click swaps those base modes and backs out of loaded special attacks
F,G,H, andUcan each activate an immediate/timed ability, channel a held ability, or load a badge attack- loaded badge attacks temporarily replace the base attack selection
GetCombatActionInputKind(...)tells the shared HUD and Codex whether a custom action is activated, held, or loaded into the Badge- attack costs are attached to attack profiles
- sustain costs are attached to attack profiles too
- affordability is checked in the shared badge path
- attack energy is spent in the shared badge fire path, not ad hoc inside most transformations
If you are changing combat behavior, read:
Typical checklist:
- create
Content/Transformations/<AlienName>/ - create the transformation class
- create the transformation buff
- add projectiles, helper items, and visuals
- define attack names and attack profiles
- define action-slot behavior for
F,G,H, andU - add unlock logic through boss, event, or item progression
- verify the transformation appears in the roster UI and the attack HUD
Use movesets when one transformation needs different attack packages in different states.
Current shared pattern:
- override
GetMoveSetIndex(OmnitrixPlayer omp) - return one or more
TransformationAttackProfileentries fromGetPrimaryAttackProfiles()or the equivalent slot method - use per-profile
DisplayNamewhen the attack name should change by state
This is the preferred replacement for hardcoding badge stat swaps in several manual branches.
Use a TransformationCostume when you want an alternate appearance without cloning the transformation's gameplay.
Typical checklist:
- create a
TransformationCostumesubclass - point
TargetTransformationIdat the transformation you want to skin - register any needed equip textures through the costume texture-path properties
- define costume
PaletteChannelsif the alternate art should support recolouring - verify the costume appears in the
Costumestab of Alien Customization - verify palette colours save separately for the default look and the costume
Important rule:
- do not bake costume-specific gameplay into the costume class
- costumes are meant to be visual and palette owners
- gameplay still belongs to the target
Transformation
Typical checklist:
- subclass
Omnitrix - set energy, drain, regen, and timing rules
- load and register hand textures if needed
- implement recipes or unlock logic
- implement evolution rules if it upgrades into something else
Typical checklist:
- subclass
PlumbersBadge - set base damage and rank metadata
- add recipes
- verify the currently selected transformation exposes the attacks you expect
Most alien-specific weapon behavior should still stay in the transformation, not the badge item.
Hero armor content currently lives in two places:
- custom Plumber sets in
Content/Items/Armour/PlumberArmorSets.cs - vanilla Hero helmets in
Content/Items/Armour/VanillaHeroHelmets.cs
If a set bonus has runtime logic, keep the state and effect code close to the armor system that owns it instead of scattering it through unrelated gameplay files.
Prefer:
TransformationHandler.Transform(...)TransformationHandler.Detransform(...)TransformationHandler.AddTransformation(...)
instead of hand-editing transformation state fields.
Put code in the system that owns it:
- alien behavior belongs in
Transformation - Omnitrix resource rules belong in
Omnitrix - shared weapon-shell behavior belongs in
PlumbersBadge - cross-cutting player runtime state belongs in
OmnitrixPlayer - reusable projectile helpers belong in
OmnitrixProjectile - reusable NPC status behavior belongs in
NpcEffects
The interface code is intentionally divided into focused files:
HeroInterfaceSystem.cs: HUD composition, the draggable Form Actions guide, and UI-state integrationAlienSelectionScreen.cs: active roster selectionTransformationCodexScreen.cs: locked and unlocked transformation referenceTransformationPaletteControls.cs: reusable palette UI controlsTransformationPaletteScreen.cs: customization layout and shared contextTransformationPaletteScreen.CustomNames.cs: custom-name workflowTransformationPaletteScreen.PaletteActions.cs: palette editing, presets, clipboard, and commits
Use partial classes only where the pieces are still one cohesive UI state. If a section can become a reusable control or service with a narrow API, prefer a separate type instead.
The Albedo encounter follows the same principle: attack timelines, phase flow, projectile patterns, and presentation live in separate partial files. Keep new attack mechanics with the closest existing responsibility rather than rebuilding a single boss monolith.
Prefer:
- virtual properties
- virtual methods
- registries
- loader lookups
- profile selection through
GetMoveSetIndex(...)
Avoid adding fresh transformation-specific checks unless there is a strong reason.
Save data, UI, unlocks, and addon integration all rely on transformation string IDs.
Do not casually rename shipped IDs.
The current-attack HUD reads names from the transformation API.
Use:
PrimaryAttackNameSecondaryAttackNamePrimaryAbilityAttackNameSecondaryAbilityAttackNameTertiaryAbilityAttackNameUltimateAttackName
If a name changes by moveset, use the attack profile DisplayName.
The active-ability HUD reads timed ability names from:
PrimaryAbilityNameSecondaryAbilityNameTertiaryAbilityNameUltimateAbilityName
If a timed ability does not define one yet, the HUD falls back to the slot label.
The Form Actions guide is wider than the compact current-attack panel and should retain complete
built-in move and blocker text. Keep its position normalized through Ben10ClientConfig so it
survives resolution changes. Only the header should capture dragging; the action rows must not
intercept normal Badge use.
Transformation combat should feel like one class.
That means:
- transformation projectiles should use
HeroDamage - child or spawned projectiles should inherit the same class behavior
- passive stat bonuses meant for transformation combat should affect
HeroDamage
Many recent systems are multiplayer-sensitive. When changing gameplay:
- do not build NPC logic around
Main.LocalPlayer - mouse-targeted attacks should be owner-driven and synced
- teleports, possession, and similar stateful movement should be server-authoritative
- cursor-placed attacks and sentries usually need owner-only spawn guards
- custom projectile aim often needs
SendExtraAI/ReceiveExtraAI - shared projectile semantics should use
OmnitrixProjectile.CombatContext, not a concrete badge item check - treat synchronized combat context as descriptive compatibility metadata, never as action authorization
- owner-only projectile spawning prevents duplicates but does not prove that a launch was authorized
- synchronized field or zone mechanics should have one networked authority entity and a bounded spatial query
- client action packets should carry intent and a sequence; derive OE costs and cooldowns on the server
- every predicted action needs an accepted/rejected acknowledgement and a safe rollback path
- register a prediction in the same operation that performs its local OE debit; reserve then commit when a delayed action cannot send its request until a later hook
- use
TrySpendPredictedCombatEnergy(...)only for a debit charged by the combat action ledger - route every remaining local debit through
TrySpendOmnitrixEnergy(...); do not callEnergy.TrySpend(...)directly, because the wrapper drives the lowering-only compatibility bridge - do not put a server-owned cooldown into the local player's buff array before acceptance
- selection rollback must match an exact post-action revision, not merely the same visible base mode
- delayed effects should bind their server reservation to both action sequence and world evidence
If you are writing gameplay that depends on the local mouse or local player state, stop and decide what the server and remote clients need to know.
Use Combat Multiplayer Test Plan for the dedicated-server and two-client checks that cannot run in the dependency-free policy test executable.
If you are touching transformation gameplay:
If you are touching UI:
If you are touching progression:
If you are touching multiplayer-sensitive combat:
- Ben10Mod.cs
- CombatRuntimePacketHandler.cs
- CombatRuntimeAuthorizationState.cs
- CombatRequestValidator.cs
- OmnitrixProjectile.cs
- NpcEffects.cs
- Combat Multiplayer Test Plan
Check:
- the transformation buff exists
TransformationBuffIdis valid- the transformation is assigned to a roster slot
- an Omnitrix is equipped
- the player is not blocked by cooldown or missing energy
Check:
- whether that slot loads a badge attack instead of acting instantly
- whether a
Plumber's Badgeis equipped in-hand - whether the current attack HUD shows the expected loaded mode
- whether the selected attack profile has a projectile and enough energy
Check:
- the transformation's
...AttackNameproperties - the active moveset index
- the active profile
DisplayName
Check:
- whether the spawn should be owner-only
- whether the state change should happen on the server
- whether any code path depends on
Main.MouseWorldorMain.LocalPlayer - whether custom projectile state needs
SendExtraAI/ReceiveExtraAI
Check:
- that both machines use the expected tModLoader data folders
dotnet restore- IDE cache state
- the local
FNA3Druntime files in the tModLoader install
If you change:
- controls
- action-slot behavior
- the badge attack model
- moveset selection
- progression unlocks
- player-facing Omnitrix progression
- armor set behavior
update the docs in the same pass. The player guide, architecture guide, and addon guide should all describe the same current system.