Skip to content

ModularShoot

ModularShoot is an attribute-driven, modular assembly gun system framework mod for NeoForge 1.21.1.

Framework positioning: This mod is a pure framework and API — it provides registration APIs, an attribute calculation pipeline, and a shooting engine, with no production content. The bundled datapack ships framework metadata (attribute metadata table) plus example content (example guns, plugins, variants, etc., for reference and testing); production content is added by other mods via the API or datapacks.

Key Features

FeatureDescription
Single ID + DataComponentAll guns share a single item ID (modularshoot:gun), differentiated via DataComponent. Same for plugins (modularshoot:plugin)
Item BindingBind other mods' items (e.g. swords, trinkets) as guns/plugins via datapack JSON (gun_items/plugin_items) or the Java API (registerGunItem/registerPluginItem) — converts within 1 tick of entering the inventory, keeping the original item's model visually
Attribute-DrivenGun behavior is entirely determined by attributes + traits. No hardcoded logic. ADD_VALUE → ADD_MULTIPLIED_BASE → ADD_MULTIPLIED_TOTAL three-stage stacking
Dual RegistrationSupports Java API registration and datapack JSON registration, sharing the same registries. /reload hot-reloads JSON; API entries are unaffected
Non-Entity BulletsBullets are lightweight data records managed by BulletManager, supporting thousands of simultaneous projectiles (per-chunk entity-candidate caching + capsule collision)
Fully Event-DrivenAll extension points exposed via events + callback APIs: shoot events, install/uninstall events, trait hooks, damage handlers, right-click/action key events
Server-AuthoritativeShooting, bullet flight, and hit detection are all server-side. Client is render-only, cheat-resistant
Random VariantsGuns/plugins declare variant pools; each pellet independently rolls a weighted random variant per shot — the winner merges traits and overrides stats, damage type and visuals
Shoot Visual FeedbackShoot textures switch at the moment of firing (per_shot/while_firing modes); in first person the gun itself plays a short recoil kick (push back + muzzle rise, settling within a few ticks), in third person the arms play the recoil pose. All three are driven by the same shoot-animation timer, pulsing in sync with every accepted shot

Quick Navigation

DocForContent
API ReferenceDevs calling framework functions in codeQuick-reference tables for all ModularShootAPI public methods
Registry ReferenceDevs understanding definition fieldsAll 10 dynamic registries and field details for each definition type
Datapack RegistrationDevs using JSON to register contentDatapack JSON file paths, fields, and format reference
ConfigurationPlayers & server owners tuning client/server behaviourEvery option in the client and common config files
Command ReferenceDevs using debug commands/modularshoot subcommand quick reference
ExamplesDevs wanting to see code directlyAll Java API and JSON examples in one place

10 Framework Registries

Registry IDPurposeRegistration Method
modularshoot:gunsGun definitionsJava API / Datapack JSON
modularshoot:pluginsPlugin definitionsJava API / Datapack JSON
modularshoot:plugin_typesPlugin type (category) definitionsDatapack JSON
modularshoot:traitsBoolean trait definitionsDatapack JSON
modularshoot:statesPersistent state definitionsDatapack JSON
modularshoot:variantsRandom variant definitionsDatapack JSON
modularshoot:shootersShooter definitions (independent-firing config templates)Java API / Datapack JSON
modularshoot:attribute_metaAttribute metadata (defaults, display info, bindings)Datapack JSON
modularshoot:gun_itemsItem → gun bindings (bind other mods' items as guns)Java API / Datapack JSON
modularshoot:plugin_itemsItem → plugin bindings (bind other mods' items as plugins)Java API / Datapack JSON

Attribute bodies (Attribute instances) must be registered using vanilla DeferredRegister into BuiltInRegistries.ATTRIBUTE. They are not in the dynamic registries above.

An attribute_meta entry's binds can be rebound to any registered vanilla attribute (e.g. minecraft:attack_damage); all three paths (mount / resolve / display) share it. See the datapack registration doc.

Preset Attributes

The framework pre-registers 10 numeric attributes (modularshoot namespace):

Attribute IDDescriptionDefault
hit_damageDamage per hit1.0
fire_rateShots per second (cap 20, ≤0 disables shooting)1.0
rangeMax bullet flight distance (blocks)50.0
accuracy_yawHorizontal spread angle (degrees)10.0
accuracy_pitchVertical spread angle (degrees)10.0
entity_penetrationEntities penetrated (0 = no penetration)0
bullet_speedBullet flight speed (blocks/sec)20.0
bullet_sizeBullet collision sphere radius (0 = raycast)0.5
block_penetrationBlocks penetrated (0 = no penetration)0
pellet_countPellets per shot (rounded, then clamped to 1~32; over-limit logs a WARN; 0 or unmounted silently degrades to a single pellet)1.0

0.1.3 new feature: Gun/plugin definitions gained the extra_values namespaced numeric extension field (keys must be fully namespaced); ModularShootAPI.getExtraValueSums / getExtraValue provide one-stop queries for "gun definition base values + accumulated values of installed plugins".

0.3.0 new features: plugins can now be registered via the Java API and dynamic definition providers (registerPlugin / registerPluginDefinitionProvider, enabling programmatic plugins such as random loot affixes); a new bullet sync extension channel (BulletSyncExtraRegistry) lets mods attach custom data to every bullet and read it on the client; the bullet-sync distance bands / update frequency / full-sync interval are now tunable in modularshoot-common.toml (see Configuration).

⚠️ 0.3.0 breaking changes: network protocol bumped to 4 (not interoperable with older versions — both sides must update together for multiplayer); getGunId / getState now return Optional; the plugin install pre-event constructor changed (adds the selected slot type and a custom cancellation reason).

New feature (unreleased): gun definitions now support the attribute_mount declaration (item / player); player-side guns no longer carry the item attribute modifier component. The framework adds getAttributeMount and registerPlayerAttributeSourceProvider; player-side tooltips read final values from the attribute holder, falling back to definition base values with a "based on holder" note when no holder can be resolved.

Key Events Overview

EventFires WhenCancellable
PreShootEventAfter shoot conditions passYes
PostShootEventAfter all pellets are registered (carries every bullet of the shot via getBullets())No
GunRightClickEventRight-click gun outside GUIYes
ActionEventAction key pressed (default R)Yes
PrePluginInstallEventAfter install validation passes (carries the framework-selected slot type via getSelectedTypeId(); cancel with a custom reason via cancel(Component))Yes
PostPluginInstallEventAfter plugin written to componentNo
PrePluginUninstallEventBefore plugin removalYes
PostPluginUninstallEventAfter plugin removedNo
ClientBulletHitEventOn the client when a hit packet is received, before the default hit sound plays (client-only, NeoForge.EVENT_BUS)Yes

Hit-effect hook: ClientBulletHitEvent fires before the default hit sound plays on the client. Its soundId field is the hit sound id resolved by the server from the gun definition's sounds slots (entity hit_entity / block hit_block / pierce hit_pierce; null when not configured). The framework spawns no default hit particles — visual effects are entirely up to listeners. Cancelling the event skips the default hit sound; when not cancelled, listeners can add custom sounds/particles in their own logic.

Released under the MIT License