Changelog#
All notable changes to the Xenon Plugin SDK will be documented in this file.
Format follows Keep a Changelog.
[Unreleased]#
Fixed#
- Ability cooldown slots now follow physical inputs and failed reads no longer look ready.
Skill1is Shift andSkill2is E (including Kiriko: Swift Step / Protection Suzu); Bastion's Tactical Grenade is secondary fire. Samples now reportUnsupported,Unavailable, orAvailable, root-variable definitions with allocation zero resolve correctly, GUID misses fail closed, and held remoteEntityobjects query current cooldowns by stable entity ID. Ultimate charge is no longer exported as a fake cooldown timer. - Button pulses no longer lose their make edge under scheduler or backend delay. The
holdMswindow now starts after the host successfully emits the press, not when the request is merely applied. Make and break are kept in distinct command phases, and the GameMemory backend waits for dispatcher consumption when observable. Multi-stage placement abilities must still wait for their preview-ready game state before submitting the confirm input. - Entity/view direction contracts are now reliable. Remote
Entity::GetForward()now derives normalized model/body facing from the validatedModelBase + 0x3B0quaternion instead of reading the local-only connection component. Local command-direction reads are normalized and useStableInstanceacross the update rebuild gap.GetCameraForward()(also exposed asGetViewDirection()) now works from bothon_frame()andon_render().GetYaw()remains a legacy alias for the newly explicitGetModelYaw()and is documented as body/model yaw, not view yaw. - Plugins no longer hang on default-constructed
PluginEntity/Entity/Entity::Local(). Recent clang versions enable the WASMbulk-memory-optfeature by default, which lowersMyStruct{}zero-init for structs larger than ~32 bytes to amemory.fillinstruction. WAMR's fast interpreter does not implement bulk-memory ops and silently traps on the opcode — every default-init ofPluginEntity(and therefore everyEntityconstructor,LocalPlayer()call, etc.) appeared to hang the on_frame call.build.batnow passes both-mno-bulk-memoryand-mno-bulk-memory-opt, forcing clang to emit expliciti32.storeloops instead. Rebuild any prebuilt plugins. If you maintain a custom build pipeline, mirror these flags. - Entity and aim queries now work from
on_frame(). Entity queries (IsEnemy,IsAlly,IsVisible,GetDistance,FindBestTarget,PredictPosition) and aim synchronization queries (AimHitsHitbox,AimAtPositionEx,AimAtBoneEx,AimHitTest) are phase-agnostic. They use the locked update snapshot inon_frame()and the lockless render snapshot inon_render(). Remaining render-only natives (get_fov_to_entity,get_screen_offset_to_bone,get_closest_bone_in_fov,find_best_target_fov,get_entity_hitboxes/get_entity_hitbox,get_lerp_history) emit the one-shot phase warning outsideon_render(). - Library plugin functions are now reachable from dependent plugins. When every call site to a library wrapper (e.g.
math::Lerp) was foldable to a constant or otherwise eliminated by-O2, wasm-ld's--gc-sectionswould drop the underlyinglib_*import declarations from the dependent plugin's imports section. The host bridge then had nothing to resolve, and calls silently failed with no diagnostic. Library import headers ininclude/xenon/libs/*.hppnow include a__attribute__((used))keepalive stub that roots every import declaration. Library authors writing new headers must follow the pattern shown in the Dependencies guide — seeinclude/xenon/libs/math_helpers.hppfor the canonical template. GetUltCharge()andEntity::GetUltCharge()now return real values. Both were readingGetLookUpSkill(0x00F8), a skill-list slot that isn't populated reliably across heroes — so the result was always0.0for every hero. They now mirror the canonical source used by the in-game cooldown UI:Entity::UltCd.current(a 0-100 percentage where 100 = ready).IsUltReady()becomes accurate as a side effect. As a bonus,Entity::GetUltCharge()is now populated for all hero entities, not just the local player.- Remaining render-phase APIs now fail loudly instead of silently. Calling
WorldToScreen,WorldToScreenUnclamped,GetCameraPosition,GetViewMatrix, or anyDraw::*function fromon_frame()returns zeros with a one-shot warning per plugin.GetCameraForward()is now phase-safe and is no longer part of this restriction. fmt::float_to_str(and thereforeTextBuilder::putFloat) no longer traps WASM when given NaN, ±Inf, or values ≥ 2³². The trappingi32.trunc_f32_uinstruction would silently killon_renderand disable the plugin after 10 consecutive frames; non-finite values now produce"nan"instead. Common trigger:putFloat(entity.GetPosition().x, ...)on a frame where the entity wasn't fully populated.Entity::Local()(used byLocalPlayer()) now setsm_validbased onalive || health > 0, mirroring the index constructor. Previously it setm_valid = trueunconditionally, soIsValid()always returned true and never gated reads — callers readingGetPosition()etc. on an unpopulated snapshot could pick up non-finite floats.
Removed#
GetUltDuration()(free function andEntitymethod) andPluginEntity::ultDurationfield. The0xD7duration variable at root varbag is generic and not reliably ult-specific; ult active/duration state should be derived fromIsUltActive()+ hero-specific variables instead. Breaking ABI change — plugins must be rebuilt against the updatedPluginEntitylayout.
Added#
GetAbilityCooldown(input),GetSecondaryFireCooldown(),SkillCooldown::IsAvailable(),IsSupported(), andIsReady()for explicit input-based cooldown queries.GetDvaSelfDestructState(out)exposes the spawned Self-Destruct mech's active state, position, and team/enemy classification. Remote D.Va ult charge is not a bomb timer.Entity::Refresh()updates a held entity snapshot and repairs moved entity-array indices by stable host ID.- Versioned aim synchronization API -
AimAtPositionEx/AimAtBoneExreturnAimStepResultV1withInvalid/Turning/Settledstate, observed angular error/velocity, command tick, and consecutive settled ticks.AimHitTestreturns detailed sphere/capsule geometry, visibility, safety-marginfireReady, explicit projectile padding, predicted/backtracked hitbox translation, andbodySlotFilterselection. - Tracked input receipts -
GetGameCommandTick,PressGameButtonTracked,PulseGameButtonTracked, andGetInputReceiptexpose queued/applied/edge-written/input-consumed/released/rejected/superseded command stages plusInputBackendV1.Supersededis terminal but may have emitted or still affect a live held/timed action, so it is not a safe retry signal. Tracked calls accept one action bit; invalid/multi-action values return0, while queue-full rejection returns a nonzero ID withRejectedstate.GameMemorycan observeInputConsumed;KernelDrivercannot and advances fromEdgeWrittentoReleased. Milestone ticks are stable-bracket observations and can remain0when the event crosses a command boundary. Input consumption is not proof that a shot fired or projectile spawned; keep aiming through cast/startup confirmation. GetMouseDelta()/GetMouseDelta(out)— reads the game's current accumulated mouse delta pair from InputManager as aVector2.SetSensitivity(value)— writes the player's in-game mouse sensitivity setting.HeroId::Shion— new hero constant (0x02E0000000000420).GetHeroName(HeroId::Shion)returns"Shion".HeroId::Sierra— new hero constant (0x02E0000000000410).GetHeroName(HeroId::Sierra)returns"Sierra".PulseGameButton(bit, holdMs = 50)— presses a game button and schedules an automatic release. Required for edge-triggered abilities (Skill1,Skill2,Ult,Reload,Interact,Melee):PressGameButtonalone holds the bit indefinitely and blocks the rising edge that fires the ability, which made spell-activating plugins appear broken. Auto-release is driven by the host input spin thread (no plugin-side timer needed).- Per-entity cooldowns and durations — cooldown/duration data is now available on all hero entities, not just the local player:
Entity::GetSecondaryFireCooldown()/GetSkill1Cooldown()/GetSkill2Cooldown()— per-entity cooldown state by physical inputEntity::GetSkill1Duration()/GetSkill2Duration()/GetSkill3Duration()— per-entity duration statePluginEntitystruct now includesskill1Cd,skill1Duration,skill2Cd,skill2Duration,skill3Cd,skill3Duration,ultCdfields (PluginCooldownstruct)GetSkill3Duration()— Skill 3 duration for the local player (was missing from the global API)SkillCooldownstruct moved toTypes.hpp(was inCore.hpp) for use by bothCore.hppandEntity.hpp- Hotkey struct (
Hotkey.hpp) — convenience type wrapping key binding, edge detection, toggle, and config persistence: Update()— call once per frame for edge detectionIsDown()/Pressed()/Released()— level and edge-triggered key stateToggle(bool&)— flip a bool on pressRender(label)— keybind menu widgetLoad(key)/Save(key)— config persistence- Ability Duration API — track remaining time on scripted abilities (e.g. Genji Dragonblade, Soldier Visor):
GetSkill1Duration()/GetSkill2Duration()— returnsSkillCooldown{current, max, enabled}wherecurrent= time remaining,max= total duration,enabled= ability actively running- Hero-specific: duration variables are resolved per-hero using the same allocIdx as cooldowns with varKey
0xD7 WorldToScreenUnclamped(worldPos, screenPos)— projects a 3D position to screen coordinates without rejecting off-screen results. Use for AABB bounding box projection where corners may extend beyond screen edges.ImGui::ColorPicker(label, color)— color swatch button that opens a full color picker popup (uses host'sXenonColorButtonwidget). Alpha is preserved but not editable in the picker.- Skill Cooldown API — per-input cooldown state for the local player:
GetSecondaryFireCooldown()/GetSkill1Cooldown()/GetSkill2Cooldown()— returns an availability-awareSkillCooldownwithIsOnCooldown(),IsReady(), andGetPercent()helpers- Hero-specific: cooldown variables are resolved per-hero automatically
GetWidowCharge()— Widowmaker sniper scope charge level- Aim Control API — full aim system for plugins, matching the internal aimbot's mechanics:
AimSetDirection(dir)— instantly set the command/firing direction vectorAimGetDirection()— read the current command/firing direction vectorAimSetAngles(pitch, yaw)— instantly set command angles in radiansAimGetAngles()— read current command angles (pitch, yaw) in radiansAimAtPosition(target, stiffness)— aim at a world position with critically-damped spring smoothingAimAtBone(entityIndex, boneId, stiffness)— aim at an entity's bone with spring smoothingAimResetSmoothing()— reset spring velocity (call on target switch)AimHitsHitbox(entityIndex, hitboxScale)— check if current aim intersects entity hitboxes (returns BodySlot or -1)atan2f(y, x)— math helper for angle calculations (was missing from freestanding WASM)
Changed#
aim_to/aim_to_bonehost bindings are now functional (were stubs). Third arg is spring stiffness (0 = instant).- Breaking: Renamed skill fields and functions for accuracy:
PluginEntity.skill1Cd/skill2Cd/skillECd→skill1Active/skill2Active/skill3Active(these are active-state flags, not cooldowns)GetSkill1Cooldown()/GetSkill2Cooldown()/GetSkillECooldown()→IsSkill1Active()/IsSkill2Active()/IsSkill3Active()(now returnbool)- WASM imports renamed:
get_skill1_cooldown→is_skill1_active,get_skill2_cooldown→is_skill2_active,get_skill_e_cooldown→is_skill3_active
Added (previous)#
Entity::GetTotalHealth()— alias forGetHealth()(total HP including all pools)Entity::IsFullHealth()— returns true if health >= maxHealthEntity::GetOverhealth()— returns current overhealth amountEntity::GetArmor()— returns current armorEntity::GetBarrier()— returns current barrierEntity::GetForward()— returns the entity's forward-facing direction vectorEntity::IsTargetable()— returns true if entity can be targeted (not invulnerable/phased)Entity::IsReloading()— returns true if the entity is currently reloadingEntity::GetHitboxCount()— returns the number of hitboxes for this entityEntity::GetHitbox(index, out)— retrieves a single hitbox by indexEntity::GetHitboxes(out, maxCount)— retrieves all hitboxes into a buffer, returns countEntity::GetLerpHistory(out, maxCount)— retrieves server tick position history (newest first)Hitboxstruct — hitbox data with boneIndex, radius, bodySlot, isCapsule, worldPos, capsuleEndLerpEntrystruct — tick + position pair for lerp historyBodySlotnamespace — body region classification constants (Head, Neck, Body, Chest, etc.)PluginEntity.overhealth— overhealth field in raw entity dataPluginEntity.hitboxCount— hitbox count in raw entity dataPluginEntity.isTargetable— targetability flag in raw entity dataPluginEntity.isReloading— reload state flag in raw entity dataPluginEntity.forward— forward direction vector in raw entity dataWeaponInfo.reloading— explicit reload state in weapon infoWeaponInfo.skillBlocked— ability-blocked state in weapon info
Added (previous)#
GetCameraPosition()— returns world-space camera position (extracted from VP matrix)GetCameraForward()— returns normalized camera forward direction (derived from VP matrix)GetViewMatrix(float* out16)— copies the raw 4x4 view-projection matrix (row-major)GetUltCharge()— returns local player ult charge percentage (0-100)IsUltReady()— returns true if ult charge >= 100%IsUltActive()— returns true if ultimate is currently activeIsSkill1Active()/IsSkill2Active()/IsSkill3Active()— returns true if skill is active (renamed fromGetSkill*Cooldown)GetHeroState()— returns bitmask withHeroState::Ulting(0x1) andHeroState::Reloading(0x2)GetRailgunCharge()/GetIllariCharge()/GetHanzoCharge()— hero-specific charge valuesGetLookupSkill(lookupId)— generic skill value lookup by IDSkillIdnamespace with known lookup IDs (UltCharge,SojournCharge,IllariCharge,HanzoCharge,SombraAmmo)HeroStatenamespace with bitmask flags (Ulting,Reloading)Entity::GetUltCharge(),IsUltActive(),IsSkill1Active(),IsSkill2Active(),IsSkill3Active()accessorsGetWeaponInfo(inputFlags, out)— query local player weapon state (valid, useable, shootable, hasGravity, projectile speed)WeaponInfostruct withhasGravityfield andInputFlagconstants (PrimaryFire,SecondaryFire,ScopedShoot,Skill1,Skill2,Ultimate,PrimaryRelease)Color::Lerp(a, b, t)— linear interpolation between colorsColor::HealthGradient(percent, alpha)— red→yellow→green health colorVector3::RotatedY(yaw)— rotate vector around Y axisDraw::TextShadow()— text with 1px shadow for readabilityDraw::TextCentered()— roughly centered text with shadowDraw::RectCorners()— tactical corner bracket rectangleImGui::ColorSliders(label, color)— 4-slider RGBA color pickerConfig::GetColor(key, default)/Config::SetColor(key, color)— load/save Color as 4 int keysIsIngame()— check if currently in a matchGetMapId()— current map identifierGetSensitivity()— in-game mouse sensitivityLogDebug(msg),LogWarning(msg),LogError(msg)— log level wrappers in Core.hppGetCurrentHero()— returns the local player's current hero pool IDEntity::GetBoundsMin()/Entity::GetBoundsMax()— AABB accessor aliases (same as GetDelta1/GetDelta2)Raycast(from, to)— world-space line trace, returnsRaycastResultwith hit position, fraction, and hit flagIsPointVisible(from, to)— quick LOS check between two world points (fraction > 0.98 = visible)IsRaycastReady()— check if the native raycast system is initializedRaycastResultstruct withIsHit()andIsVisible()helpersGameButtonnamespace — game-level button constants (LMouse, RMouse, Jump, Crouch, Skill1, Skill2, Ult, Reload, Melee, etc.)GetClientPing()/GetServerPing()— query current network latency in msPressGameButton(bit)/ReleaseGameButton(bit)— inject game-level button inputs
Changed#
PluginEntity.ultChargenow returns real ult charge for the local player (was always 0)PluginEntity.ultActivenow returns real ult active state (was always 0)PluginEntity.skill1Active/skill2Active/skill3Active.xnow returns 1.0 when skill is active (was always 0; renamed fromskill1Cd/skill2Cd/skillECd)GetCrosshairPosition()now returns screen center instead of(0, 0)IsAimKeyDown()now checks right mouse button instead of returning falseenemy_esp.cpp— refactored to use SDK drawing helpers (removed ~55 lines of local boilerplate)enemy_outlines.cpp— refactored to useColor,ImGui::ColorSliders,Config::GetColor/SetColorenemy_glow_outlines.cpp— refactored to useColor,Config::GetColor/SetColor- Clarified
SetOutlineGlowcomment — it is equivalent toSetOutline, uses the glow import path internally
[1.0.0] — 2026-02-28#
Initial documented release. SDK headers, build system, and examples.
Core#
Log,GetTime,IsKeyDown,ScreenSize,ScreenCenter,WorldToScreen
Entity#
Entityclass with full state queries (health, position, bones, visibility, team)Players()range iterator,LocalPlayer(),GetPlayer(),GetPlayerCount()FindBestTarget(flags),FindBestTargetInFov(fov, bone, flags)CalcAngle,GetHeroName,PredictPosition,GetFovTo,GetScreenOffsetTo,GetClosestBoneInFovSetOutline,SetOutlineGlow+ Visible/Occluded shorthandsTargetFlagsnamespace (Enemy, Team, Visible, LowHP)
Drawing#
Draw::Line,Circle,CircleFilled,Rect,RectFilled,Text,HealthBar
ImGui#
Checkbox,SliderFloat,SliderInt,Combo,CollapsingHeader,Separator,Hotkey
Config#
GetBool,GetFloat,GetInt,SetBool,SetFloat,SetInt,Save
Types#
Vector2,Vector3,Color,Team,EntityTypeBonenamespace (actual game skeleton indices)HeroIdnamespace (all heroes through Jetpack Cat)VKkey constants,OutlineType,PluginFlags
Build#
build.bat— single-file and batch compilation--libraryflag for library plugins- Dependency system with
XENON_PLUGIN_INFO_DEPS