promotional bannermobile promotional banner

Overgrown's Apoli

A data-driven power system that's build for custom abilities, mechanics, and entire origin systems from JSON alone.
Back to Files

Apoli (1.26.0)

File nameapoli-1.26.0.jar
Uploader
OvergrownMCOvergrownMC
Uploaded
Aug 6, 2026
Downloads
14
Size
28.3 MB
Mod Loaders
Fabric
File ID
8590266
Type
R
Release
Supported game versions
  • 1.20.1

Curse Maven Snippet

Fabric

modImplementation "curse.maven:apoli-1631826:8590266"

Learn more about Curse Maven

What's new

[1.26.0] — 2026-08-06

Added

  • apoli:function and apoli:run_function: A reusable, parameterized action bundle other powers call by id, modelled on vanilla's macro functions. Write the behaviour once with [name] placeholders in the body, then call it with:
{
  "type": "apoli:run_function",
  "function": "example:hurt",
  "arguments": {
    "amount": 3
  }
}

The entity does not need to hold the function power. Substitution is by value, not by text, so a number stays a number and an argument can be a whole JSON object, a boolean or an expression - vanilla's macros can only splice strings. A function with no parameters is parsed once at load and costs exactly what the action inside costs; parameterized ones are built on first use per distinct argument set and cached (8 variants, least-recently-used evicted). Declaring a parameter the body never uses, or using one that isn't declared, is a load error. Recursion stops at 16 nested calls.

  • Global powers: data/<namespace>/global_powers/<name>.json grants powers to entities by entity type, with no origin and no command:
{
  "entity_types": [
    "minecraft:pig",
    "#minecraft:undead"
  ],
  "powers": [
    ...
  ]
}

entity_types accepts an id or a list, #-prefixed entries are entity type tags, and omitting the field entirely matches every entity. Also supports replace, order and loading_priority. Powers arrive under the source apoli:global, so they sit alongside origin- and command-granted powers and are revoked cleanly when a set stops matching. Which powers a given entity type receives is resolved once per entity type per data pack load and cached, so spawning a thousand mobs does not re-evaluate a thousand times; when no global power sets exist at all the whole system costs one boolean check per entity load.

  • apoli:modify_type_tag: Changes whether an entity counts as a member of an entity type tag, for every check the game makes against that tag. tag takes one id or a list (a leading # is optional) and included: false removes the entity from a tag its type is genuinely in. This is what drives Smite (#minecraft:undead), Bane of Arthropods (#minecraft:arthropod), mob AI, type=#tag selectors and your own data pack's tags. Costs nothing when no loaded power uses it.

Removed

  • apoli:entity_group is gone. It parsed its group field and then did nothing at all. No enchantment bonus, no AI change, no selector effect. apoli:modify_type_tag replaces it and still answers to the apoli:entity_group id, including the legacy group values: undead, arthropod, illager and aquatic map to the matching vanilla tag, and default removes the entity from all four. Existing packs keep loading, and now actually work.

Fixed

  • On Minecraft 1.20.1, entity type tags now drive combat behaviour too. Pre-1.21 Minecraft reads Smite, Bane of Arthropods and the undead heal-inversion off the old mob-type system rather than off tags, so apoli:modify_type_tag bridges to it there. Without this the power would change type=#tag selectors on 1.20.1 but none of the behaviour a pack actually wants.

[1.25.1] — 2026-08-06

Fixed

  • A player with no network connection could crash the server tick loop. Every per-recipient send is now guarded. A ServerPlayer that is not in the player list, meaning: one spawned by a command, another mod's fake player, or a player still mid-login, has no connection, and the networking layer throws rather than doing nothing. Any such player holding a power was registered for power syncing, so the next tick tried to send to it and took the server thread down with it.

[1.23.2] — 2026-08-06

Fixed

  • apoli:action_on_sending_message stopped halfway through its filters. A filter with prevent: true returned immediately, so every filter after it and every lower-priority power — was skipped. Now every matching filter runs its actions and prevent only suppresses the broadcast. Filters are also matched against the message the player actually typed, so a replacement from an earlier filter can no longer stop a later one from matching; replacements still chain onto each other.
  • (Origins) A conditional origin layer kept the wrong origin after you switched origins. Layer revalidation only cleared a layer when it had no available origins at all, so a layer whose two conditioned groups always leave exactly one origin available never revalidated — the old origin, its powers and its HUD stayed applied. It now clears whenever the origin you hold is no longer among the layer's available origins, and re-picks in the same pass so an auto_choose layer lands on the right origin immediately.

[1.23.1] — 2026-08-05

Added

  • apoli:action_on_sending_message now fires for command messages too/say, /me and /msg go through the same filters as typed chat. Previously only plain chat was hooked, so a magic word typed as a command did nothing.
  • Message filters gain replacement. A matching filter can rewrite the message instead of swallowing it, with regex capture groups available as $1, $2 and so on. Several filters rewrite in turn, each seeing the previous one's output. prevent still wins over replacement.
  • /apoli:message <text> runs the whole message pipeline against <text> and reports which powers are active, which filters matched, and what would have happened to the message. This is the thing to reach for when a magic word isn't firing.

Changed

  • Translation-key filters (#{some.key}) no longer index every translation of every mod at startup. Only the keys your filters actually name are collected, the language files are read once on first use rather than during mod init, and a pack with no #{...} placeholder never opens a language file at all. On a large modpack this was tens of thousands of strings held for the whole session to serve a handful of lookups.

Fixed

  • A failing entity action inside action_on_sending_message could break chat silently. Chat broadcast runs inside a FutureChain, whose exceptionally handler swallows the failure into a generic "Chain link failed" line. The handler now catches its own exceptions, names the power and the message in the log, and lets the message through.

[1.23.0] — 2026-08-04

Added

  • apoli:replace_sound_emission and apoli:replace_sound_reception — swap out the sounds an entity makes or a player hears. Keys in the sounds map are either an exact sound id or a regular expression matched against the whole id; values are a sound id, an object with volume/pitch, or a weighted array. Regex keys can feed capture groups into the replacement, so "minecraft:entity\\.zombie\\.(.*)": "minecraft:entity.pig.$1" rewrites every zombie sound in one rule. minecraft:empty mutes. replace: false layers the replacement on top of the original instead of taking it away, and priority orders several such powers on the same holder.
  • apoli:action_on_collision — runs a bi-entity action while the holder is overlapping another entity, with bientity_condition / target_condition, an optional radius to inflate the check, a cooldown and hud_render. It does not need an attack, and unlike vanilla pushing it also fires for armour stands, boats, items and projectiles in flight.
  • apoli:play_sound gains follow_entity, global and internal. The sound can track the entity instead of pinning to a position, ignore distance falloff, and be hidden from the entity itself so only other players hear it.
  • Skill trees and skills accept a texture icon. icon is now the same Icon data type Origins uses, so {"texture": "example:textures/gui/x.png"} works anywhere an item did, in skill_trees files and in a power's skill block. Item ids and item stacks are unchanged.
  • apoli:action_on_sending_message filters gain match_mode, literal and case_insensitive. match_mode picks how much of the message the pattern has to cover — contains (the existing default), full, word, starts_with, ends_with — so an exact magic word is now one field instead of hand-anchored regex. A filter may also be written as a bare string.

Fixed

  • apoli:action_on_sending_message ignored message_type and priority. The chat hook never passed a chat type, so any power that set message_type could never match and simply never fired; priority was parsed and then never read. Both work now.
  • An invalid regex in a message filter is no longer silent. It still falls back to matching the pattern as literal text, but now says so in the log.
  • Skill trees logged a bogus "has no skill tree at the top of its parent chain" warning for every skill in the pack on each data-pack load. Skills were registered before their trees, so the first of the two rebuilds validated against an empty tree map.

[1.22.0] — 2026-08-04

Added

  • apoli:custom_model_render geometry mode honours show_first_person. The field existed but only ever did anything in texture mode, so a Blockbench model bound to the arms was invisible on your own hands. It now draws the model's right_arm / left_arm bones — and everything nested under them — onto the vanilla first-person arm, posed from the same bones the third-person model uses. body_parts still applies, so a power that only shows a head bone draws nothing in first person, and the vanilla arm is still underneath: hide it with apoli:modify_model_parts if the model is meant to replace it.
  • apoli:show_both_arms gains ghost armsghost_arms (0–16), ghost_spacing and ghost_alpha. Each ghost is a translucent copy of the same arm frozen at an earlier point of the swing, so one punch trails a fan behind it and a fast stream of swings reads as a flurry. They are drawn only while an arm is swinging, and only on arms that are drawn at all. They go through the same renderPlayerArm path as the real arm, so custom_model_render, model_color, modify_player_model, modify_model_parts and disguise skins all apply to them for free.
  • apoli:attach_rope gains start_length — the length the rope is created at, clamped to min_lengthmax_length.

Fixed

  • apoli:creative_flight ignored its condition. The power granted flight on add and then re-granted it every tick without ever testing the condition, so a conditional flight power was permanent flight. It now re-evaluates every tick: while the condition fails (or the power is suppressed) flight is taken away and the holder drops, and it comes back when the condition passes. Creative and spectator mode are never touched.
  • apoli:custom_model_render — a rig's second layer detached from its limb. Only the first bone naming a body part bound to it, so a model with both right_arm and right_sleeve (or head and hat_layer, or body and jacket) animated one and froze the other at its rest pose. Standing still it looked right; sneaking, blocking with a shield or winding up a trident moved the bound bone and left the other behind, which read as the model coming apart. Every bone that names a body part now binds to it.
  • apoli:custom_model_render — a body-part bone under a rotated group animated twice. Body-part bones are posed in the player's frame, so they are now always lifted out of their parent group, not just when no ancestor carried a rotation. A bone that loses a parent rotation this way is named in the load log.
  • apoli:custom_model_render — slim skins were half a pixel out. The rest pose the animation is measured against was baked from the slim player layer, whose arms sit at y = 2.5; vanilla normalises them to the wide y = 2.0 every frame, so every arm-bound bone on a slim player was offset by 0.5. The rest pose is now always the wide bake, which is the basis vanilla animates from for both models.
  • apoli:attach_ropemax_length did nothing on a rope that can't be reeled. Every rope was created at whatever distance its two ends happened to be apart, and only the reel keys could change that. A controllable grapple wants to start taut, so that is still its default — but a tether is not controllable and has no way to pay itself out, so "max_length": 20 on one meant "stuck at however far apart you were", not "may roam 20 blocks". Ropes that are not controllable now start at max_length, and start_length overrides either default.

[1.21.2] — 2026-08-03

Fixed

  • apoli:custom_model_render is drawn in the inventory preview while the camera is in first person. Both modes suppressed themselves whenever the holder was the local player and the camera was first person. That guard was aimed at the first-person hand view, but the hand is rendered by PlayerRenderer.renderHand, which draws the arm parts directly and never runs render layers — so it never had anything to suppress there. What it did suppress was every other whole-body render of the local player, all of which happen with the camera still in first person: the inventory preview and any screen using it, and your own body while sleeping. The model showed in third person and vanished in first person for the same screen. The guard is gone; vanilla already declines to render the camera entity's body during the first-person world pass, so there is nothing left to guard against.
  • apoli:disguise_as has the same fix. The disguise renderer hooks EntityRenderDispatcher.render, which is exactly what the inventory preview calls, and carried the same first-person guard — so a disguised player saw their own undisguised body in the preview while everyone else saw the disguise. The preview now shows the disguise.

[1.21.1] — 2026-08-03

Follow-up to 1.21.0. Bedrock models now match Blockbench's preview exactly — verified per cube corner and per UV rect against Blockbench's own import and export code, not by eye.

Fixed

  • apoli:custom_model_render — box UV is laid out on floored cube sizes. Blockbench rounds each cube dimension down before packing the six box-UV faces; only its Java-entity format keeps the fractional size. Apoli was using the raw float size, so every cube with a fractional dimension sampled the wrong part of the texture — up to 2.65 UV pixels out on a 32×32 sheet. Faces that overran their island's edge into empty texture also read as gaps between cubes, so this fixes two separate-looking symptoms. Cubes with whole-number sizes are unaffected. The cube's geometry still uses the exact fractional size.
  • apoli:custom_model_render — every bone is bindable, not just root bones. A rig built inside a Blockbench wrapper group (the normal case) never picked up the player's animation, because only top-level bones were indexed by name.
  • apoli:custom_model_render — a body-part bone nested under another body-part bone no longer animates twice. Java's player parts are siblings in root space — body never carries right_arm's swing — so a head bone parented to a body bone was picking up the body's animation and its own. Such bones are now re-parented to the model root with a root-relative bind offset, which is exactly bind-pose-preserving. Skipped when an ancestor carries its own rotation, where nesting is what the author meant.

Added

  • apoli:custom_model_render — body-part names are matched loosely. Case, spaces, _ and - are ignored, and alias spellings bind to the seven vanilla parts: headwear/hat_layer/head_layerhat; torso/waist/jacket/body_layerbody; arm_right/right_sleeve/right_arm_layerright_arm, and the left and leg equivalents including right_pants/left_pants.

Changed

  • body_parts in geometry mode now names bones in your own model, not just the seven vanilla parts. Behaviour change: a non-empty body_parts now hides every bone it does not name (hiding a bone hides everything nested under it). Leave body_parts out to draw the whole model, as before.

[1.21.0] — 2026-08-03

apoli:custom_model_render geometry mode was correct in Blockbench and "exploded" in game. Two independent causes, both fixed.

Fixed

  • Per-cube rotation and pivot are now applied. Bedrock rotates individual cubes; Java's ModelPart.Cube cannot. Each rotated cube now becomes a synthetic child bone <bone>_r<n> carrying that cube's pivot and rotation — the same transformation Blockbench's own Java exporter performs. Previously every rotated cube was drawn unrotated, which flattened angled parts and pulled the model apart at the joints.
  • Body-part binding no longer teleports bones. Binding copied the vanilla part's position along with its rotation, so a bone named head snapped to the vanilla head pivot, body to the model origin, and the arms to ±5. Binding is now the vanilla part's delta from its rest pose, applied on top of the pose you authored — a bone rotates about its own pivot and stays where Blockbench put it. Models built on the vanilla skeleton behave exactly as before.

Added

  • Per-face UV. "uv": { "north": { "uv": […], "uv_size": […] }, … } is now read per face, including cubes that mix per-face and box UV within one model. Previously the north face's UV was applied to all six sides. Bedrock face names map onto Java's as eastwest, updown, north/south unchanged.

Known limitations

  • uv_rotation (Bedrock format 1.21.0+) is not supported; a model using it logs a warning and draws that face unrotated.
  • Only minecraft:geometry[0] is read from a .geo.json.
  • A cube with a zero-size axis is a flat plane, and both of its faces land on the same plane — the game z-fights it, as Blockbench does. Give the cube a small inflate or a non-zero thickness.
  • Geometry mode renders in third person only.

This mod has no related projects