InteractiveStuffDocumentation · 0.8.0The complete guide

InteractiveStuff documentation

Make held items react to the way you play. Learn the mod, configure its effects, and build your own first-person interactions with Vyn.

0.8.0Fabric · Client side0.8.0 feature set

Here to play?

Start with installation, try the bundled interactions, then tune the mod and pack settings.

Install InteractiveStuff

Here to create?

Build a resource pack, transform a held model, and add particles, physics and player-facing options.

Create your first pack

What the mod does

InteractiveStuff adds visual and sound interactions to held items. Its rendering hooks let resource packs replace or split models, move their parts, apply biome colors, simulate spring motion and draw first-person particles. The bundled InteractiveStuff resource pack supplies the ready-to-use effects.

The mod provides the engine and global controls. A resource pack provides scripts, models, textures and its own options. This distinction matters: disabling a pack effect does not disable the engine for other packs.

This offline guide covers the InteractiveStuff 0.8.0 feature set. Keyframe animation files and controls are documented separately in the 0.9.0 guide. Use the contents or search for an item, option or function. Expandable source listings and copy buttons work without a server.

Install & get started

#
  1. Install Fabric Loader for the Minecraft release you play.
  2. Download the matching InteractiveStuff 0.8.0 JAR and Fabric API. Put both in that instance’s mods folder.
  3. Optionally install the matching Mod Menu build. YACL is not required.
  4. Launch the client and check Options → Resource Packs. Keep the bundled InteractiveStuff pack enabled when you want its effects or import its libraries.
  5. Enter a world in first person. Try holding an enchanting table near a bookshelf, or crouch on a redstone block with a note block.

Install the mod on the client. Its held-item effects do not require a server installation and do not add server-side gameplay or physical world entities.

Open the settings

With Mod Menu installed, use Mods → InteractiveStuff → Configure. Without Mod Menu, use the InteractiveStuff button in Minecraft’s Options screen. A configurable settings keybind is also available under Controls; it starts unbound.

Match your Minecraft target

These are the minimum versions declared by this project’s current profiles, rather than a claim about the newest available releases. Use Fabric API and Mod Menu builds for the same Minecraft version. Optional Mod Menu builds may impose a higher Loader minimum of their own.

The render callback’s itemRendered is the actual current render model. Apply rendering changes to it there. The objects returned by player.getMainHandItem() and related getters are useful for inspecting stacks; changing one outside a rendering callback is not a persistent replacement for the active renderer.

Global objects

NameType / purpose
playerLocal Player wrapper
worldCurrent World wrapper
configInteractiveStuffConfig read-only access
keyKey translation helper
modLoaderInstalled mod, pack and game-version queries
particlesSession-wide particle counts and clearing

Global functions

FunctionBehavior
importScript(packId, script)Execute an importable library in this script’s environment. The script name omits .vyn.
excludeScript(packId, script)Exclude the specified script from active callback dispatch.
debugText(text)Queue diagnostic text when the calling pack’s debug mode is enabled.
debugText(text, visible)Use an explicit boolean visibility flag, useful for a pack-owned counter switch.
translateText(key, ...arguments)Resolve a Minecraft language key to a displayed string, with optional format arguments.
getDelta()Frame duration normalized to 60 FPS: approximately 1 at 60 FPS. Paused frames use 0; the clock caps elapsed time at 50 ms (delta 3). It is not seconds or game ticks.
getFrameId()Monotonic frame identifier for preventing duplicate work in one render frame.
wait ticks do … endSchedule a block after a tick delay; this does not block the rest of the script. A nominal second is 20 ticks.
Delayed second sound
task onSwingHand do
    check !player.isHoldingItem("minecraft:stick") do reply nothing end
    player.playSound("minecraft:ui.button.click", 0.4, 1.0)
    wait 10 do
        player.playSound("minecraft:ui.button.click", 0.4, 1.4)
    end
end

State lifetime

Top-level declarations run when a script loads; state persists between callbacks until reload/session reset. Scheduled work, model queues and particles are cleared when the session changes or scripts reload. A runtime error marks that script failed; check the game log, fix it and reload resources.

A render callback may run for both hands. Do not advance shared progress blindly in each call. Use getFrameId(), state keyed by logical hand and item ID, or the bundled smoothItemProgress helper. Logical main/off hand and physical left/right hand are different; prefer the appropriate ItemModel hand query.

Bundled item interactions

#

InteractiveStuff ships with a resource pack of Vyn scripts that add client-side, first-person item rendering effects. These examples are enabled with the bundled pack and can be configured in the resource-pack options.

EffectExamples
Animated item partsChest lids, pistons, vaults, redstone lamps, the enchanting-table book, sculk sensors, water buckets and extinguishing torches.
Item physicsMovement-driven motion for the mace, item frames, paintings and hanging signs, controlled by the pack physics options.
Particles and soundsEnchanting-table glyphs, sculk activation bursts, note-block notes and the note-block interaction sounds.
Color and appearanceBiome-aware tinting for foliage, saplings and water, plus the enchanting-table book display option.
Other item effectsTotem motion, item-model variants and helper functions shared by the bundled scripts.

The scripts live in assets/interactivestuff/scripts/ inside the bundled resource pack. You can inspect them as examples when creating your own Vyn resource pack.

Mod settings & navigation

#

InteractiveStuff has a built-in Minecraft-style settings screen; YACL is not required. Open it from InteractiveStuff in Mod Menu, from the Minecraft Options screen when Mod Menu is absent, or with the optional settings keybind.

Use the sidebar to switch categories, search to find a setting, and hover a control for its tooltip. Most changes are staged until you press Apply. Cancel discards the draft; reset and reload controls restore saved values. Hit-sound materials, excluded blocks and per-item pitch overrides use searchable inventory-style selectors.

Mod settings control the engine globally. Settings declared by a resource pack appear in the Packs area, grouped by pack and category, and are saved per pack. See Bundled resource-pack options.

Bundled resource-pack options

#

These controls belong to the bundled resource pack, rather than the mod's global engine settings. Open Packs, expand the bundled pack, then choose a category. Values are saved per resource pack.

CategoryOptions
AppearanceBlock, sapling and water tinting; showing the enchanting-table book.
PhysicsMaster physics switch; separate item-physics switches for the mace, item frames, paintings and hanging signs.
AnimationsShake intensity; pistons, chests, vaults, redstone lamps, enchanting books, sculk sensors, water-bucket animation and torch extinguishing.
ParticlesSeparate emission switches for enchanting tables, sculk sensors and note blocks.
SoundsNote-block sounds for crouching or swinging interactions.
DebugVyn debug text and particle counters.

Pack authors can declare their own options with ConfigOption; see Declare resource-pack settings.

Interactive hit sounds

#

Interactive hit sounds play a configured sound when a supported held item is swung. Configure them in the mod settings screen.

  1. Enable Interactive hits.
  2. Add or edit a material, then use the searchable item list to choose its items.
  3. Choose a registered sound and preview it before applying.
  4. Set volume, base pitch, random pitch and whether the swing must hit a block.
  5. Add per-item pitch overrides when a particular item needs a different pitch.
  6. Add excluded blocks when a block should suppress the hit sound.

Turning off Interactive hits disables the dependent hit-sound controls. Excluded blocks affect the sound response; they do not disable a resource pack's Vyn effects. The cooldown controls the ticks between hit sounds.

Create your first resource pack

#

Create a normal Minecraft resource pack with a pack.mcmeta, then add a script under the InteractiveStuff namespace:

MyPack/
  pack.mcmeta
  assets/
    interactivestuff/
      scripts/
        stick.vyn

A minimal script can target a held item and adjust its render model:

assets/interactivestuff/scripts/stick.vyn
lock interactiveItems = ["minecraft:stick"]

task onItemUpdate takes itemRendered do
    itemRendered.rotate(0, 0, 15)
end

Enable the pack in Minecraft's Resource Packs screen. interactiveItems filters which held items receive onItemUpdate; other events require their own item checks. Continue with Vyn language essentials and Models, transforms & colors.

Vyn language essentials

#

Vyn is the scripting language used by InteractiveStuff resource packs. Scripts are loaded from assets/interactivestuff/scripts/. Use make for a value that can be reassigned and lock for a value that should stay fixed. A task declares reusable code or an event callback.

Variables, conditions and tasks
lock interactiveItems = ["minecraft:stick"]
make tilt = 15

task onItemUpdate takes itemRendered do
    check itemRendered.getCount() > 0 do
        itemRendered.rotate(0, 0, tilt)
    end
end

Use check condition do ... end for a condition and otherwise for its alternate branch. Use reply value to return from a task. Lists and maps provide collections; new Type(...) constructs exposed Java-backed Vyn objects. importScript(packId, scriptName) loads a helper script from a resource pack. For the full language grammar and expressions, consult the Vyn documentation.

Events, globals & timing

#
Event taskWhen it runsCommon use
task onTick doEach active client game tick while the world is ready and unpaused.Timers, state updates and checks that do not need to run every render.
task onItemUpdate takes itemRendered doFor each matching held item render.Transforms, replacement models and particle emitters. The argument is the current render model.
task onSwingHand doWhen the local hand-swing callback fires.React to a swing; check the held item yourself when needed.
task onPlaySound takes sound doWhen the client observes a sound notification.Inspect its ID, pitch, volume or position. Avoid recursively responding to a sound your script plays.
task onKeyPress takes keyInput doFor a gameplay key press when no screen is open.Compare the integer key code; this is not a text-character event.

Global objects

GlobalPurpose
playerLocal player state, held stacks, movement and block queries.
worldWorld state, block and biome queries.
configRead-only access to mod configuration values.
modLoaderInstalled mod, selected resource-pack and game-version queries.
particlesSession particle counts and particle clearing.
keyLegacy translation helper; prefer translateText for displayed text.

Use onTick for gameplay-tick state and onItemUpdate for render-local changes. Event tasks are callbacks, not ordinary functions you call manually. For particles, declare emitters once and bind them during the relevant render callback; see First-person particles.

Models, transforms & colors

#

Replace a model or add a part

itemRendered.setItemModel(id) replaces the appearance of the current rendered stack. new ItemModel(itemId) and itemRendered.copy() enqueue an additional model for that render; they are useful for lids, pages and overlays. Use the current model when you want replacement.

Parented extra model
lock interactiveItems = ["minecraft:stick"]

task onItemUpdate takes itemRendered do
    check player.getGamemode() == "spectator" do reply nothing end
    itemRendered.rotateZ(12)

    make ornament = new ItemModel("minecraft:diamond")
    ornament.setParent(itemRendered)
    ornament.translate(0, 0.25, 0)
    ornament.scale(0.2, 0.2, 0.2)
end

Translations, pivots and particle anchors use render/model units, not screen pixels. Model JSON element coordinates are normally expressed on a 0–16 grid, while these render coordinates use unit-scale values; the first-person display transform also affects the final position. Rotations take degrees. Scaling multiplies the model’s scale; shear takes six cross-axis factors.

Pivot & parenting

setPivot(x,y,z) controls the transform anchor. A parent supplies its transform and inherited light/glint where not overridden. Self-parenting and cycles are rejected. detach() removes the parent relationship. Use the pivot marker tools to place hinges in both physical hands.

Turn toward a world position

lookAt(position) aims at a block center. The overload lookAt(position, group, speed, weight) smooths a shared group’s orientation; use the same group for cooperating parts. Speed and weight must be 0–1, and the group must be nonempty, at most 80 characters. setLookAtClearance(surface,minX,minY,minZ,maxX,maxY,maxZ) supplies a clearance plane and local bounds for that aiming path; it is not general collision physics.

Which color function?

FunctionAffects
setTint(index, packedColor)Only model faces with the requested tint index. Prefer this for grass, foliage and water masks.
setTint(packedColor)The fallback tint override, stored at index −1; it can recolor untinted faces as well. Use the indexed overload to preserve dirt or wood.
setColor(r,g,b)The model’s RGB multiplier. Use 0–255 channels.
setQuadColor(packedColor)A selection-color override applied through the selected quad path.
setOpacity(value)Opacity multiplier, clamped to 0–1.
Tint grass without tinting dirt
lock interactiveItems = ["minecraft:grass_block"]

task onItemUpdate takes itemRendered do
    check player.getGamemode() == "spectator" do reply nothing end
    make position = player.getSteppingBlock().getPosition()
    itemRendered.setTint(0, world.getGrassColor(position))
end

Use the right resolver: world.getGrassColor(position), getFoliageColor, getDryFoliageColor or getWaterColor. They return packed colors for setTint. The model must have suitable tintindex faces. For RGB/ARGB integers with no alpha bits, the tint setter supplies opaque alpha.

Quad selection

setQuads(start,end) sets the affected half-open range; unselected geometry remains visible unless exclusive selection is used. The end index is excluded; an end of 0 means the full layer. select(start,end) also enables exclusive selection. Quad ordering depends on the baked model and version, so a range is not a stable identifier for a named model part.

Custom assets across versions

Legacy model routing and modern item-model definitions differ by Minecraft target. Inspect the bundled models and version adapters before distributing a cross-version pack; do not assume one modern item-definition JSON works on 1.20.1. Data-component operations also depend on each target’s supported component bridge.

Bundled chest parts now load Minecraft’s chest images through block-atlas sprite aliases, so active resource-pack replacements of minecraft:textures/entity/chest/*.png are used. Edited sapling masks, bucket water and extinguished-torch images remain pack assets because they are effect-specific.

First-person particles

#

A ParticleEmitter is a persistent Vyn object. Declare it once at script load, configure it, and attach it to the current model inside onItemUpdate. It owns separate channels for the two logical hands.

These are custom first-person billboards. They use vanilla particle appearances or custom PNGs, but they do not instantiate Minecraft’s native world-particle behaviors. Choosing minecraft:enchant, for example, chooses its textures; you still define its motion.

Complete continuous emitter
lock interactiveItems = ["minecraft:stick"]

make sparks = new ParticleEmitter("minecraft:end_rod")
sparks.setPosition(0.5, 0.9, 0.5)
sparks.setSpread(0.08, 0.02, 0.08)
sparks.setVelocity(0, 0.012, 0)
sparks.setVelocitySpread(0.003, 0.004, 0.003)
sparks.setRate(0.15)
sparks.setLifetime(30)
sparks.setSizeOverLife(0.05, 0, "easeOutQuad")
sparks.setOpacityOverLife(1, 0, "easeInQuad")
sparks.setSpace("world")

task onItemUpdate takes itemRendered do
    check player.getGamemode() == "spectator" do reply nothing end
    sparks.attach(itemRendered)
end

Coordinate spaces

SpaceAfter spawningUse
itemPositions continue to follow the attached model’s transform.Glows or particles that move with the item.
viewThe spawn anchor is applied once; particles remain in view coordinates as the player moves/turns. This is the default.Screen-relative first-person flourishes.
worldThe spawn anchor is converted to world coordinates once; camera motion changes their projection.Particles that appear to remain behind when you walk or turn.

setAttached(true) is the older shortcut for item space; setAttached(false) selects view space. Use setSpace("world") explicitly for world-relative behavior. In world space, callback positions and spawn positions are world coordinates; in item space they are local coordinates. Initial velocity is transformed by the spawn anchor in detached spaces.

Timing, spread & motion

  • Rate is particles per game tick, per active hand. 0.05 is about one particle per second per hand at 20 TPS and 100% density.
  • Lifetime, lifetime spread and animation frame duration use ticks. Lifetime is at least one tick.
  • Spread is independently randomized at each birth from −amount to +amount on each axis. Use it for randomized emitter offsets instead of calling random once at load.
  • Velocity is displacement per tick in the relevant coordinate space. Velocity spread independently randomizes those components.
  • Gravity subtracts from Y velocity each tick. Negative gravity rises. Drag multiplies velocity by 1 − drag, after gravity.

Lifetime curves & easing

Size, opacity, color and billboard rotation support start/end curves and extra keyframes. Progress is normalized from 0 to 1. A key’s easing controls the interval arriving at that key. Set the start/end curve before adding intermediate keys.

Lifetime curve configuration
sparks.setSizeOverLife(0.04, 0, "easeOutCubic")
sparks.addSizeKeyframe(0.2, 0.12, "easeOutBack")
sparks.setOpacityOverLife(0, 0, "linear")
sparks.addOpacityKeyframe(0.1, 1, "easeOutQuad")
sparks.addOpacityKeyframe(0.7, 0.8, "linear")
sparks.setColorOverLife(80, 220, 255, 140, 90, 255, "easeInOutSine")
sparks.setRotationOverLife(0, 180, "linear")

Supported families: linear, step, quad, cubic, quart, quint, sine, expo, circ, back, bounce and elastic. Use the in, out and inOut prefixes; an optional ease prefix and hyphens/underscores are accepted. For example, easeOutCubic starts fast and slows near the end.

A mathematical illustration of the easing curve, rather than a Minecraft rendering preview. Back easing can overshoot.

Textures & frame lists

setType("minecraft:smoke") reads the active resource pack’s assets/minecraft/particles/smoke.json texture list. A custom type can supply the same JSON shape in its own namespace. Each texture entry resolves under textures/particle/.

assets/mypack/particles/spark.json
{
  "textures": [
    "mypack:spark_0",
    "mypack:spark_1",
    "mypack:spark_2"
  ]
}

Store those frames as assets/mypack/textures/particle/spark_0.png and so on. Then call setType("mypack:spark"). Or bypass the frame list with setTexture("mypack:textures/particle/spark.png") for one PNG. Calling setType again clears a direct-texture override.

Animation modeBehavior
lifetimeDistribute the texture-list frames over the particle’s lifetime. Default mode.
loopAdvance and loop using the specified frame duration in ticks.
randomChoose one list entry for each particle and keep it.
firstAlways use the first list entry.

Choose with setAnimation(mode, frameTicks). The duration must be 0.05–12000 ticks, even for modes that do not use it. A frame list may contain at most 256 textures. A missing type or list falls back to a generic appearance and logs a warning; direct textures should always point to an existing PNG.

Visibility, lifecycle & limits

Depth testing defaults on: setDepthTest(false) draws an effect without the normal depth test. setLight(-1) inherits item light; 0–15 forces brightness. World space does not add collision, world lighting simulation, multiplayer visibility or third-person rendering.

stop() stops new births and clears pending bursts; living particles finish naturally. clear() removes an emitter’s living particles and pending work without changing its enabled state. Item changes finish naturally by default; setClearOnItemChange(true) removes them immediately instead. Changing emitter properties affects newly born particles; an existing particle keeps its birth settings unless its callback changes it.

The engine permits up to 256 emitters per session, up to 128 queued burst births per hand, and up to 128 births per hand per tick. Emission rate is 0–64 per tick. Global density and the 0–4096 particle cap still apply to bursts. Use particles.getDroppedCount() to inspect births dropped at the active-particle cap.

Particle recipes & callbacks

#

Burst once when an action happens

Continuous emission defaults to zero. Keep the rate at zero and request a burst from an event, not every render frame. This example emits from a held stick on a swing.

One-shot swing burst
lock interactiveItems = ["minecraft:stick"]
make puff = new ParticleEmitter("minecraft:smoke")
puff.setRate(0)
puff.setPosition(0.5, 1, 0.5)
puff.setSpread(0.12, 0.04, 0.12)
puff.setVelocity(0, 0.018, 0)
puff.setLifetime(24)
puff.setSizeOverLife(0.06, 0.14, "easeOutQuad")
puff.setOpacityOverLife(0.7, 0, "linear")
puff.setSpace("world")

task onItemUpdate takes itemRendered do
    check player.getGamemode() == "spectator" do reply nothing end
    puff.attach(itemRendered)
end

task onSwingHand do
    check player.getGamemode() == "spectator" do reply nothing end
    check player.isHoldingItem("minecraft:stick") do puff.burst(6) end
end

burst(count) queues both channels; births require an attached, active held item. If both hands hold the filtered item, both can emit. Use burstForHand(true,count) for logical main hand or burstForHand(false,count) for off hand. emit() is a one-particle burst. A stopped emitter must be started before queued births can occur.

Ease a rise and set color per particle

There is no setVelocityOverLife function. For an exact fast-to-slow rise, calculate a position curve from the particle’s progress. Put these tasks in the same script as the emitter.

Per-particle eased rise
~ Configure these after declaring puff, before the event tasks.
puff.setVelocity(0, 0, 0)
puff.setOnSpawn("chooseParticleColor")
puff.setOnUpdate("easeParticleRise")

task chooseParticleColor takes particle do
    particle.setColor(80, 200, 255)
    particle.setSize(0.14)
end

task easeParticleRise takes particle do
    make remaining = 1 - particle.getProgress()
    make eased = 1 - remaining * remaining * remaining
    particle.setPosition(particle.getX(), particle.getSpawnY() + 0.5 * eased, particle.getZ())
end

Use setOnSpawn("taskName"), setOnUpdate("taskName") and setOnDeath("taskName"). Each task receives a Particle. Update runs after the engine’s motion/lifetime step while the particle is alive. Death callbacks run for normal tick-driven removals; bulk clear(), reload and global disabling are cleanup operations and do not promise a death callback for every particle.

For pitch-based colors, calculate RGB in the spawn callback from the pitch used for that note. The bundled note_block.vyn includes an example using a normalized 0.5–2.0 pitch and three phase-shifted sine functions.

Fresh random offsets on every birth

Independent birth randomization
puff.setPosition(0.5, 1, 0.5)
puff.setSpread(0.15, 0, 0.15)
puff.setVelocitySpread(0.004, 0.002, 0.004)

Each new particle receives a fresh X/Z offset around the center. Use a spawn callback with random() when you need a non-box distribution. setSeed(integer) makes an emitter’s random sequence repeatable for testing.

Physics library

#

The bundled physics_lib.vyn is an importable spring simulation, rather than a rigid-body or collision engine. It produces angleX and angleZ for the calling script to apply to a model. Movement, changes in camera input and synthetic wind feed the springs.

Movement-driven spring sway
importScript("interactivestuff:interactive_resourcepack", "physics_lib")
lock interactiveItems = ["minecraft:stick"]

task onItemUpdate takes itemRendered do
    check player.getGamemode() == "spectator" do reply nothing end
    runPhysicsEngine(0.01, 0.15, -1.0, 0.3, 0.5, 1.0, 45, 0.1)
    itemRendered.setPivot(0.5, 0.2, 0.5)
    itemRendered.rotate(angleX, 0, angleZ)
end

runPhysicsEngine(stiffness, damping, moveRes, lookRes, vertRes, inertial, maxAngle, windMult)

ParameterMeaning
stiffnessRestoring spring force. Increase it for a quicker return to rest.
dampingResistance to angular velocity. Increase it to settle oscillation sooner.
moveResResponse to changes in horizontal player velocity. A negative value reverses its direction.
lookResResponse to changes in the camera pitch/yaw input values.
vertResResponse to changes in vertical player velocity.
inertialMultiplies movement/look impulses and synthetic wind force. Larger values increase those forces; this is not a physical mass in kilograms.
maxAngleClamp in degrees on both output axes; the boundary partially reflects angular velocity.
windMultStrength of time-varying synthetic wind. Zero disables it; it is not Minecraft weather.

The example’s values are an illustrative starting point, not enforced ranges. The library steps at most once per render frame in each importing script environment. It resets after a rendering gap or significant time interruption and subdivides integration steps. If two items in the same script call it with different settings in one frame, the first call chooses that frame’s parameters.

State & the master switch

The imported library declares its own physics_enabled pack option and resets when that value changes. resetPhysicsEngine() clears angles, velocities and timing state. Importing into separate scripts gives them separate state; the library does not automatically create an independent spring for every model part.

Movement values are already adapted

player.getVelocityX/Y/Z() return motion transformed into the player’s horizontal yaw frame and scaled by approximately one third. The Y getter removes a small resting gravity offset. Treat them as animation inputs, not unscaled world-space blocks per tick. In the library, the Z input is negated before calculating impulses.

Shake versus spring motion

The pack’s Shake intensity scales random jitter in totems, sculk, torches and maces. Physics switches control the spring behavior of frames, paintings, hanging signs and maces. Shake at 0% does not turn off all spring sway; the master physics switch does not suppress unrelated random jitter.

The bundled effect_helpers library also exposes createProgressState(), smoothItemProgress(state,item,target,speed), resetItemProgress(state,item), packToggle(id,category), packShakeOption(), hasMod(id) and isRedstonePowered(). These are pack library tasks, not built-in globals.

Declare resource-pack settings

#

Create a ConfigOption at the top level of a script. The mod registers it under the real owning pack, displays it in Packs and stores the player’s value separately from your declaration. Read its value during callbacks to control an effect.

Toggle, slider & change callback
lock interactiveItems = ["minecraft:stick"]

make enabled = new ConfigOption("stick_sway", "toggle", true)
enabled.setLabel("My stick sway")
enabled.setTooltip("Tilt the held stick while crouching.")
enabled.setCategory("Movement")
enabled.setOnChange("onSwayChanged")

make strength = new ConfigOption("stick_angle", "number", 20)
strength.setLabel("Crouch angle")
strength.setCategory("Movement")
strength.setRange(0, 60, 1)

task onSwayChanged takes option, previous, value do
    ~ Reset your effect state here if it needs a fresh start.
end

task onItemUpdate takes itemRendered do
    check !enabled.getBoolean() do reply nothing end
    check player.getGamemode() == "spectator" do reply nothing end
    check player.isSneaking() do itemRendered.rotateZ(strength.getNumber()) end
end

Available types

TypeDefault valueConfigurationReader
toggletrue / falseLabel, tooltip, category, callbackgetBoolean()
numberFinite numbersetRange(min,max,step)getNumber()
dropdownOne of the choice stringssetChoices(["a","b"])getText()
color"#RRGGBB"Six hexadecimal RGB digitsgetColor() / getRed/Green/Blue()
textStringsetMaxLength(length)getText()
Dropdown, color & text
make style = new ConfigOption("style", "dropdown", "Soft")
style.setLabel("Particle style")
style.setChoices(["Soft", "Sharp", "None"])
style.setCategory("Particles")

make color = new ConfigOption("color", "color", "#50C8FF")
color.setLabel("Particle color")
color.setCategory("Particles")

make caption = new ConfigOption("caption", "text", "Hello")
caption.setLabel("Debug caption")
caption.setMaxLength(80)
caption.setCategory("Debug")

Declaration rules

  • Constructors and schema setters are only valid during script loading. Do not create settings in onTick, a particle callback or a render event.
  • IDs use 1–96 letters, digits, dots, hyphens or underscores. Keep them stable when updating a pack.
  • Duplicate declarations in the same pack must agree on their normalized schema. Reusing an ID with a different type/default/range is an error.
  • Numbers require finite min < max and 0 < step ≤ max − min. Values snap to the step and clamp to the range. The default range is 0–1 with step 0.01, so set a range for values such as 20.
  • Dropdowns require 1–64 choices; the default must be a listed choice. Text defaults to a maximum of 256 characters; the allowed maximum is 1–4096.
  • Labels allow 160 characters, tooltips 1024, category names 96, callback task names 160. Strings may be literal text or resolvable language keys.

Apply, callbacks & persistence

setOnChange("taskName") registers a task called with (option, previous, value) when a committed value changes. Editing a draft or cancelling does not call it. Use callbacks to stop emitters or reset progress, while ordinary rendering callbacks read the current saved value.

Player values are saved globally per resource pack in config/interactivestuff.json. Reloading a pack rebuilds declarations without deliberately overwriting its saved choices. Renaming the pack’s actual folder/ZIP identity can give it a different saved-settings namespace.

Localized labels

Use a translation key in setLabel, setTooltip or setCategory, and provide that key in assets/<namespace>/lang/en_us.json. translateText is the preferred helper when you need the resolved string inside Vyn.

Debugging & performance

#

Find your model anchors

Enable Debug → Pivot markers and call setPivot in the script. Purple/violet diamonds identify main/off-hand pivots. Enable Emitter markers for green/orange crosses at emitter origins. The symbols distinguish them even when their colors are hard to tell apart.

Markers through items shows markers over the held model. Turn it off to test whether a point is actually hidden by the model’s surface. This is a diagnostic visibility option; it does not alter particle depth settings. Pivot markers do not require particles to be enabled.

Pack-controlled text

The bundled pack supplies Vyn debug text and particle counters under its own Debug category. A custom pack can declare independent options and use the explicit visibility form:

Pack-owned debug text
make showDebug = new ConfigOption("debug_mode", "toggle", false)
showDebug.setLabel("Debug text")
showDebug.setCategory("Debug")

task onTick do
    debugText("Particles: " + particles.getCount(), showDebug.getBoolean())
end

Keep rendering light

  • Declare settings and emitters once at load, rather than every frame.
  • Use interactiveItems to avoid dispatching a script for irrelevant items.
  • Keep broad block searches out of render loops where possible. hasNearbyBlock and getNearestBlock cache queries by tick and player block position; nearest search accepts a radius up to 32.
  • Use low rates and reasonable lifetimes. At steady state, a rough budget is rate × lifetime × active hands × density multiplier.
  • Use built-in lifetime curves instead of per-particle update tasks when a curve is sufficient.
  • Avoid calling burst() each render frame for a one-shot event.
  • Check both physical hands and different GUI/FOV/resource-pack combinations before distributing a pack.

Script failures include the owning pack/script and phase in logs/latest.log. Reload resources after correcting a failed script. An error can disable just the failing script, so a partly working pack can still have a load or callback failure.

ItemModel

#

Model rendering, stack information, transforms, tint, lighting, parts and targeting.

Use the render callback’s itemRendered, or create an extra model with new ItemModel(itemId).

61 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
new ItemModel(String itemId)ItemModelCreate and enqueue an additional render model from an item registry ID. Use in the render event. An invalid ID resolves to air.
getName()StringReturns the registry name of the item.
isDamaged()booleanReturns whether the item is currently damaged.
isDamageable()booleanReturns whether the item can take damage.
isEnchantable()booleanReturns whether the item can be enchanted.
isExclusive()booleanReturns whether this model is in exclusive (quad-selection) mode.
isStackable()booleanReturns whether the item can stack.
getCount()intReturns the current stack count.
getMaxCount()intReturns the maximum stack size.
getBobbingAnimationTime()intReturns the bobbing animation time of the item.
getLight()intRead the packed light override, inherit it from a parent, or return −1 for the default.
getGlint()intReturns the current glint override value, or -1 if not set. Inherits from parent if unset.
getRotationX()doubleRead the quaternion’s X component, or 0 before rotation. This is not an Euler angle in degrees.
getRotationY()doubleRead the quaternion’s Y component, or 0 before rotation. This is not an Euler angle in degrees.
getRotationZ()doubleRead the quaternion’s Z component, or 0 before rotation. This is not an Euler angle in degrees.
isMainHand()booleanWhether this model’s render context is the logical main hand.
isOffHand()booleanWhether this model’s render context is the logical off hand.
isLeftHand()booleanWhether this model is rendered in the physical left hand, including left-handed player settings.
isRightHand()booleanWhether this model is rendered in the physical right hand.
isFirstPerson()booleanWhether the model display context is first-person left or right hand.
getDisplayContextName()StringRead the Minecraft item display-context enum name.
setItemModel(String model)nothingReplace this rendered stack’s visual model through the version-specific item-model bridge. This does not change the inventory stack or enqueue a duplicate.
setBobbingTime(int bobbingTime)nothingSets the bobbing animation time.
setDataComponent(String key, Object value)nothingApply a component value through the target’s component bridge. On 1.20.1 only its mapped legacy keys or nbt:TagName are supported; unsupported modern components are rejected.
getDataComponent(String key)StringRead a serialized data-component value through the target’s component/legacy bridge.
hasDataComponent(String key)booleanReturns whether the item has the specified data component.
getDataComponentIds()List<String>Returns a list of all data component keys on this item.
getDataComponents()List<Map<String, String>>Returns all data components as a list of key-value maps.
removeDataComponent(String key)nothingRemoves a data component by key.
setQuads(int start, int end)nothingSet the affected half-open quad range [start,end). Unselected geometry remains visible unless exclusive selection is used. End 0 means the whole layer; negative bounds clamp to zero.
setColor(int r, int g, int b)nothingSet this model’s RGB multiplier using 0–255 channels.
setOpacity(double opacity)nothingSet model opacity, clamped to 0–1.
setLight(int light)nothingForce brightness 0–15, or −1 to inherit/default. The getter returns the packed light representation, not necessarily this original level.
setGlint(int glint)nothingSet glint override, clamped to −1..2: −1 default, 0 off, 1–2 renderer glint modes.
setTint(int index, int color)nothingSet a packed RGB/ARGB tint for one index; the one-argument overload sets the fallback index −1. The fallback index −1 can also affect untinted faces; a specific index preserves other faces. Alpha is made opaque when no alpha bits are supplied.
setTint(int color)nothingSet a packed RGB/ARGB tint for one index; the one-argument overload sets the fallback index −1. The fallback index −1 can also affect untinted faces; a specific index preserves other faces. Alpha is made opaque when no alpha bits are supplied.
setQuadColor(int color)nothingSet the selected-quad color override. An RGB value without alpha bits becomes opaque.
translate(double dx, double dy, double dz)nothingMoves the model by the given offset.
translateX(double dx)nothingAdd a translation in render units on the indicated axis/axes.
translateY(double dy)nothingAdd a translation in render units on the indicated axis/axes.
translateZ(double dz)nothingAdd a translation in render units on the indicated axis/axes.
rotateAxis(double angle, double axisX, double axisY, double axisZ)nothingRotates the model by angle degrees around an arbitrary axis vector.
lookAt(Position position)nothingAim at the center of a block position. The grouped overload shares smoothed orientation for cooperating parts: nonblank group ≤80 characters; finite speed and weight 0–1.
lookAt(Position position, String group, double speed, double weight)nothingAim at the center of a block position. The grouped overload shares smoothed orientation for cooperating parts: nonblank group ≤80 characters; finite speed and weight 0–1.
setLookAtClearance(double surface, double minX, double minY, double minZ, double maxX, double maxY, double maxZ)nothingProvide a surface height and local min/max bounds for clearance during model targeting. This does not enable world collision physics.
rotate(double dx, double dy, double dz)nothingRotates the model around all three axes (in degrees).
rotateX(double angle)nothingCompose a rotation in degrees on the indicated axis/axes. Applied around the current pivot.
rotateY(double angle)nothingCompose a rotation in degrees on the indicated axis/axes. Applied around the current pivot.
rotateZ(double angle)nothingCompose a rotation in degrees on the indicated axis/axes. Applied around the current pivot.
scale(double sx, double sy, double sz)nothingMultiplies the model's scale on all axes.
scaleX(double sx)nothingMultiply scale on the indicated axis/axes.
scaleY(double sy)nothingMultiply scale on the indicated axis/axes.
scaleZ(double sz)nothingMultiply scale on the indicated axis/axes.
shear(double xy, double xz, double yx, double yz, double zx, double zy)nothingApplies a shear transformation using the six shear components.
smooth(double current, double target, double speed)doubleReturn current + (target − current) × (1 − (1 − speed)^getDelta()). Use a speed in 0–1 for normal frame-independent interpolation; retain the result yourself.
setPivot(double x, double y, double z)nothingSet the local transform pivot in render units. Default is (0.5,0.5,0.5); diagnostic markers are recorded when enabled.
setParent(ItemModel parent)nothingAssign a parent transform; self-parenting and cycles are rejected.
select(int quadStart, int quadEnd)nothingSet the half-open quad range and enable exclusive selection. End 0 means the full layer.
copy()ItemModelCopy the current working model and transforms, preserve its hand/context and enqueue it as an additional render model.
detach()nothingRemove this model’s parent relationship.
hasParent()booleanReturns whether this model currently has a parent.

Player

#

Local player state, movement, held stacks, block queries and client-local sound.

Available globally as player. Nullable block/item results must be checked against nothing.

99 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
getSteppingBlock()BlockReturns the block the player is currently standing on.
getNearbyBlocks(int blockRadius)List<Block>Return a list of non-air blocks in a cubic radius around the player. This scans the cube; keep the radius small and avoid repeating it every frame.
getTargetBlock()BlockReturn the targeted block, or nothing when the crosshair is not targeting a block. Check before calling methods on it.
getGamemode()StringReturns the player's current gamemode as a string.
getPosition()PositionReturn the player’s integer block position, or nothing without a player. Use eye-position getters for fractional eye coordinates.
getWorld()WorldReturns the current world the player is in.
getActiveItem()ItemModelReturns the item currently being used (e.g. while eating or drawing a bow).
getMainHandItem()ItemModelReturns the item in the player's main hand.
getOffHandItem()ItemModelReturns the item in the player's off hand.
getBlockingItem()ItemModelReturn the version-adapted blocking item, or nothing if absent. It is not guaranteed to exist in every state.
getWeaponItem()ItemModelReturn the version-adapted weapon item, or nothing without a living player.
getPickBlockItem()ItemModelReturn the living entity’s pick-result item, not the item currently targeted by the crosshair.
getHealth()doubleReturns the player's current health.
getFoodLevel()doubleReturns the player's current food/hunger level (0–20).
getSaturationLevel()doubleReturns the player's current saturation level.
getNauseaIntensity()floatReturns the current nausea effect intensity.
getExperienceLevel()intReturns the player's current experience level.
getItemUseTime()intReturns how many ticks the player has been using their current item.
getItemUseTimeLeft()intReturns how many ticks remain before the player finishes using their current item.
getArmor()intReturns the player's current total armor value.
getEyePosX()doubleReturns the X coordinate of the player's eye position.
getEyePosY()doubleReturns the Y coordinate of the player's eye position.
getEyePosZ()doubleReturns the Z coordinate of the player's eye position.
getHeadYaw()doubleReturns the player's current head yaw (horizontal rotation in degrees).
getDamageTiltYaw()doubleReturns the yaw direction of the most recent damage tilt effect.
getStepHeight()doubleReturns the player's step height (how tall a block they can step up without jumping).
getExperienceProgress()doubleReturns the player's progress toward the next experience level (0.0–1.0).
getVelocityX()doubleReturn yaw-relative X motion scaled by 0.3333, suitable as an animation input. It is not raw world-axis velocity.
getVelocityY()doubleReturn vertical motion scaled by 0.3333, with the small resting gravity offset suppressed.
getVelocityZ()doubleReturn yaw-relative Z motion scaled by 0.3333. The physics library negates it before using it.
hasNearbyBlock(String blockId, int radius)booleanTest for a matching block in a cubic radius. Nonnegative radius; cached for the current tick and player block position.
getNearestBlock(String blockId, int radius)BlockReturn the nearest matching block center within a cubic radius 0–32, or nothing. Queries are cached for the current tick and player block position.
getCameraPitchVelocity()floatReturns the rate of change of the camera's pitch (vertical look speed).
getCameraYawVelocity()floatReturns the rate of change of the camera's yaw (horizontal look speed).
isSwimming()booleanReturns whether the player is swimming.
isSprinting()booleanReturns whether the player is sprinting.
isInLava()booleanReturns whether the player is in lava.
isInFluid()booleanReturns whether the player is in any fluid.
isOnFire()booleanReturns whether the player is on fire.
isTouchingWater()booleanReturns whether the player is touching water.
isFlyingVehicle()booleanReturns whether the player is in a flying vehicle.
isClimbing()booleanReturns whether the player is climbing (e.g. ladder or vine).
isDescending()booleanReturns whether the player is descending (e.g. on a ladder).
isCrawling()booleanReturns whether the player is crawling.
isBlocking()booleanReturns whether the player is blocking with a shield.
isRiding()booleanReturns whether the player is riding an entity.
isPushable()booleanReturns whether the player can be pushed by other entities.
isPushedByFluids()booleanReturns whether the player is pushed by fluid currents.
isInvulnerable()booleanReturns whether the player is currently invulnerable.
isUsingItem()booleanReturns whether the player is currently using an item.
isUsingRiptide()booleanReturns whether the player is currently using a riptide trident.
isUsingSpyglass()booleanReturns whether the player is currently using a spyglass.
isFrozen()booleanReturns whether the player is frozen (in powder snow).
isGlowing()booleanReturns whether the player has the glowing effect (server-side).
isGlowingLocal()booleanReturns whether the player is glowing locally (client-side).
isAtCloudHeight()booleanReturns whether the player is at cloud height.
isAutoJumpEnabled()booleanReturns whether auto-jump is enabled.
isHoldingOntoLadder()booleanReturns whether the player is holding onto a ladder.
isLimitedCraftingEnabled()booleanReturns whether limited crafting is enabled for this player.
isInsideWall()booleanReturns whether the player is clipping inside a block.
isJumping()booleanReturns whether the player is currently jumping.
isOnRail()booleanReturns whether the player is on a rail.
isFireImmune()booleanReturns whether the player is immune to fire damage.
isFirstPerson()booleanReturns whether the player is currently in first-person perspective.
isSubmergedInWater()booleanReturns whether the player is fully submerged in water.
isSneaking()booleanReturns whether the player is sneaking.
isOnGround()booleanReturns whether the player is on the ground.
showsDeathScreen()booleanReturns whether the player would show the death screen upon dying.
isHorizontalCollision()booleanReturns whether the player is colliding with a block horizontally.
isInSneakingPose()booleanReturns whether the player is in a sneaking pose (visual).
shouldSlowDown()booleanReturns whether the player's movement should be slowed (e.g. in a cobweb or powder snow).
isRidingJumpable()booleanReturns whether the player is riding a mount that can jump.
getMountJumpStrength()floatReturns the jump strength of the player's current mount.
isCamera()booleanReturns whether the player is the current camera entity.
getPermissionLevel()intReturns the player's current operator permission level (0–4).
isMainPlayer()booleanReturns whether this is the main/local player.
getMoodPercentage()floatReturns the player's current mood percentage (used for cave ambience sounds).
canBreatheInWater()booleanReturns whether the player can breathe underwater (e.g. via a helmet enchantment or effect).
hasLandedInFluid()booleanReturns whether the player has just landed in a fluid.
isBaby()booleanReturns whether the player is in baby form.
getScaleFactor()floatReturns the player's scale factor (used for size-affecting effects).
getScale()floatReturns the player's current scale.
shouldSwimInFluids()booleanReturns whether the player should swim when in fluids.
canTakeDamage()booleanReturns whether the player is currently able to take damage.
isPartOfGame()booleanReturn the entity visibility-eligibility predicate used by this adapter (canBeSeenByAnyone), rather than a network connection check.
isDead()booleanReturns whether the player is dead.
getLuck()floatReturns the player's current luck attribute value.
getAbsorptionAmount()floatReturns the player's current absorption (bonus health) amount.
getMaxAbsorption()floatReturns the player's maximum absorption amount.
getStuckArrowCount()intReturns the number of arrows currently stuck in the player.
getStingerCount()intReturns the number of bee stingers currently stuck in the player.
hasNoDrag()booleanReturns whether the player has no drag applied (no velocity dampening from fluids or air).
isMainHand(ItemModel item)booleanCheck whether the supplied model wraps the exact source stack in the logical main hand.
isOffHand(ItemModel item)booleanCheck whether the supplied model wraps the exact source stack in the logical off hand.
isHoldingItem(String itemId)booleanTest whether either hand holds the namespaced item ID.
playSound(String soundId, double volume, double pitch)nothingPlay a local player sound from a registered sound ID and volume/pitch, or a Sound wrapper. Sound events are client-side.
playSound(Sound sound)nothingPlay a local player sound from a registered sound ID and volume/pitch, or a Sound wrapper. Sound events are client-side.
playSoundWorld(Position position, String soundId, double volume, double pitch)nothingPlay a local sound at an integer Position in the client world. This does not send a server/world sound event to other players.
playSoundWorld(Position position, Sound sound)nothingPlay a local sound at an integer Position in the client world. This does not send a server/world sound event to other players.

World

#

Blocks, biome colors, dimensions and world time.

Available globally as world, or through player.getWorld().

15 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
getBlock(int x, int y, int z)BlockReturn a block wrapper at integer coordinates, or nothing when there is no world.
getDimension()StringReturn the dimension’s namespaced registry ID, or an empty string without a world.
isDay()booleanReturns whether it is currently daytime.
getTimeOfDay()longReturns the time of day in ticks (0–24000).
getTime()longReturns the total world time elapsed in ticks (ever-increasing).
calculateDistanceBetweenPositions(Position pos1, Position pos2)longDelegate to Position.getDistanceTo: Euclidean distance truncated to an integer.
getBiomeAt(int x, int y, int z)StringReturns the biome ID at the given Position.
getBiomeColorAt(int x, int y, int z)intCompatibility alias for the foliage color resolver, not a universal color for every block type.
getBiomeAt(Position position)StringReturns the biome ID at the given Position.
getGrassColor(Position position)intResolve a packed grass color at the position. Use with tint-bearing grass faces.
getDryFoliageColor(Position position)intResolve the target version’s adapted dry-foliage color at the position.
getFoliageColor(Position position)intResolve a packed foliage color at the position.
getWaterColor(Position position)intResolve a packed biome-water color at the position.
getBiomeColorAt(Position position)intCompatibility alias for the foliage color resolver, not a universal color for every block type.
toString()StringReturns a string summary of the world state.

Block

#

Block registry ID, tags, position, instrument and state predicates.

Obtained from player/world queries; no Vyn constructor is exposed.

13 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
getName()StringReturns the item/block name as a string.
hasBlockTag(String tagID)booleanMatches block tag names using a case-insensitive substring test. Pass a full namespaced tag ID for a precise intended match.
getPosition()PositionReturns the block's world position.
getBlockLightLevel()intReturns the block light level at this block's position (0–15).
getSkyLightLevel()intReturns the sky light level at this block's position (0–15).
getInstrument()StringReturns the note block instrument sound ID associated with this block.
isSolid()booleanReports whether the state acts as a redstone conductor at this position; it is not a general collision-shape test.
isAir()booleanReturns whether this block is air.
isBurnable()booleanReports whether the state can be ignited by lava.
isTransparent()booleanReports the target version’s skylight-propagation test.
isOpaque()booleanReports whether the state can occlude other faces.
isOpaqueFullCube()booleanReports the target version’s solid-render test.
toString()StringReturns the full block state as a string, including block properties.

Position

#

Mutable integer positions and integer-truncated distance.

Construct with new Position(x,y,z). Coordinates are integer block coordinates.

10 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
new Position(int x, int y, int z)PositionCreate a mutable integer block-position wrapper. Particle callback positions are separate double-valued coordinates.
getDistanceTo(Position other)longCompute Euclidean distance and truncate to a long/integer; this is not squared distance or a fractional result.
getDistanceTo(int x, int y, int z)longCompute Euclidean distance and truncate to a long/integer; this is not squared distance or a fractional result.
getX()intReturns the X coordinate.
getY()intReturns the Y coordinate.
getZ()intReturns the Z coordinate.
setX(int x)nothingSets the X coordinate.
setY(int y)nothingSets the Y coordinate.
setZ(int z)nothingSets the Z coordinate.
toString()StringReturns a string representation of this position.

Sound

#

A sound-data wrapper. Changing the wrapper is not a documented cancellation/replacement of an already dispatched game sound.

Construct with new Sound(soundId,volume,pitch,position), or inspect onPlaySound’s argument.

10 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
new Sound(String soundId, double volume, double pitch, Position position)SoundCreates a new Sound object.
getPosition()PositionReturns the world position this sound is associated with.
getName()StringReturns the namespaced sound ID.
getVolume()doubleReturns the volume of the sound.
getPitch()doubleReturns the pitch of the sound.
setSoundId(String soundId)nothingSets the namespaced sound ID.
setVolume(double volume)nothingSets the volume.
setPitch(double pitch)nothingSets the pitch.
setPosition(Position position)nothingSets the world position.
toString()StringReturns a string representation of this sound.

ParticleEmitter

#

Persistent first-person emission with per-hand channels, motion and lifetime controls.

Construct once with new ParticleEmitter(typeId), then attach in onItemUpdate.

42 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
new ParticleEmitter(String type)ParticleEmitterCreate and register one persistent emitter from a namespaced particle-appearance ID. Declare once at script load.
attach(ItemModel item)nothingBind to the current ItemModel’s final anchor and logical hand for the current rendering frame. Required for births.
emit()nothingQueue a burst of one for each hand channel; active attached channels can emit.
burst(int count)nothingQueue up to 128 pending births per hand. Global density and cap apply; a stopped emitter must be started.
burstForHand(boolean mainHand, int count)nothingQueue a burst for one logical hand: true main hand, false off hand. Pending count clamps to 128.
start()nothingEnable new emissions. Does not reset a rate or clear old particles.
stop()nothingDisable new emissions and clear pending bursts. Existing particles finish naturally.
clear()nothingRemove this emitter’s living particles and pending/fractional births. The enabled state remains unchanged.
getCount()intCount living particles in both hand channels of this emitter.
setSeed(int seed)nothingSeed this emitter’s pseudorandom generator using a Vyn-compatible integer.
setType(String type)nothingSet a namespaced appearance ID read from particles/<path>.json; clears a direct texture override.
setTexture(String texture)nothingUse a direct namespaced PNG path such as mypack:textures/particle/spark.png.
setRate(double particlesPerTick)nothingSet 0–64 particles per tick per active hand. Default 0; 0.05 is one per second at 20 TPS and full density.
setLifetime(double ticks)nothingSet lifetime 1–12000 ticks. Default 20.
setLifetimeSpread(double ticks)nothingSet uniform ± lifetime randomness, 0–12000 ticks. Resulting lifetime is at least one tick. Default 0.
setPosition(double x, double y, double z)nothingSet the local spawn center in render units. Default (0,0,0).
setSpread(double x, double y, double z)nothingSet fresh uniform ± birth-position randomness per axis, each 0–100. Default (0,0,0).
setVelocity(double x, double y, double z)nothingSet initial displacement per tick along each axis. Default (0,0,0).
setVelocitySpread(double x, double y, double z)nothingSet fresh uniform ± initial-velocity randomness per axis, each 0–100. Default zero.
setGravity(double gravity)nothingSet acceleration subtracted from Y velocity each tick. Negative values rise. Default 0.
setDrag(double drag)nothingSet velocity damping 0–1; each tick multiplies velocity by 1 − drag. Default 0.
setAttached(boolean attached)nothingCompatibility shortcut: true selects item space, false selects view space. Does not select world space.
setSpace(String space)nothingChoose exactly item, view or world. Default view. World coordinates still render only through the first-person pass.
setDepthTest(boolean depth)nothingEnable/disable particle depth testing. Default true. Separate from diagnostic-marker visibility.
setClearOnItemChange(boolean clear)nothingChoose immediate removal on item changes (true), or natural completion (false, default).
setLight(int light)nothingSet −1 to inherit item brightness (default), or a forced level 0–15.
setAnimation(String mode, double frameTicks)nothingChoose lifetime (default), loop, random or first. Frame duration must be 0.05–12000 ticks; default 1.
setSize(double size)nothingSet constant size 0–100. Default 0.06.
setOpacity(double opacity)nothingSet constant opacity 0–1. Default 1.
setRotation(double degrees)nothingSet constant billboard rotation in degrees. Default 0.
setSizeOverLife(double start, double end, String easing)nothingSet start/end size (0–100) with easing over normalized lifetime.
setOpacityOverLife(double start, double end, String easing)nothingSet start/end opacity (0–1) with easing over normalized lifetime.
setRotationOverLife(double start, double end, String easing)nothingSet start/end billboard rotation in degrees with easing over normalized lifetime.
addSizeKeyframe(double progress, double size, String easing)nothingAdd/replace a size key at progress 0–1, size 0–100. Easing controls the interval arriving at this key.
addOpacityKeyframe(double progress, double opacity, String easing)nothingAdd/replace an opacity key at progress 0–1, opacity 0–1. Easing controls the incoming interval.
addRotationKeyframe(double progress, double degrees, String easing)nothingAdd/replace a degree-rotation key at progress 0–1. Easing controls the incoming interval.
setColor(int red, int green, int blue)nothingSet constant RGB channels, each integer 0–255. Default white.
setColorOverLife(int r1, int g1, int b1, int r2, int g2, int b2, String easing)nothingInterpolate start/end RGB channels 0–255 with the supplied easing.
addColorKeyframe(double progress, int red, int green, int blue, String easing)nothingAdd/replace an RGB key at progress 0–1; channels 0–255. Easing controls the incoming interval.
setOnSpawn(String task)nothingName a task receiving each newly born Particle; an empty string disables the callback.
setOnUpdate(String task)nothingName a task receiving each live Particle after its tick motion step.
setOnDeath(String task)nothingName a task for tick-driven particle removals. Bulk clear/reload cleanup does not promise individual death calls.

Particle

#

Per-particle state and overrides, in the particle’s selected simulation space.

Received in emitter spawn/update/death callbacks. No Vyn constructor is exposed.

19 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
getAge()doubleReturn current age in ticks.
getLifetime()doubleReturn this birth’s randomized lifetime in ticks.
getProgress()doubleReturn age/lifetime capped at 1.
getX()doubleRead current X in this particle’s simulation space.
getY()doubleRead current Y in this particle’s simulation space.
getZ()doubleRead current Z in this particle’s simulation space.
getSpawnX()doubleRead birth X in simulation space; it remains unchanged after setPosition calls.
getSpawnY()doubleRead birth Y in simulation space; it remains unchanged after setPosition calls.
getSpawnZ()doubleRead birth Z in simulation space; it remains unchanged after setPosition calls.
isAlive()booleanWhether the particle has not been killed or expired.
isMainHand()booleanWhether this particle was born in the logical main-hand channel.
isOffHand()booleanWhether this particle was born in the logical off-hand channel.
kill()nothingMark this particle dead for removal.
setPosition(double x, double y, double z)nothingSet current position in simulation space. Does not change recorded spawn coordinates.
setVelocity(double x, double y, double z)nothingSet per-tick motion for this particle only.
setOpacity(double opacity)nothingReplace this particle’s opacity curve with a constant, clamped to 0–1.
setSize(double size)nothingReplace this particle’s size curve with a nonnegative constant.
setRotation(double degrees)nothingReplace this particle’s billboard-rotation curve with constant degrees.
setColor(int red, int green, int blue)nothingReplace this particle’s color curves with constant RGB channels clamped to 0–255.

Particles

#

Counts and clearing across this client session’s emitters.

Available globally as particles.

4 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
getCount()intReturn the number of living particles across all registered emitters and hands.
getEmitterCount()intReturn the number of currently registered session emitters.
getDroppedCount()longReturn cumulative births dropped by the active-particle cap during this session. Not a count of every disabled or rejected request.
clear()nothingClear particles and queued work across all emitters. Emitter registrations and enabled states remain; session reset handles those separately.

ConfigOption

#

Pack-owned settings declarations and player-owned persisted values.

Construct and configure at script load. Read values during callbacks.

16 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
new ConfigOption(String id, String type, Object defaultValue)ConfigOptionDeclare a player-facing setting while the script loads. Types: toggle, number, dropdown, color, text. The owning resource pack supplies the saved-value namespace.
setLabel(String value)nothingSet literal label text or a translation key, at most 160 characters. Loading only.
setTooltip(String value)nothingSet literal help text or a translation key, at most 1024 characters. Loading only.
setCategory(String value)nothingGroup the option inside its pack. Literal text or translation key, at most 96 characters. Loading only.
setOnChange(String task)nothingName a task receiving (option, previous, value) after a committed change; at most 160 characters. Loading only.
setRange(double min, double max, double increment)nothingFor numbers, set finite min < max and 0 < increment ≤ max − min. Values snap to the increment and clamp. Default range 0–1, step 0.01.
setChoices(List<String> values)nothingSet 1–64 dropdown strings (each up to 160 characters); duplicates are removed. The default must be among the choices.
setMaxLength(int length)nothingSet text maximum to 1–4096 characters. Default 256. Loading only.
getValue()ObjectRead the current saved, normalized value using its declared type.
getBoolean()booleanRead a toggle. Use only with type toggle.
getNumber()doubleRead a numeric option. Use only with type number.
getText()StringRead a text or dropdown string.
getColor()StringRead a color string normalized to uppercase #RRGGBB.
getRed()intRead the red color channel, 0–255.
getGreen()intRead the green color channel, 0–255.
getBlue()intRead the blue color channel, 0–255.

InteractiveStuffConfig

#

Global engine settings and legacy pack-accessor compatibility.

Available globally as config; use ConfigOption for new pack-specific settings.

14 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
isParticlesEnabled()booleanRead the global particle permission.
getParticleLimit()intRead the global active-particle cap.
getParticleDensity()intRead the global emission percentage (normally 0–200).
isParticleDebugCountersEnabled()booleanRead the current calling pack’s debug_counters choice; this is not a global marker switch.
isResourcePackDebugModeEnabled ()booleanRead the current calling pack’s debug_mode choice; this controls the one-argument debugText helper.
isInteractiveHitsEnabled()booleanReturns whether the interactive hits feature is enabled.
isSculkSensorFeatureEnabled()Legacy booleanLegacy accessor for the calling pack’s animate_sculk_sensor setting, falling back to sculk_sensors. Prefer a ConfigOption.
getHitCooldownTicks()intReturns the cooldown duration (in ticks) between hits.
isTextureChangesEnabled()Legacy booleanLegacy accessor for the calling pack’s texture_changes flag. Prefer individual ConfigOption controls.
isNoteBlockCrouchFeatureEnabled()Legacy booleanLegacy accessor for the pack’s note_block_sounds setting, falling back to note_block_crouch. Prefer ConfigOption.
getCooldownTicks()intAlias of the global interactive-hit cooldown getter.
isSpecializedNoteblockHitsEnabled()Legacy booleanLegacy accessor for note_block_sounds, falling back to note_block_hits. Prefer ConfigOption.
isResourcePackMatrixEditingEnabled()booleanReturns whether resource pack matrix editing is enabled.
isResourcePackColorChangingEnabled()booleanReturns whether resource pack color changing is enabled.

ModLoader

#

Installed mod IDs, selected resource-pack IDs and game-version string.

Available globally as modLoader.

3 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
isModLoaded(String modid)booleanCheck whether Fabric reports a mod ID installed. This does not tell you whether that mod’s in-game feature is currently enabled.
isResourcePackLoaded(String packName)booleanCompare an exact selected pack ID, case-insensitively. Use the real ID such as file/MyPack.zip, not only the display name.
getRawGameVersion()StringReturn the game-version string through the compatibility adapter. Despite the method name, this no longer requires Fabric Loader’s getRawGameVersion API.

Key

#

Legacy translation helper.

Available globally as key. Prefer translateText for new display text.

1 exposed signatures. Return types retain their source names: int/long/float/double are numeric values; void is shown as nothing.

Signature / returnBehavior
getTranslatedKey(String key)StringLegacy translation helper returning the component’s toString representation. Prefer the global translateText(key, ...arguments) for displayed text.

Model & texture reference

#

Custom model IDs use interactivestuff:<name> in the bundled scripts. Models are baked resources: their first-person display transforms, tint indices and atlas sources matter as much as the script transforms.

Bundled item model names

interactivestuff:acacia_sapling_leaves, interactivestuff:acacia_sapling_wood, interactivestuff:book_left, interactivestuff:book_right, interactivestuff:bucket, interactivestuff:chest_base, interactivestuff:chest_top, interactivestuff:copper_chest_base, interactivestuff:copper_chest_top, interactivestuff:dark_oak_sapling_leaves, interactivestuff:dark_oak_sapling_wood, interactivestuff:ender_chest_base, interactivestuff:ender_chest_top, interactivestuff:exposed_copper_chest_base, interactivestuff:exposed_copper_chest_top, interactivestuff:jungle_sapling_leaves, interactivestuff:jungle_sapling_wood, interactivestuff:oak_sapling_leaves, interactivestuff:oak_sapling_wood, interactivestuff:oxidized_copper_chest_base, interactivestuff:oxidized_copper_chest_top, interactivestuff:piston_base, interactivestuff:piston_top, interactivestuff:redstone_torch_off, interactivestuff:spruce_sapling_leaves, interactivestuff:spruce_sapling_wood, interactivestuff:sticky_piston_top, interactivestuff:torch_off, interactivestuff:trapped_chest_base, interactivestuff:trapped_chest_top, interactivestuff:water_of_the_bucket, interactivestuff:weathered_copper_chest_base, interactivestuff:weathered_copper_chest_top

Edited item textures retained by the pack

These are effect-specific layers rather than exact vanilla copies. An overriding pack can replace them in the interactivestuff namespace.

acacia_sapling_leaves.png, acacia_sapling_wood.png, bucket.png, dark_oak_sapling_leaves.png, dark_oak_sapling_wood.png, jungle_sapling_leaves.png, jungle_sapling_wood.png, oak_sapling_leaves.png, oak_sapling_wood.png, spruce_sapling_leaves.png, spruce_sapling_wood.png, torch_off.png, water_of_the_bucket.png

Native chest texture aliases

Model sprite aliasImage supplied by Minecraft / active pack
interactivestuff:vanilla/chest/normalminecraft:textures/entity/chest/normal.png
interactivestuff:vanilla/chest/trappedminecraft:textures/entity/chest/trapped.png
interactivestuff:vanilla/chest/enderminecraft:textures/entity/chest/ender.png
interactivestuff:vanilla/chest/copperminecraft:textures/entity/chest/copper.png
interactivestuff:vanilla/chest/copper_exposedminecraft:textures/entity/chest/copper_exposed.png
interactivestuff:vanilla/chest/copper_weatheredminecraft:textures/entity/chest/copper_weathered.png
interactivestuff:vanilla/chest/copper_oxidizedminecraft:textures/entity/chest/copper_oxidized.png

The aliases are registered by assets/minecraft/atlases/blocks.json. 1.20.1, 1.21.1 and 1.21.5 register only normal/trapped/ender aliases and give dormant copper part models a normal-chest fallback, because those releases have no native copper chest assets. There is no active Christmas chest model in the current pack.

Bundled script browser

#

These are the exact seventeen Vyn files bundled in the current shared source. Expand a file to read or copy it. They document real implementation details and are not seventeen independent templates to paste together. The two helper libraries are importable; files with event tasks are active scripts.

chests.vyn49 lines
chests.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make animationOption = packToggle("animate_chests", "animation")
animationOption.setOnChange("onAnimationChanged")

lock interactiveItems = ["minecraft:chest", "minecraft:ender_chest", "minecraft:trapped_chest", "minecraft:copper_chest", "minecraft:waxed_copper_chest", "minecraft:exposed_copper_chest", "minecraft:waxed_exposed_copper_chest", "minecraft:weathered_copper_chest", "minecraft:waxed_weathered_copper_chest", "minecraft:oxidized_copper_chest", "minecraft:waxed_oxidized_copper_chest"]

lock chestVariants = new Map
chestVariants.put("minecraft:chest", "chest")
chestVariants.put("minecraft:ender_chest", "ender_chest")
chestVariants.put("minecraft:trapped_chest", "trapped_chest")
chestVariants.put("minecraft:copper_chest", "copper_chest")
chestVariants.put("minecraft:waxed_copper_chest", "copper_chest")
chestVariants.put("minecraft:exposed_copper_chest", "exposed_copper_chest")
chestVariants.put("minecraft:waxed_exposed_copper_chest", "exposed_copper_chest")
chestVariants.put("minecraft:weathered_copper_chest", "weathered_copper_chest")
chestVariants.put("minecraft:waxed_weathered_copper_chest", "weathered_copper_chest")
chestVariants.put("minecraft:oxidized_copper_chest", "oxidized_copper_chest")
chestVariants.put("minecraft:waxed_oxidized_copper_chest", "oxidized_copper_chest")

make openingState = createProgressState()

task onAnimationChanged takes option, previous, value do
    make openingState = createProgressState()
end

task onItemUpdate takes itemRendered do
    check !animationOption.getBoolean() do reply nothing end
    make variant = chestVariants.get(itemRendered.getName())
    check variant == nothing do reply nothing end

    itemRendered.setItemModel("interactivestuff:" + variant + "_base")
    make chestTop = new ItemModel("minecraft:" + variant)
    chestTop.setItemModel("interactivestuff:" + variant + "_top")
    chestTop.setParent(itemRendered)
    make openProgress = smoothItemProgress(openingState, itemRendered, getFallTarget(), 0.2)

    check itemRendered.isRightHand() do
        chestTop.setPivot(0.12, 0.17, -0.063)
        chestTop.rotateAxis(-90 * (openProgress / 100), 1, 0, 1.25)
    otherwise
        chestTop.setPivot(-0.12, 0.17, -0.063)
        chestTop.rotateAxis(-90 * (openProgress / 100), 1, 0, -1.25)
    end
end

task getFallTarget do
    reply max(-7, min(100, player.getVelocityY() * -100))
end
debug.vyn55 lines
debug.vyn
make debugModeOption = new ConfigOption("debug_mode", "toggle", false)
debugModeOption.setLabel("interactive_stuff.config.menu.setting.debug")
debugModeOption.setCategory("interactive_stuff.config.menu.pack.category.debug")
debugModeOption.setTooltip("interactive_stuff.config.menu.setting.debug.tooltip")

make debugCounterOption = new ConfigOption("debug_counters", "toggle", false)
debugCounterOption.setLabel("interactive_stuff.config.menu.setting.counter")
debugCounterOption.setCategory("interactive_stuff.config.menu.pack.category.debug")
debugCounterOption.setTooltip("interactive_stuff.config.menu.setting.counter.tooltip")

task onTick do
    check !debugModeOption.getBoolean() do
        showParticleCounters()
        reply nothing
    end
    debugText("§6Game version: " + modLoader.getRawGameVersion())
    debugText("§aMain Hand Item: " + player.getMainHandItem().getName())
    check player.getBlockingItem() != nothing do
        debugText("§aBlocking Item: " + player.getBlockingItem().getName())
    end
    check player.getWeaponItem() != nothing do
        debugText("§aWeapon Item: " + player.getWeaponItem().getName())
    end
    check player.getPickBlockItem() != nothing do
        debugText("§aPick Block Item: " + player.getPickBlockItem().getName())
    end
    debugText("§cHealth: " + player.getHealth())
    debugText("§aStepping Block: " + player.getSteppingBlock().getName())
    check player.getTargetBlock() != nothing do
        debugText("§aTarget Block: " + player.getTargetBlock().getName())
    end
    debugText("§2Gamemode: " + player.getGamemode())
    debugText("§6Pos: " + player.getPosition().getX() + ", " + player.getPosition().getY() + ", " + player.getPosition().getZ())
    debugText("§bDimension: " + player.getWorld().getDimension())
    debugText("§eIs 1st Person?: " + player.isFirstPerson())
    debugText("§eIs Sneaking?: " + player.isSneaking())
    debugText("§eIs Swimming?: " + player.isSwimming())
    debugText("§eIs Sprinting?: " + player.isSprinting())
    debugText("§eOn Ground?: " + player.isOnGround())
    debugText("§eIs Jumping?: " + player.isJumping())
    debugText("§eIs Underwater?: " + player.isSubmergedInWater())
    debugText("§eIs at cloud height?: " + player.isAtCloudHeight())
    debugText("§bX Velocity: " + player.getVelocityX())
    debugText("§bY Velocity: " + player.getVelocityY())
    debugText("§bZ Velocity: " + player.getVelocityZ())
    debugText("§bCamera Pitch: " + player.getCameraPitchVelocity())
    debugText("§bCamera Yaw: " + player.getCameraYawVelocity())
    showParticleCounters()
end

task showParticleCounters do
    check debugCounterOption.getBoolean() do
        debugText(translateText("interactivestuff.debug.particles", particles.getCount(), config.getParticleLimit(), particles.getEmitterCount(), particles.getDroppedCount()), true)
    end
end
effect_helpers.vyn63 lines
effect_helpers.vyn
task packToggle takes id, category do
    make option = new ConfigOption(id, "toggle", true)
    option.setLabel("interactive_stuff.config.menu.pack." + id)
    option.setTooltip("interactive_stuff.config.menu.pack." + id + ".tooltip")
    option.setCategory("interactive_stuff.config.menu.pack.category." + category)
    reply option
end

task packShakeOption do
    make option = new ConfigOption("shake_intensity", "number", 100)
    option.setLabel("interactive_stuff.config.menu.pack.shake_intensity")
    option.setTooltip("interactive_stuff.config.menu.pack.shake_intensity.tooltip")
    option.setCategory("interactive_stuff.config.menu.pack.category.animation")
    option.setRange(0, 200, 5)
    reply option
end

make compatibility = new Map

task hasMod takes id do
    check !compatibility.containsKey(id) do
        compatibility.put(id, modLoader.isModLoaded(id))
    end
    reply compatibility.get(id)
end

task createProgressState do
    reply new Map
end

task smoothItemProgress takes state, item, target, speed do
    make hand = "off"
    check item.isMainHand() do make hand = "main" end
    make frame = getFrameId()
    make itemId = item.getName()
    check !state.containsKey(hand + "Frame") do
        state.put(hand + "Frame", -1)
        state.put(hand + "Progress", 0)
        state.put(hand + "Item", itemId)
    end
    check state.get(hand + "Item") != itemId || state.get(hand + "Frame") < (frame - 1) do
        state.put(hand + "Progress", 0)
        state.put(hand + "Item", itemId)
        state.put(hand + "Frame", -1)
    end
    check state.get(hand + "Frame") != frame do
        state.put(hand + "Progress", item.smooth(state.get(hand + "Progress"), target, speed))
        state.put(hand + "Frame", frame)
    end
    reply state.get(hand + "Progress")
end

task resetItemProgress takes state, item do
    make hand = "off"
    check item.isMainHand() do make hand = "main" end
    state.put(hand + "Progress", 0)
    state.put(hand + "Frame", getFrameId())
    state.put(hand + "Item", item.getName())
end

task isRedstonePowered do
    reply player.isSneaking() && player.getSteppingBlock().getName() == "minecraft:redstone_block"
end
enchanting_table.vyn135 lines
enchanting_table.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make particleOption = packToggle("particles_enchanting_table", "particles")
particleOption.setOnChange("onParticleOptionChanged")

make showBookOption = packToggle("show_enchanting_book", "appearance")
showBookOption.setOnChange("onBookOptionChanged")

make animateBookOption = packToggle("animate_enchanting_book", "animation")
animateBookOption.setOnChange("onBookOptionChanged")

lock interactiveItems = ["minecraft:enchanting_table"]

make openingState = createProgressState()
make openingProgress = 0
make bookshelfPosition = nothing

make glyphParticles = new ParticleEmitter("minecraft:enchant")
glyphParticles.setSpace("world")
glyphParticles.setPosition(0.5, 0.05, 0.69)
glyphParticles.setSpread(0.08, 0.04, 0.08)
glyphParticles.setLifetime(90)
glyphParticles.setSizeOverLife(0.055, 0.085, "easeOutSine")
glyphParticles.setOpacityOverLife(0, 0, "linear")
glyphParticles.addOpacityKeyframe(0.15, 0.85, "easeOutQuad")
glyphParticles.addOpacityKeyframe(0.7, 0.7, "linear")
glyphParticles.setColor(131, 131, 145)
glyphParticles.setAnimation("random", 1)
glyphParticles.setLight(15)
glyphParticles.setOnSpawn("drawGlyphFromBookshelf")

task drawGlyphFromBookshelf takes particle do
    check bookshelfPosition == nothing do
        particle.kill()
        reply nothing
    end
    make targetX = particle.getX()
    make targetY = particle.getY()
    make targetZ = particle.getZ()
    make centerX = bookshelfPosition.getX() + 0.5
    make centerY = bookshelfPosition.getY() + 0.5
    make centerZ = bookshelfPosition.getZ() + 0.5
    make directionX = targetX - centerX
    make directionY = targetY - centerY
    make directionZ = targetZ - centerZ
    make distanceX = max(directionX, -directionX)
    make distanceY = max(directionY, -directionY)
    make distanceZ = max(directionZ, -directionZ)
    make sourceX = centerX + (random() - 0.5) * 0.7
    make sourceY = centerY + (random() - 0.5) * 0.7
    make sourceZ = centerZ + (random() - 0.5) * 0.7
    check distanceX >= distanceY && distanceX >= distanceZ do
        make sourceX = centerX + 0.51
        check directionX < 0 do make sourceX = centerX - 0.51 end
    otherwise
        check distanceY >= distanceZ do
            make sourceY = centerY + 0.51
            check directionY < 0 do make sourceY = centerY - 0.51 end
        otherwise
            make sourceZ = centerZ + 0.51
            check directionZ < 0 do make sourceZ = centerZ - 0.51 end
        end
    end
    particle.setPosition(sourceX, sourceY, sourceZ)
    make ticks = particle.getLifetime()
    particle.setVelocity((targetX - sourceX) / ticks, (targetY - sourceY) / ticks, (targetZ - sourceZ) / ticks)
end

task syncParticleEmission do
    check particleOption.getBoolean() && showBookOption.getBoolean() && animateBookOption.getBoolean() && bookshelfPosition != nothing && openingProgress > 0 do
        glyphParticles.start()
    otherwise
        glyphParticles.stop()
    end
end

task onParticleOptionChanged takes option, oldValue, newValue do
    syncParticleEmission()
end

task onBookOptionChanged takes option, oldValue, newValue do
    make openingState = createProgressState()
    make openingProgress = 0
    syncParticleEmission()
end

syncParticleEmission()

task onItemUpdate takes itemRendered do
    check itemRendered.getName() != "minecraft:enchanting_table" do reply nothing end
    check hasMod("punchy") do
        itemRendered.translate(0, 0.2, 0)
        itemRendered.scale(0.8, 0.8, 0.8)
    end

    make bookshelf = player.getNearestBlock("minecraft:bookshelf", 2)
    make bookshelfPosition = nothing
    make progress = 0
    check bookshelf != nothing do
        make bookshelfPosition = bookshelf.getPosition()
        make progress = 100
    end

    check showBookOption.getBoolean() && animateBookOption.getBoolean() do
        make openingProgress = smoothItemProgress(openingState, itemRendered, progress, 0.2)
    otherwise
        make openingProgress = 0
        resetItemProgress(openingState, itemRendered)
    end
    glyphParticles.setRate(0.05 * (openingProgress / 100))
    syncParticleEmission()
    check !showBookOption.getBoolean() do
        glyphParticles.attach(itemRendered)
        reply nothing
    end

    make book_left = new ItemModel("minecraft:book")
    make book_right = new ItemModel("minecraft:book")
    setupBookHalf(book_left, itemRendered, "interactivestuff:book_left", 60)
    setupBookHalf(book_right, itemRendered, "interactivestuff:book_right", -60)
    glyphParticles.attach(book_left)
end

task setupBookHalf takes book, item, model, angle do
    book.setParent(item)
    make hingeX = -0.015625
    check !item.isRightHand() do make hingeX = 0.015625 end
    book.setPivot(hingeX, 0.13, 0)
    make lookWeight = 0
    check animateBookOption.getBoolean() do make lookWeight = 1 end
    book.lookAt(bookshelfPosition, "enchanting_book", 0.16, lookWeight)
    book.setLookAtClearance(0.125, -0.16, 0.09, -0.13, 0.16, 0.29, 0.13)
    book.rotate(0, 0, angle * (openingProgress / 100))
    book.setItemModel(model)
end
itemframes.vyn62 lines
itemframes.vyn
importScript("interactivestuff:interactive_resourcepack","physics_lib")

make framePhysicsOption = packToggle("physics_item_frames", "physics")
framePhysicsOption.setOnChange("onItemPhysicsChanged")

make paintingPhysicsOption = packToggle("physics_paintings", "physics")
paintingPhysicsOption.setOnChange("onItemPhysicsChanged")

make signPhysicsOption = packToggle("physics_hanging_signs", "physics")
signPhysicsOption.setOnChange("onItemPhysicsChanged")

task onItemPhysicsChanged takes option, previous, value do
    resetPhysicsEngine()
end

lock interactiveItems = ["minecraft:item_frame", "minecraft:glow_item_frame", "minecraft:painting", "minecraft:oak_hanging_sign", "minecraft:spruce_hanging_sign", "minecraft:birch_hanging_sign", "minecraft:jungle_hanging_sign", "minecraft:acacia_hanging_sign", "minecraft:dark_oak_hanging_sign", "minecraft:crimson_hanging_sign", "minecraft:warped_hanging_sign", "minecraft:cherry_hanging_sign", "minecraft:mangrove_hanging_sign", "minecraft:pale_oak_hanging_sign", "minecraft:bamboo_hanging_sign"]


lock FRAME_ITEMS = new List
FRAME_ITEMS.add("minecraft:item_frame", "minecraft:glow_item_frame", "minecraft:painting", "minecraft:oak_hanging_sign", "minecraft:spruce_hanging_sign", "minecraft:birch_hanging_sign", "minecraft:jungle_hanging_sign", "minecraft:acacia_hanging_sign", "minecraft:dark_oak_hanging_sign", "minecraft:crimson_hanging_sign", "minecraft:warped_hanging_sign", "minecraft:cherry_hanging_sign", "minecraft:mangrove_hanging_sign", "minecraft:pale_oak_hanging_sign", "minecraft:bamboo_hanging_sign")

task onItemUpdate takes itemRendered do
    check player.getGamemode() != "spectator" && isFrame(itemRendered) do
        check !hasMod("holdmyitems") do
            itemRendered.setPivot(0, 0.4, 0)
            check isPhysicsEnabled(itemRendered) do
                runPhysicsEngine(0.01, 0.1, 0.6, 0.3, 0.2, 0.8, 60, 0)
                itemRendered.rotate(angleX, 0, -angleZ)
            end

            check itemRendered.isRightHand() do
				check !hasMod("punchy") do
					itemRendered.rotateY(65)
					itemRendered.rotateX(25)
				end
            otherwise
				check !hasMod("punchy") do
					itemRendered.rotateY(-65)
					itemRendered.rotateX(25)
				end
            end
			check hasMod("punchy") do
				itemRendered.translate(0,-0.6,0)
			end
        end
    end
end

task isFrame takes item do
    reply FRAME_ITEMS.contains(item.getName())
end

task isPhysicsEnabled takes item do
    check !masterPhysicsOption.getBoolean() do reply false end
    check item.getName() == "minecraft:item_frame" || item.getName() == "minecraft:glow_item_frame" do
        reply framePhysicsOption.getBoolean()
    end
    check item.getName() == "minecraft:painting" do
        reply paintingPhysicsOption.getBoolean()
    end
    reply signPhysicsOption.getBoolean()
end
leaves.vyn23 lines
leaves.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make effectOption = packToggle("tint_blocks", "appearance")

lock interactiveItems = ["minecraft:grass_block", "minecraft:oak_leaves", "minecraft:spruce_leaves", "minecraft:jungle_leaves", "minecraft:acacia_leaves", "minecraft:dark_oak_leaves"]

task onItemUpdate takes itemRendered do
    check effectOption.getBoolean() && player.getGamemode() != "spectator" do
        check isItemleaves(itemRendered) do
            itemRendered.setItemModel(itemRendered.getName())

            itemRendered.setTint(0,player.getWorld().getFoliageColor(player.getSteppingBlock().getPosition()))
        end
        check itemRendered.getName() == "minecraft:grass_block" do
            itemRendered.setItemModel(itemRendered.getName())
            itemRendered.setTint(0,player.getWorld().getGrassColor(player.getSteppingBlock().getPosition()))
        end
    end
end

task isItemleaves takes item do
    reply item.getName() == "minecraft:oak_leaves" || item.getName() == "minecraft:spruce_leaves" || item.getName() == "minecraft:jungle_leaves" || item.getName() == "minecraft:acacia_leaves" || item.getName() == "minecraft:dark_oak_leaves"
end
mace.vyn44 lines
mace.vyn
importScript("interactivestuff:interactive_resourcepack","physics_lib")

make physicsOption = packToggle("physics_mace", "physics")
physicsOption.setOnChange("onMacePhysicsChanged")
masterPhysicsOption.setOnChange("onMacePhysicsChanged")

make shakeOption = packShakeOption()

lock interactiveItems = ["minecraft:mace"]


make smoothFallLean = 0
make fallLeanState = createProgressState()

task resetMacePhysics do
    resetPhysicsEngine()
    make smoothFallLean = 0
    check fallLeanState.size() > 0 do make fallLeanState = createProgressState() end
end

task onMacePhysicsChanged takes option, previous, value do
    resetMacePhysics()
end

task onItemUpdate takes itemRendered do
    check !masterPhysicsOption.getBoolean() || !physicsOption.getBoolean() do
        resetMacePhysics()
        reply nothing
    end
    check player.getGamemode() != "spectator" && itemRendered.getName() == "minecraft:mace" && !hasMod("punchy") do

        runPhysicsEngine(0.01, 0.2, 0.1, 0.2, 0.4, 1.0, 45, 0)


        make targetLean = player.getVelocityY() * -20
        make smoothFallLean = smoothItemProgress(fallLeanState, itemRendered, targetLean, 0.5)

        itemRendered.setPivot(0, -0.4, 0)

        itemRendered.rotateX(angleX + smoothFallLean)
        itemRendered.rotateZ(angleZ)
        itemRendered.translate(((random() - 0.5) * smoothFallLean / 5000 * shakeOption.getNumber() / 100), ((random() - 0.5) * smoothFallLean / 5000 * shakeOption.getNumber() / 100), 0)
    end
end
note_block.vyn95 lines
note_block.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make particleOption = packToggle("particles_note_block", "particles")
particleOption.setOnChange("onParticleOptionChanged")

make noteSoundOption = packToggle("note_block_sounds", "sounds")

lock interactiveItems = ["minecraft:note_block"]

make wasSneaking = false

make pitchIndex = -1
make notePitch = 0.5

lock NOTE_SPAWN_Y = 1.05
lock NOTE_RISE = 0.5

lock song_pattern = new List
song_pattern.add(0.5,0.5,0.5,0.5,0.75,0.6,0.6,0.6,0.75,0.6,0.8)

make noteParticle = new ParticleEmitter("minecraft:note")
noteParticle.setPosition(0.5, NOTE_SPAWN_Y, 0.5)
noteParticle.setSpread(0.15, 0, 0.15)
noteParticle.setLifetime(16)
noteParticle.setRate(0)
noteParticle.setSize(0.14)
noteParticle.setSpace("world")
noteParticle.setOnSpawn("colorNoteParticle")
noteParticle.setOnUpdate("riseNoteParticle")

task syncParticleEmission do
    check particleOption.getBoolean() do
        noteParticle.start()
    otherwise
        noteParticle.stop()
    end
end

task onParticleOptionChanged takes option, oldValue, newValue do
    syncParticleEmission()
end

syncParticleEmission()

task colorNoteParticle takes particle do
    make normalizedPitch = max(0, min(1, (notePitch - 0.5) / 1.5))
    make red = int(max(0, sin(normalizedPitch * 6.283185307) * 0.65 + 0.35) * 255)
    make green = int(max(0, sin((normalizedPitch + 0.333333333) * 6.283185307) * 0.65 + 0.35) * 255)
    make blue = int(max(0, sin((normalizedPitch + 0.666666667) * 6.283185307) * 0.65 + 0.35) * 255)
    particle.setColor(red, green, blue)
end

task riseNoteParticle takes particle do
    make remaining = 1 - particle.getProgress()
    make eased = 1 - remaining * remaining * remaining
    particle.setPosition(particle.getX(), particle.getSpawnY() + NOTE_RISE * eased, particle.getZ())
end

task onItemUpdate takes itemRendered do
    syncParticleEmission()
    noteParticle.attach(itemRendered)
end

task onTick do
    check player.getWorld() == nothing do
        reply nothing
    end
    check player.getGamemode() != "spectator" do
        make isSneaking = player.isSneaking()
        check player.getSteppingBlock().getName() == "minecraft:redstone_block" && player.isHoldingItem("minecraft:note_block") && isSneaking && !wasSneaking do
            check pitchIndex < (song_pattern.size() - 1) do
                make pitchIndex = pitchIndex + 1
            otherwise
                make pitchIndex = 0
            end
            make notePitch = song_pattern.get(pitchIndex)
            check noteSoundOption.getBoolean() do
                player.playSound("minecraft:block.note_block.harp", 0.5, notePitch)
            end
            check particleOption.getBoolean() do
                noteParticle.burst(1)
            end
        end
        make wasSneaking = isSneaking
    end
end

task onSwingHand do
    check noteSoundOption.getBoolean() do
        check player.getGamemode() != "spectator" && player.getGamemode() != "adventure" && player.isHoldingItem("minecraft:note_block") && player.getTargetBlock() != nothing do
            make targetBlock = player.getTargetBlock()
            player.playSoundWorld(targetBlock.getPosition(), targetBlock.getInstrument(), 0.2, 1.0)
        end
    end
end
physics_lib.vyn119 lines
physics_lib.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make masterPhysicsOption = packToggle("physics_enabled", "physics")
masterPhysicsOption.setOnChange("onMasterPhysicsChanged")

make angleX = 0
make angleZ = 0
make velX = 0
make velZ = 0
make prevCamPitch = 0
make prevCamYaw = 0
make prevMoveX = 0
make prevMoveY = 0
make prevMoveZ = 0
make isFirstFrame = 1
make lastUpdateFrame = -1
make lastUpdateTime = 0

task runPhysicsEngine takes stiffness, damping, moveRes, lookRes, vertRes, inertial, maxAngle, windMult do
    check !masterPhysicsOption.getBoolean() do
        resetPhysicsEngine()
        reply 0
    end
    make now = getFrameId()

    check lastUpdateFrame == now do
        reply 0
    end

    make dt = getDelta()
    make nowTime = time()
    check (lastUpdateFrame >= 0 && (now - lastUpdateFrame) > 1) || (lastUpdateTime > 0 && ((nowTime - lastUpdateTime) > 250 || nowTime < lastUpdateTime)) do
        resetPhysicsEngine()
    end
    make lastUpdateFrame = now
    make lastUpdateTime = nowTime
    check dt <= 0 do
        make isFirstFrame = 1
        reply 0
    end

    make currCamPitch = player.getCameraPitchVelocity()
    make currCamYaw = player.getCameraYawVelocity()
    make currMoveX = player.getVelocityX()
    make currMoveY = player.getVelocityY()
    make currMoveZ = -player.getVelocityZ()

    check isFirstFrame == 1 do
        make prevCamPitch = currCamPitch
        make prevCamYaw = currCamYaw
        make prevMoveX = currMoveX
        make prevMoveY = currMoveY
        make prevMoveZ = currMoveZ
        make isFirstFrame = 0
    end

    make dYaw = currCamYaw - prevCamYaw
    check dYaw > 180 do make dYaw = dYaw - 360 end
    check dYaw < -180 do make dYaw = dYaw + 360 end
    make dPitch = currCamPitch - prevCamPitch

    make accX = (currMoveX - prevMoveX) * moveRes
    make accZ = (currMoveZ - prevMoveZ) * moveRes
    make accY = (currMoveY - prevMoveY) * vertRes

    make windForceX = 0
    make windForceZ = 0
    check windMult != 0 do
        make windVal = sin(nowTime * 0.0015)
        make gustVal = sin(nowTime * 0.004)
        make envVal = sin(nowTime * 0.0008) * 0.5 + 0.5
        make envVal = envVal * envVal
        make windForceX = (windVal * 0.02 + gustVal * 0.04 * envVal) * windMult * inertial
        make windForceZ = (windVal * 0.02 * 0.6 - gustVal * 0.04 * envVal * 0.4) * windMult * inertial
    end

    make impulseX = ((dPitch * lookRes) + (accZ * 10.0) + (accY * 5.0)) * inertial
    make impulseZ = ((dYaw * lookRes) + (accX * 10.0)) * inertial

    make steps = int(dt)
    check steps < dt do make steps = steps + 1 end
    make stepDelta = dt / steps
    cycle step from 1 to steps do
        make springDampX = (0 - angleX * stiffness) - (velX * damping)
        make springDampZ = (0 - angleZ * stiffness) - (velZ * damping)
        make velX = velX + (springDampX + windForceX) * stepDelta
        make velZ = velZ + (springDampZ + windForceZ) * stepDelta
        check step == 1 do
            make velX = velX + impulseX
            make velZ = velZ + impulseZ
        end
        make angleX = angleX + velX * stepDelta
        make angleZ = angleZ + velZ * stepDelta
        check angleX > maxAngle do make angleX = maxAngle make velX = velX * -0.5 end
        check angleX < -maxAngle do make angleX = -maxAngle make velX = velX * -0.5 end
        check angleZ > maxAngle do make angleZ = maxAngle make velZ = velZ * -0.5 end
        check angleZ < -maxAngle do make angleZ = -maxAngle make velZ = velZ * -0.5 end
    end

    make prevCamPitch = currCamPitch
    make prevCamYaw = currCamYaw
    make prevMoveX = currMoveX
    make prevMoveY = currMoveY
    make prevMoveZ = currMoveZ
end

task resetPhysicsEngine do
    make angleX = 0
    make angleZ = 0
    make velX = 0
    make velZ = 0
    make isFirstFrame = 1
    make lastUpdateFrame = -1
    make lastUpdateTime = 0
end

task onMasterPhysicsChanged takes option, previous, value do
    resetPhysicsEngine()
end
pistons.vyn61 lines
pistons.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make animationOption = packToggle("animate_pistons", "animation")
animationOption.setOnChange("onAnimationChanged")

lock interactiveItems = ["minecraft:piston", "minecraft:sticky_piston"]
lock pistonExtensions = new Map
pistonExtensions.put("minecraft:piston", 0.25)
pistonExtensions.put("minecraft:sticky_piston", 0.275)

make wasSneaking = false
make isSneaking = false
make wasOnRedstone = false
make isOnRedstone = false
make extensionState = createProgressState()

task onAnimationChanged takes option, previous, value do
    make extensionState = createProgressState()
end

task onTick do
    check !animationOption.getBoolean() do reply nothing end
    check player.getWorld() == nothing do reply nothing end

    make isSneaking = player.isSneaking()
    make isOnRedstone = player.getSteppingBlock().getName() == "minecraft:redstone_block"
    check (player.isHoldingItem("minecraft:piston") || player.isHoldingItem("minecraft:sticky_piston")) && player.isFirstPerson() do
        check isOnRedstone && isSneaking && (!wasOnRedstone || !wasSneaking) do
            player.playSound("minecraft:block.piston.extend", 0.5, 1)
        end
        check (isOnRedstone && !isSneaking && wasSneaking) || (!isOnRedstone && wasOnRedstone && wasSneaking) do
            player.playSound("minecraft:block.piston.contract", 0.5, 1)
        end
    end
    make wasSneaking = isSneaking
    make wasOnRedstone = isOnRedstone
end

task onItemUpdate takes itemRendered do
    check !animationOption.getBoolean() do reply nothing end
    check !player.isFirstPerson() do reply nothing end
    make itemId = itemRendered.getName()
    make extension = pistonExtensions.get(itemId)
    check extension == nothing do reply nothing end

    check hasMod("punchy") do
        itemRendered.translate(0, 0.18, 0)
        itemRendered.scale(0.9, 0.9, 0.9)
    end
    itemRendered.setItemModel("interactivestuff:piston_base")
    make pistonTop = new ItemModel(itemId)
    pistonTop.setItemModel("interactivestuff:" + replace(itemId, "minecraft:", "") + "_top")
    pistonTop.setParent(itemRendered)

    make target = 0
    make vy = player.getVelocityY()
    check vy < -0.1 do make target = min(100, vy * -100) end
    check isOnRedstone && isSneaking do make target = 100 end
    make progress = smoothItemProgress(extensionState, itemRendered, target, 0.45)
    pistonTop.translate(0, extension * (progress / 100), 0)
end
redstone_items.vyn17 lines
redstone_items.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make lampOption = packToggle("animate_redstone_lamp", "animation")

lock interactiveItems = ["minecraft:redstone_lamp", "minecraft:redstone_torch"]

task onItemUpdate takes itemRendered do
    check player.getGamemode() != "spectator" do
        check lampOption.getBoolean() && itemRendered.getName() == "minecraft:redstone_lamp" && isRedstonePowered() do
            itemRendered.setItemModel("interactivestuff:redstone_lamp_on")
            itemRendered.setLight(15)
        end
        check itemRendered.getName() == "minecraft:redstone_torch" && isRedstonePowered() do
            itemRendered.setItemModel("interactivestuff:redstone_torch_off")
        end
    end
end
sapling.vyn23 lines
sapling.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make effectOption = packToggle("tint_saplings", "appearance")

lock interactiveItems = ["minecraft:oak_sapling", "minecraft:spruce_sapling", "minecraft:jungle_sapling", "minecraft:acacia_sapling", "minecraft:dark_oak_sapling"]

task onItemUpdate takes itemRendered do
    check effectOption.getBoolean() && player.getGamemode() != "spectator" do
        check isItemSapling(itemRendered) do
            make name = replace(itemRendered.getName(),"minecraft:","")
            itemRendered.setItemModel("interactivestuff:" + name + "_wood")

            make saplingLeaves = new ItemModel("minecraft:oak_sapling")
            saplingLeaves.setItemModel("interactivestuff:" + name + "_leaves")

            saplingLeaves.setTint(0,player.getWorld().getFoliageColor(player.getSteppingBlock().getPosition()))
        end
    end
end

task isItemSapling takes item do
    reply item.getName() == "minecraft:oak_sapling" || item.getName() == "minecraft:spruce_sapling" || item.getName() == "minecraft:jungle_sapling" || item.getName() == "minecraft:acacia_sapling" || item.getName() == "minecraft:dark_oak_sapling"
end
sculk_sensors.vyn168 lines
sculk_sensors.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make particleOption = packToggle("particles_sculk_sensor", "particles")
particleOption.setOnChange("onParticleOptionChanged")
make shakeOption = packShakeOption()
make sculkEnabledOption = packToggle("animate_sculk_sensor", "animation")
sculkEnabledOption.setOnChange("onSculkOptionChanged")

lock interactiveItems = ["minecraft:sculk_sensor", "minecraft:calibrated_sculk_sensor"]
lock RANGE_NORMAL = 8
lock RANGE_CALIBRATED = 16
lock COOLDOWN_NORMAL = 40
lock COOLDOWN_CALIBRATED = 20
lock IGNORED_SOUND_IDS = new List
IGNORED_SOUND_IDS.add("minecraft:block.sculk_sensor.clicking", "minecraft:block.sculk_sensor.clicking_stop", "minecraft:block.wool.fall", "minecraft:block.wool.break", "minecraft:block.wool.place", "minecraft:block.wool.step", "minecraft:block.wool.hit")

task createSensorState do
    make state = new Map
    state.put("item", "")
    state.put("cooldown", 0)
    state.put("pulse", 0)
    reply state
end

make mainSensor = createSensorState()
make offSensor = createSensorState()
make wasOnGround = false

make sculkParticles = new ParticleEmitter("minecraft:smoke")
sculkParticles.setPosition(0.5, 0.7, 0.5)
sculkParticles.setSpread(0.28, 0.06, 0.28)
sculkParticles.setLifetime(16)
sculkParticles.setRate(0)
sculkParticles.setVelocity(0, 0.01, 0)
sculkParticles.setVelocitySpread(0.012, 0.004, 0.012)
sculkParticles.setSizeOverLife(0.025, 0.065, "easeOutCubic")
sculkParticles.setOpacityOverLife(0.9, 0, "easeInQuad")
sculkParticles.setLight(15)
sculkParticles.setSpace("world")
sculkParticles.setColor(27,111,117)
sculkParticles.setColorOverLife(27,111,117, 255, 0, 10, "easeInQuad")

task syncParticleEmission do
    check particleOption.getBoolean() && sculkEnabledOption.getBoolean() do
        sculkParticles.start()
    otherwise
        sculkParticles.stop()
    end
end

task onParticleOptionChanged takes option, oldValue, newValue do
    syncParticleEmission()
end

task resetSensor takes state do
    check state.get("item") == "" do reply nothing end
    state.put("item", "")
    state.put("cooldown", 0)
    state.put("pulse", 0)
end

task onSculkOptionChanged takes option, oldValue, newValue do
    resetSensor(mainSensor)
    resetSensor(offSensor)
    syncParticleEmission()
end

task syncSensorItem takes state, itemId do
    check state.get("item") != itemId do
        state.put("item", itemId)
        state.put("cooldown", 0)
        state.put("pulse", 0)
    end
end

task isHoldingSensor do
    reply player.isHoldingItem("minecraft:sculk_sensor") || player.isHoldingItem("minecraft:calibrated_sculk_sensor")
end

task updateCooldown takes state do
    state.put("pulse", max(0, state.get("pulse") - 1))
    make cooldown = state.get("cooldown")
    check cooldown <= 0 do reply false end
    state.put("cooldown", cooldown - 1)
    reply cooldown == 1
end

task onTick do
    check !sculkEnabledOption.getBoolean() do reply nothing end
    make isOnGround = player.isOnGround()
    check player.getGamemode() == "spectator" || !player.isFirstPerson() || !isHoldingSensor() do
        resetSensor(mainSensor)
        resetSensor(offSensor)
        make wasOnGround = isOnGround
        reply nothing
    end
    syncSensorItem(mainSensor, player.getMainHandItem().getName())
    syncSensorItem(offSensor, player.getOffHandItem().getName())
    make mainEnded = updateCooldown(mainSensor)
    make offEnded = updateCooldown(offSensor)
    check mainEnded || offEnded do
        player.playSound("minecraft:block.sculk_sensor.clicking_stop", 1.0, 1.0)
    end
    check !wasOnGround && isOnGround && !player.isSneaking() && !player.getSteppingBlock().hasBlockTag("wool") do
        processVibration(player.getPosition())
    end
    make wasOnGround = isOnGround
end

task onPlaySound takes sound do
    check !sculkEnabledOption.getBoolean() do reply nothing end
    check player.getGamemode() == "spectator" || !player.isFirstPerson() || !isHoldingSensor() do reply nothing end
    check !IGNORED_SOUND_IDS.contains(sound.getName()) do
        processVibration(sound.getPosition())
    end
end

task activateSensor takes state, mainHand, distance do
    check state.get("cooldown") > 0 do reply false end
    make itemId = state.get("item")
    make range = RANGE_NORMAL
    make cooldown = COOLDOWN_NORMAL
    check itemId == "minecraft:calibrated_sculk_sensor" do
        make range = RANGE_CALIBRATED
        make cooldown = COOLDOWN_CALIBRATED
    otherwise
        check itemId != "minecraft:sculk_sensor" do reply false end
    end
    check distance > range do reply false end
    state.put("cooldown", cooldown)
    state.put("pulse", 5)
    check particleOption.getBoolean() do sculkParticles.burstForHand(mainHand, 6) end
    reply true
end

task processVibration takes position do
    syncSensorItem(mainSensor, player.getMainHandItem().getName())
    syncSensorItem(offSensor, player.getOffHandItem().getName())
    make distance = player.getPosition().getDistanceTo(position)
    make mainActivated = activateSensor(mainSensor, true, distance)
    make offActivated = activateSensor(offSensor, false, distance)
    check mainActivated || offActivated do
        player.playSound("minecraft:block.sculk_sensor.clicking", 1.0, 1.0)
    end
end

task onItemUpdate takes itemRendered do
    check !sculkEnabledOption.getBoolean() || player.getGamemode() == "spectator" do reply nothing end
    make state = offSensor
    check itemRendered.isMainHand() do make state = mainSensor end
    make itemId = itemRendered.getName()
    syncSensorItem(state, itemId)
    syncParticleEmission()
    sculkParticles.attach(itemRendered)
    check state.get("cooldown") > 0 do
        check itemId == "minecraft:sculk_sensor" do
            itemRendered.setItemModel("interactivestuff:sculk_sensor_active")
        otherwise
            itemRendered.setItemModel("interactivestuff:calibrated_sculk_sensor_active")
        end
        make intensity = 0
        check state.get("pulse") > 0 do make intensity = 0.02 * shakeOption.getNumber() / 100 end
        itemRendered.translate((random() - 0.5) * intensity, (random() - 0.5) * intensity, 0)
        itemRendered.setLight(15)
    end
end

syncParticleEmission()
torches_and_more.vyn85 lines
torches_and_more.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make shakeOption = packShakeOption()

make effectOption = packToggle("torch_extinguish", "animation")
effectOption.setOnChange("onTorchOptionChanged")

lock interactiveItems = ["minecraft:campfire", "minecraft:torch", "minecraft:redstone_torch", "minecraft:soul_campfire", "minecraft:soul_torch", "minecraft:wall_torch", "minecraft:copper_torch"]

lock HOT_ITEMS = new List
HOT_ITEMS.add("minecraft:campfire", "minecraft:torch", "minecraft:redstone_torch", "minecraft:soul_campfire", "minecraft:soul_torch", "minecraft:wall_torch", "minecraft:copper_torch")

make wasSubmerged = false
make intensity = 0.0

task onTorchOptionChanged takes option, previous, value do
    make wasSubmerged = nothing
    make intensity = 0
end

task onTick do
    check !effectOption.getBoolean() do reply nothing end
    make isSubmerged = player.isSubmergedInWater()
    check player.getGamemode() != "spectator" && player.isFirstPerson() && wasSubmerged != nothing && wasSubmerged != isSubmerged do
        check isPlayerHoldingAnyHotItem() do
            check isSubmerged do
                player.playSound("minecraft:block.fire.extinguish", 0.6, 1.0)
                make intensity = 0.015
            otherwise
                player.playSound("minecraft:item.flintandsteel.use", 0.6, 1.0)
            end
        end
    end
    make wasSubmerged = isSubmerged
    make intensity = max(0, intensity - 0.001)
end

task onItemUpdate takes itemRendered do
    check effectOption.getBoolean() && player.getGamemode() != "spectator" do
        check player.isSubmergedInWater() do
            make isTheItem = false
            check isWaterTorch(itemRendered) && itemRendered.getName() != "minecraft:redstone_torch" do
                itemRendered.setItemModel("interactivestuff:torch_off")
                make isTheItem = true
            end
            check itemRendered.getName() == "minecraft:redstone_torch" do
                itemRendered.setItemModel("interactivestuff:redstone_torch_off")
                make isTheItem = true
            end
            check isWaterCampFire(itemRendered) do
                itemRendered.setItemModel("interactivestuff:campfire_off")
                make isTheItem = true
            end
            check isTheItem do
                itemRendered.translate(((random() - 0.5) * intensity * shakeOption.getNumber() / 100), ((random() - 0.5) * intensity * shakeOption.getNumber() / 100), 0)
            end
        otherwise
            check isHoldingHotItem(itemRendered) do
                check itemRendered.getName() != "minecraft:redstone_torch" || !isRedstonePowered() do
                    itemRendered.setLight(15)
                end
            end
        end
    end
end


task isHoldingHotItem takes itemRendered do
    reply HOT_ITEMS.contains(itemRendered.getName())
end


task isPlayerHoldingAnyHotItem do
    reply HOT_ITEMS.contains(player.getMainHandItem().getName()) || HOT_ITEMS.contains(player.getOffHandItem().getName())
end

task isWaterTorch takes itemHeld do
    make id = itemHeld.getName()
    reply id == "minecraft:torch" || id == "minecraft:redstone_torch" || id == "minecraft:soul_torch" || id == "minecraft:copper_torch"
end

task isWaterCampFire takes itemHeld do
    make id = itemHeld.getName()
    reply id == "minecraft:campfire" || id == "minecraft:soul_campfire"
end
totem.vyn14 lines
totem.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make shakeOption = packShakeOption()

lock interactiveItems = ["minecraft:totem_of_undying"]


task onItemUpdate takes itemRendered do
    check player.getGamemode() != "spectator" && itemRendered.getName() == "minecraft:totem_of_undying" do
        make intensity = max(0, (20 - player.getHealth()) / 10)
        itemRendered.translate(((random() - 0.5) * intensity / 250 * shakeOption.getNumber() / 100), ((random() - 0.5) * intensity / 250 * shakeOption.getNumber() / 100), 0)
        itemRendered.rotateAxis(((random() - 0.5) * intensity / 250 * shakeOption.getNumber() / 100), 1,1,1)
    end
end
vault.vyn56 lines
vault.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make effectOption = packToggle("animate_vault", "animation")

lock interactiveItems = ["minecraft:vault"]

lock lootItems = new List
lootItems.add("minecraft:wind_charge","minecraft:emerald","minecraft:iron_ingot","minecraft:diamond","minecraft:ominous_bottle","minecraft:honey_bottle","minecraft:golden_apple","minecraft:golden_carrot","minecraft:enchanted_book")

lock cooldown = 2000
make lastChangeTime = 0
make currentLootItem = nothing

task getRandomItem do
    reply lootItems.get(random(0, lootItems.size() - 1))
end

task onItemUpdate takes itemRendered do
    check effectOption.getBoolean() && player.getGamemode() != "spectator" do
        check itemRendered.getName() == "minecraft:vault" do
			check hasMod("punchy") do
				itemRendered.translate(0,0,0)
			otherwise
				itemRendered.translate(0, 0.2, 0)
			end

            make now = time()

            check currentLootItem == nothing do
                make currentLootItem = getRandomItem()
                make lastChangeTime = now
            end

            check (now - lastChangeTime) >= cooldown do
                make currentLootItem = getRandomItem()
                make lastChangeTime = now
            end

            make lootModel = new ItemModel(currentLootItem)
            check itemRendered.isRightHand() do
                lootModel.setPivot(0.02, 0.05, 0.02)
                lootModel.translate(-0.05, 0.15, 0)
            otherwise
                lootModel.setPivot(-0.02, 0.05, 0.02)
                lootModel.translate(0.05, 0.15, 0)
            end

			check hasMod("punchy") do
				lootModel.translate(0, -0.22, 0)
			end
            lootModel.scale(0.3, 0.3, 0.3)
            lootModel.rotateAxis((now * 0.25) % 360, 0, 1, 0)
            lootModel.rotateX(25)
        end
    end
end
water_bucket.vyn21 lines
water_bucket.vyn
importScript("interactivestuff:interactive_resourcepack", "effect_helpers")

make effectOption = packToggle("tint_water", "appearance")
make animationOption = packToggle("animate_water_bucket", "animation")

lock interactiveItems = ["minecraft:water_bucket"]

task onItemUpdate takes itemRendered do
    check !effectOption.getBoolean() && !animationOption.getBoolean() do reply nothing end
    check player.getGamemode() == "spectator" || itemRendered.getName() != "minecraft:water_bucket" do reply nothing end
    check animationOption.getBoolean() && player.isSubmergedInWater() do
        itemRendered.setItemModel("minecraft:bucket")
        reply nothing
    end
    check !effectOption.getBoolean() do reply nothing end
    itemRendered.setItemModel("interactivestuff:bucket")
    make waterOfBucket = new ItemModel("minecraft:bucket")

    waterOfBucket.setItemModel("interactivestuff:water_of_the_bucket")
    waterOfBucket.setTint(0, player.getWorld().getWaterColor(player.getSteppingBlock().getPosition()))
end

Troubleshooting

#

No settings button in Options

If Mod Menu is installed, the separate Options entry is intentionally hidden. Open the mod’s Configure screen from Mod Menu, or assign the optional settings keybind.

A script never runs

Check the enabled pack, the exact assets/interactivestuff/scripts/ path, the .vyn extension and latest.log. Check interactiveItems against the actual held item ID. Reload with F3+T after changes.

A library import fails

Use the real pack ID, include file/ for local folder/ZIP packs, omit .vyn from the script name, and keep the providing pack enabled. Imported libraries must not define automatic event tasks.

A setting does not appear

Declare ConfigOption and call its schema setters at load time, not inside an event. Check for conflicting duplicate schemas, invalid defaults or a missing callback task. Then reload resources.

A model appears twice

new ItemModel and copy enqueue additional models. To replace the current held appearance, call setItemModel on itemRendered in its render event.

Grass dirt is tinted too

Use setTint(0, world.getGrassColor(position)) on a model whose grass faces have tintindex 0. A whole-model RGB multiplier also colors the dirt.

Foliage is gray or the color is wrong

Check the pack’s tint option, global Color changes, tint indices, and whether the script uses the foliage resolver. Confirm you are using the correct target JAR and active custom model. The global getBiomeColorAt alias means foliage, not all color types.

Particles move with the camera

Use setSpace("world") if particles should stay where they were born. setAttached(false) selects view space instead.

Particles keep emitting

Use setRate(0) plus a burst in the action event. A burst inside onItemUpdate will repeat every render frame. stop stops births; clear removes already living particles.

No particles are visible

Check the global particle switch/density/cap, the pack emission switch, first-person view, emitter enabled state and per-frame attach. Confirm its item filter and texture path. The enchanting effect also needs a visible animated book and a nearby bookshelf.

A burst has fewer particles than requested

Global density scales bursts too, the active cap can drop births, pending counts clamp to 128 per hand, and only attached active hands can spawn.

Motion is faster with two held items

Store progress per logical hand and item, or guard simulation with getFrameId. Repeatedly updating one global number in both render calls advances it twice.

A key callback does not fire

onKeyPress is for gameplay key input while no screen is open. Check the integer key code. It is not a GUI character-typing hook.

A modern data component fails on 1.20.1

That target uses a limited legacy mapping. Unsupported modern component IDs are rejected; use a supported legacy key/nbt:TagName and test the target.

Punchy or another first-person renderer changes behavior

Some rendering mods bypass vanilla render paths, and bundled scripts also have explicit installed-mod checks. Partial visual overlap is not proof of full compatibility. Test the matching target and toggles; consult the scripts before combining effects.

Multi-version project & Gradle

#

The repository is an all-in-one Gradle project. Open its root in the IDE. Shared production code and resources live in src/main/; version-specific adapters live in versions/<profile>/src/. A version override with the same path wins over the inherited file, so a root edit reaches every target that does not override it.

The profile chain is defined in the version properties. The 26.1 patch modules reuse the 26.1.X profile with their own patch dependencies. Treat generated files in build/ as outputs; edit the source roots instead.

Everyday commands
./gradlew commands
./gradlew prepareIde
./gradlew buildSelected
./gradlew runSelected -Ptarget_version=26.3
./gradlew buildAll
./gradlew checkAll
./gradlew releaseAll
MinecraftBuildRun clientGenerate IDE game sources
1.20.1build1_20_1run1_20_1sources1_20_1
1.21.1build1_21_1run1_21_1sources1_21_1
1.21.5build1_21_5run1_21_5sources1_21_5
1.21.10build1_21_10run1_21_10sources1_21_10
1.21.11build1_21_11run1_21_11sources1_21_11
26.1build26_1run26_1sources26_1
26.1.1build26_1_1run26_1_1sources26_1_1
26.1.2build26_1_2run26_1_2sources26_1_2
26.2build26_2run26_2sources26_2
26.3build26_3run26_3sources26_3

Per-target tasks also work with qualified paths, such as ./gradlew :1.20.1:build. Windows users can use gradlew.bat in place of ./gradlew.

Outputs & checks

releaseAll collects the ten checked release JARs and checksums into build/dist/0.8.0/ and creates build/dist/InteractiveStuff-0.8.0-all-versions.zip. It does not publish. Local developer docs/tests are excluded from the mod JARs and release ZIP.

Where local client-test fixtures are present, testAll or a version task such as test26_3 runs graphical client checks sequentially and needs a display. Those fixtures were deliberately kept local and are not a promise that a fresh public checkout includes every local test source. Ordinary build/check tasks include the checks available to that checkout.

Publishing commands

previewPublish prepares a dry-run plan. publishAll performs real uploads to configured Modrinth/CurseForge projects with credentials; run it only when you intend to release. It reads root changelog.md by default, or -Pchangelog_file=/path/to/notes.md. The full changelog goes on 26.3 by default; other uploads contain a short reference. -Pchangelog_target changes the full-notes target.

Publishing settings include release_type (release/beta/alpha), publish_platforms and publish_dry_run=true. Credentials use MODRINTH_TOKEN and CURSEFORGE_TOKEN from the environment or private Gradle properties. This documentation build performs no uploads.

About this reference

#

Prepared for InteractiveStuff 0.8.0 from the local source on 30 October 2026. The original wiki provided the type-by-type organization; current annotations, implementations, settings schemas and bundled scripts determined the API and feature coverage.

This file embeds its styles, scripts, mod icon and reading fonts. No CDN, network fetch, account or installation is needed to read it. External source/download links naturally require a connection. You can print it or save it as PDF through your browser.

InteractiveStuff by OmarDotContent. Mod artwork is the project’s existing icon. Embedded Noto Sans / Noto Sans Display fonts are by the Noto project, licensed under the SIL Open Font License 1.1; the license is included in this file. Minecraft names describe integration with Minecraft Java Edition; this is community documentation.

Embedded font license
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Noto
Upstream-Contact: https://github.com/googlei18n/noto-fonts/issues
 http://groups.google.com/group/noto-font
Source: https://github.com/googlei18n/noto-fonts

Files: *
Copyright:
  2010,2012-2020, Google Inc.
  2015-2020, Google LLC.
License-Grant:
 This Font Software is licensed under the SIL Open Font License,
 Version 1.1.
License: OFL-1.1

Files:
 debian/*
Copyright:
  2013-2020, Jonas Smedegaard <dr@jones.dk>
  2020, Purism, SPC
  2013, Vasudev Kamath <kamathvasudev@gmail.com>
License-Grant:
 This program is free software;
 you can redistribute it and/or modify it
 under the terms of the GNU General Public License
 as published by the Free Software Foundation;
 either version 3, or (at your option) any later version.
License: GPL-3+

Files:
 debian/NotoMono/NotoMono-Regular.ttf
Copyright:
  2007, Google Inc.
License-Grant:
 This Font Software is licensed under the SIL Open Font License,
 Version 1.1.
License: OFL-1.1

License: OFL-1.1
 PREAMBLE
 .
 The goals of the Open Font License (OFL) are to stimulate
 worldwide development of collaborative font projects,
 to support the font creation efforts
 of academic and linguistic communities,
 and to provide a free and open framework
 in which fonts may be shared and improved
 in partnership with others.
 .
 The OFL allows the licensed fonts to be used, studied,
 modified and redistributed freely
 as long as they are not sold by themselves.
 The fonts, including any derivative works,
 can be bundled, embedded,
 redistributed and/or sold with any software
 provided that any reserved names are not used
 by derivative works.
 The fonts and derivatives, however,
 cannot be released under any other type of license.
 The requirement for fonts to remain under this license
 does not apply to any document
 created using the fonts or their derivatives.
 .
 DEFINITIONS
 .
 "Font Software" refers to the set of files
 released by the Copyright Holder(s) under this license
 and clearly marked as such.
 This may include source files, build scripts and documentation.
 .
 "Reserved Font Name" refers to any names specified as such
 after the copyright statement(s).
 .
 "Original Version" refers to the collection
 of Font Software components
 as distributed by the Copyright Holder(s).
 .
 "Modified Version" refers to any derivative
 made by adding to, deleting, or substituting --
 in part or in whole --
 any of the components of the Original Version,
 by changing formats
 or by porting the Font Software to a new environment.
 .
 "Author" refers to any designer, engineer, programmer,
 technical writer or other person
 who contributed to the Font Software.
 .
 PERMISSION & CONDITIONS
 .
 Permission is hereby granted, free of charge,
 to any person obtaining a copy of the Font Software,
 to use, study, copy, merge, embed, modify, redistribute,
 and sell modified and unmodified copies of the Font Software,
 subject to the following conditions:
 .
 1) Neither the Font Software nor any of its individual components,
 in Original or Modified Versions,
 may be sold by itself.
 .
 2) Original or Modified Versions of the Font Software
 may be bundled, redistributed and/or sold with any software,
 provided that each copy contains the above copyright notice
 and this license.
 These can be included either as stand-alone text files,
 human-readable headers or in the appropriate
 machine-readable metadata fields within text or binary files
 as long as those fields can be easily viewed by the user.
 .
 3) No Modified Version of the Font Software may use
 the Reserved Font Name(s)
 unless explicit written permission is granted
 by the corresponding Copyright Holder.
 This restriction only applies to the primary font name
 as presented to the users.
 .
 4) The name(s) of the Copyright Holder(s)
 or the Author(s) of the Font Software
 shall not be used to promote,
 endorse or advertise any Modified Version,
 except to acknowledge the contribution(s)
 of the Copyright Holder(s) and the Author(s)
 or with their explicit written permission.
 .
 5) The Font Software,
 modified or unmodified, in part or in whole,
 must be distributed entirely under this license,
 and must not be distributed under any other license.
 The requirement for fonts to remain under this license
 does not apply to any document
 created using the Font Software.
 .
 TERMINATION
 .
 This license becomes null and void
 if any of the above conditions are not met.
 .
 DISCLAIMER
 .
 THE FONT SOFTWARE IS PROVIDED "AS IS",
 WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 INCLUDING BUT NOT LIMITED TO
 ANY WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT.
 IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE
 FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 INCLUDING ANY GENERAL, SPECIAL, INDIRECT,
 INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF THE USE OR INABILITY TO USE
 THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

License: GPL-3+
License-Reference: /usr/share/common-licenses/GPL-3
InteractiveStuff by OmarDotContent · Documentation for 0.8.0Back to top ↑