Skip to content

[Perf] Combat performance pass and throw system cleanup#310

Merged
Mere-Solace merged 11 commits into
devfrom
opt/optimization-apis
Apr 1, 2026
Merged

[Perf] Combat performance pass and throw system cleanup#310
Mere-Solace merged 11 commits into
devfrom
opt/optimization-apis

Conversation

@Mere-Solace

@Mere-Solace Mere-Solace commented Apr 1, 2026

Copy link
Copy Markdown
Owner

Summary

This PR encompasses a comprehensive performance optimization pass and cleanup of the throw system, addressing six linked issues and adding critical infrastructure improvements.

Issues Addressed

Changes

  • Fix: Re-register ability projectiles in InteractiveItemArbiter so they can be picked up and dashed to
  • Fix: Knife throw impact type, ImpactType check moved to itemStack to avoid PDC loss through ItemDisplay round-trip
  • Fix: Throw-cancel log gated behind Debug.system()
  • Update: ItemStack API modernization (ItemStack.of(), customName()) across the codebase
  • New: HUD override infrastructure and custom smithing interaction system (ROTATE throw style)

Test Plan

Please verify the following before merging:

  • Throw/Catch/Dash: Knife throw mechanic works correctly; catch/recall interacts properly with thrown items; dash-to-thrown-item works and picks up projectiles
  • Hostile AI Transitions: Mobs transition states smoothly; no unexpected state loops; aggro/de-aggro works correctly
  • Team-Based Friendly Fire: Team assignments respected; no hits on teammates; team switching doesn't cause issues
  • Ability Slot Behavior: Ability slots switch correctly; proper item swapping on slot change; ability costs applied correctly
  • Performance: Monitor for any frame rate improvements; check debug logs for expected reduced allocations

Mere-Solace and others added 11 commits March 29, 2026 21:36
- Mark knife projectile as unbreakable so handleItemDamageAndCheckIfBroken
  does not destroy itemStack before the ImpactType check runs (root cause of
  the always-knockback bug: fresh items reported max damage due to
  hasDamageValue() returning false, triggering the break path on first hit)
- Switch ImpactType.fromItem check from display.getItemStack() to itemStack
  to avoid PDC loss through the ItemDisplay entity round-trip
- Add Prefab.Attacks.KNIFE_THROW: 1 shard, 15 toughness, 0 soulfire
- Add hitPacket / knockbackFunction overrides to ThrownItem so ability
  throws can supply custom hit resolution without subclassing
- Knife knockback: velocity * 0.2 added to the target's current velocity
- Ability projectiles (tagged ABILITY_ID_KEY) no longer catchable by
  thrower, not registered in InteractiveItemArbiter, and dispose silently
  on entity death instead of dropping a world item
ItemStack API:
- Replace new ItemStack(Material.X) with ItemStack.of() across the codebase
- Replace ItemMeta.displayName() / .displayName() with .customName()

Input system:
- Rename InputExecutionTree.hasChildren() -> isTerminal() (clearer semantics)
- Add getPlainTextInputSequence() debug helper to InputExecutionTree
- Reset input tree on charge-action release in InputRegistrar
- Reset input tree in MovementAction when player is holding an ability item

Ability slot / SwordPlayer:
- Add AbilitySlotManager.restoreSlot() and syncSlotItemFromState() so the
  ability slot can be restored after the gunpowder placeholder replaces it
- Fix endHoldingRight() to call restoreSlot() when releasing an ability slot,
  rather than blindly restoring the pre-hold item stack
- Fix onHoldRight() to null-check and clone hand snapshots
- Remove stale isAbilityItem() overloads from SwordPlayer (replaced by
  notHoldingAbilityItem() / AbilitySlotManager)
- Remove unused punchingWithLink, commandingWithLink, targetIndicatorTask fields

AbilitySlotItem:
- Merge EQUIPPED and EMPTY placeholder visual (both now show gray pane)
- Change DEPLETED placeholder material to GRAY_DYE

