# 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});"