Source code for fabricpy.actions

# fabricpy/actions.py
"""Action helper functions for generated Fabric mods.

These helpers return Java code snippets that perform in-game actions such as
replacing blocks, teleporting players, applying potion effects, and more.
They are intended for use in block event handlers (:meth:`Block.on_left_click`,
:meth:`Block.on_right_click`, :meth:`Block.on_break`).

All functions return a **string** of Java source code.  Multiple actions can
be combined by returning them as a **list** from a hook method — the framework
joins them automatically.

The default variable names (``player``, ``world``, ``pos``) match the
parameter names available inside every block event callback generated by
fabricpy.  Override them only if you use a custom callback signature.

Example:
    Using actions inside a block hook::

        from fabricpy.actions import (
            summon_lightning, play_sound, give_xp, launch_player,
        )

        class ThunderBlock(fabricpy.Block):
            def on_break(self):
                return [
                    summon_lightning(),
                    play_sound("LIGHTNING_BOLT_THUNDER"),
                    give_xp(50),
                    launch_player(dy=1.5),
                ]
"""

from __future__ import annotations

__all__ = [
    "replace_block",
    "teleport_player",
    "apply_effect",
    "play_sound",
    "summon_lightning",
    "drop_item",
    "launch_player",
    "place_fire",
    "extinguish_area",
    "give_xp",
    "remove_xp",
    "damage_nearby",
    "heal_nearby",
    "delayed_action",
    "sculk_event",
]


# ------------------------------------------------------------------ #
#  Block manipulation                                                 #
# ------------------------------------------------------------------ #


[docs] def replace_block( block: str, *, pos_var: str = "pos", world_var: str = "world", ) -> str: """Replace the block at the event position with another block. Args: block: ``Blocks`` constant name, e.g. ``"DIAMOND_BLOCK"``, ``"AIR"``, ``"GOLD_BLOCK"``, ``"OBSIDIAN"``. pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. Returns: Java code that replaces the block. Example:: replace_block("DIAMOND_BLOCK") # → world.setBlockAndUpdate(pos, Blocks.DIAMOND_BLOCK.defaultBlockState()); """ return ( f"{world_var}.setBlockAndUpdate({pos_var}, " f"Blocks.{block.upper()}.defaultBlockState());" )
# ------------------------------------------------------------------ # # Player movement # # ------------------------------------------------------------------ #
[docs] def teleport_player( x: float, y: float, z: float, *, relative: bool = False, player_var: str = "player", pos_var: str = "pos", ) -> str: """Teleport the player to a position. Args: x: X coordinate (absolute, or offset when *relative* is ``True``). y: Y coordinate (absolute, or offset when *relative* is ``True``). z: Z coordinate (absolute, or offset when *relative* is ``True``). relative: When ``True`` the coordinates are offsets from the block position referenced by *pos_var*. player_var: Name of the player variable in scope. pos_var: Name of the ``BlockPos`` variable in scope (used when *relative* is ``True``). Returns: Java code that teleports the player. Example:: teleport_player(100, 200, 100) # absolute teleport_player(0, 10, 0, relative=True) # 10 blocks above the block """ if relative: return ( f"{player_var}.teleportTo(" f"{pos_var}.getX() + {x}, " f"{pos_var}.getY() + {y}, " f"{pos_var}.getZ() + {z});" ) return f"{player_var}.teleportTo({x}, {y}, {z});"
[docs] def launch_player( dx: float = 0.0, dy: float = 1.0, dz: float = 0.0, *, player_var: str = "player", ) -> str: """Launch the player upward or apply knockback. Adds to the player's velocity and marks it for immediate client sync. Args: dx: Horizontal X velocity component. dy: Vertical Y velocity component (positive = upward). dz: Horizontal Z velocity component. player_var: Name of the player variable in scope. Returns: Java code that launches the player. Example:: launch_player(dy=2.0) # launch straight up launch_player(dx=3.0, dy=0.5, dz=0.0) # knockback sideways """ return f"{player_var}.push({dx}, {dy}, {dz});\n{player_var}.hurtMarked = true;"
# ------------------------------------------------------------------ # # Status effects # # ------------------------------------------------------------------ #
[docs] def apply_effect( effect: str, duration: int = 200, amplifier: int = 0, *, player_var: str = "player", ) -> str: """Apply a potion / status effect to the player. Args: effect: ``MobEffects`` constant name (Mojang mappings), e.g. ``"SPEED"``, ``"STRENGTH"``, ``"REGENERATION"``, ``"NIGHT_VISION"``, ``"INVISIBILITY"``, ``"JUMP_BOOST"``, ``"SLOW_FALLING"``, ``"POISON"``, ``"FIRE_RESISTANCE"``, ``"LUCK"``. duration: Duration in ticks (20 ticks = 1 second). Defaults to 200 (10 seconds). amplifier: Effect amplifier (0 = level I, 1 = level II, …). player_var: Name of the player variable in scope. Returns: Java code that applies the effect. Example:: apply_effect("SPEED", duration=600, amplifier=1) """ return ( f"{player_var}.addEffect(new MobEffectInstance(" f"MobEffects.{effect.upper()}, {duration}, {amplifier}));" )
# ------------------------------------------------------------------ # # Sound # # ------------------------------------------------------------------ #
[docs] def play_sound( sound: str, volume: float = 1.0, pitch: float = 1.0, *, source: str = "BLOCKS", pos_var: str = "pos", world_var: str = "world", ) -> str: """Play a sound at the block position. Args: sound: ``SoundEvents`` constant name (Mojang mappings — without the ``ENTITY_``/``BLOCK_``/``ITEM_`` prefix), e.g. ``"LIGHTNING_BOLT_THUNDER"``, ``"ANVIL_LAND"``, ``"EXPERIENCE_ORB_PICKUP"``, ``"ENDER_DRAGON_GROWL"``. volume: Sound volume (1.0 = normal). pitch: Sound pitch (1.0 = normal, higher = faster). source: ``SoundSource`` constant: ``"BLOCKS"``, ``"PLAYERS"``, ``"HOSTILE"``, ``"NEUTRAL"``, ``"MASTER"``, etc. pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. Returns: Java code that plays the sound. .. note:: Use Mojang-mapped field names (without ``ENTITY_``/``BLOCK_``/ ``ITEM_`` prefixes). A handful of ``SoundEvents`` fields are ``Holder<SoundEvent>`` rather than plain ``SoundEvent`` (e.g. ``NOTE_BLOCK_PLING``, ``UI_BUTTON_CLICK``). These are **not** compatible with this helper — use a plain ``SoundEvent`` field instead (the vast majority). Example:: play_sound("ANVIL_LAND", volume=2.0, pitch=1.5) """ return ( f"{world_var}.playSound(null, {pos_var}, " f"SoundEvents.{sound.upper()}, " f"SoundSource.{source.upper()}, {volume}f, {pitch}f);" )
# ------------------------------------------------------------------ # # Entity spawning # # ------------------------------------------------------------------ #
[docs] def summon_lightning( *, pos_var: str = "pos", world_var: str = "world", ) -> str: """Summon a lightning bolt at the block position. Only fires on the logical server (``ServerLevel`` check). Args: pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. Returns: Java code that summons lightning. Example:: summon_lightning() """ lines = [ f"if ({world_var} instanceof ServerLevel serverLevel) {{", f" LightningBolt bolt = new LightningBolt(EntityType.LIGHTNING_BOLT, serverLevel);", f" bolt.setPos({pos_var}.getX() + 0.5, (double) {pos_var}.getY(), {pos_var}.getZ() + 0.5);", f" serverLevel.addFreshEntity(bolt);", f"}}", ] return "\n".join(lines)
# ------------------------------------------------------------------ # # Item drops # # ------------------------------------------------------------------ #
[docs] def drop_item( item: str, count: int = 1, *, pos_var: str = "pos", world_var: str = "world", ) -> str: """Drop item(s) at the block position. Args: item: ``Items`` constant name, e.g. ``"DIAMOND"``, ``"GOLD_INGOT"``, ``"EMERALD"``, ``"IRON_INGOT"``. count: Number of items to drop. pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. Returns: Java code that drops items. Example:: drop_item("DIAMOND", count=3) """ return ( f"Block.popResource({world_var}, {pos_var}, " f"new ItemStack(Items.{item.upper()}, {count}));" )
# ------------------------------------------------------------------ # # Fire / extinguish # # ------------------------------------------------------------------ #
[docs] def place_fire( *, above: bool = True, pos_var: str = "pos", world_var: str = "world", ) -> str: """Place fire at or above the block position. Only places fire if the target position is currently air. Args: above: If ``True`` (default), place fire one block above *pos*. If ``False``, place at *pos* directly. pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. Returns: Java code that places fire. Example:: place_fire() # fire above the block place_fire(above=False) # fire at the block position """ target = f"{pos_var}.above()" if above else pos_var return ( f"if ({world_var}.getBlockState({target}).isAir()) {{\n" f" {world_var}.setBlockAndUpdate({target}, Blocks.FIRE.defaultBlockState());\n" f"}}" )
[docs] def extinguish_area( radius: int = 3, *, pos_var: str = "pos", world_var: str = "world", ) -> str: """Extinguish fire blocks in a cubic radius around the position. Args: radius: Radius in blocks to scan for fire (default 3). pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. Returns: Java code that replaces all ``FIRE`` blocks with ``AIR``. Example:: extinguish_area(radius=5) """ lines = [ f"for (int dx = -{radius}; dx <= {radius}; dx++) {{", f" for (int dy = -{radius}; dy <= {radius}; dy++) {{", f" for (int dz = -{radius}; dz <= {radius}; dz++) {{", f" BlockPos checkPos = {pos_var}.offset(dx, dy, dz);", f" if ({world_var}.getBlockState(checkPos).getBlock() == Blocks.FIRE) {{", f" {world_var}.setBlockAndUpdate(checkPos, Blocks.AIR.defaultBlockState());", f" }}", f" }}", f" }}", f"}}", ] return "\n".join(lines)
# ------------------------------------------------------------------ # # Experience # # ------------------------------------------------------------------ #
[docs] def give_xp( amount: int, *, player_var: str = "player", ) -> str: """Give experience points to the player. Args: amount: Number of experience points to give. player_var: Name of the player variable in scope. Returns: Java code that gives XP. Example:: give_xp(100) """ return f"{player_var}.giveExperiencePoints({amount});"
[docs] def remove_xp( amount: int, *, player_var: str = "player", ) -> str: """Remove experience points from the player. Args: amount: Positive number of experience points to remove. player_var: Name of the player variable in scope. Returns: Java code that removes XP (passes a negative value internally). Example:: remove_xp(50) """ return f"{player_var}.giveExperiencePoints(-{abs(amount)});"
# ------------------------------------------------------------------ # # Area-of-effect: damage / heal # # ------------------------------------------------------------------ #
[docs] def damage_nearby( amount: float, radius: float = 5.0, *, exclude_player: bool = True, pos_var: str = "pos", world_var: str = "world", player_var: str = "player", ) -> str: """Damage nearby living entities with magic damage. Args: amount: Damage amount (half-hearts). radius: Search radius in blocks (default 5). exclude_player: If ``True`` (default), the triggering player is excluded from the damage. pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. player_var: Name of the player variable in scope. Returns: Java code that damages nearby entities. Example:: damage_nearby(6.0, radius=8.0) """ predicate = f"e -> e != {player_var}" if exclude_player else "e -> true" lines = [ f"{world_var}.getEntitiesOfClass(LivingEntity.class,", f" new AABB({pos_var}).inflate({radius}),", f" {predicate}", f").forEach(e -> e.hurt({world_var}.damageSources().magic(), {amount}f));", ] return "\n".join(lines)
[docs] def heal_nearby( amount: float, radius: float = 5.0, *, pos_var: str = "pos", world_var: str = "world", ) -> str: """Heal nearby living entities. Args: amount: Health to restore (half-hearts). radius: Search radius in blocks (default 5). pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. Returns: Java code that heals nearby entities. Example:: heal_nearby(4.0, radius=10.0) """ lines = [ f"{world_var}.getEntitiesOfClass(LivingEntity.class,", f" new AABB({pos_var}).inflate({radius})", f").forEach(e -> e.heal({amount}f));", ] return "\n".join(lines)
# ------------------------------------------------------------------ # # Delayed / scheduled actions # # ------------------------------------------------------------------ #
[docs] def delayed_action( action_code: str, ticks: int = 20, *, world_var: str = "world", ) -> str: """Schedule an action to run after a delay. Wraps the given Java code in a ``TickTask`` scheduled on the server, guarded by a ``ServerLevel`` instanceof check. Args: action_code: Java code to execute after the delay. This is typically produced by another action helper. ticks: Delay in game ticks (20 ticks ≈ 1 second). world_var: Name of the ``Level`` variable in scope. Returns: Java code that schedules the delayed action. Example:: delayed_action(give_xp(100), ticks=40) # give XP after 2 seconds delayed_action(summon_lightning(), ticks=60) """ indented = "\n".join(f" {line}" for line in action_code.splitlines()) lines = [ f"if ({world_var} instanceof ServerLevel _delayLevel) {{", f" final int _targetTick = _delayLevel.getServer().getTickCount() + {ticks};", f" final boolean[] _delayFired = {{false}};", f" ServerTickEvents.END_SERVER_TICK.register(_srv -> {{", f" if (!_delayFired[0] && _srv.getTickCount() >= _targetTick) {{", f" _delayFired[0] = true;", indented, f" }}", f" }});", f"}}", ] return "\n".join(lines)
# ------------------------------------------------------------------ # # Sculk sensor / game events # # ------------------------------------------------------------------ #
[docs] def sculk_event( event: str, *, pos_var: str = "pos", world_var: str = "world", player_var: str = "player", ) -> str: """Emit a game event that sculk sensors can detect. Args: event: ``GameEvent`` constant name, e.g. ``"BLOCK_CHANGE"``, ``"BLOCK_DESTROY"``, ``"EXPLODE"``, ``"STEP"``, ``"ENTITY_SHAKE"``, ``"NOTE_BLOCK_PLAY"``, ``"INSTRUMENT_PLAY"``, ``"LIGHTNING_STRIKE"``. pos_var: Name of the ``BlockPos`` variable in scope. world_var: Name of the ``Level`` variable in scope. player_var: Name of the player variable in scope (emitter). Returns: Java code that emits the game event. Example:: sculk_event("BLOCK_CHANGE") sculk_event("EXPLODE") """ return f"{world_var}.gameEvent({player_var}, GameEvent.{event.upper()}, {pos_var});"