CtfTeam:
- Replace manual getDyeColor/getTextColor with Lombok @Getter
- Modernize world null-check and ItemStack construction

Misc:
- Add throwDirect(float) overload to ThrowAction delegating to Vector3f form
- Add non-uniform displayScaleVector support to VisualProjectile
Replaces the previous flat suppression approach with a 20s sequence
in SwordPlayer.testHudSequence(): wither hearts (5s), poisoned hearts
(5s), empty food bar via HUD override (5s), and a bubble animation
full-to-empty-to-full via TimeArbiter (5s). Removes the redundant
enableTimedTestOverride/disableTestOverride/TEST_OVERRIDE_KEY API from
HudOverrideManager.
Ability throws (ABILITY_ID_KEY tagged items) were skipping
InteractiveItemArbiter.put() after the 10c6f57 cleanup, making them
invisible to pickup and dash-to interactions. Remove the guard so they
are tracked like any other thrown item; the onGrab refund path and the
disposeWithNewInteractiveItem silent-dispose override continue to handle
ability-specific behavior correctly.

Also adds the custom smithing interaction system (CustomInteractionManager,
CustomInventoryInteraction, HUD override infrastructure) and the ROTATE
throw style used by smithing refits.
#303, #304, #305)

#305 MovementListener: guard Debug.listener() string concat behind
  Config.Debug.LOGGING_VERBOSE_LISTENER — eliminates 100+ String
  allocations/sec per player from a no-op debug path.

#304 HostileStateMachine: gate broadcastMessage() calls in
  onAnyTransition/afterAnyTransition behind LOGGING_VERBOSE_HOSTILE —
  eliminates O(n) getNearbyEntities world scan on every mob AI transition.

#303 Hostile/HostileStateMachine: add cachedDistSqToTarget field,
  refreshTargetDistanceCache() called once at tick() start; all 13
  transition conditions replaced with getCachedDistSqToTarget() —
  eliminates 3-5 Location allocations per mob per tick.

#301 SwordEntity: cache SwordTeam in cachedTeam field set by joinTeam();
  hit() now compares getCachedTeam() fields directly instead of calling
  SwordTeam.fromEntity() twice; joinTeam() drops the values() loop and
  removes only the previously held tag — eliminates 4 allocations per hit.
Add effectiveTransitionCache: Map<Class<?>, List<Transition>> to
StateMachine. tick() now calls effectiveTransitionsFor(currentState.getClass())
which lazily computes and caches the filtered subset of transitions whose
from-class is assignable from the current state class.

Result: O(k) per-tick evaluation where k = transitions for the current state,
instead of O(N) across all transitions with isAssignableFrom on every entry.
For HostileStateMachine (~20 transitions, 10 distinct from-classes): each tick
evaluates 3-7 conditions instead of 20. Cache is invalidated on addTransition()
for correctness.

Flyweight state instance caching skipped: several state classes (RecallingState,
GrabImpaleState, StandbyState, RecoverState) hold task handle fields that are
set in onEnter and cancelled in onExit — caching would require auditing every
onExit for full cleanup, adding fragility risk for marginal gain on a code path
that only runs on transitions.
@Mere-Solace Mere-Solace self-assigned this Apr 1, 2026
@Mere-Solace Mere-Solace added size: l Large, multiple days priority: critical Critical priority status: testing labels Apr 1, 2026
@Mere-Solace Mere-Solace merged commit 8ec9bc2 into dev Apr 1, 2026
1 check passed
@Mere-Solace Mere-Solace deleted the opt/optimization-apis branch April 1, 2026 12:49
Mere-Solace added a commit that referenced this pull request Apr 1, 2026
Keep cachedDistSqToTarget field in Hostile (#303) and
ThrowPhase.THROWING in ThrowAction (#306). Take incoming
SwordPlayer which includes testHudSequence() and the
4-arg itemNameDisplay(Component, Material) overloads.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: critical Critical priority size: l Large, multiple days status: testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant