mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-11 04:17:38 +00:00
Compare commits
106
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57edfe76c9 | ||
|
|
4fd56166cd | ||
|
|
60405941db | ||
|
|
89e2c6ba8f | ||
|
|
4ce271c7eb | ||
|
|
7cc6f45790 | ||
|
|
dccf6050ca | ||
|
|
33a8000579 | ||
|
|
1aa60be6f6 | ||
|
|
c8f9c23fa4 | ||
|
|
3b1a0f849e | ||
|
|
8d01e5e30a | ||
|
|
80de069b41 | ||
|
|
493e80548e | ||
|
|
be3c8a0a42 | ||
|
|
7b9fc5f076 | ||
|
|
11fb22f964 | ||
|
|
f9ac402e98 | ||
|
|
c0cc787925 | ||
|
|
90061e73a4 | ||
|
|
01367b9b26 | ||
|
|
248249c089 | ||
|
|
dc0f929050 | ||
|
|
25292427d6 | ||
|
|
78b4ece8a6 | ||
|
|
c5785b8579 | ||
|
|
7e7f6f0bf6 | ||
|
|
a6b5982b64 | ||
|
|
398b773fc4 | ||
|
|
aed68f24b8 | ||
|
|
8f08220346 | ||
|
|
3bbead72c8 | ||
|
|
316bb0dc95 | ||
|
|
6a88c2ee37 | ||
|
|
3616990a2f | ||
|
|
c6e76306ab | ||
|
|
846aa44330 | ||
|
|
683864246b | ||
|
|
8ff1549b83 | ||
|
|
e4c2da3be3 | ||
|
|
1148be8cdc | ||
|
|
4294a2bbfa | ||
|
|
5bc3110818 | ||
|
|
87e8d06ef6 | ||
|
|
01b7bf3f12 | ||
|
|
62ac523be3 | ||
|
|
6f6beca9c7 | ||
|
|
ae03c22180 | ||
|
|
d68a7418dc | ||
|
|
e309f6d32e | ||
|
|
14e43af371 | ||
|
|
3cbef80a2b | ||
|
|
800d90d386 | ||
|
|
81ffc12766 | ||
|
|
b2c7d63085 | ||
|
|
b48ffe42fb | ||
|
|
0e27295854 | ||
|
|
708226a929 | ||
|
|
230913519e | ||
|
|
ce96c56c4a | ||
|
|
e2ae028492 | ||
|
|
1260ce3f64 | ||
|
|
32c04f8126 | ||
|
|
5fe9ecfbdb | ||
|
|
8a17b86478 | ||
|
|
e0207ddd09 | ||
|
|
6b78320aa8 | ||
|
|
1a84d01d4a | ||
|
|
d22d10316b | ||
|
|
f58cccc86f | ||
|
|
b08d2fe813 | ||
|
|
f0f784bd70 | ||
|
|
72fc61dc6b | ||
|
|
e68f68ca47 | ||
|
|
627c86f5ba | ||
|
|
b644567cf9 | ||
|
|
891ef4f1e6 | ||
|
|
9142325a9e | ||
|
|
561c03cbe9 | ||
|
|
0296267f0d | ||
|
|
cb459acc28 | ||
|
|
b30735ecf9 | ||
|
|
0e681855b7 | ||
|
|
8bb6055e05 | ||
|
|
e7918018a2 | ||
|
|
1e1989d230 | ||
|
|
be93a6f969 | ||
|
|
640ebd0ab0 | ||
|
|
4bb31eb98d | ||
|
|
be21ff50f6 | ||
|
|
d7103de564 | ||
|
|
c00eb24a8d | ||
|
|
b65c9e0319 | ||
|
|
de033896b1 | ||
|
|
dc8d4caf12 | ||
|
|
20069f3815 | ||
|
|
f2fa112f0a | ||
|
|
fca967c827 | ||
|
|
24404b5a23 | ||
|
|
a106d86969 | ||
|
|
fac3be3a0b | ||
|
|
b49c107778 | ||
|
|
53b7f79440 | ||
|
|
7b21453469 | ||
|
|
ebfd9d034d | ||
|
|
d825bcbdda |
@@ -22,6 +22,7 @@ reference is in **[docs/API.md](docs/API.md)**.
|
||||
| Inline textures | Draws inline texture markup (`\|T…\|t`) and atlas markup (`\|A…\|a`) as icons in FontStrings, chat, and tooltips. This covers item and spell icons, raid-target markers, and the coin icons in money strings. `GetStringWidth` and `GetStringHeight` count the icons, so measured width and text wrapping stay correct. Done in pure C++ by hooking the engine's text pipeline — no addon. |
|
||||
| [Soft targeting](docs/API.md#soft-interact-softinteract--anyinteract) | The Interact Key and soft targets. Bind **Interact With Target** to a key to interact with the nearest NPC, corpse, mailbox, chest or gathering node in front of you. The [`softenemy` and `softfriend`](docs/API.md#soft-enemy-and-soft-friend-softenemy--softfriend--anyenemy--anyfriend) tokens point at the nearest enemy and friendly unit without a change of target. With no target, harmful spells go to `softenemy` and helpful spells go to `softfriend`. `PLAYER_SOFT_*_CHANGED` events fire when the units change. |
|
||||
| [Animations](docs/API.md#animation) | Animation groups on frames, textures and font strings, made in Lua or XML: `CreateAnimationGroup`, looping, smoothing, scripts and templates, with `Alpha`, `Translation`, `Scale`, `Rotation` and `Path` applied. |
|
||||
| [Model scenes](docs/API.md#modelscene) | The `ModelScene` frame type shows many models in one 3D scene, with its own camera, light and fog. Each model is an actor that you place, turn, scale and animate. An actor shows a creature display, a model file or a copy of a unit. It can also seat a rider, show spell visual kits and pause on one frame. A scene can show a WMO, for example a dungeon, around the actors, with its props and their sounds. An actor can move on the WMO as a character moves in the world: it runs, jumps, falls, swims and walks to a point. You hear the sounds of a scene from its camera, or from an actor. A follow camera follows an actor as the camera of the world follows your character, and stops in front of walls. An actor can glow, show a selection circle and show a name over its head, and a scene can show the click-to-move marker. |
|
||||
| Tooltip line cap | Removes `GameTooltip`'s 30-line limit for every `GameTooltipTemplate` frame (`GameTooltip`, `ShoppingTooltip1/2`, `ItemRefTooltip`, AtlasLoot, …). A tooltip adds a new line pair when it needs one, so long tooltips and comparison blocks (for example, pfUI's eqcompare) keep all their lines. |
|
||||
| [XML attributes](docs/API.md#parentkey-in-xml) | More XML attributes work: `parentKey="Name"` makes `parent.Name` point to a frame, texture or font string, `<OnLoad function="Name"/>` uses a global function as a script handler, `<OnLoad method="Name"/>` uses the frame's own function, `mixin="A, B"` copies mixins onto the object, `<KeyValues>` sets fields on the object, a button's `<NormalFont style="..."/>` sets its font, and `inherits="A, B"` applies more than one template. `parentArray="Name"` adds the object to a list on its parent. An `<Anchor>` takes `x` and `y` offsets and `relativeKey="$parent.Key"`. `<OnEnter inherit="prepend">` runs the template's handler and then its own, and `inherit="append"` runs them in the other order. A frame with `intrinsic="true"` makes a new frame type. Frames take `scale`, `registerForDrag`, `clipChildren`, `useParentLevel`, `dontSavePosition`, `propagateKeyboardInput`, `propagateMouseInput`, `enableMouseClicks`, `enableMouseMotion` and `motionScriptsWhileDisabled`, and buttons take `registerForClicks`. Textures take `alpha`, `atlas`, `useAtlasSize`, `mask`, `desaturated`, `rotation`, `horizTile`, `vertTile`, `hWrapMode` and `vWrapMode`. Font strings take `alpha`, `wordwrap`, `indented`, `degrees` and `radians`. Status bars take `reverseFill`, `rotatesTexture` and `fillStyle`, and edit boxes take `countInvisibleLetters`. |
|
||||
| [Macro conditions](docs/API.md#more-commands-with-conditions) | `[conditions]` and `@unit` work on 38 slash commands, from `/cast` and `/use` to `/target`, `/follow`, `/cancelaura` and the `/pet` family. With `/cast [@mouseover,harm][] Fireball`, the spell goes to the mouseover unit. With no mouseover unit, it goes to your target. `@cursor` places a ground-target spell or item under the mouse and `@player` places it at your feet. A character name works where a unit token does (`[target=Feral]`). Addons read the same rules through `SecureCmdOptionParse`. |
|
||||
@@ -88,6 +89,8 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [Frame: StatusBar](docs/API.md#statusbarsetreversefillisreversefill--statusbargetreversefill) | `:SetReverseFill` / `:GetReverseFill`, `:SetRotatesTexture` / `:GetRotatesTexture`, `:SetFillStyle` / `:GetFillStyle`, `:SetColorFill`, `:SetStatusBarDesaturated` / `:IsStatusBarDesaturated`, `:SetStatusBarDesaturation` / `:GetStatusBarDesaturation`, `:SetTimerDuration` / `:GetTimerDuration`, `:SetValue` / `:SetMinMaxValues` with `interpolation`, `:GetInterpolatedValue`, `:IsInterpolating`, `:SetToTargetValue` |
|
||||
| [Frame: Slider](docs/API.md#sliderenable--sliderdisable--sliderisenabled) | `:Enable` / `:Disable` / `:IsEnabled` |
|
||||
| [Frame: Cooldown](docs/API.md#createframecooldown--name-parent-template) | `CreateFrame("Cooldown")`, `:SetCooldown`, `:SetCooldownDuration`, `:SetCooldownFromExpirationTime`, `:Clear`, `:GetCooldownTimes`, `:GetCooldownDuration`, `:GetCooldownDisplayDuration`, `:Pause` / `:Resume`, `:SetPaused` / `:IsPaused`, `:SetReverse` / `:GetReverse`, `:SetDrawEdge` / `:GetDrawEdge`, `:SetDrawSwipe` / `:GetDrawSwipe`, `:SetDrawBling` / `:GetDrawBling`, `:SetSwipeColor`, `:SetEdgeColor`, `:SetSwipeTexture`, `:SetEdgeTexture`, `:SetBlingTexture`, `:SetRotation` / `:GetRotation`, `:SetHideCountdownNumbers` / `:GetHideCountdownNumbers`, `:GetCountdownFontString`, `:SetCountdownAbbrevThreshold` / `:GetCountdownAbbrevThreshold`, `:SetCountdownMillisecondsThreshold` / `:GetCountdownMillisecondsThreshold`, `:SetMinimumCountdownDuration` / `:GetMinimumCountdownDuration`, `OnCooldownDone`, `CooldownFrame_Set`, `CooldownFrame_Clear`, `CooldownFrame_SetDisplayAsPercentage` |
|
||||
| [Frame: ModelScene](docs/API.md#modelscene) | `CreateFrame("ModelScene")`, `<ModelScene>` (`drawLayer`, `fogNear`, `fogFar`, `<FogColor>`, `<ViewInsets>`), `:CreateActor`, `:GetNumActors`, `:GetActorAtIndex`, `:SetCameraPosition` / `:GetCameraPosition`, `:SetCameraOrientationByYawPitchRoll`, `:SetCameraOrientationByAxisVectors`, `:GetCameraForward`, `:GetCameraRight`, `:GetCameraUp`, `:SetCameraFieldOfView` / `:GetCameraFieldOfView`, `:SetCameraNearClip` / `:GetCameraNearClip`, `:SetCameraFarClip` / `:GetCameraFarClip`, `:SetViewInsets` / `:GetViewInsets`, `:SetViewTranslation` / `:GetViewTranslation`, `:Project3DPointTo2D`, `"FollowCamera"` (`:SetFollowActor` / `:GetFollowActor`), `:SetLightType` / `:GetLightType`, `:SetLightVisible` / `:IsLightVisible`, `:SetLightDirection` / `:GetLightDirection`, `:SetLightPosition` / `:GetLightPosition`, `:SetLightAmbientColor` / `:GetLightAmbientColor`, `:SetLightDiffuseColor` / `:GetLightDiffuseColor`, `:SetFogColor` / `:GetFogColor`, `:SetFogNear` / `:GetFogNear`, `:SetFogFar` / `:GetFogFar`, `:ClearFog`, `:SetDrawLayer` / `:GetDrawLayer`, `:SetPaused`, `:GetSceneTime`, `:After`, `:NewTimer`, `:NewTicker`, `:SetAllowOverlappedModels` / `:GetAllowOverlappedModels`, `:SetGroundTerrain` / `:GetGroundTerrain`, `:SetWorldModel`, `:SetWorldModelByMapID`, `:ClearWorldModel`, `:SetPlayWorldModelSounds` / `:GetPlayWorldModelSounds`, `:TraceSegment`, `:GetLiquidAtPoint`, `:SweepCollider`, `:SetSoundListenerActor` / `:GetSoundListenerActor`, `:GetActorAtCursor`, `:GetCursorRay`, `:GetActorsInRadius`, `:SetMoveMarker` / `:ClearMoveMarker` / `:GetMoveMarker`, `OnActorEnter` / `OnActorLeave` |
|
||||
| [Frame: ModelSceneActor](docs/API.md#actorsetmodelbycreaturedisplayiddisplayid--useactiveplayercustomizations) | `:SetModelByCreatureDisplayID`, `:SetModelByPath`, `:SetModelByUnit`, `:SetModelByUnitCreatureDisplayID`, `:GetModelUnitGUID`, `:Dress`, `:Undress`, `:UndressSlot`, `:TryOn`, `:DressPlayerSlot`, `:IsSlotAllowed` / `:IsSlotVisible`, `:GetItemTransmogInfo`, `:GetItemTransmogInfoList`, `:SetItemTransmogInfo`, `:SetSheathed` / `:GetSheathed`, `:SetSheathedCategory`, `:UseUnitSheatheCategory`, `:SetAutoDress` / `:GetAutoDress`, `:IsGeoReady`, `:ClearModel`, `:IsLoaded`, `:GetModelPath`, `:SetPosition` / `:GetPosition`, `:SetYaw` / `:GetYaw`, `:SetPitch` / `:GetPitch`, `:SetRoll` / `:GetRoll`, `:SetScale` / `:GetScale`, `:SetUseCenterForOrigin` / `:IsUsingCenterForOrigin`, `:GetActiveBoundingBox`, `:GetMaxBoundingBox`, `:Show` / `:Hide` / `:SetShown` / `:IsShown` / `:IsVisible`, `:SetAlpha` / `:GetAlpha`, `:SetAnimation` / `:GetAnimation`, `:GetAnimationVariation`, `:SetAnimationBlendOperation` / `:GetAnimationBlendOperation`, `:SetPaused` / `:GetPaused`, `:SetSpellVisualKit` / `:GetSpellVisualKit`, `:ApplySpellVisualKit`, `:PlayAnimationKit` / `:StopAnimationKit`, `:AttachToMount`, `:DetachFromMount`, `:CalculateMountScale`, `:GetAttachmentPosition`, `:AttachToActor`, `:DetachFromActor`, `:PlaySpellVisual`, `:StopSpellVisual`, `:PlaySound`, `:SetPlayAnimationSounds` / `:GetPlayAnimationSounds`, `:SetShadowShown` / `:IsShadowShown`, `:SetHighlight` / `:IsHighlighted`, `:SetSelectionCircle` / `:ClearSelectionCircle` / `:GetSelectionCircle`, `:SetNameText` / `:ClearNameText` / `:GetNameText`, `:GetNamePosition`, `:SetMovementEnabled` / `:IsMovementEnabled`, `:StartMovingForward`, `:StartMovingBackward`, `:StopMoving`, `:StartStrafingLeft`, `:StartStrafingRight`, `:StopStrafing`, `:StartTurningLeft`, `:StartTurningRight`, `:StopTurning`, `:Jump`, `:StartPitchingUp`, `:StartPitchingDown`, `:StopPitching`, `:IsFalling`, `:IsSwimming`, `:MoveTo`, `:SetWalking` / `:IsWalking`, `:GetCameraPivotHeight`, `OnLoad`, `OnUpdate`, `OnModelLoading`, `OnModelLoaded`, `OnModelCleared`, `OnAnimFinished`, `OnSpellVisualImpact`, `OnMoveFinished`, `<Actor>` templates |
|
||||
| [Frame: ScrollingMessageFrame](docs/API.md#smfsethyperlinksenabledenabled--smfgethyperlinksenabled) | `:SetHyperlinksEnabled` / `:GetHyperlinksEnabled`, `:GetMessageInfo` (with the extra `:AddMessage` values), `:BackFillMessage` (adds a message as the oldest), `:RemoveMessagesByPredicate`, `:SetScrollOffset` / `:GetScrollOffset`, `:GetMaxScrollRange`, `:ScrollByAmount`, `:SetInsertMode` / `:GetInsertMode`, `:AddOnDisplayRefreshedCallback` |
|
||||
| [Frame: scripts](docs/API.md#onenable-and-ondisable-scripts) | `OnEnable`, `OnDisable`, `OnMinMaxChanged`, `OnAttributeChanged`, `OnCharComposition`, `OnTextChanged` (`userInput`), `PreClick`, `PostClick`, `SetModernScriptArgs` / `GetModernScriptArgs` |
|
||||
| [FriendList](docs/API.md#friendlist) | `AddOrRemoveFriend`, `C_FriendList.GetFriendInfo`, `C_FriendList.GetFriendInfoByIndex`, `C_FriendList.GetNumFriends`, `C_FriendList.GetNumOnlineFriends`, `C_FriendList.GetNumWhoResults`, `C_FriendList.GetWhoInfo`, `C_FriendList.IsFriend`, `C_FriendList.IsIgnored`, `C_FriendList.IsIgnoredByGuid`, `C_FriendList.IsWhoQueryPending`, `C_FriendList.SendWhoQueryByName`, `C_FriendList.SetFriendNotes`, `C_FriendList.SetFriendNotesByIndex` |
|
||||
@@ -96,7 +99,7 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [GameTooltip](docs/API.md#gametooltip) | `GameTooltip:AddSpellByID`, `GameTooltip:AddTexture`, `GameTooltip:GetGameObject`, `GameTooltip:GetItem`, `GameTooltip:GetMinimumWidth`, `GameTooltip:GetOwner`, `GameTooltip:GetPadding`, `GameTooltip:GetSpell`, `GameTooltip:GetUnit`, `GameTooltip:GetUnitGUID`, `GameTooltip:HasGameObject`, `GameTooltip:HasItem`, `GameTooltip:HasSpell`, `GameTooltip:HasUnit`, `GameTooltip:IsEquippedItem`, `GameTooltip:IsUnit`, `GameTooltip:SetAnchorType`, `GameTooltip:SetEquipmentSet`, `GameTooltip:SetFrameStack`, `GameTooltip:SetHyperlink` (full links, `spell:`, `quest:`), `GameTooltip:SetHyperlinkCompareItem`, `GameTooltip:SetInventoryItemByID`, `GameTooltip:SetItemByGUID`, `GameTooltip:SetItemByID`, `GameTooltip:SetSpellByID`, `GameTooltip:SetTalentByID`, `GameTooltip:SetTotem`, `GameTooltip:SetUnitAura`, `OnTooltipSetItem`, `OnTooltipSetSpell`, `OnTooltipSetUnit`, `OnTooltipSetGameObject`, `OnTooltipSetFrameStack` (scripts) |
|
||||
| [Gossip](docs/API.md#gossip) | `C_GossipInfo.CloseGossip`, `C_GossipInfo.GetActiveQuests`, `C_GossipInfo.GetAvailableQuests`, `C_GossipInfo.GetNumActiveQuests`, `C_GossipInfo.GetNumAvailableQuests`, `C_GossipInfo.GetNumOptions`, `C_GossipInfo.GetOptions`, `C_GossipInfo.GetPoiForUiMapID`, `C_GossipInfo.GetPoiInfo`, `C_GossipInfo.GetText`, `C_GossipInfo.SelectActiveQuest`, `C_GossipInfo.SelectAvailableQuest`, `C_GossipInfo.SelectOption`, `C_GossipInfo.SelectOptionByIndex`, `GetNumGossipActiveQuests`, `GetNumGossipAvailableQuests`, `GetNumGossipOptions` |
|
||||
| [Hooks](docs/API.md#hooks) | `hooksecurefunc` |
|
||||
| [Input](docs/API.md#input) | `GetMouseButtonClicked`, `GetMouseButtonName`, `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsMouseButtonDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
|
||||
| [Input](docs/API.md#input) | `GetCursorDelta`, `GetMouseButtonClicked`, `GetMouseButtonName`, `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsMouseButtonDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
|
||||
| [Instance](docs/API.md#instance) | `GetInstanceInfo`, `IsActiveBattlefieldArena` |
|
||||
| [Item: C_Item](docs/API.md#item) | `C_Item.DoesItemExist` / `C_Item.DoesItemExistByID`, `C_Item.EquipItemByName`, `C_Item.GetCurrentItemLevel`, `C_Item.GetDetailedItemLevelInfo`, `C_Item.GetEnchantInfo`, `C_Item.GetItemClassInfo`, `C_Item.GetItemCount`, `C_Item.GetItemData` / `C_Item.GetItemDataByID`, `C_Item.GetItemFamily`, `C_Item.GetItemGUID`, `C_Item.GetItemIcon` / `C_Item.GetItemIconByID`, `C_Item.GetItemID`, `C_Item.GetItemInfo`, `C_Item.GetItemInfoInstant`, `C_Item.GetItemInventorySlotInfo`, `C_Item.GetItemInventorySlotKey`, `C_Item.GetItemInventoryType` / `C_Item.GetItemInventoryTypeByID`, `C_Item.GetItemLink`, `C_Item.GetItemLocation`, `C_Item.GetItemMaxStackSize` / `C_Item.GetItemMaxStackSizeByID`, `C_Item.GetItemName` / `C_Item.GetItemNameByID`, `C_Item.GetItemQuality` / `C_Item.GetItemQualityByID`, `C_Item.GetItemSellPrice` / `C_Item.GetItemSellPriceByID`, `C_Item.GetItemSetID` / `C_Item.GetItemSetIDByID`, `C_Item.GetItemSetInfo`, `C_Item.GetItemSpell`, `C_Item.GetItemStatDelta`, `C_Item.GetItemStats`, `C_Item.GetItemSubClassInfo`, `C_Item.GetItemTempEnchantInfo`, `C_Item.GetItemUniqueness` / `C_Item.GetItemUniquenessByID`, `C_Item.GetStackCount`, `C_Item.GetWeaponEnchantInfo`, `C_Item.IsBound`, `C_Item.IsConsumableItem`, `C_Item.IsEquippableItem`, `C_Item.IsEquippedItem`, `C_Item.IsItemDataCached` / `C_Item.IsItemDataCachedByID`, `C_Item.IsItemGUIDInInventory`, `C_Item.IsItemInRange`, `C_Item.IsItemOpenable`, `C_Item.IsLocked`, `C_Item.IsUsableItem`, `C_Item.ItemHasRange`, `C_Item.LockItem`, `C_Item.LockItemByGUID`, `C_Item.UnlockItem`, `C_Item.UnlockAllItems`, `C_Item.PickupItem`, `C_Item.RequestLoadItemData` / `C_Item.RequestLoadItemDataByID`, `C_Item.UseAtCursor`, `C_Item.UseAtUnit`, `C_Item.UseItemByName` |
|
||||
| [Item: info](docs/API.md#getitemclassinfoclassid) | `GetItemClassInfo`, `GetItemSubClassInfo`, `GetItemIcon`, `IsConsumableItem`, `IsCurrentItem`, `IsDressableItem`, `IsEquippedItemType`, `IsHarmfulItem`, `IsHelpfulItem`, `IsUsableItem` |
|
||||
@@ -112,7 +115,9 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [Map](docs/API.md#map) | `C_Map.CanSetUserWaypointOnMap`, `C_Map.ClearUserWaypoint`, `C_Map.GetAreaInfo`, `C_Map.GetAreas`, `C_Map.GetAreaTriggerInfo`, `C_Map.GetAreaTriggers`, `C_Map.GetBestMapForUnit`, `C_Map.GetFallbackWorldMapID`, `C_Map.GetMapAreaIDs`, `C_Map.GetMapArtLayers`, `C_Map.GetMapArtLayerTextures`, `C_Map.GetMapChildrenInfo`, `C_Map.GetMapInfo`, `C_Map.GetMapInfoAtPosition`, `C_Map.GetMapOverlays`, `C_Map.GetMapPosFromWorldPos`, `C_Map.GetMapRectOnMap`, `C_Map.GetMapWorldSize`, `C_Map.GetPlayerMapPosition`, `C_Map.GetUserWaypoint`, `C_Map.GetUserWaypointFromHyperlink`, `C_Map.GetUserWaypointHyperlink`, `C_Map.GetUserWaypointPositionForMap`, `C_Map.GetWorldPosFromMapPos`, `C_Map.HasUserWaypoint`, `C_Map.MapHasArt`, `C_Map.SetUserWaypoint` |
|
||||
| [MapExplorationInfo](docs/API.md#mapexplorationinfo) | `C_MapExplorationInfo.GetExploredAreaIDsAtPosition`, `C_MapExplorationInfo.GetExploredMapTextures`, `C_MapExplorationInfo.GetUnexploredMapTextures` |
|
||||
| [MerchantFrame](docs/API.md#merchantframe) | `C_MerchantFrame.GetBuybackItemID`, `C_MerchantFrame.GetItemInfo`, `C_MerchantFrame.GetNumJunkItems`, `C_MerchantFrame.IsMerchantItemRefundable`, `C_MerchantFrame.IsSellAllJunkEnabled`, `C_MerchantFrame.SellAllJunkItems`, `GetBuybackItemLink` |
|
||||
| [Model](docs/API.md#model) | `DressUpModel:TryOn` (links, recipes, names, hand, enchant glow, result), `DressUpModel:UndressSlot`, `DressUpModel:IsGeoReady`, `DressUpModel:SetSheathed` / `GetSheathed`, `DressUpModel:SetAutoDress` / `GetAutoDress`, `Model:SetGlow`, `PlayerModel:RefreshCamera`, `PlayerModel:SetCamDistanceScale`, `PlayerModel:SetCreature` (`displayID`), `PlayerModel:SetDisplayInfo`, `PlayerModel:SetItem`, `PlayerModel:SetPortraitZoom`, `PlayerModel:SetRotation` (`animate`), `PlayerModel:SetUnit` (result), `PlayerModel:CanSetUnit`, `PlayerModel:GetDisplayInfo`, `PlayerModel:SetAnimation`, `PlayerModel:FreezeAnimation`, `PlayerModel:HasAnimation`, `PlayerModel:SetDoBlend` / `GetDoBlend`, `PlayerModel:SetKeepModelOnHide` / `GetKeepModelOnHide`, `PlayerModel:ApplySpellVisualKit` |
|
||||
| [Model](docs/API.md#model) | `DressUpModel:TryOn` (links, recipes, names, hand, enchant glow, result), `DressUpModel:UndressSlot`, `DressUpModel:IsGeoReady`, `DressUpModel:SetSheathed` / `GetSheathed`, `DressUpModel:SetAutoDress` / `GetAutoDress`, `DressUpModel:GetItemTransmogInfo` / `GetItemTransmogInfoList` / `SetItemTransmogInfo`, `DressUpModel:IsSlotAllowed` / `IsSlotVisible`, `Model:SetGlow`, `PlayerModel:RefreshCamera`, `PlayerModel:SetCamDistanceScale`, `PlayerModel:SetCreature` (`displayID`), `PlayerModel:SetDisplayInfo`, `PlayerModel:SetItem`, `PlayerModel:SetPortraitZoom`, `PlayerModel:SetRotation` (`animate`), `PlayerModel:SetUnit` (result), `PlayerModel:CanSetUnit`, `PlayerModel:GetDisplayInfo`, `PlayerModel:SetAnimation`, `PlayerModel:FreezeAnimation`, `PlayerModel:HasAnimation`, `PlayerModel:SetDoBlend` / `GetDoBlend`, `PlayerModel:SetKeepModelOnHide` / `GetKeepModelOnHide`, `PlayerModel:ApplySpellVisualKit` |
|
||||
| [ModelInfo](docs/API.md#c_modelinfogetmodelsceneinfobyidmodelsceneid) | `C_ModelInfo.GetModelSceneInfoByID`, `C_ModelInfo.GetModelSceneCameraInfoByID`, `C_ModelInfo.GetModelSceneActorInfoByID`, `C_ModelInfo.GetModelSceneActorDisplayInfoByID` |
|
||||
| [Mount](docs/API.md#mount) | `C_MountJournal.GetMountFromItem`, `C_MountJournal.GetMountFromSpell`, `C_MountJournal.GetMountInfoExtraByID` |
|
||||
| [NamePlate](docs/API.md#nameplate) | `C_NamePlate.GetNamePlateForGUID`, `C_NamePlate.GetNamePlateForUnit`, `C_NamePlate.GetNamePlateGUIDs`, `C_NamePlate.GetNamePlates` |
|
||||
| [NameCache](docs/API.md#namecache) | `C_PlayerCache.GetPlayerInfoByName`, `C_PlayerCache.IsEnabled`, `C_PlayerCache.RememberPlayer`, `C_PlayerCache.SetEnabled`, `GetPlayerInfoByGUID`, `UnitNameFromGUID` |
|
||||
| [NewItems](docs/API.md#newitems) | `C_NewItems.ClearAll`, `C_NewItems.IsNewItem`, `C_NewItems.RemoveNewItem` |
|
||||
@@ -161,6 +166,7 @@ the glue state because GlueXML had no way to reach them otherwise.
|
||||
| [CharacterList](docs/API.md#characterlist) | `GetSavedCharacterOrder`, `SetSavedCharacterOrder` (persist to `WTF\Account\...\ClassicAPI.txt`) |
|
||||
| CVar | `GetCVar`, `SetCVar`, `RegisterCVar`, `GetCVarDefault`, `C_CVar.GetCVarBool`, `C_CVar.SetTempCVar`, `C_CVar.RemoveTempCVar` (storage is process-global — writes from glue are visible in-world and vice versa) |
|
||||
| [Glue](docs/API.md#glue) | `C_Glue.IsFirstLoadThisSession`, `C_Glue.IsOnGlueScreen` |
|
||||
| [Input](docs/API.md#getcursordelta) | `GetCursorDelta` |
|
||||
| Script | `RunScript` (compile and run a Lua chunk in the glue state's globals — useful for slash-command-style helpers in GlueXML) |
|
||||
| State | `IsLoggedIn` |
|
||||
|
||||
@@ -221,7 +227,7 @@ open the game menu and click AddOn Options, then ClassicAPI.
|
||||
| [Combat log](docs/API.md#kept-entries) | `combatLogRetentionTime` | `300` | The number of seconds that the combat log keeps an entry. |
|
||||
| [Stand, dismount and form](docs/API.md#stand-dismount-and-form-settings-autostand-autodismount-autounshift) | `autoStand` | `0` | `1` makes you stand up when you cast, loot or open the flight map while you sit. |
|
||||
| | `autoDismount` | `0` | `1` makes you dismount when you cast, use an object, loot, attack or open the flight map while you are mounted. |
|
||||
| | `autoUnshift` | `0` | `1` makes you leave your form when you cast a spell that cannot be cast in it, talk to an NPC, use an object or an item, or open the flight map. |
|
||||
| | `autoUnshift` | `0` | `1` makes you leave your form when you cast a spell that cannot be cast in it, use an item, or open the flight map. It also lets you talk to NPCs and use objects in druid forms and Ghost Wolf. |
|
||||
| [Bag sorting](docs/API.md#c_containergetsortbagsrighttoleft--c_containersetsortbagsrighttoleftenable) | `sortBagsRightToLeft` | `0` | `1` makes a sort fill your bags from the last slot of the last bag. |
|
||||
| | `backpackAutosortDisabled` | `0` | `1` keeps the items in your backpack in place when you sort your bags. |
|
||||
| | `bankAutosortDisabled` | `0` | `1` keeps the items in the main bank slots in place when you sort your bank. |
|
||||
@@ -398,6 +404,7 @@ when launching with `-console`), not as Lua functions. See the
|
||||
| Screen location type | `Enum.ScreenLocationType.{Center,Left,Right,Top,Bottom,TopLeft,TopRight,LeftOutside,RightOutside,LeftRight,TopBottom,LeftRightOutside}` (0–11) |
|
||||
| Self-resurrect option type | `Enum.SelfResurrectOptionType.{Spell,Item}` (0–1) |
|
||||
| Autocomplete entry flag | `Enum.AutoCompleteEntryFlag.{InGroup,InGuild,Friend,Bnet,InteractedWith,Online,InAOI,AccountCharacter,RecentPlayer}` (bits 1–256) |
|
||||
| Model blend operation | `Enum.ModelBlendOperation.{None,Anim}` (0–1) |
|
||||
| Autocomplete priority | `Enum.AutoCompletePriority.{Other,Interacted,InGroup,Guild,Friend,AccountCharacter,AccountCharacterSameRealm}` (0–6) |
|
||||
|
||||
</details>
|
||||
|
||||
+2807
-936
File diff suppressed because it is too large
Load Diff
+7
-4
@@ -80,10 +80,13 @@ static void __stdcall LoadGlueScriptFunctions_h() {
|
||||
}
|
||||
|
||||
// Every Lua-side `frame:RegisterEvent(...)` is a chance to claim a slot
|
||||
// for any custom event still waiting. By this point the engine's table
|
||||
// is fully populated and SuperWoWhook / other DLLs have done their
|
||||
// post-rebuild writes, so the table state is settled and our walk from the
|
||||
// low end finds genuinely free slots.
|
||||
// for any custom event still waiting. By the first FrameXML registration
|
||||
// the engine's table is fully populated and SuperWoWhook / other DLLs have
|
||||
// done their post-rebuild writes, so the walk from the low end finds
|
||||
// genuinely free slots. A registration that arrives EARLIER (another DLL's
|
||||
// frame, between LoadScriptFunctions and the table build — issue #134)
|
||||
// still sees glue's 26-entry table; `RetryClaims` refuses that table and
|
||||
// re-validates its cached slots against the live one on every call.
|
||||
static void __fastcall FrameRegisterEvent_h(void *frame, void *edx,
|
||||
const char *eventName) {
|
||||
Event::Custom::RetryClaims();
|
||||
|
||||
+29
-16
@@ -30,15 +30,23 @@ float PixelToInternal(float px) {
|
||||
const float div = Read<float>(Offsets::VAR_COORD_ASPECT_COMPENSATION);
|
||||
if (div == 0.0f)
|
||||
return 0.0f;
|
||||
return px * mul / (div * static_cast<float>(Offsets::UI_COORD_SCALE_UNIT));
|
||||
return px * mul / (div * Read<float>(Offsets::FLOAT_SCRIPT_COORD_SCALE));
|
||||
}
|
||||
|
||||
double InternalToPixel(float internal) {
|
||||
const float mul = Read<float>(Offsets::VAR_COORD_DDC_WIDTH_SCALE);
|
||||
const float div = Read<float>(Offsets::VAR_COORD_ASPECT_COMPENSATION);
|
||||
if (mul == 0.0f)
|
||||
using GetScaledScreenAspect_t = float(__cdecl *)();
|
||||
// Declared double so the quotient is read from ST0, as the engine's
|
||||
// pushes store it with `fstp qword`, not narrowed to float.
|
||||
using DivideByScreenScaleX_t = double(__stdcall *)(float);
|
||||
if (Read<float>(Offsets::VAR_COORD_DDC_WIDTH_SCALE) == 0.0f)
|
||||
return 0.0;
|
||||
return static_cast<double>(internal) * div * Offsets::UI_COORD_SCALE_UNIT / mul;
|
||||
// `aspect * 1024` is exact, so the float product rounds once, as the
|
||||
// engine's `fstp dword` does.
|
||||
const float aspect =
|
||||
reinterpret_cast<GetScaledScreenAspect_t>(Offsets::FUN_COORD_GET_SCALED_SCREEN_ASPECT)();
|
||||
const float scaled = aspect * Read<float>(Offsets::FLOAT_SCRIPT_COORD_SCALE) * internal;
|
||||
return reinterpret_cast<DivideByScreenScaleX_t>(Offsets::FUN_COORD_DIVIDE_BY_SCREEN_SCALE_X)(
|
||||
scaled);
|
||||
}
|
||||
|
||||
bool StringToFlag(const char *text) {
|
||||
@@ -252,6 +260,12 @@ void *ResolveEditBox(void *L, int idx, bool raiseError) {
|
||||
void *ResolveModel(void *L, int idx, bool raiseError) {
|
||||
return ResolveTypedObject(L, idx, Offsets::VAR_MODEL_LUA_TYPE_ID, raiseError);
|
||||
}
|
||||
void *ResolvePlayerModel(void *L, int idx, bool raiseError) {
|
||||
return ResolveTypedObject(L, idx, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID, raiseError);
|
||||
}
|
||||
void *ResolveDressUpModel(void *L, int idx, bool raiseError) {
|
||||
return ResolveTypedObject(L, idx, Offsets::VAR_DRESSUPMODEL_LUA_TYPE_ID, raiseError);
|
||||
}
|
||||
|
||||
// Looks up `_G[name]`. If absent, creates a fresh table and binds it.
|
||||
// Leaves the resulting table on top of the stack.
|
||||
@@ -361,11 +375,11 @@ void RegisterGlueFunction(const char *name, CFunction func, const Doc::Function
|
||||
Api::Documentation::RecordGlobal(name, doc);
|
||||
}
|
||||
|
||||
void RegisterFrameMethods(void *context, const FrameMethodEntry *table, int count,
|
||||
const Doc::Method *docs, int docCount) {
|
||||
void RegisterFrameMethods(void *context, ArrayRef<FrameMethodEntry> methods,
|
||||
Doc::MethodList docs) {
|
||||
auto fn = reinterpret_cast<RegisterFrameMethods_t>(Offsets::FUN_REGISTER_FRAME_METHODS);
|
||||
fn(table, count, context);
|
||||
Api::Documentation::RecordFrameMethods(context, table, count, docs, docCount);
|
||||
fn(methods.data, methods.count, context);
|
||||
Api::Documentation::RecordFrameMethods(context, methods, docs);
|
||||
}
|
||||
|
||||
// Registers `func` at `_G[tableName][methodName]`. If the namespace
|
||||
@@ -431,17 +445,16 @@ static bool SetIntegerTable(const char *parent, const char *sub,
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegisterIntegerEnum(const char *parent, const char *sub,
|
||||
const EnumIntegerEntry *entries, int count,
|
||||
void RegisterIntegerEnum(const char *parent, const char *sub, ArrayRef<EnumIntegerEntry> entries,
|
||||
const char *docSystem) {
|
||||
if (SetIntegerTable(parent, sub, entries, count))
|
||||
Api::Documentation::RecordEnum(parent, sub, entries, count, docSystem);
|
||||
if (SetIntegerTable(parent, sub, entries.data, entries.count))
|
||||
Api::Documentation::RecordEnum(parent, sub, entries, docSystem);
|
||||
}
|
||||
|
||||
void RegisterConstants(const char *sub, const EnumIntegerEntry *entries, int count,
|
||||
void RegisterConstants(const char *sub, ArrayRef<EnumIntegerEntry> entries,
|
||||
const char *docSystem) {
|
||||
if (SetIntegerTable("Constants", sub, entries, count))
|
||||
Api::Documentation::RecordConstants(sub, entries, count, docSystem);
|
||||
if (SetIntegerTable("Constants", sub, entries.data, entries.count))
|
||||
Api::Documentation::RecordConstants(sub, entries, docSystem);
|
||||
}
|
||||
|
||||
void SetFieldNumber(void *L, const char *key, double value) {
|
||||
|
||||
+49
-18
@@ -58,6 +58,18 @@ inline T &Ref(uintptr_t address) {
|
||||
return *reinterpret_cast<T *>(address);
|
||||
}
|
||||
|
||||
// An engine constant at a fixed VA, read each time it is used, so the value is
|
||||
// the one the engine's own code reads: a runtime patch shows, and so does a
|
||||
// global the engine's static initializers fill after this DLL loads. Converts
|
||||
// to T, so it stands in a float expression as a float would.
|
||||
//
|
||||
// const Game::Live<float> kSkin{Offsets::VAR_MOVER_SKIN};
|
||||
template <typename T>
|
||||
struct Live {
|
||||
uintptr_t address;
|
||||
operator T() const { return Read<T>(address); }
|
||||
};
|
||||
|
||||
// Calls `fn(object)` for each object of one of a frame's intrusive lists,
|
||||
// OFF_FRAME_REGION_LIST or OFF_FRAME_CHILD_LIST: node+4 next, node+8 object,
|
||||
// a NULL or low-bit-1 link ends it - the walk the engine's own frame code
|
||||
@@ -89,8 +101,10 @@ void *ResolveUnitToken(const char *token);
|
||||
|
||||
// UI pixels <-> internal layout (anchor) units. Script_SetPoint converts its
|
||||
// pixel offsets with `px * [VAR_COORD_DDC_WIDTH_SCALE] / ([VAR_COORD_ASPECT_COMPENSATION]
|
||||
// * UI_COORD_SCALE_UNIT)`; Script_GetLeft and Script_GetStringWidth push the
|
||||
// inverse. A region's LOCAL pixels must be multiplied by its effective scale
|
||||
// * [FLOAT_SCRIPT_COORD_SCALE])`; every script coordinate push (GetLeft,
|
||||
// GetStringWidth, GetFont's height, GetCursorPosition, OnCursorChanged) is the
|
||||
// inverse, `DivideByScreenScaleX(GetScaledScreenAspect() * 1024 * value)`,
|
||||
// which InternalToPixel calls in that order. A region's LOCAL pixels must be multiplied by its effective scale
|
||||
// (OFF_REGION_EFFECTIVE_SCALE) first. Both return 0 while the scale globals
|
||||
// are unset, so an early caller gets a zero offset rather than a wild one.
|
||||
float PixelToInternal(float px);
|
||||
@@ -147,6 +161,18 @@ int StrCmpI(const char *a, const char *b, int n = 0x7FFFFFFF);
|
||||
// literals + file-scope arrays): no heap, no dynamic init.
|
||||
// - Summaries are user-facing text: one sentence, plain words, present
|
||||
// tense, no patch versions.
|
||||
|
||||
// A view over a file-scope `const T[]` that takes its count from the array's
|
||||
// type, so no call site passes one; `{}` means none.
|
||||
template <typename T>
|
||||
struct ArrayRef {
|
||||
const T *data = nullptr;
|
||||
int count = 0;
|
||||
constexpr ArrayRef() = default;
|
||||
template <size_t N>
|
||||
constexpr ArrayRef(const T (&a)[N]) : data(a), count(static_cast<int>(N)) {}
|
||||
};
|
||||
|
||||
namespace Doc {
|
||||
|
||||
struct Field {
|
||||
@@ -172,14 +198,7 @@ constexpr Field Vararg(const char *type, const char *doc = nullptr) {
|
||||
return {"...", type, true, nullptr, doc};
|
||||
}
|
||||
|
||||
// A view over a file-scope `const Field[]`; `{}` means none.
|
||||
struct FieldList {
|
||||
const Field *data = nullptr;
|
||||
int count = 0;
|
||||
constexpr FieldList() = default;
|
||||
template <size_t N>
|
||||
constexpr FieldList(const Field (&a)[N]) : data(a), count(static_cast<int>(N)) {}
|
||||
};
|
||||
using FieldList = ArrayRef<Field>;
|
||||
|
||||
// A registered function. Positional init: `{summary, args, rets}` for a
|
||||
// table function, `{summary, args, rets, "XGlobals"}` for a global, and
|
||||
@@ -200,6 +219,7 @@ struct Method {
|
||||
const char *name;
|
||||
const Function *doc;
|
||||
};
|
||||
using MethodList = ArrayRef<Method>;
|
||||
|
||||
// A table shape a function returns or accepts → `Tables{Type="Structure"}`
|
||||
// in `system`. Self-registering: nothing binds a structure to Lua, so it
|
||||
@@ -240,7 +260,9 @@ constexpr int UpvalueIndex(int i) { return GLOBALS_INDEX - i; }
|
||||
// `lua_call` / `lua_pcall` nresults sentinel meaning "all".
|
||||
constexpr int MULTRET = -1;
|
||||
|
||||
// Type tag values returned by `Type()` (lua_type).
|
||||
// Type tag values returned by `Type()` (lua_type). TYPE_NONE is an absent
|
||||
// argument (past the top of the stack).
|
||||
constexpr int TYPE_NONE = -1;
|
||||
constexpr int TYPE_NIL = 0;
|
||||
constexpr int TYPE_BOOLEAN = 1;
|
||||
constexpr int TYPE_LIGHTUSERDATA = 2;
|
||||
@@ -479,6 +501,8 @@ void *ResolveTexture(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolveFontString(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolveEditBox(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolveModel(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolvePlayerModel(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolveDressUpModel(void *L, int idx = 1, bool raiseError = true);
|
||||
|
||||
// Every name these registrars bind is ALSO bound under `_G.ClassicAPI` —
|
||||
// `ClassicAPI.GetSpellInfo`, `ClassicAPI.C_Item.IsBound` — as an escape
|
||||
@@ -523,10 +547,11 @@ struct FrameMethodEntry {
|
||||
|
||||
// Registers a batch of methods on a per-frame-type registry (e.g.
|
||||
// GameTooltipMethodRegistry for `tooltip:Foo()` calls). `context` is the
|
||||
// registry address — see Offsets::VAR_*_METHOD_REGISTRY. `docs` pairs
|
||||
// descriptors to the entries by NAME (any order, any subset).
|
||||
void RegisterFrameMethods(void *context, const FrameMethodEntry *table, int count,
|
||||
const Doc::Method *docs = nullptr, int docCount = 0);
|
||||
// registry address — see Offsets::VAR_*_METHOD_REGISTRY. `methods` and
|
||||
// `docs` are file-scope arrays; `docs` pairs descriptors to the entries by
|
||||
// NAME (any order, any subset).
|
||||
void RegisterFrameMethods(void *context, ArrayRef<FrameMethodEntry> methods,
|
||||
Doc::MethodList docs = {});
|
||||
|
||||
// Registers `func` at `_G[tableName][methodName]`, creating the namespace
|
||||
// table if it doesn't already exist. This is how modern WoW C_*-style APIs
|
||||
@@ -557,14 +582,14 @@ struct EnumIntegerEntry {
|
||||
// `Enum.AddOnSecurityStatus = { Secure=0, Insecure=1, ... }` shapes.
|
||||
// `docSystem` is the documentation System the enumeration belongs to
|
||||
// ("SpellBook", "Item", …); the entries themselves are the documentation.
|
||||
void RegisterIntegerEnum(const char *parent, const char *sub,
|
||||
const EnumIntegerEntry *entries, int count,
|
||||
// `entries` is a file-scope array.
|
||||
void RegisterIntegerEnum(const char *parent, const char *sub, ArrayRef<EnumIntegerEntry> entries,
|
||||
const char *docSystem = nullptr);
|
||||
|
||||
// Registers `Constants[sub] = { entries }`, Blizzard's engine constant
|
||||
// tables (`Constants.MoneyFormattingConstants`). Documented as a "Constants"
|
||||
// table of `docSystem`.
|
||||
void RegisterConstants(const char *sub, const EnumIntegerEntry *entries, int count,
|
||||
void RegisterConstants(const char *sub, ArrayRef<EnumIntegerEntry> entries,
|
||||
const char *docSystem);
|
||||
|
||||
// Set `t[key] = value` on the table currently at stack[-1] (the most
|
||||
@@ -777,6 +802,12 @@ bool RunHookRegistrations();
|
||||
// issue #33, Frame::Attributes). A pure-C++ cache keyed by spellID/itemID is
|
||||
// NOT fragile and must not register.
|
||||
//
|
||||
// It runs AFTER the frames are gone: CGGameUI::Shutdown destroys every frame
|
||||
// (and each Model frame's scene with every model in it) before
|
||||
// CGGameUI::Initialize resets the Lua state. So a cleanup here only FORGETS
|
||||
// pointers; it never releases an engine object tied to a frame. Release those
|
||||
// when the frame dies (Model::Teardown for Model frames).
|
||||
//
|
||||
// Same lifetime + ordering rules as ModuleAutoRegister: static-init chaining,
|
||||
// undefined cross-TU order — fine, because each callback only clears its OWN
|
||||
// state. This is the "clear stale state" category only; a save-before-teardown
|
||||
|
||||
+1617
-33
File diff suppressed because it is too large
Load Diff
+1
-3
@@ -170,9 +170,7 @@ static void RegisterLuaFunctions() {
|
||||
&Script_HasSpellActionButtons, &kHasSpellActionButtons);
|
||||
Game::Lua::RegisterTableFunction("C_ActionBar", "IsOnBarOrSpecialBar",
|
||||
&Script_IsOnBarOrSpecialBar, &kIsOnBarOrSpecialBar);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ActionBarSet", kActionBarSet,
|
||||
static_cast<int>(sizeof(kActionBarSet) / sizeof(kActionBarSet[0])),
|
||||
"ActionBar");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ActionBarSet", kActionBarSet, "ActionBar");
|
||||
}
|
||||
|
||||
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
#include "addons/TocRewrite.h"
|
||||
#include "bindings/Inject.h"
|
||||
#include "embed/Resource.h"
|
||||
#include "storm/Memory.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
@@ -153,8 +154,6 @@ Embed::Bytes LookupEmbedded(const char *relPath) {
|
||||
// by the caller's standard SMemFree in turn.
|
||||
using AddOns::EngineIO::FileExistsFn;
|
||||
using AddOns::EngineIO::FileReadFn;
|
||||
using AddOns::EngineIO::SMemAllocFn;
|
||||
using AddOns::EngineIO::SMemFreeFn;
|
||||
|
||||
// The trampolines to the original FUN_FILE_READ / FUN_FILE_EXISTS (installed
|
||||
// by the co-hooks below).
|
||||
@@ -277,8 +276,7 @@ void DecideSource(int addon) {
|
||||
// Free the disk buffer we just borrowed — we only needed it for
|
||||
// the version-line scrape. The actual content for the engine's
|
||||
// TOC read comes through the regular hook path below.
|
||||
auto SMemFree = reinterpret_cast<SMemFreeFn>(Offsets::FUN_STORM_SMEM_FREE);
|
||||
SMemFree(diskBuf, __FILE__, __LINE__, 0);
|
||||
Storm::Free(diskBuf, __FILE__, __LINE__);
|
||||
|
||||
// Missing or unparseable disk version → assume it's older than
|
||||
// anything we ship, embedded wins.
|
||||
@@ -320,10 +318,8 @@ int ReadAddonFile(int addon, const char *relPath, int unused, const char *path,
|
||||
flag1, flag2);
|
||||
}
|
||||
|
||||
auto SMemAlloc = reinterpret_cast<SMemAllocFn>(
|
||||
Offsets::FUN_STORM_SMEM_ALLOC);
|
||||
const size_t totalSize = entry.size + extraBytes;
|
||||
void *buf = SMemAlloc(totalSize, __FILE__, __LINE__, 0);
|
||||
void *buf = Storm::Alloc(totalSize, __FILE__, __LINE__);
|
||||
if (buf == nullptr) return 0;
|
||||
std::memcpy(buf, entry.data, entry.size);
|
||||
if (extraBytes > 0) {
|
||||
|
||||
@@ -32,10 +32,6 @@ using FileReadFn = int(__stdcall *)(int unused, const char *path, void **outBuf,
|
||||
// FUN_FILE_EXISTS (0x00648A30) — __stdcall, RET 8.
|
||||
using FileExistsFn = int(__stdcall *)(const char *path, int mode);
|
||||
|
||||
// FUN_STORM_SMEM_ALLOC / FUN_STORM_SMEM_FREE — __stdcall, RET 0x10. Buffers
|
||||
// FUN_FILE_READ hands out are freed with SMemFreeFn; a buffer we allocate with
|
||||
// SMemAllocFn is freed cleanly by the engine's own SMemFree in turn.
|
||||
using SMemAllocFn = void *(__stdcall *)(size_t size, const char *file, int line, int flags);
|
||||
using SMemFreeFn = void(__stdcall *)(void *buf, const char *file, int line, int flags);
|
||||
// Buffers FUN_FILE_READ hands out are freed with Storm::Free (storm/Memory.h).
|
||||
|
||||
} // namespace AddOns::EngineIO
|
||||
|
||||
+1
-5
@@ -408,11 +408,7 @@ static void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterTableFunction("C_AddOns", "GetAddOnInterfaceVersion",
|
||||
&Script_GetAddOnInterfaceVersion,
|
||||
&kGetAddOnInterfaceVersion);
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "AddOnSecurityStatus",
|
||||
kAddOnSecurityStatusEntries,
|
||||
sizeof(kAddOnSecurityStatusEntries) /
|
||||
sizeof(kAddOnSecurityStatusEntries[0]));
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "AddOnSecurityStatus", kAddOnSecurityStatusEntries);
|
||||
}
|
||||
|
||||
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "addons/EngineIO.h"
|
||||
#include "addons/Toc.h"
|
||||
#include "luasyntax/AddonNamespace.h"
|
||||
#include "storm/Memory.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -51,7 +52,6 @@ using IsLoadedFn_t = uint8_t(__fastcall *)(const char *name);
|
||||
// FUN_FILE_READ address routes through Embedded's file-read hook, which forwards
|
||||
// every non-`!!!ClassicAPI` path to the original untouched — what we want here.
|
||||
using AddOns::EngineIO::FileReadFn;
|
||||
using AddOns::EngineIO::SMemFreeFn;
|
||||
|
||||
// True iff the addon's `.toc` has a line `## AllowAddOnTableAccess: <nonzero>`.
|
||||
// The value is read as a TOC boolean flag: parse the leading integer, true iff
|
||||
@@ -87,8 +87,7 @@ bool TocAllowsTableAccess(const char *name) {
|
||||
allowed = anyDigit && value != 0;
|
||||
}
|
||||
|
||||
auto smemFree = reinterpret_cast<SMemFreeFn>(Offsets::FUN_STORM_SMEM_FREE);
|
||||
smemFree(buf, __FILE__, __LINE__, 0);
|
||||
Storm::Free(buf, __FILE__, __LINE__);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
#include "addons/Embedded.h"
|
||||
#include "addons/EngineIO.h"
|
||||
#include "addons/Registry.h"
|
||||
#include "storm/Memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
@@ -235,9 +236,7 @@ bool TryHashToc(const char *name, uint32_t *outHash) {
|
||||
if (!ReadToc(name, &buf, &size))
|
||||
return false;
|
||||
*outHash = HashTocMetadata(static_cast<const char *>(buf), size);
|
||||
reinterpret_cast<AddOns::EngineIO::SMemFreeFn>(
|
||||
static_cast<uintptr_t>(Offsets::FUN_STORM_SMEM_FREE))(
|
||||
buf, __FILE__, __LINE__, 0);
|
||||
Storm::Free(buf, __FILE__, __LINE__);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "addons/EngineIO.h"
|
||||
#include "addons/Toc.h"
|
||||
#include "script/LoadFile.h"
|
||||
#include "storm/Memory.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -66,7 +67,6 @@ constexpr size_t NPOS = static_cast<size_t>(-1);
|
||||
// FlavorToc applies), the exists-check, and the Storm free. See addons/EngineIO.h.
|
||||
using AddOns::EngineIO::FileExistsFn;
|
||||
using AddOns::EngineIO::FileReadFn;
|
||||
using AddOns::EngineIO::SMemFreeFn;
|
||||
using LoadTocFilesFn = uint32_t(__fastcall *)(char *tocPath, int *a2, int *a3);
|
||||
using NameFn = const char *(*)(); // no-arg readers (return in EAX)
|
||||
|
||||
@@ -160,8 +160,7 @@ SvFlags ReadSvFlags(const char *tocPath) {
|
||||
f.perChar =
|
||||
AddOns::Toc::FindValue(b, size, "SavedVariablesPerCharacter", &v, &n) && n > 0;
|
||||
|
||||
reinterpret_cast<SMemFreeFn>(static_cast<uintptr_t>(Offsets::FUN_STORM_SMEM_FREE))(
|
||||
buf, __FILE__, __LINE__, 0);
|
||||
Storm::Free(buf, __FILE__, __LINE__);
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
#include "addons/EngineIO.h"
|
||||
#include "addons/TocRewrite.h"
|
||||
#include "addons/Toc.h"
|
||||
#include "localization/Locale.h"
|
||||
|
||||
#include "Offsets.h"
|
||||
#include "storm/Memory.h"
|
||||
#include "turtle/Detect.h"
|
||||
|
||||
#include <cstddef>
|
||||
@@ -27,24 +29,12 @@ namespace AddOns::TocRewrite {
|
||||
|
||||
namespace {
|
||||
|
||||
using AddOns::EngineIO::SMemAllocFn;
|
||||
using AddOns::EngineIO::SMemFreeFn;
|
||||
using AddOns::Toc::EqCI;
|
||||
using AddOns::Toc::Lower;
|
||||
|
||||
bool IsSpace(char c) { return c == ' ' || c == '\t'; }
|
||||
|
||||
// The client locale code ("enUS", "frFR", …) — the exact string
|
||||
// GetLocale() returns (Offsets.h VAR_LOCALE_NAME_TABLE derivation).
|
||||
const char *ClientLocale() {
|
||||
uint32_t idx = *reinterpret_cast<const uint32_t *>(
|
||||
static_cast<uintptr_t>(Offsets::VAR_LOCALE_INDEX));
|
||||
if (idx > 7) idx = 0; // slot 8 overreads the string pool — clamp
|
||||
const char *const *table = reinterpret_cast<const char *const *>(
|
||||
static_cast<uintptr_t>(Offsets::VAR_LOCALE_NAME_TABLE));
|
||||
const char *s = table[idx];
|
||||
return (s != nullptr) ? s : "enUS";
|
||||
}
|
||||
using Localization::Locale::Client;
|
||||
|
||||
// True iff the comma-separated `list` (spaces allowed) contains `token` (CI).
|
||||
bool ListContains(const char *list, size_t listLen, const char *token) {
|
||||
@@ -66,7 +56,7 @@ bool ListContains(const char *list, size_t listLen, const char *token) {
|
||||
const char *VariableValue(const char *head, size_t headLen) {
|
||||
if (EqCI(head, headLen, "Family")) return "Classic";
|
||||
if (EqCI(head, headLen, "Game")) return "Vanilla";
|
||||
if (EqCI(head, headLen, "TextLocale")) return ClientLocale();
|
||||
if (EqCI(head, headLen, "TextLocale")) return Client();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -158,7 +148,7 @@ bool EvalCondition(const char *head, size_t headLen, const char *args,
|
||||
if (EqCI(head, headLen, "ExcludeLoadGameType"))
|
||||
return !ListContainsGameType(args, argsLen);
|
||||
if (EqCI(head, headLen, "AllowLoadTextLocale"))
|
||||
return ListContains(args, argsLen, ClientLocale());
|
||||
return ListContains(args, argsLen, Client());
|
||||
if (EqCI(head, headLen, "AllowLoad"))
|
||||
return ListContains(args, argsLen, "game"); // addon files: in-game only
|
||||
return false;
|
||||
@@ -297,15 +287,13 @@ void Transform(const char *path, void **outBuf, size_t *outSize) {
|
||||
std::memcmp(rebuilt.data(), content, size) == 0)
|
||||
return; // brackets/commas were only in unaffected lines — nothing changed
|
||||
|
||||
auto SMemAlloc = reinterpret_cast<SMemAllocFn>(Offsets::FUN_STORM_SMEM_ALLOC);
|
||||
auto SMemFree = reinterpret_cast<SMemFreeFn>(Offsets::FUN_STORM_SMEM_FREE);
|
||||
const size_t newLen = rebuilt.size();
|
||||
void *buf = SMemAlloc(newLen + 1, __FILE__, __LINE__, 0); // +1 NUL (extraBytes=1)
|
||||
void *buf = Storm::Alloc(newLen + 1, __FILE__, __LINE__); // +1 NUL (extraBytes=1)
|
||||
if (buf == nullptr) return; // OOM — keep the original
|
||||
std::memcpy(buf, rebuilt.data(), newLen);
|
||||
static_cast<char *>(buf)[newLen] = '\0';
|
||||
|
||||
SMemFree(*outBuf, __FILE__, __LINE__, 0);
|
||||
Storm::Free(*outBuf, __FILE__, __LINE__);
|
||||
*outBuf = buf;
|
||||
if (outSize != nullptr) *outSize = newLen;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "frame/Layout.h"
|
||||
#include "frame/Alpha.h"
|
||||
#include "frame/Destruction.h"
|
||||
#include "texture/Transform.h"
|
||||
@@ -115,7 +116,6 @@ using SetColor_t = void(__fastcall *)(void *region, void *edx, const uint32_t *b
|
||||
using StoreCorners_t = void(__fastcall *)(void *region, void *edx, float *rect);
|
||||
using Crop_t = void(__fastcall *)(void *region, void *edx, float *rect);
|
||||
using SetStringPos_t = void(__fastcall *)(void *block, const float *position);
|
||||
using NdcToDdc_t = void(__fastcall *)(float *outX, float *outY, float x, float y);
|
||||
using SizeChanged_t = void(__fastcall *)(void *layout, void *edx, const float *oldRect);
|
||||
using InvalidateNode_t = void(__fastcall *)(void *node);
|
||||
|
||||
@@ -216,7 +216,7 @@ bool GetStringPos(void *block, float out[3]) {
|
||||
if (node == nullptr)
|
||||
return false;
|
||||
const float *position = Game::Ptr<const float>(node, Offsets::OFF_TEXT_NODE_POSITION);
|
||||
reinterpret_cast<NdcToDdc_t>(Offsets::FUN_COORD_NDC_TO_DDC)(&out[0], &out[1], position[0], position[1]);
|
||||
Frame::Layout::NdcToDdc(&out[0], &out[1], position[0], position[1]);
|
||||
out[2] = position[2];
|
||||
return true;
|
||||
}
|
||||
@@ -560,9 +560,8 @@ void RegisterLuaFunctions() {
|
||||
RegisterClass(Anim::s_rotationClass);
|
||||
RegisterClass(Anim::s_pathClass);
|
||||
RegisterClass(ControlPoint::s_class);
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), kRegionMethods,
|
||||
static_cast<int>(sizeof(kRegionMethods) / sizeof(kRegionMethods[0])),
|
||||
kRegionDocs, static_cast<int>(sizeof(kRegionDocs) / sizeof(kRegionDocs[0])));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), kRegionMethods, kRegionDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -59,6 +59,8 @@ bool FloatAttr(const void *node, const char *name, float &out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// object[parentKey] = child, as 4.3.4's LoadXML does for both.
|
||||
void BindParentKey(void *object, Object *child, const char *key) {
|
||||
if (key == nullptr || *key == '\0')
|
||||
@@ -145,6 +147,8 @@ void LoadKeyValues(Object *object, const void *node, void *status) {
|
||||
Game::Lua::SetTop(L, top);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// LoadXML_AnimOrigin: <Origin point="..."><Offset x= y=/></Origin>.
|
||||
void LoadOrigin(Anim *anim, const void *node) {
|
||||
const void *origin = FindChild(node, "Origin");
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
// (RegisterXMLTemplate), whatever its tag, so `inherits` resolves through
|
||||
// Xml::Template as 4.3.4's XMLHashNodeRef does.
|
||||
namespace Animation {
|
||||
class Object;
|
||||
class Group;
|
||||
class Anim;
|
||||
class ControlPoint;
|
||||
@@ -39,4 +40,13 @@ void LoadControlPoint(ControlPoint *point, const void *node, void *status);
|
||||
// The template node `name` resolves to, or nullptr.
|
||||
const void *Template(const char *name);
|
||||
|
||||
// What every script object's element shares (Model::Scene's actors too):
|
||||
// object[key] = child for parentKey, an append to object[key] for
|
||||
// parentArray, the <Scripts> (a body, a `function` global or a `method`
|
||||
// field) and the <KeyValues>.
|
||||
void BindParentKey(void *object, Object *child, const char *key);
|
||||
void BindParentArray(void *object, Object *child, const char *key);
|
||||
void LoadScripts(Object *object, const void *node);
|
||||
void LoadKeyValues(Object *object, const void *node, void *status);
|
||||
|
||||
} // namespace Animation::Xml
|
||||
|
||||
@@ -665,10 +665,15 @@ void RecordTable(const char *table, const char *name, const Function *doc) {
|
||||
Record(KIND_TABLE, table, name, nullptr, doc);
|
||||
}
|
||||
|
||||
void RecordFrameMethods(const void *registry, const Game::Lua::FrameMethodEntry *table,
|
||||
int count, const Method *docs, int docCount) {
|
||||
void RecordFrameMethods(const void *registry,
|
||||
Game::ArrayRef<Game::Lua::FrameMethodEntry> methods,
|
||||
Game::Doc::MethodList docList) {
|
||||
if (g_pass == ENV_NONE)
|
||||
return;
|
||||
const Game::Lua::FrameMethodEntry *table = methods.data;
|
||||
const int count = methods.count;
|
||||
const Method *docs = docList.data;
|
||||
const int docCount = docList.count;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const Function *doc = nullptr;
|
||||
for (int d = 0; d < docCount; ++d) {
|
||||
@@ -720,14 +725,14 @@ void RecordValueTable(Kind kind, const char *parent, const char *sub,
|
||||
|
||||
} // namespace
|
||||
|
||||
void RecordEnum(const char *parent, const char *sub, const Game::Lua::EnumIntegerEntry *entries,
|
||||
int count, const char *system) {
|
||||
RecordValueTable(KIND_ENUM, parent, sub, entries, count, system);
|
||||
void RecordEnum(const char *parent, const char *sub,
|
||||
Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries, const char *system) {
|
||||
RecordValueTable(KIND_ENUM, parent, sub, entries.data, entries.count, system);
|
||||
}
|
||||
|
||||
void RecordConstants(const char *sub, const Game::Lua::EnumIntegerEntry *entries, int count,
|
||||
void RecordConstants(const char *sub, Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries,
|
||||
const char *system) {
|
||||
RecordValueTable(KIND_CONSTANTS, "Constants", sub, entries, count, system);
|
||||
RecordValueTable(KIND_CONSTANTS, "Constants", sub, entries.data, entries.count, system);
|
||||
}
|
||||
|
||||
} // namespace Api::Documentation
|
||||
|
||||
@@ -32,14 +32,15 @@ void EndPass();
|
||||
|
||||
void RecordGlobal(const char *name, const Game::Doc::Function *doc);
|
||||
void RecordTable(const char *table, const char *name, const Game::Doc::Function *doc);
|
||||
void RecordFrameMethods(const void *registry, const Game::Lua::FrameMethodEntry *table,
|
||||
int count, const Game::Doc::Method *docs, int docCount);
|
||||
void RecordFrameMethods(const void *registry,
|
||||
Game::ArrayRef<Game::Lua::FrameMethodEntry> methods,
|
||||
Game::Doc::MethodList docs);
|
||||
// Names the widget system of a method hash this DLL owns (a frame class it
|
||||
// adds), alongside the engine registries the exporter already knows.
|
||||
void NameWidgetSystem(const void *registry, const char *system);
|
||||
void RecordEnum(const char *parent, const char *sub, const Game::Lua::EnumIntegerEntry *entries,
|
||||
int count, const char *system);
|
||||
void RecordConstants(const char *sub, const Game::Lua::EnumIntegerEntry *entries, int count,
|
||||
void RecordEnum(const char *parent, const char *sub,
|
||||
Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries, const char *system);
|
||||
void RecordConstants(const char *sub, Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries,
|
||||
const char *system);
|
||||
|
||||
} // namespace Api::Documentation
|
||||
|
||||
+2
-9
@@ -1023,16 +1023,9 @@ CompressState *FindOrClaimCompressState(uint64_t targetGuid,
|
||||
// would compress it, this gate excludes it exactly as the server's does.
|
||||
// (Drain Soul passes via its effect-2 periodic damage: aura=[86,3,0].)
|
||||
bool HasPeriodicDamageOrLeech(const uint8_t *rec) {
|
||||
if (rec == nullptr)
|
||||
return false;
|
||||
constexpr int32_t kPeriodicDamage = 3, kPeriodicLeech = 53;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const int32_t aura = *reinterpret_cast<const int32_t *>(
|
||||
rec + Offsets::OFF_SPELL_RECORD_EFFECT_APPLY_AURA_NAME + i * 4);
|
||||
if (aura == kPeriodicDamage || aura == kPeriodicLeech)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return Spell::Lookup::HasAuraEffect(rec, kPeriodicDamage) ||
|
||||
Spell::Lookup::HasAuraEffect(rec, kPeriodicLeech);
|
||||
}
|
||||
|
||||
// Compression pairs by CASTER, mirroring the server (each warlock's Dark
|
||||
|
||||
@@ -457,12 +457,10 @@ static void RegisterLuaFunctions() {
|
||||
&Script_GetAutoCompleteResults, &kGetAutoCompleteResults);
|
||||
Game::Lua::RegisterTableFunction("C_AutoComplete", "IsRecognizedName", &Script_IsRecognizedName,
|
||||
&kIsRecognizedName);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "AutoCompleteEntryFlag", kEntryFlagEntries,
|
||||
sizeof(kEntryFlagEntries) / sizeof(kEntryFlagEntries[0]),
|
||||
"AutoComplete");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "AutoCompletePriority", kPriorityEntries,
|
||||
sizeof(kPriorityEntries) / sizeof(kPriorityEntries[0]),
|
||||
"AutoComplete");
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "AutoCompleteEntryFlag", kEntryFlagEntries, "AutoComplete");
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "AutoCompletePriority", kPriorityEntries, "AutoComplete");
|
||||
}
|
||||
|
||||
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "Inject.h"
|
||||
#include "Common.h"
|
||||
#include "Offsets.h"
|
||||
#include "storm/Memory.h"
|
||||
|
||||
#include "embed/Resource.h"
|
||||
#include "embedded_resources.h"
|
||||
@@ -213,8 +214,6 @@ const char *FindSplicePosition(const char *content, size_t size,
|
||||
return FindOutsideComment(content, end, "</Bindings>");
|
||||
}
|
||||
|
||||
using SMemAlloc_t = void *(__stdcall *)(size_t size, const char *file, int line, int flags);
|
||||
using SMemFree_t = void(__stdcall *)(void *buf, const char *file, int line, int flags);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -257,10 +256,8 @@ bool TryHandle(int unused, const char *path, void **outBuf, size_t *outSize,
|
||||
const size_t splicePos = static_cast<size_t>(splice - content);
|
||||
const size_t newSize = origSize + config.innerSize;
|
||||
|
||||
auto SMemAlloc = reinterpret_cast<SMemAlloc_t>(Offsets::FUN_STORM_SMEM_ALLOC);
|
||||
auto SMemFree = reinterpret_cast<SMemFree_t>(Offsets::FUN_STORM_SMEM_FREE);
|
||||
|
||||
void *newBuf = SMemAlloc(newSize + extraBytes, __FILE__, __LINE__, 0);
|
||||
void *newBuf = Storm::Alloc(newSize + extraBytes, __FILE__, __LINE__);
|
||||
if (newBuf == nullptr) {
|
||||
// Allocation failed — fall back to the original buffer so the
|
||||
// engine still loads its own bindings.
|
||||
@@ -277,7 +274,7 @@ bool TryHandle(int unused, const char *path, void **outBuf, size_t *outSize,
|
||||
if (extraBytes > 0)
|
||||
std::memset(dest + newSize, 0, extraBytes);
|
||||
|
||||
SMemFree(origBuf, __FILE__, __LINE__, 0);
|
||||
Storm::Free(origBuf, __FILE__, __LINE__);
|
||||
|
||||
if (outBuf != nullptr) *outBuf = newBuf;
|
||||
if (outSize != nullptr) *outSize = newSize;
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "baselib/Ascii.h"
|
||||
#include "frame/ClickEvents.h"
|
||||
#include "input/Modifier.h"
|
||||
#include "storm/Memory.h"
|
||||
#include "xml/FrameLoad.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -219,8 +220,7 @@ void LoadModifiedClicks(const char *path, void *status) {
|
||||
return;
|
||||
void *tree = reinterpret_cast<TreeLoad_t>(Offsets::FUN_XML_TREE_LOAD)(
|
||||
static_cast<const char *>(data), static_cast<uint32_t>(size));
|
||||
reinterpret_cast<AddOns::EngineIO::SMemFreeFn>(Offsets::FUN_STORM_SMEM_FREE)(data, __FILE__,
|
||||
__LINE__, 0);
|
||||
Storm::Free(data, __FILE__, __LINE__);
|
||||
if (tree == nullptr)
|
||||
return; // the engine loader reports the parse error
|
||||
const void *root = Game::Read<const void *>(tree, 0);
|
||||
|
||||
+21
-9
@@ -72,18 +72,25 @@ struct State {
|
||||
uint32_t endMs = 0;
|
||||
bool active = false; // at least one reset has happened this session
|
||||
bool dirty = false; // needs a PLAYER_SWING fire on the next WorldTick
|
||||
bool held = false; // the latest reset was recorded but not reported yet
|
||||
};
|
||||
State g_swing[3];
|
||||
|
||||
// `notify` false records the server's new timer without firing PLAYER_SWING:
|
||||
// the reset is real, but no swing is coming yet (see OnSpellGo).
|
||||
// the reset is real, but no swing is coming yet (see OnSpellGo). It stays
|
||||
// `held` until the attack start reports it or a later reported reset replaces it.
|
||||
void MarkReset(SwingType type, uint32_t durationMs, bool notify = true) {
|
||||
if (durationMs == 0)
|
||||
return; // no weapon for this hand right now -- nothing to report
|
||||
State &s = g_swing[type];
|
||||
s.endMs = NowMs() + durationMs;
|
||||
s.active = true;
|
||||
s.dirty = s.dirty || notify;
|
||||
if (notify) {
|
||||
s.dirty = true;
|
||||
s.held = false;
|
||||
} else {
|
||||
s.held = true;
|
||||
}
|
||||
}
|
||||
|
||||
using HasAttackTarget_t = int(__fastcall *)(const void *unit);
|
||||
@@ -410,11 +417,17 @@ void OnPacket(uint32_t opcode, CDataStore *packet) {
|
||||
auto *playerObj = const_cast<uint8_t *>(Unit::Identity::PlayerObject());
|
||||
if (InMeleeRange(playerObj, victimObj)) {
|
||||
MarkReset(OFF_HAND, AttackTimeMs(OFF_HAND));
|
||||
// A cast reset made before the attack still holds the
|
||||
// main-hand swing back; report it now that one is coming.
|
||||
// A cast reset made before the attack is still held back;
|
||||
// report it now that a swing is coming. Only a held reset:
|
||||
// a target switch mid-attack also lands here, and there the
|
||||
// main-hand swing was never reset -- re-reporting its
|
||||
// remaining time would read as a fresh, short swing.
|
||||
State &main = g_swing[MAIN_HAND];
|
||||
if (main.active && Time::Clock::Remaining(NowMs(), main.endMs) > 0)
|
||||
main.dirty = true;
|
||||
if (main.held) {
|
||||
main.held = false;
|
||||
if (Time::Clock::Remaining(NowMs(), main.endMs) > 0)
|
||||
main.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -477,6 +490,7 @@ void OnWorldTick() {
|
||||
if (!s.dirty)
|
||||
continue;
|
||||
s.dirty = false;
|
||||
s.held = false;
|
||||
if (slot < 0)
|
||||
continue;
|
||||
const uint32_t remaining = Time::Clock::Remaining(now, s.endMs);
|
||||
@@ -497,9 +511,7 @@ const Game::Lua::EnumIntegerEntry kSwingTypeEntries[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "PlayerSwingType", kSwingTypeEntries,
|
||||
sizeof(kSwingTypeEntries) / sizeof(kSwingTypeEntries[0]), "SwingTimer");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "PlayerSwingType", kSwingTypeEntries, "SwingTimer");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -618,17 +618,6 @@ uint32_t SpellSchoolMask(const uint8_t *spell) {
|
||||
: 0;
|
||||
}
|
||||
|
||||
bool HasAuraEffect(const uint8_t *spell, int auraName) {
|
||||
if (spell == nullptr)
|
||||
return false;
|
||||
for (int i = 0; i < Offsets::SPELL_RECORD_EFFECT_COUNT; ++i) {
|
||||
if (Game::Read<int>(spell, Offsets::OFF_SPELL_RECORD_EFFECT_APPLY_AURA_NAME + i * 4) ==
|
||||
auraName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Entry Begin(uint64_t source, uint64_t dest, int event, const uint8_t *spell) {
|
||||
Entry e;
|
||||
e.event = event;
|
||||
|
||||
@@ -167,10 +167,6 @@ uint32_t SchoolMask(uint32_t school);
|
||||
// The spell's school as a mask (4.3.4's SpellRec m_schoolMask), 0 for nullptr.
|
||||
uint32_t SpellSchoolMask(const uint8_t *spell);
|
||||
|
||||
// Whether one of the spell's three effects applies the aura `auraName`
|
||||
// (EffectApplyAuraName).
|
||||
bool HasAuraEffect(const uint8_t *spell, int auraName);
|
||||
|
||||
// GetCombatLogEntry: an entry for `event` from `source` to `dest` (either may
|
||||
// be 0), with the spell prefix when `spell` is a record.
|
||||
Entry Begin(uint64_t source, uint64_t dest, int event, const uint8_t *spell);
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include "aura/Data.h"
|
||||
#include "net/PacketDispatch.h"
|
||||
#include "object/Resolve.h"
|
||||
#include "spell/Lookup.h"
|
||||
#include "time/Clock.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -501,7 +502,7 @@ const uint8_t *ShieldSpell(uint64_t owner, uint32_t school) {
|
||||
const uint8_t *spell = SpellRecord(static_cast<int>(spellId));
|
||||
if (spell == nullptr ||
|
||||
Game::Read<uint32_t>(spell, Offsets::OFF_SPELL_RECORD_SCHOOL) != school ||
|
||||
!HasAuraEffect(spell, AURA_DAMAGE_SHIELD))
|
||||
!Spell::Lookup::HasAuraEffect(spell, AURA_DAMAGE_SHIELD))
|
||||
continue;
|
||||
if (found != nullptr && found != spell)
|
||||
return nullptr;
|
||||
|
||||
@@ -145,12 +145,8 @@ void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterGlobalFunction("ConsoleGetAllCommands", &Script_GetAllCommands);
|
||||
Game::Lua::RegisterGlobalFunction("ConsolePrintAllMatchingCommands",
|
||||
&Script_ConsolePrintAllMatchingCommands);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCommandType", kCommandTypeEntries,
|
||||
sizeof(kCommandTypeEntries) /
|
||||
sizeof(kCommandTypeEntries[0]));
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCategory", kCategoryEntries,
|
||||
sizeof(kCategoryEntries) /
|
||||
sizeof(kCategoryEntries[0]));
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCommandType", kCommandTypeEntries);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCategory", kCategoryEntries);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -42,8 +42,7 @@ const Game::Lua::EnumIntegerEntry kEntries[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "BagIndex", kEntries,
|
||||
sizeof(kEntries) / sizeof(kEntries[0]), "BagIndexConstants");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "BagIndex", kEntries, "BagIndexConstants");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+31
-13
@@ -55,6 +55,7 @@
|
||||
#include "item/Location.h"
|
||||
#include "item/Record.h"
|
||||
#include "item/TypeInfo.h"
|
||||
#include "localization/Locale.h"
|
||||
#include "unit/Identity.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -86,12 +87,29 @@ char16_t ToLower(char16_t c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
// Appends the lowercased `c` with 4.3.4's accent folding for `locale`
|
||||
// (frFR 2, deDE 3, esES/esMX 6/7, ruRU 8); æ, œ and ß become two letters.
|
||||
// Returns the new length.
|
||||
size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
|
||||
switch (locale) {
|
||||
case 2: // frFR
|
||||
// 4.3.4's accent-folding sets. 4.3.4 picks one by its locale index (frFR 2,
|
||||
// deDE 3, esES/esMX 6/7, ruRU 8); 1.12's index has other slots, so the set
|
||||
// is picked by locale name.
|
||||
enum class Folding { None, French, German, Spanish, Russian };
|
||||
|
||||
Folding ClientFolding() {
|
||||
using Localization::Locale::Is;
|
||||
if (Is("frFR"))
|
||||
return Folding::French;
|
||||
if (Is("deDE"))
|
||||
return Folding::German;
|
||||
if (Is("esES") || Is("esMX"))
|
||||
return Folding::Spanish;
|
||||
if (Is("ruRU"))
|
||||
return Folding::Russian;
|
||||
return Folding::None;
|
||||
}
|
||||
|
||||
// Appends the lowercased `c` with `folding`'s accent folding; æ, œ and ß
|
||||
// become two letters. Returns the new length.
|
||||
size_t AppendFolded(Folding folding, char16_t c, char16_t *out, size_t n) {
|
||||
switch (folding) {
|
||||
case Folding::French:
|
||||
switch (c) {
|
||||
case 0xE0: case 0xE2: case 0xE4: c = u'a'; break;
|
||||
case 0xE6: out[n++] = u'a'; c = u'e'; break;
|
||||
@@ -103,14 +121,13 @@ size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
|
||||
case 0xF9: case 0xFA: case 0xFB: case 0xFC: c = u'u'; break;
|
||||
}
|
||||
break;
|
||||
case 3: // deDE
|
||||
case Folding::German:
|
||||
if (c == 0xDF) {
|
||||
out[n++] = u's';
|
||||
c = u's';
|
||||
}
|
||||
break;
|
||||
case 6: // esES
|
||||
case 7: // esMX
|
||||
case Folding::Spanish:
|
||||
switch (c) {
|
||||
case 0xE1: c = u'a'; break;
|
||||
case 0xE9: c = u'e'; break;
|
||||
@@ -120,10 +137,12 @@ size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
|
||||
case 0xF1: c = u'n'; break;
|
||||
}
|
||||
break;
|
||||
case 8: // ruRU
|
||||
case Folding::Russian:
|
||||
if (c == 0x451)
|
||||
c = 0x435;
|
||||
break;
|
||||
case Folding::None:
|
||||
break;
|
||||
}
|
||||
out[n++] = c;
|
||||
return n;
|
||||
@@ -141,11 +160,10 @@ size_t Fold(const char *text, char16_t (&out)[kFoldCap]) {
|
||||
// One unit short of the buffer: a full conversion stores no terminator.
|
||||
reinterpret_cast<Convert_t>(Offsets::FUN_SUNI_CONVERT_UTF8_TO_16)(
|
||||
wide, kFoldCap - 1, text, 0x7FFFFFFF, nullptr, nullptr);
|
||||
const int locale =
|
||||
*reinterpret_cast<const int *>(static_cast<uintptr_t>(Offsets::VAR_LOCALE_INDEX));
|
||||
const Folding folding = ClientFolding();
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; wide[i] != 0 && n + 2 < kFoldCap; ++i)
|
||||
n = AppendFolded(locale, ToLower(wide[i]), out, n);
|
||||
n = AppendFolded(folding, ToLower(wide[i]), out, n);
|
||||
out[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -131,10 +131,7 @@ int __fastcall Script_RequestLoadCreatureByID(void *L) {
|
||||
Game::Lua::PushBool(L, false);
|
||||
return 1;
|
||||
}
|
||||
auto *cache = reinterpret_cast<void *>(Offsets::VAR_CREATURE_CACHE);
|
||||
const bool accepted =
|
||||
Cache::QueryLoad::RequestLoad(cache, static_cast<uint32_t>(creatureID));
|
||||
Game::Lua::PushBool(L, accepted);
|
||||
Game::Lua::PushBool(L, RequestLoad(static_cast<uint32_t>(creatureID)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -381,4 +378,9 @@ const uint8_t *CacheRecord(uint32_t creatureID) {
|
||||
return creatureID != 0 ? PeekCreature(creatureID) : nullptr;
|
||||
}
|
||||
|
||||
bool RequestLoad(uint32_t creatureID) {
|
||||
return Cache::QueryLoad::RequestLoad(reinterpret_cast<void *>(Offsets::VAR_CREATURE_CACHE),
|
||||
creatureID);
|
||||
}
|
||||
|
||||
} // namespace Creature::Info
|
||||
|
||||
@@ -24,6 +24,12 @@ namespace Creature::Info {
|
||||
// engine's SetDisplay takes, and `PlayerModel:SetCreature` passes it there.
|
||||
const uint8_t *CacheRecord(uint32_t creatureID);
|
||||
|
||||
// Starts loading `creatureID` into the creature cache; fires
|
||||
// `CREATURE_DATA_LOAD_RESULT(creatureID, success)` when it lands (at once
|
||||
// when already cached). False only when the load could not be tracked.
|
||||
// Backs `C_CreatureInfo.RequestLoadCreatureByID`.
|
||||
bool RequestLoad(uint32_t creatureID);
|
||||
|
||||
// The faction group a player race belongs to, resolved the way
|
||||
// `Script_UnitFactionGroup` does it for a player whose object isn't in the
|
||||
// world (its name-cache branch, which reads the race): ChrRaces -> FactionTemplate
|
||||
|
||||
@@ -27,10 +27,8 @@ const Game::Lua::EnumIntegerEntry kMoneyFormattingConstants[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterConstants("MoneyFormattingConstants", kMoneyFormattingConstants,
|
||||
sizeof(kMoneyFormattingConstants) /
|
||||
sizeof(kMoneyFormattingConstants[0]),
|
||||
"MoneyConstants");
|
||||
Game::Lua::RegisterConstants(
|
||||
"MoneyFormattingConstants", kMoneyFormattingConstants, "MoneyConstants");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -119,9 +119,7 @@ void OnTick() {
|
||||
const Tick::WorldTick::AutoSubscribe _tick{&OnTick};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "UICursorType", kCursorTypeEntries,
|
||||
sizeof(kCursorTypeEntries) / sizeof(kCursorTypeEntries[0]));
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "UICursorType", kCursorTypeEntries);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -206,9 +206,8 @@ void RegisterLuaFunctions() {
|
||||
&Script_GetSelfResurrectOptions, &kGetSelfResurrectOptions);
|
||||
Game::Lua::RegisterTableFunction("C_DeathInfo", "UseSelfResurrectOption",
|
||||
&Script_UseSelfResurrectOption, &kUseSelfResurrectOption);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "SelfResurrectOptionType", kOptionTypeEntries,
|
||||
sizeof(kOptionTypeEntries) / sizeof(kOptionTypeEntries[0]),
|
||||
"DeathInfo");
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "SelfResurrectOptionType", kOptionTypeEntries, "DeathInfo");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <iterator>
|
||||
|
||||
namespace Duration::Object {
|
||||
|
||||
@@ -451,8 +450,7 @@ const Game::Doc::Function kCreateDuration{"Creates a zero duration object that c
|
||||
void RegisterLuaFunctions() {
|
||||
CreateMetatable(Lua::State());
|
||||
Lua::RegisterTableFunction("C_DurationUtil", "CreateDuration", &Script_CreateDuration, &kCreateDuration);
|
||||
Lua::RegisterIntegerEnum("Enum", "DurationTimeModifier", kModifiers, static_cast<int>(std::size(kModifiers)),
|
||||
"LuaDurationObjectShared");
|
||||
Lua::RegisterIntegerEnum("Enum", "DurationTimeModifier", kModifiers, "LuaDurationObjectShared");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -780,8 +779,8 @@ void RegisterLuaFunctions() {
|
||||
CreateMetatable(Lua::State());
|
||||
Lua::RegisterTableFunction("C_DurationUtil", "CreateDurationTextBinding", &Script_CreateDurationTextBinding,
|
||||
&kCreateDurationTextBinding);
|
||||
Lua::RegisterIntegerEnum("Enum", "DurationTextBindingProperty", kProperties,
|
||||
static_cast<int>(std::size(kProperties)), "DurationTextBindingShared");
|
||||
Lua::RegisterIntegerEnum(
|
||||
"Enum", "DurationTextBindingProperty", kProperties, "DurationTextBindingShared");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -125,8 +125,8 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -83,9 +83,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "frame/Layout.h"
|
||||
#include "text/InlineTexture.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -59,21 +60,17 @@ using IndexAtPoint_t = void(__fastcall *)(void *editBox, void *edx, float x, flo
|
||||
using FontHeight_t = float(__fastcall *)(void *fs, void *edx, int mode);
|
||||
using MeasureRun_t = float(__fastcall *)(void *fs, void *edx, const uint8_t *text, int len);
|
||||
using PixelAlignY_t = float(__stdcall *)(float length);
|
||||
using GetRect_t = int(__fastcall *)(void *anchor, void *edx, float *out);
|
||||
using ClearAllPoints_t = void(__fastcall *)(void *anchor);
|
||||
using SetPoint_t = void(__thiscall *)(void *anchor, int point, void *relAnchor, int relPoint, float x, float y,
|
||||
int flag);
|
||||
using Resize_t = void(__fastcall *)(void *anchor, void *edx, int force);
|
||||
using Extent_t = float(__fastcall *)(void *anchor, void *edx);
|
||||
using ScreenAspect_t = float(__cdecl *)();
|
||||
using DivideByScreenScaleX_t = float(__stdcall *)(float value);
|
||||
using FireVariadic_t = void(__cdecl *)(void *frame, uint32_t *slotPtr, const char *fmt, ...);
|
||||
|
||||
PlaceCaret_t g_origPlaceCaret = nullptr;
|
||||
PlaceSelectionPiece_t g_origPlaceSelectionPiece = nullptr;
|
||||
IndexAtPoint_t g_origIndexAtPoint = nullptr;
|
||||
|
||||
void *AnchorOf(void *region) { return static_cast<uint8_t *>(region) + Offsets::OFF_REGION_ANCHOR; }
|
||||
using Frame::Layout::Of;
|
||||
|
||||
// A multi-line box's rows: its label, the buffer the line table indexes (the
|
||||
// '*' copy of a password box) and the table — each line's first byte, the
|
||||
@@ -179,9 +176,9 @@ bool AnchorX(const Rows &rows, int line, uint32_t pos, float &x, int &relPoint)
|
||||
|
||||
// Anchors `region` as the placer did, at `y`.
|
||||
void Anchor(const Rows &rows, void *region, int relPoint, float x, float y, int flag) {
|
||||
void *anchor = AnchorOf(region);
|
||||
void *anchor = Of(region);
|
||||
reinterpret_cast<ClearAllPoints_t>(Offsets::FUN_LAYOUT_CLEAR_ALL_POINTS)(anchor);
|
||||
reinterpret_cast<SetPoint_t>(Offsets::FUN_REGION_SET_POINT)(anchor, Offsets::FRAMEPOINT_TOPLEFT, AnchorOf(rows.fs),
|
||||
reinterpret_cast<SetPoint_t>(Offsets::FUN_REGION_SET_POINT)(anchor, Offsets::FRAMEPOINT_TOPLEFT, Of(rows.fs),
|
||||
relPoint, x, y, flag);
|
||||
}
|
||||
|
||||
@@ -192,7 +189,7 @@ void __fastcall IndexAtPoint_h(void *editBox, void *edx, float x, float y, int *
|
||||
if (!MultiLineRows(editBox, rows))
|
||||
return g_origIndexAtPoint(editBox, edx, x, y, index);
|
||||
float rect[4] = {0.0f, 0.0f, 0.0f, 0.0f}; // bottom, left, top, right
|
||||
if (reinterpret_cast<GetRect_t>(Offsets::FUN_REGION_GET_RECT)(AnchorOf(rows.fs), nullptr, rect) == 0)
|
||||
if (!Frame::Layout::GetRect(rows.fs, rect))
|
||||
return g_origIndexAtPoint(editBox, edx, x, y, index);
|
||||
// The engine walks rows down from the label's top while the click is
|
||||
// below the row (strictly), stopping at the last line. Walk the rows as
|
||||
@@ -240,7 +237,7 @@ void __fastcall PlaceSelectionPiece_h(void *editBox, void *edx, void *piece, int
|
||||
return;
|
||||
const float y = -(RowHeight(rows) / rows.scale) * static_cast<float>(line) - shift;
|
||||
Anchor(rows, piece, relPoint, x, y, 0);
|
||||
reinterpret_cast<Resize_t>(Offsets::FUN_LAYOUT_RESIZE)(AnchorOf(piece), nullptr, 1);
|
||||
Frame::Layout::Resize(piece, true);
|
||||
}
|
||||
|
||||
// --- the caret ----------------------------------------------------------------
|
||||
@@ -276,15 +273,12 @@ void __fastcall PlaceCaret_h(void *editBox) {
|
||||
return;
|
||||
// OnCursorChanged(x, y, width, height) as the engine fires it: each
|
||||
// value by the anchor→script factor, pushed as a double.
|
||||
void *anchor = AnchorOf(caret);
|
||||
void *anchor = Of(caret);
|
||||
void **vtable = *static_cast<void ***>(anchor);
|
||||
const float width = reinterpret_cast<Extent_t>(vtable[7])(anchor, nullptr); // +0x1C GetWidth
|
||||
const float height = reinterpret_cast<Extent_t>(vtable[8])(anchor, nullptr); // +0x20 GetHeight
|
||||
const auto aspect = reinterpret_cast<ScreenAspect_t>(Offsets::FUN_COORD_GET_SCALED_SCREEN_ASPECT);
|
||||
const auto divide = reinterpret_cast<DivideByScreenScaleX_t>(Offsets::FUN_COORD_DIVIDE_BY_SCREEN_SCALE_X);
|
||||
const float factor = Game::Read<float>(Offsets::FLOAT_SCRIPT_COORD_SCALE);
|
||||
const auto push = [&](float value) { return static_cast<double>(divide(aspect() * factor * value)); };
|
||||
const double args[4] = {push(x), push(y), push(width), push(height)};
|
||||
const double args[4] = {Game::InternalToPixel(x), Game::InternalToPixel(y), Game::InternalToPixel(width),
|
||||
Game::InternalToPixel(height)};
|
||||
void *L = Game::Lua::State();
|
||||
const int savedTop = (L != nullptr) ? Game::Lua::GetTop(L) : 0;
|
||||
reinterpret_cast<FireVariadic_t>(Offsets::FUN_FRAME_RUN_SCRIPT_VARIADIC)(
|
||||
|
||||
+1
-3
@@ -86,9 +86,7 @@ void RegisterLuaFunctions() {
|
||||
Common::RedirectCall(Offsets::CALL_EDITBOX_IME_COMPOSITION_INSERT, Offsets::FUN_EDITBOX_INSERT,
|
||||
reinterpret_cast<const void *>(&CompositionInsert));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -30,8 +30,7 @@ const Game::Lua::EnumIntegerEntry kEntries[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "InputContext", kEntries,
|
||||
sizeof(kEntries) / sizeof(kEntries[0]), "InputConstants");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "InputContext", kEntries, "InputConstants");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -234,9 +234,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY),
|
||||
g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## Interface: 11200
|
||||
## Title: !!!ClassicAPI
|
||||
## Notes: Library to emulate aspects of the modern WoW API on 1.12.1 / Lua 5.0.
|
||||
## Notes-ruRU: Библиотека, эмулирующая аспекты современного API WoW на 1.12.1 / Lua 5.0.
|
||||
## Version: DEV
|
||||
## Author: Brues
|
||||
## X-Website: https://github.com/brues-code/ClassicAPI
|
||||
@@ -143,6 +144,17 @@ FrameXML\ColorSwatch.lua
|
||||
FrameXML\ColorSwatch.xml
|
||||
FrameXML\ScriptErrorsFrame.lua
|
||||
FrameXML\ScriptErrorsFrame.xml
|
||||
FrameXML\ModelScene\CameraRegistry.lua
|
||||
FrameXML\ModelScene\CameraBaseMixin.lua
|
||||
FrameXML\ModelScene\OrbitCameraMixin.lua
|
||||
FrameXML\ModelScene\FollowCameraMixin.lua
|
||||
FrameXML\ModelScene\ModelSceneMixin.lua
|
||||
FrameXML\ModelScene\ModelSceneMixin.xml
|
||||
FrameXML\ModelScene\ModelSceneActorMixin.lua
|
||||
FrameXML\ModelScene\ModelSceneActorMixin.xml
|
||||
FrameXML\ModelScene\ModelSceneControlFrame.lua
|
||||
FrameXML\ModelScene\ModelSceneControlFrame.xml
|
||||
FrameXML\DressUpFrame.xml
|
||||
FrameXML\DressUpFrame.lua
|
||||
|
||||
Menu\MenuConstants.lua
|
||||
|
||||
@@ -24,3 +24,22 @@ end
|
||||
function C_AutoComplete.GetAutoCompleteRealms()
|
||||
return {}
|
||||
end
|
||||
|
||||
-- C_ModelInfo's getters come from the DLL. Retail's active-scene bookkeeping
|
||||
-- "does nothing in public clients" (its own documentation).
|
||||
function C_ModelInfo.AddActiveModelScene(modelSceneFrame, modelSceneID)
|
||||
end
|
||||
|
||||
function C_ModelInfo.AddActiveModelSceneActor(modelSceneFrameActor, modelSceneActorID)
|
||||
end
|
||||
|
||||
function C_ModelInfo.ClearActiveModelScene(modelSceneFrame)
|
||||
end
|
||||
|
||||
function C_ModelInfo.ClearActiveModelSceneActor(modelSceneFrameActor)
|
||||
end
|
||||
|
||||
-- This client has no alternate player forms.
|
||||
function C_PlayerInfo.GetAlternateFormInfo()
|
||||
return false, false
|
||||
end
|
||||
|
||||
@@ -10,10 +10,203 @@
|
||||
-- - An uncached item skips the IsDressableItem test, which reads only the
|
||||
-- cache. 1.12.1 opens no tooltip before a chat link's Control-click, so
|
||||
-- the test would drop most chat links; TryOn loads the item instead.
|
||||
-- - An item the model cannot wear goes on to DressUpMountLink, retail's
|
||||
-- next step in DressUpLink: 1.12.1's FrameXML (ItemRef, bags, loot, the
|
||||
-- auction house) calls DressUpItemLink alone.
|
||||
-- - It returns true when it showed the item or its mount, and false for a
|
||||
-- link of another type, as retail's does.
|
||||
|
||||
function DressUpItemLink(link)
|
||||
if ( not link or (C_Item.IsItemDataCachedByID(link) and not IsDressableItem(link)) ) then
|
||||
return;
|
||||
-- A link DressUpItemLink takes: an item link, or anything that is not a
|
||||
-- link (an item ID or name).
|
||||
local function IsItemLink(link)
|
||||
local linkType = type(link) == "string" and LinkUtil.ExtractLink(link);
|
||||
return not linkType or linkType == "item";
|
||||
end
|
||||
|
||||
-- 4.3.4's DressUpItemLink body: the item, when the model can wear it.
|
||||
local function DressUpWearable(link)
|
||||
if ( C_Item.IsItemDataCachedByID(link) and not IsDressableItem(link) ) then
|
||||
return false;
|
||||
end
|
||||
DressUpItem(link);
|
||||
return true;
|
||||
end
|
||||
|
||||
function DressUpItemLink(link)
|
||||
if ( not link or not IsItemLink(link) ) then
|
||||
return false;
|
||||
end
|
||||
return DressUpWearable(link) or DressUpMountLink(link);
|
||||
end
|
||||
|
||||
-- Retail's DressUpLink (Mainline/DressUpFrames.lua), without battle pets.
|
||||
function DressUpLink(link, forcedFrame)
|
||||
return link and ((IsItemLink(link) and DressUpWearable(link)) or DressUpMountLink(link, forcedFrame));
|
||||
end
|
||||
|
||||
-- Retail's mount preview (Mainline/DressUpFrames.lua DressUpMountLink and
|
||||
-- DressUpMount, Blizzard_SharedXMLGame/DressUpModelFrameMixin.lua's mode and
|
||||
-- last link) on 1.12.1's dressing room. Mount mode shows DressUpFrameModelScene
|
||||
-- in place of DressUpModel and hides the Reset button, as retail's SetMode
|
||||
-- hides it outside player mode; DressUpItem goes back to player mode.
|
||||
--
|
||||
-- Differences from the retail source:
|
||||
-- - DressUpFrame keeps its own background (retail switches to the warrior
|
||||
-- one) and has no view insets to set (retail sets them to 0, the default).
|
||||
-- - There are no mount links, so a link never sets the model itself, and
|
||||
-- shouldSetModelFromHyperlink changes nothing.
|
||||
-- - A mount whose creature is not cached yet returns false, as retail does
|
||||
-- with no display, and opens when CREATURE_DATA_LOAD_RESULT reports the
|
||||
-- creature: retail has every display on disk, this client asks the server.
|
||||
|
||||
DressUpFrame.ModelScene = DressUpFrameModelScene;
|
||||
DressUpFrame.ModelScene.ControlFrame:SetModelScene(DressUpFrame.ModelScene);
|
||||
|
||||
function DressUpFrame:GetLastLink()
|
||||
return self.lastLink;
|
||||
end
|
||||
|
||||
function DressUpFrame:SetLastLink(link)
|
||||
self.lastLink = link;
|
||||
end
|
||||
|
||||
function DressUpFrame:GetMode()
|
||||
return self.mode;
|
||||
end
|
||||
|
||||
function DressUpFrame:SetMode(mode)
|
||||
self.mode = mode;
|
||||
local inPlayerMode = mode ~= "mount";
|
||||
DressUpModel:SetShown(inPlayerMode);
|
||||
DressUpFrameResetButton:SetShown(inPlayerMode);
|
||||
self.ModelScene:SetShown(not inPlayerMode);
|
||||
end
|
||||
|
||||
DressUpFrame.ModelScene:SetResetCallback(function()
|
||||
local link = DressUpFrame:GetLastLink();
|
||||
if link then
|
||||
DressUpLink(link, DressUpFrame);
|
||||
end
|
||||
end);
|
||||
|
||||
-- The stock DressUpItem sets the unit only when it opens the window, and the
|
||||
-- model was hidden while a mount showed.
|
||||
local originalDressUpItem = DressUpItem;
|
||||
function DressUpItem(item)
|
||||
if ( DressUpFrame:GetMode() == "mount" ) then
|
||||
DressUpFrame:SetMode("player");
|
||||
DressUpFrame:SetLastLink(nil);
|
||||
if ( DressUpFrame:IsVisible() ) then
|
||||
DressUpModel:SetUnit("player");
|
||||
end
|
||||
end
|
||||
return originalDressUpItem(item);
|
||||
end
|
||||
|
||||
function DressUpMountLink(link, forcedFrame)
|
||||
if( link ) then
|
||||
local mountID = 0;
|
||||
|
||||
local linkType, linkOptions = LinkUtil.ExtractLink(link);
|
||||
if linkType == "item" then
|
||||
local linkID = LinkUtil.SplitLinkOptions(linkOptions);
|
||||
mountID = C_MountJournal.GetMountFromItem(tonumber(linkID));
|
||||
elseif linkType == "spell" then
|
||||
local linkID = LinkUtil.SplitLinkOptions(linkOptions);
|
||||
mountID = C_MountJournal.GetMountFromSpell(tonumber(linkID));
|
||||
end
|
||||
|
||||
if ( mountID ) then
|
||||
return DressUpMount(mountID, forcedFrame, false, link);
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local pendingMount;
|
||||
|
||||
-- The creature GetMountInfoExtraByID loads for a mount: a mount ID is its
|
||||
-- spell ID, and the mount aura's misc value is the creature.
|
||||
local SPELL_AURA_MOUNTED = 78;
|
||||
local function MountCreatureID(mountID)
|
||||
local effects = C_Spell.GetSpellEffectInfo(mountID);
|
||||
if ( effects ) then
|
||||
for _, effect in ipairs(effects) do
|
||||
if ( effect.auraName == SPELL_AURA_MOUNTED ) then
|
||||
return effect.miscValue;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DressUpMount(mountID, forcedFrame, shouldSetModelFromHyperlink, link)
|
||||
if ( not mountID or mountID == 0 ) then
|
||||
return false;
|
||||
end
|
||||
|
||||
local creatureDisplayID, _, _, isSelfMount, _, modelSceneID, animID, spellVisualKitID, disablePlayerMountPreview = C_MountJournal.GetMountInfoExtraByID(mountID);
|
||||
if ( not creatureDisplayID ) then
|
||||
pendingMount = { mountID = mountID, creatureID = MountCreatureID(mountID), forcedFrame = forcedFrame, link = link };
|
||||
return false;
|
||||
end
|
||||
pendingMount = nil;
|
||||
|
||||
local frame = forcedFrame or DressUpFrame;
|
||||
|
||||
--Show the frame
|
||||
if ( not frame:IsShown() or frame:GetMode() ~= "mount" ) then
|
||||
frame:SetMode("mount");
|
||||
ShowUIPanel(frame);
|
||||
end
|
||||
frame:SetLastLink(link);
|
||||
frame.ModelScene:ClearScene();
|
||||
local forceEvenIfSame = true;
|
||||
frame.ModelScene:TransitionToModelSceneID(modelSceneID, CAMERA_TRANSITION_TYPE_IMMEDIATE, CAMERA_MODIFICATION_TYPE_DISCARD, forceEvenIfSame);
|
||||
|
||||
local mountActor = frame.ModelScene:GetActorByTag("unwrapped");
|
||||
if mountActor then
|
||||
mountActor:SetModelByCreatureDisplayID(creatureDisplayID);
|
||||
|
||||
-- mount self idle animation
|
||||
if (isSelfMount) then
|
||||
mountActor:SetAnimationBlendOperation(Enum.ModelBlendOperation.None);
|
||||
mountActor:SetAnimation(618); -- MountSelfIdle
|
||||
else
|
||||
mountActor:SetAnimationBlendOperation(Enum.ModelBlendOperation.Anim);
|
||||
mountActor:SetAnimation(0);
|
||||
end
|
||||
frame.ModelScene:AttachPlayerToMount(mountActor, animID, isSelfMount, disablePlayerMountPreview);
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
local creatureLoadFrame = CreateFrame("Frame");
|
||||
creatureLoadFrame:RegisterEvent("CREATURE_DATA_LOAD_RESULT");
|
||||
creatureLoadFrame:SetScript("OnEvent", function(self, event, creatureID, success)
|
||||
local pending = pendingMount;
|
||||
if ( not pending or creatureID ~= pending.creatureID ) then
|
||||
return;
|
||||
end
|
||||
pendingMount = nil;
|
||||
if ( success ) then
|
||||
DressUpMount(pending.mountID, pending.forcedFrame, false, pending.link);
|
||||
end
|
||||
end);
|
||||
|
||||
-- Control-click on a mount in the spellbook previews it. Retail has no such
|
||||
-- click (its mounts are in the mount journal); this client keeps mounts in
|
||||
-- the spellbook, where stock 1.12.1 casts on a Control-click. A mount spell
|
||||
-- never casts here, even while its creature loads; any other spell does.
|
||||
local originalSpellButtonOnClick = SpellButton_OnClick;
|
||||
function SpellButton_OnClick(drag)
|
||||
if ( not drag and IsControlKeyDown() and SpellBookFrame.bookType ~= BOOKTYPE_PET ) then
|
||||
local id = SpellBook_GetSpellID(this:GetID());
|
||||
local link = id <= MAX_SPELLS and GetSpellLink(id, SpellBookFrame.bookType);
|
||||
if ( link and C_MountJournal.GetMountFromSpell(link) ) then
|
||||
this:SetChecked("false");
|
||||
DressUpMountLink(link);
|
||||
return;
|
||||
end
|
||||
end
|
||||
return originalSpellButtonOnClick(drag);
|
||||
end
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<!-- The dressing room's mount mode (DressUpFrame.lua): a scene shown in
|
||||
place of DressUpModel, with retail's control frame. Like retail's, it
|
||||
fills the backdrop above the buttons: the four DressUpBackground
|
||||
textures, cut off at the top of the Reset / Close row (centred 422
|
||||
below the frame top, 22 tall, so 48 above the backdrop's bottom).
|
||||
DressUpModel's own rect is no guide: it is a scale 2 frame that
|
||||
reaches past the backdrop and over the buttons. -->
|
||||
<ModelScene name="DressUpFrameModelScene" parent="DressUpFrame" inherits="ModelSceneMixinTemplate" hidden="true">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="DressUpBackgroundTopLeft"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="DressUpBackgroundBotRight">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="48"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<Frame parentKey="ControlFrame" inherits="ModelSceneControlFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" x="0" y="-10"/>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
</Frames>
|
||||
</ModelScene>
|
||||
</Ui>
|
||||
@@ -0,0 +1,103 @@
|
||||
-- Verbatim backport of retail's Blizzard_SharedXML/Shared/ModelSceneCameras/CameraBaseMixin.lua.
|
||||
|
||||
|
||||
CameraBaseMixin = {};
|
||||
|
||||
function CameraBaseMixin:SetOwningScene(owningScene)
|
||||
if self.owningScene ~= owningScene then
|
||||
if self.owningScene then
|
||||
self:OnRemoved();
|
||||
end
|
||||
|
||||
self.owningScene = owningScene;
|
||||
|
||||
if self.owningScene then
|
||||
self:OnAdded();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CameraBaseMixin:GetOwningScene()
|
||||
return self.owningScene;
|
||||
end
|
||||
|
||||
CAMERA_TRANSITION_TYPE_IMMEDIATE = 1; -- Switch to the camera view instantly
|
||||
CAMERA_TRANSITION_TYPE_INTERPOLATION = 2; -- Switch to the camera view by interpolating from the current view
|
||||
|
||||
CAMERA_MODIFICATION_TYPE_DISCARD = 1; -- Discard any modifications performed to the view by script
|
||||
CAMERA_MODIFICATION_TYPE_MAINTAIN = 2; -- Retain any modifications performed to the view by script
|
||||
|
||||
-- Override this to handle transitions to a modelSceneCamera data set
|
||||
function CameraBaseMixin:ApplyFromModelSceneCameraInfo(modelSceneCameraInfo, transitionType, modificationType)
|
||||
|
||||
end
|
||||
|
||||
-- Common accessors from the owning scene
|
||||
function CameraBaseMixin:IsLeftMouseButtonDown()
|
||||
return self:GetOwningScene():IsLeftMouseButtonDown();
|
||||
end
|
||||
|
||||
function CameraBaseMixin:IsRightMouseButtonDown()
|
||||
return self:GetOwningScene():IsRightMouseButtonDown();
|
||||
end
|
||||
|
||||
function CameraBaseMixin:SetPosition(x, y, z)
|
||||
self:GetOwningScene():SetCameraPosition(x, y, z);
|
||||
end
|
||||
|
||||
function CameraBaseMixin:GetPosition()
|
||||
return self:GetOwningScene():GetCameraPosition();
|
||||
end
|
||||
|
||||
function CameraBaseMixin:GetForwardVector()
|
||||
return self:GetOwningScene():GetCameraForward();
|
||||
end
|
||||
|
||||
function CameraBaseMixin:GetRightVector()
|
||||
return self:GetOwningScene():GetCameraRight();
|
||||
end
|
||||
|
||||
function CameraBaseMixin:GetUpVector()
|
||||
return self:GetOwningScene():GetCameraUp();
|
||||
end
|
||||
|
||||
-- Override this with a uniquely identifying name for the camera type
|
||||
function CameraBaseMixin:GetCameraType()
|
||||
return "UnknownCamera";
|
||||
end
|
||||
|
||||
-- Override this to perform any per-frame updates
|
||||
function CameraBaseMixin:OnUpdate(elapsed)
|
||||
end
|
||||
|
||||
-- Override this with code to synchronize the camera state with the model scene API, may be called by the ModelScene to synchronize deferred camera state in case it needs to be queried
|
||||
function CameraBaseMixin:SynchronizeCamera()
|
||||
end
|
||||
|
||||
-- Override this to be notified when the mouse is down on the model scene
|
||||
function CameraBaseMixin:OnMouseDown(button)
|
||||
end
|
||||
|
||||
-- Override this to be notified when the mouse is up on the model scene
|
||||
function CameraBaseMixin:OnMouseUp(button)
|
||||
end
|
||||
|
||||
-- Override this to be notified when the mouse wheel is changed on the model scene
|
||||
function CameraBaseMixin:OnMouseWheel(delta)
|
||||
end
|
||||
|
||||
-- Override this to be notified when the camera is added to a scene
|
||||
function CameraBaseMixin:OnAdded()
|
||||
end
|
||||
|
||||
-- Override this to be notified when the camera is remove from a scene
|
||||
function CameraBaseMixin:OnRemoved()
|
||||
end
|
||||
|
||||
-- Override this to be notified when the camera becomes activated
|
||||
function CameraBaseMixin:OnActivated()
|
||||
end
|
||||
|
||||
-- Override this to be notified when the camera becomes deactivated
|
||||
function CameraBaseMixin:OnDeactivated()
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Verbatim backport of retail's Blizzard_SharedXML/Shared/ModelSceneCameras/CameraRegistry.lua.
|
||||
|
||||
|
||||
CameraRegistry = { cameraTypeToFactoryFunction = {}, };
|
||||
|
||||
function CameraRegistry:AddCameraFactory(cameraTypeName, factoryFunction)
|
||||
self.cameraTypeToFactoryFunction[cameraTypeName] = factoryFunction;
|
||||
end
|
||||
|
||||
function CameraRegistry:AddCameraFactoryFromMixin(cameraTypeName, mixin)
|
||||
self:AddCameraFactory(cameraTypeName, function() return CreateFromMixins(mixin); end);
|
||||
end
|
||||
|
||||
function CameraRegistry:CreateCameraByType(cameraTypeName)
|
||||
if self.cameraTypeToFactoryFunction[cameraTypeName] then
|
||||
return self.cameraTypeToFactoryFunction[cameraTypeName]();
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,133 @@
|
||||
-- ClassicAPI extension: an orbit camera that follows an actor and stops in
|
||||
-- front of the WMO between the actor and the eye.
|
||||
|
||||
--[[
|
||||
FollowCameraMixin - An OrbitCamera whose target is an actor's pivot: its position, raised by the pivot height.
|
||||
|
||||
The pivot height is the one the world's follow camera eases to for a unit (actor:GetCameraPivotHeight), and
|
||||
it eases the same way: the camera's first height is taken at once (BeginHeightTransition 0x00512790), and
|
||||
each new one is armed (ArmHeight 0x005126B0) for |change| / cameraHeightSmoothSpeed seconds and eased with
|
||||
the camera's cosine curve (0x0050F160). Until the actor's model has loaded, the camera waits, as the world's
|
||||
does (0x0050E900).
|
||||
|
||||
The eye is placed the way the world's follow camera places it (the boom sweep of its solver 0x0050E570):
|
||||
a trace of the camera's faces from the pivot to the eye (0x0050E816) shortens the distance, which then
|
||||
backs off 1/9 yard (0x0050E871 to 0x0050E8A4). With no actor to follow, it is a plain OrbitCamera.
|
||||
|
||||
Create it beside the scene's own camera and give it that camera's settings:
|
||||
local _, cameraIDs = C_ModelInfo.GetModelSceneInfoByID(modelSceneID);
|
||||
local camera = scene:AddCamera(CameraRegistry:CreateCameraByType("FollowCamera"));
|
||||
camera:ApplyFromModelSceneCameraInfo(C_ModelInfo.GetModelSceneCameraInfoByID(cameraIDs[1]), CAMERA_TRANSITION_TYPE_IMMEDIATE, CAMERA_MODIFICATION_TYPE_DISCARD);
|
||||
camera:SetFollowActor(actor);
|
||||
scene:SetActiveCamera(camera);
|
||||
]]
|
||||
|
||||
FollowCameraMixin = CreateFromMixins(OrbitCameraMixin);
|
||||
|
||||
local CAMERA_NAME = "FollowCamera";
|
||||
CameraRegistry:AddCameraFactoryFromMixin(CAMERA_NAME, FollowCameraMixin);
|
||||
|
||||
-- The solver skips the sweep when the distance is not past the near clip by
|
||||
-- more than this (0x008026BC, 2^-20).
|
||||
local SWEEP_MIN_DISTANCE = 9.5367431640625e-07;
|
||||
-- A distance shortened by more than this (0x008029D4, 2^-22) backs off by
|
||||
-- BOOM_BACKOFF (0x00808A00, 1/9 yard), to no less than 0.
|
||||
local SHORTENED_EPSILON = 2.384185791015625e-07;
|
||||
local BOOM_BACKOFF = 1 / 9;
|
||||
-- A height within this of the armed or the live one arms nothing (0x00801360).
|
||||
local ARM_EPSILON = 0.001;
|
||||
-- The ease runs while the live height is this far or more from its target
|
||||
-- (0x008029D4, 2^-22).
|
||||
local EASE_EPSILON = 2.384185791015625e-07;
|
||||
|
||||
-- "public" functions
|
||||
function FollowCameraMixin:GetCameraType() -- override
|
||||
return CAMERA_NAME;
|
||||
end
|
||||
|
||||
-- The target becomes the actor's pivot. Nil stops following.
|
||||
function FollowCameraMixin:SetFollowActor(actor)
|
||||
self.followActor = actor;
|
||||
end
|
||||
|
||||
function FollowCameraMixin:GetFollowActor()
|
||||
return self.followActor;
|
||||
end
|
||||
|
||||
-- "private" functions
|
||||
|
||||
-- ArmHeight (0x005126B0) with no delay and a factor of 1, and its inner
|
||||
-- BeginHeightTransition (0x00512790).
|
||||
function FollowCameraMixin:ArmPivotHeight(height, now)
|
||||
local live = self.pivotHeight or 0;
|
||||
if self.pivotEasing and math.abs(self.pivotTarget - height) < ARM_EPSILON then
|
||||
return;
|
||||
end
|
||||
if math.abs(live - height) < ARM_EPSILON then
|
||||
return;
|
||||
end
|
||||
self.pivotTarget = height;
|
||||
if not self.pivotSnapped then
|
||||
self.pivotSnapped = true;
|
||||
self.pivotHeight = height;
|
||||
self.pivotEasing = false;
|
||||
return;
|
||||
end
|
||||
self.pivotFrom = live;
|
||||
self.pivotStart = now;
|
||||
self.pivotDuration = math.abs(height - live) / tonumber(GetCVar("cameraHeightSmoothSpeed"));
|
||||
self.pivotEasing = true;
|
||||
end
|
||||
|
||||
-- The height channel of the camera's ease (0x0050F160).
|
||||
function FollowCameraMixin:EasePivotHeight(now)
|
||||
if math.abs(self.pivotTarget - self.pivotHeight) < EASE_EPSILON then
|
||||
self.pivotEasing = false;
|
||||
return;
|
||||
end
|
||||
if not self.pivotEasing or self.pivotStart > now then
|
||||
return;
|
||||
end
|
||||
local t = (now - self.pivotStart) / self.pivotDuration;
|
||||
if t >= 1 then
|
||||
self.pivotHeight = self.pivotTarget;
|
||||
else
|
||||
self.pivotHeight = self.pivotFrom + (self.pivotTarget - self.pivotFrom) * (1 - math.cos(math.pi * t)) * 0.5;
|
||||
end
|
||||
end
|
||||
|
||||
function FollowCameraMixin:OnUpdate(elapsed) -- override
|
||||
local actor = self.followActor;
|
||||
if actor then
|
||||
local height = actor:GetCameraPivotHeight(self:GetZoomDistance());
|
||||
if not height then
|
||||
return;
|
||||
end
|
||||
local now = GetTime();
|
||||
self:ArmPivotHeight(height, now);
|
||||
self:EasePivotHeight(now);
|
||||
local x, y, z = actor:GetPosition();
|
||||
self:SetTarget(x, y, z + self.pivotHeight);
|
||||
end
|
||||
OrbitCameraMixin.OnUpdate(self, elapsed);
|
||||
end
|
||||
|
||||
function FollowCameraMixin:CalculatePositionByDistanceFromTarget(targetX, targetY, targetZ, zoomDistance, axisAngleX, axisAngleY, axisAngleZ) -- override
|
||||
if not self.followActor then
|
||||
return OrbitCameraMixin.CalculatePositionByDistanceFromTarget(self, targetX, targetY, targetZ, zoomDistance, axisAngleX, axisAngleY, axisAngleZ);
|
||||
end
|
||||
|
||||
local scene = self:GetOwningScene();
|
||||
local distance = zoomDistance;
|
||||
if distance - scene:GetCameraNearClip() > SWEEP_MIN_DISTANCE then
|
||||
local eyeX, eyeY, eyeZ = OrbitCameraMixin.CalculatePositionByDistanceFromTarget(self, targetX, targetY, targetZ, distance, axisAngleX, axisAngleY, axisAngleZ);
|
||||
local _, _, _, _, _, _, fraction = scene:TraceSegment(targetX, targetY, targetZ, eyeX, eyeY, eyeZ, true);
|
||||
if fraction then
|
||||
distance = distance * fraction;
|
||||
end
|
||||
if math.abs(zoomDistance - distance) > SHORTENED_EPSILON then
|
||||
distance = math.max(distance - BOOM_BACKOFF, 0);
|
||||
end
|
||||
end
|
||||
return OrbitCameraMixin.CalculatePositionByDistanceFromTarget(self, targetX, targetY, targetZ, distance, axisAngleX, axisAngleY, axisAngleZ);
|
||||
end
|
||||
@@ -0,0 +1,144 @@
|
||||
-- Verbatim backport of retail's Blizzard_SharedXML/ModelSceneActorMixin.lua.
|
||||
|
||||
|
||||
ModelSceneActorMixin = {};
|
||||
|
||||
-- "public" methods
|
||||
function ModelSceneActorMixin:ApplyFromModelSceneActorInfo(actorInfo)
|
||||
self:SetUseCenterForOrigin(actorInfo.useCenterForOriginX, actorInfo.useCenterForOriginY, actorInfo.useCenterForOriginZ);
|
||||
|
||||
self:SetPosition(actorInfo.position:GetXYZ());
|
||||
self:SetYaw(actorInfo.yaw);
|
||||
self:SetPitch(actorInfo.pitch);
|
||||
self:SetRoll(actorInfo.roll);
|
||||
|
||||
self.requestedScale = nil;
|
||||
|
||||
local actorDisplayInfo = actorInfo.modelActorDisplayID and C_ModelInfo.GetModelSceneActorDisplayInfoByID(actorInfo.modelActorDisplayID);
|
||||
if actorDisplayInfo then
|
||||
if actorDisplayInfo.animationKitID then
|
||||
self:PlayAnimationKit(actorDisplayInfo.animationKitID);
|
||||
else
|
||||
self:SetAnimation(actorDisplayInfo.animation, actorDisplayInfo.animationVariation, actorDisplayInfo.animSpeed);
|
||||
end
|
||||
|
||||
self:SetSpellVisualKit(actorDisplayInfo.spellVisualKitID);
|
||||
|
||||
self:SetAlpha(actorDisplayInfo.alpha);
|
||||
self:SetRequestedScale(actorDisplayInfo.scale);
|
||||
else
|
||||
self:StopAnimationKit();
|
||||
self:SetSpellVisualKit(nil);
|
||||
self:SetAnimation(0, 0, 1.0);
|
||||
self:SetAlpha(1.0);
|
||||
self:SetRequestedScale(1.0);
|
||||
end
|
||||
|
||||
self:SetNormalizedScaleAggressiveness(actorInfo.normalizeScaleAggressiveness or 0.0);
|
||||
|
||||
self:MarkScaleDirty();
|
||||
self:UpdateScale();
|
||||
|
||||
C_ModelInfo.AddActiveModelSceneActor(self, actorInfo.modelActorID);
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:OnModelCleared()
|
||||
C_ModelInfo.ClearActiveModelSceneActor(self);
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:SetOnSizeChangedCallback(onSizeChangedCallback)
|
||||
self.onSizeChangedCallback = onSizeChangedCallback;
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:GetOnSizeChangedCallback()
|
||||
return self.onSizeChangedCallback;
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:SetOnModelLoadedCallback(onModelLoadedCallback)
|
||||
self.onModelLoadedCallback = onModelLoadedCallback;
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:GetOnModelLoadedCallback()
|
||||
return self.onModelLoadedCallback;
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:SetNormalizedScaleAggressiveness(normalizedScaleAggressiveness)
|
||||
if self.normalizedScaleAggressiveness ~= normalizedScaleAggressiveness then
|
||||
self.normalizedScaleAggressiveness = normalizedScaleAggressiveness;
|
||||
self:MarkScaleDirty();
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:GetNormalizedScaleAggressiveness()
|
||||
return self.normalizedScaleAggressiveness or 0;
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:SetRequestedScale(requestedScale)
|
||||
if self.requestedScale ~= requestedScale then
|
||||
self.requestedScale = requestedScale;
|
||||
self:MarkScaleDirty();
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:GetRequestedScale()
|
||||
return self.requestedScale or self:GetScale();
|
||||
end
|
||||
|
||||
MODEL_SCENE_ACTOR_DIMENSIONS_FOR_NORMALIZATION = { width = .9984, depth = 1.121, height = 2.118 }; -- Roughly the dimensions of a human male model (but why male models?)
|
||||
function ModelSceneActorMixin:CalculateNormalizedScale(normalizedScaleAggressiveness)
|
||||
if self:IsLoaded() and normalizedScaleAggressiveness > 0.0 then
|
||||
local bottomX, bottomY, bottomZ, topX, topY, topZ = self:GetActiveBoundingBox(); -- Could be nil for invisible models
|
||||
if bottomX and bottomY and bottomZ and topX and topY and topZ then
|
||||
local width = topX - bottomX;
|
||||
local depth = topY - bottomY;
|
||||
local height = topZ - bottomZ;
|
||||
|
||||
local widthScale = width / MODEL_SCENE_ACTOR_DIMENSIONS_FOR_NORMALIZATION.width;
|
||||
local depthScale = depth / MODEL_SCENE_ACTOR_DIMENSIONS_FOR_NORMALIZATION.depth;
|
||||
local heightScale = height / MODEL_SCENE_ACTOR_DIMENSIONS_FOR_NORMALIZATION.height;
|
||||
|
||||
local maxScale = math.max(widthScale, math.max(depthScale, heightScale));
|
||||
if maxScale == 0 then
|
||||
return 1.0;
|
||||
end
|
||||
|
||||
return 1 / Lerp(1.0, maxScale, normalizedScaleAggressiveness);
|
||||
end
|
||||
end
|
||||
|
||||
return 1.0;
|
||||
end
|
||||
|
||||
-- "private" methods
|
||||
function ModelSceneActorMixin:OnReleased()
|
||||
self:SetOnSizeChangedCallback(nil);
|
||||
self:SetOnModelLoadedCallback(nil);
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:OnUpdate()
|
||||
self:UpdateScale();
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:OnModelLoaded()
|
||||
self:MarkScaleDirty();
|
||||
|
||||
if self.onModelLoadedCallback then
|
||||
self.onModelLoadedCallback(self);
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:UpdateScale()
|
||||
if self.scaleDirty and self:IsLoaded() then
|
||||
self.scaleDirty = nil;
|
||||
local effectiveScale = self:GetRequestedScale() * self:CalculateNormalizedScale(self:GetNormalizedScaleAggressiveness());
|
||||
self:SetScale(effectiveScale);
|
||||
|
||||
if self.onSizeChangedCallback then
|
||||
self.onSizeChangedCallback(self);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneActorMixin:MarkScaleDirty()
|
||||
self.scaleDirty = true;
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
UI.xsd">
|
||||
<Actor name="ModelSceneActorTemplate" mixin="ModelSceneActorMixin" virtual="true">
|
||||
<Scripts>
|
||||
<OnModelCleared method="OnModelCleared" />
|
||||
<OnModelLoaded method="OnModelLoaded" />
|
||||
<OnUpdate method="OnUpdate" />
|
||||
</Scripts>
|
||||
</Actor>
|
||||
</Ui>
|
||||
@@ -0,0 +1,282 @@
|
||||
-- Verbatim backport of retail's Blizzard_SharedXML/ModelSceneControlFrame.lua.
|
||||
|
||||
|
||||
local DEFAULT_ZOOM_INCREMENT = 1;
|
||||
local DEFAULT_ROTATE_INCREMENT = 0.05;
|
||||
|
||||
--------------------------------------------------
|
||||
-- MODEL SCENE CONTROL FRAME MIXIN
|
||||
ModelSceneControlFrameMixin = {};
|
||||
function ModelSceneControlFrameMixin:OnLoad()
|
||||
if self.enableZoom then
|
||||
local increment = self:GetZoomIncrement();
|
||||
self.zoomInButton:SetZoomAmount(increment);
|
||||
self.zoomInButton:Init();
|
||||
|
||||
self.zoomOutButton:SetZoomAmount(-increment);
|
||||
self.zoomOutButton:Init();
|
||||
end
|
||||
self.zoomInButton:SetShown(self.enableZoom);
|
||||
self.zoomOutButton:SetShown(self.enableZoom);
|
||||
|
||||
if self.enableRotate then
|
||||
local increment = self:GetRotateIncrement();
|
||||
self.rotateLeftButton:SetRotation("left");
|
||||
self.rotateLeftButton:SetRotationIncrement(increment);
|
||||
self.rotateLeftButton:Init();
|
||||
|
||||
self.rotateRightButton:SetRotation("right");
|
||||
self.rotateRightButton:SetRotationIncrement(increment);
|
||||
self.rotateRightButton:Init();
|
||||
end
|
||||
self.rotateLeftButton:SetShown(self.enableRotate);
|
||||
self.rotateRightButton:SetShown(self.enableRotate);
|
||||
|
||||
if self.enableReset then
|
||||
self.resetButton:Init();
|
||||
end
|
||||
self.resetButton:SetShown(self.enableReset);
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:OnShow()
|
||||
self:UpdateLayout();
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:OnEnter()
|
||||
self:SetAlpha(1);
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:OnLeave()
|
||||
self:SetAlpha(0.5);
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:SetZoomIncrement(increment)
|
||||
self.zoomIncrement = increment or DEFAULT_ZOOM_INCREMENT;
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:GetZoomIncrement()
|
||||
return self.zoomIncrement or DEFAULT_ZOOM_INCREMENT;
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:SetRotateIncrement(increment)
|
||||
self.rotationIncrement = increment or DEFAULT_ROTATE_INCREMENT;
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:GetRotateIncrement()
|
||||
return self.rotationIncrement or DEFAULT_ROTATE_INCREMENT;
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:SetModelScene(modelScene)
|
||||
self.modelScene = modelScene;
|
||||
|
||||
self.zoomInButton:SetModelScene(modelScene);
|
||||
self.zoomOutButton:SetModelScene(modelScene);
|
||||
self.rotateLeftButton:SetModelScene(modelScene);
|
||||
self.rotateRightButton:SetModelScene(modelScene);
|
||||
self.resetButton:SetModelScene(modelScene);
|
||||
end
|
||||
|
||||
function ModelSceneControlFrameMixin:UpdateLayout()
|
||||
local lastButton;
|
||||
local hPadding = self.buttonHorizontalPadding or 0;
|
||||
local vPadding = 0;
|
||||
local buttonSize = 32;
|
||||
local totalHeight = buttonSize;
|
||||
local totalWidth = 2; -- Initial non-zero total width to prevent flickering from negataive padding
|
||||
|
||||
local function LayoutButton(button)
|
||||
button:ClearAllPoints();
|
||||
if lastButton then
|
||||
button:SetPoint("LEFT", lastButton, "RIGHT", hPadding, vPadding);
|
||||
else
|
||||
button:SetPoint("LEFT", 0, vPadding);
|
||||
end
|
||||
totalWidth = totalWidth + buttonSize + hPadding;
|
||||
lastButton = button;
|
||||
end
|
||||
|
||||
if self.enableZoom then
|
||||
-- Make sure that zoom is supported on the model scene.
|
||||
if self.modelScene then
|
||||
local camera = self.modelScene:GetActiveCamera();
|
||||
self.canZoom = camera ~= nil and camera:GetZoomAvailable();
|
||||
self.zoomInButton:SetShown(self.canZoom);
|
||||
self.zoomOutButton:SetShown(self.canZoom);
|
||||
end
|
||||
|
||||
if self.canZoom then
|
||||
LayoutButton(self.zoomInButton);
|
||||
LayoutButton(self.zoomOutButton);
|
||||
end
|
||||
end
|
||||
|
||||
if self.enableRotate then
|
||||
LayoutButton(self.rotateLeftButton);
|
||||
LayoutButton(self.rotateRightButton);
|
||||
end
|
||||
|
||||
if self.enableReset then
|
||||
LayoutButton(self.resetButton);
|
||||
end
|
||||
self:SetSize(totalWidth, totalHeight);
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
-- MODEL SCENE CONTROL BUTTON MIXIN
|
||||
ModelSceneControlButtonMixin = {};
|
||||
function ModelSceneControlButtonMixin:Init(clickTypes, atlas, tooltip, tooltipText)
|
||||
self:RegisterForClicks(clickTypes);
|
||||
if atlas then
|
||||
self.Icon:SetAtlas(atlas);
|
||||
end
|
||||
self.tooltip = tooltip;
|
||||
self.tooltipText = tooltipText;
|
||||
end
|
||||
|
||||
function ModelSceneControlButtonMixin:SetModelScene(modelScene)
|
||||
self.modelScene = modelScene;
|
||||
end
|
||||
|
||||
function ModelSceneControlButtonMixin:OnMouseDown()
|
||||
self.Icon:AdjustPointsOffset(1, -1);
|
||||
self:GetParent().buttonDown = self;
|
||||
end
|
||||
|
||||
function ModelSceneControlButtonMixin:OnMouseUp()
|
||||
self.Icon:AdjustPointsOffset(-1, 1);
|
||||
self:GetParent().buttonDown = nil;
|
||||
end
|
||||
|
||||
function ModelSceneControlButtonMixin:OnClick()
|
||||
--override for your mouse click
|
||||
end
|
||||
|
||||
function ModelSceneControlButtonMixin:OnEnter()
|
||||
self:GetParent():SetAlpha(1);
|
||||
if ( GetCVar("UberTooltips") == "1" ) then
|
||||
local tooltip = GetAppropriateTooltip();
|
||||
local topLevelParent = GetAppropriateTopLevelParent();
|
||||
GameTooltip_SetDefaultAnchor(tooltip, topLevelParent);
|
||||
GameTooltip_SetTitle(tooltip, self.tooltip);
|
||||
if ( self.tooltipText ) then
|
||||
GameTooltip_AddBodyLine(tooltip, self.tooltipText);
|
||||
end
|
||||
tooltip:Show();
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneControlButtonMixin:OnLeave()
|
||||
self:GetParent():SetAlpha(0.5);
|
||||
|
||||
local tooltip = GetAppropriateTooltip();
|
||||
tooltip:Hide();
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
-- MODEL ZOOM BUTTON MIXIN
|
||||
ModelSceneZoomButtonMixin = CreateFromMixins(ModelSceneControlButtonMixin);
|
||||
function ModelSceneZoomButtonMixin:OnLoad()
|
||||
self.zoomAmount = 0;
|
||||
end
|
||||
|
||||
function ModelSceneZoomButtonMixin:Init()
|
||||
if self.zoomAmount < 0 then
|
||||
ModelSceneControlButtonMixin.Init(self, "AnyUp", "common-icon-zoomout", ZOOM_OUT, KEY_MOUSEWHEELDOWN);
|
||||
else
|
||||
ModelSceneControlButtonMixin.Init(self, "AnyUp", "common-icon-zoomin", ZOOM_IN, KEY_MOUSEWHEELUP);
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneZoomButtonMixin:SetZoomAmount(amountToZoom)
|
||||
self.zoomAmount = amountToZoom;
|
||||
end
|
||||
|
||||
function ModelSceneZoomButtonMixin:OnClick()
|
||||
if self.modelScene then
|
||||
self.modelScene:OnMouseWheel(self.zoomAmount);
|
||||
else
|
||||
assertsafe(fale, "Model Scene not specified");
|
||||
end
|
||||
PlaySound(SOUNDKIT.IG_INVENTORY_ROTATE_CHARACTER);
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
-- MODEL SCENE ROTATE BUTTON MIXIN
|
||||
ModelScenelRotateButtonMixin = CreateFromMixins(ModelSceneControlButtonMixin);
|
||||
function ModelScenelRotateButtonMixin:OnLoad()
|
||||
ModelSceneControlButtonMixin.OnLoad(self);
|
||||
self.rotateDirection = "none";
|
||||
end
|
||||
|
||||
function ModelScenelRotateButtonMixin:Init()
|
||||
if (self.rotateDirection == "left") then
|
||||
ModelSceneControlButtonMixin.Init(self, "AnyUp", "common-icon-rotateleft", ROTATE_LEFT, ROTATE_TOOLTIP);
|
||||
elseif (self.rotateDirection == "right") then
|
||||
ModelSceneControlButtonMixin.Init(self, "AnyUp", "common-icon-rotateright", ROTATE_RIGHT, ROTATE_TOOLTIP);
|
||||
else
|
||||
assertsafe(false, "Invalid rotation specified: "..tostring(self.rotateDirection));
|
||||
end
|
||||
end
|
||||
|
||||
function ModelScenelRotateButtonMixin:SetRotation(rotation)
|
||||
self.rotateDirection = rotation;
|
||||
end
|
||||
|
||||
function ModelScenelRotateButtonMixin:SetRotationIncrement(increment)
|
||||
self.rotationIncrement = increment or 0.05;
|
||||
end
|
||||
|
||||
function ModelScenelRotateButtonMixin:OnMouseDown()
|
||||
ModelSceneControlButtonMixin.OnMouseDown(self);
|
||||
if self.modelScene then
|
||||
self.modelScene:AdjustCameraYaw(self.rotateDirection, self.rotationIncrement);
|
||||
else
|
||||
assertsafe(false, "Model Scene not specified");
|
||||
end
|
||||
PlaySound(SOUNDKIT.IG_INVENTORY_ROTATE_CHARACTER);
|
||||
end
|
||||
|
||||
function ModelScenelRotateButtonMixin:OnMouseUp()
|
||||
ModelSceneControlButtonMixin.OnMouseUp(self);
|
||||
if self.modelScene then
|
||||
self.modelScene:StopCameraYaw();
|
||||
end
|
||||
end
|
||||
|
||||
function ModelScenelRotateButtonMixin:OnSmartNavClick()
|
||||
self:RotateStep();
|
||||
end
|
||||
|
||||
function ModelScenelRotateButtonMixin:RotateStep()
|
||||
local camera = self.modelScene and self.modelScene:GetActiveCamera();
|
||||
if not camera then
|
||||
return;
|
||||
end
|
||||
|
||||
if self.rotateDirection == "left" then
|
||||
camera:AdjustYaw(-1, -1, 0.25);
|
||||
else
|
||||
camera:AdjustYaw(1, 1, 0.25);
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
-- MODEL SCENE RESET BUTTON MIXIN
|
||||
ModelSceneResetButtonMixin = CreateFromMixins(ModelSceneControlButtonMixin);
|
||||
function ModelSceneResetButtonMixin:OnLoad()
|
||||
ModelSceneControlButtonMixin.OnLoad(self);
|
||||
end
|
||||
|
||||
function ModelSceneResetButtonMixin:Init()
|
||||
local tooltipText = nil;
|
||||
ModelSceneControlButtonMixin.Init(self, "AnyUp", "common-icon-undo", RESET_POSITION, tooltipText);
|
||||
end
|
||||
|
||||
function ModelSceneResetButtonMixin:OnClick()
|
||||
if self.modelScene then
|
||||
self.modelScene:Reset();
|
||||
else
|
||||
assertsafe(false, "Model Scene not specified");
|
||||
end
|
||||
PlaySound(SOUNDKIT.IG_INVENTORY_ROTATE_CHARACTER);
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
UI.xsd">
|
||||
<!-- Retail's Blizzard_SharedXML/ModelSceneControlFrame.xml. The one change:
|
||||
HitRectInsets takes this client's <AbsInset> child. The atlases are
|
||||
built in (src/texture/AtlasData.h, EMBEDDED ART). -->
|
||||
<Button name="ModelSceneControlButtonTemplate" mixin="ModelSceneControlButtonMixin" virtual="true">
|
||||
<Size x="32" y="32"/>
|
||||
<HitRectInsets>
|
||||
<AbsInset left="4" right="4" top="4" bottom="4"/>
|
||||
</HitRectInsets>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture parentKey="Icon" >
|
||||
<Size x="16" y="16"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<!--<Texture parentKey="Icon" setAllPoints="true" />-->
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnMouseDown method="OnMouseDown" />
|
||||
<OnMouseUp method="OnMouseUp" />
|
||||
<OnClick method="OnClick" />
|
||||
<OnEnter method="OnEnter" />
|
||||
<OnLeave method="OnLeave" />
|
||||
</Scripts>
|
||||
<NormalTexture parentKey="NormalTexture" atlas="common-button-square-gray-up"/>
|
||||
<HighlightTexture parentKey="HighlightTexture" alphaMode="ADD" alpha="0.4">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Icon" relativePoint="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeKey="$parent.Icon" relativePoint="BOTTOMRIGHT"/>
|
||||
</Anchors>
|
||||
</HighlightTexture>
|
||||
<PushedTexture parentKey="PushedTexture" atlas="common-button-square-gray-down">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="1" y="-1"/>
|
||||
<Anchor point="BOTTOMRIGHT" x="1" y="-1"/>
|
||||
</Anchors>
|
||||
</PushedTexture>
|
||||
</Button>
|
||||
<Frame name="ModelSceneControlFrameTemplate" virtual="true" mixin="ModelSceneControlFrameMixin" alpha="0.5" hidden="true">
|
||||
<Size x="32" y="32"/>
|
||||
<KeyValues>
|
||||
<KeyValue key="enableZoom" value="true" type="boolean" />
|
||||
<KeyValue key="enableRotate" value="true" type="boolean" />
|
||||
<KeyValue key="enableReset" value="true" type="boolean" />
|
||||
<KeyValue key="rotationIncrement" value="0.05" type="number" />
|
||||
<KeyValue key="zoomIncrement" value="1" type="number" />
|
||||
<KeyValue key="buttonHorizontalPadding" value="-6" type="number" />
|
||||
</KeyValues>
|
||||
<Frames>
|
||||
<Button parentKey="zoomInButton" hidden="true" mixin="ModelSceneZoomButtonMixin" inherits="ModelSceneControlButtonTemplate"/>
|
||||
<Button parentKey="zoomOutButton" hidden="true" mixin="ModelSceneZoomButtonMixin" inherits="ModelSceneControlButtonTemplate"/>
|
||||
<Button parentKey="rotateLeftButton" hidden="true" mixin="ModelScenelRotateButtonMixin" inherits="ModelSceneControlButtonTemplate"/>
|
||||
<Button parentKey="rotateRightButton" hidden="true" mixin="ModelScenelRotateButtonMixin" inherits="ModelSceneControlButtonTemplate"/>
|
||||
<Button parentKey="resetButton" hidden="true" mixin="ModelSceneResetButtonMixin" inherits="ModelSceneControlButtonTemplate"/>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad method="OnLoad" />
|
||||
<OnShow method="OnShow" />
|
||||
<OnEnter method="OnEnter" />
|
||||
<OnLeave method="OnLeave" />
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -0,0 +1,571 @@
|
||||
-- Backport of retail's Blizzard_SharedXML/ModelSceneMixin.lua, verbatim except where
|
||||
-- marked "ClassicAPI:".
|
||||
|
||||
|
||||
ModelSceneMixin = {}
|
||||
|
||||
-- "public" functions
|
||||
function ModelSceneMixin:OnLoad()
|
||||
self.cameras = {};
|
||||
self.actorTemplate = "ModelSceneActorTemplate";
|
||||
self.tagToActor = {};
|
||||
self.tagToCamera = {};
|
||||
|
||||
if self.allowOverlappedModels then
|
||||
self:SetAllowOverlappedModels(true);
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:ClearScene()
|
||||
self.modelSceneID = nil;
|
||||
|
||||
self:ReleaseAllActors();
|
||||
self:ReleaseAllCameras();
|
||||
|
||||
C_ModelInfo.ClearActiveModelScene(self);
|
||||
end
|
||||
|
||||
function ModelSceneMixin:GetModelSceneID()
|
||||
return self.modelSceneID;
|
||||
end
|
||||
|
||||
-- Adjusts this scene to mirror a model scene from static data without transition
|
||||
function ModelSceneMixin:SetFromModelSceneID(modelSceneID, forceEvenIfSame, noAutoCreateActors)
|
||||
local modelSceneType, cameraIDs, actorIDs = C_ModelInfo.GetModelSceneInfoByID(modelSceneID);
|
||||
if not modelSceneType then
|
||||
return;
|
||||
end
|
||||
|
||||
if self.modelSceneID ~= modelSceneID or forceEvenIfSame then
|
||||
self.modelSceneID = modelSceneID;
|
||||
self:ReleaseAllActors();
|
||||
self:ReleaseAllCameras();
|
||||
|
||||
if not noAutoCreateActors then
|
||||
for actorIndex, actorID in ipairs(actorIDs) do
|
||||
self:CreateActorFromScene(actorID);
|
||||
end
|
||||
end
|
||||
|
||||
for cameraIndex, cameraID in ipairs(cameraIDs) do
|
||||
self:CreateCameraFromScene(cameraID);
|
||||
end
|
||||
end
|
||||
|
||||
C_ModelInfo.AddActiveModelScene(self, self.modelSceneID);
|
||||
EventRegistry:TriggerEvent("ModelScene.SetFromModelSceneID", self, self.modelSceneID);
|
||||
end
|
||||
|
||||
function ModelSceneMixin:SetResetCallback(callback)
|
||||
self.resetCallback = callback;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:Reset()
|
||||
if self.modelSceneID then
|
||||
self:TransitionToModelSceneID(self.modelSceneID, self.cameraTransitionType, self.cameraModificationType, self.forceEvenIfSame);
|
||||
if self.resetCallback then
|
||||
self.resetCallback(self);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Adjusts this scene to mirror a model scene from static data but with transition effects
|
||||
-- Only actors/cameras with script tags will be transitioned
|
||||
function ModelSceneMixin:TransitionToModelSceneID(modelSceneID, cameraTransitionType, cameraModificationType, forceEvenIfSame)
|
||||
local modelSceneType, cameraIDs, actorIDs = C_ModelInfo.GetModelSceneInfoByID(modelSceneID);
|
||||
if not modelSceneType or #cameraIDs == 0 or #actorIDs == 0 then
|
||||
return;
|
||||
end
|
||||
|
||||
if self.modelSceneID ~= modelSceneID or forceEvenIfSame then
|
||||
self.modelSceneID = modelSceneID;
|
||||
self.cameraTransitionType = cameraTransitionType;
|
||||
self.cameraModificationType = cameraModificationType;
|
||||
self.forceEvenIfSame = forceEvenIfSame;
|
||||
|
||||
local actorsToRelease = {};
|
||||
for actor in self:EnumerateActiveActors() do
|
||||
actorsToRelease[actor] = true;
|
||||
end
|
||||
|
||||
local oldTagToActor = self.tagToActor;
|
||||
self.tagToActor = {};
|
||||
|
||||
for actorIndex, actorID in ipairs(actorIDs) do
|
||||
local actor = self:CreateOrTransitionActorFromScene(oldTagToActor, actorID);
|
||||
if actor then
|
||||
actorsToRelease[actor] = nil;
|
||||
end
|
||||
end
|
||||
|
||||
for actor in pairs(actorsToRelease) do
|
||||
self.actorPool:Release(actor);
|
||||
end
|
||||
|
||||
local oldTagToCamera = self.tagToCamera;
|
||||
self.tagToCamera = {};
|
||||
|
||||
self.cameras = {};
|
||||
|
||||
local needsNewCamera = true;
|
||||
for cameraIndex, cameraID in ipairs(cameraIDs) do
|
||||
local camera = self:CreateOrTransitionCameraFromScene(oldTagToCamera, cameraTransitionType, cameraModificationType, cameraID);
|
||||
if camera == self.activeCamera then
|
||||
needsNewCamera = false;
|
||||
end
|
||||
end
|
||||
|
||||
if needsNewCamera then
|
||||
self:SetActiveCamera(self.cameras[1]);
|
||||
end
|
||||
end
|
||||
|
||||
C_ModelInfo.AddActiveModelScene(self, self.modelSceneID);
|
||||
EventRegistry:TriggerEvent("ModelScene.TransitionToModelSceneID", self, self.modelSceneID);
|
||||
end
|
||||
|
||||
-- There may be inactive (pooled) actors maintained by this scene, these function only returns the active actors
|
||||
function ModelSceneMixin:GetNumActiveActors()
|
||||
return self.actorPool and self.actorPool:GetNumActive() or 0;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:EnumerateActiveActors()
|
||||
if self.actorPool then
|
||||
return self.actorPool:EnumerateActive();
|
||||
end
|
||||
return nop;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:GetActorByTag(tag)
|
||||
return self.tagToActor[tag];
|
||||
end
|
||||
|
||||
function ModelSceneMixin:AttachPlayerToMount(mountActor, animID, isSelfMount, disablePlayerMountPreview, spellVisualKitID, usePlayerNativeForm, optionalPlayerRiderTag)
|
||||
local playerRiderTag = optionalPlayerRiderTag or "player-rider";
|
||||
local playerActor = self:GetPlayerActor(playerRiderTag);
|
||||
if (playerActor) then
|
||||
if disablePlayerMountPreview or isSelfMount then
|
||||
playerActor:ClearModel();
|
||||
else
|
||||
local sheatheWeapons = true;
|
||||
local autoDress = true;
|
||||
local hideWeapons = true;
|
||||
|
||||
if C_Glue.IsOnGlueScreen() then
|
||||
local characterIndex = nil; -- defaults to selected character.
|
||||
playerActor:SetPlayerModelFromGlues(characterIndex, sheatheWeapons, autoDress, hideWeapons, usePlayerNativeForm);
|
||||
else
|
||||
playerActor:SetModelByUnit("player", sheatheWeapons, autoDress, hideWeapons, usePlayerNativeForm);
|
||||
end
|
||||
local calcMountScale = mountActor:CalculateMountScale(playerActor);
|
||||
local inverseScale = 1 / calcMountScale;
|
||||
playerActor:SetRequestedScale( inverseScale );
|
||||
mountActor:AttachToMount(playerActor, animID, spellVisualKitID);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GetPlayerActorLabelTag(forceAlternateForm, overrideRaceFilename, overrideGender)
|
||||
local playerRaceName = overrideRaceFilename;
|
||||
local playerGender = overrideGender;
|
||||
local playerRaceNameActorTag = nil;
|
||||
local hasAlternateForm, inAlternateForm = false, false;
|
||||
if not overrideRaceFilename or not overrideGender then
|
||||
if C_Glue.IsOnGlueScreen() then
|
||||
local characterGuid = GetCharacterGUID(GetCharacterSelection());
|
||||
if characterGuid then
|
||||
local basicCharacterInfo = GetBasicCharacterInfo(characterGuid);
|
||||
playerRaceName = overrideRaceFilename or basicCharacterInfo.raceFilename;
|
||||
playerGender = overrideGender or basicCharacterInfo.genderEnum;
|
||||
end
|
||||
else
|
||||
hasAlternateForm, inAlternateForm = C_PlayerInfo.GetAlternateFormInfo();
|
||||
local _, raceFilename = UnitRace("player");
|
||||
playerRaceName = overrideRaceFilename or raceFilename;
|
||||
playerGender = overrideGender or UnitSex("player");
|
||||
end
|
||||
end
|
||||
if not playerRaceName or not playerGender then
|
||||
return playerRaceName, playerRaceNameActorTag;
|
||||
end
|
||||
playerRaceName = playerRaceName:lower();
|
||||
if hasAlternateForm and (inAlternateForm or forceAlternateForm) then
|
||||
playerRaceName = playerRaceName.."-alt";
|
||||
end
|
||||
playerGender = (playerGender == 2) and "male" or "female";
|
||||
playerRaceNameActorTag = playerRaceName.."-"..playerGender;
|
||||
return playerRaceName, playerRaceNameActorTag;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:GetPlayerActor(overrideActorName, forceAlternateForm, overrideRaceFilename, overrideGender)
|
||||
local actor;
|
||||
if overrideActorName then
|
||||
actor = self:GetActorByTag(overrideActorName);
|
||||
else
|
||||
local playerRaceName, playerRaceNameActorTag = GetPlayerActorLabelTag(forceAlternateForm, overrideRaceFilename, overrideGender);
|
||||
actor = self:GetActorByTag(playerRaceNameActorTag);
|
||||
if not actor then
|
||||
actor = self:GetActorByTag(playerRaceName);
|
||||
if not actor then
|
||||
actor = self:GetActorByTag("player");
|
||||
if not actor then
|
||||
actor = self:GetActorByTag("player-rider");
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return actor;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:ReleaseAllActors()
|
||||
if self.actorPool then
|
||||
self.actorPool:ReleaseAll();
|
||||
|
||||
if self.dropShadowPool then
|
||||
self.dropShadowPool:ReleaseAll();
|
||||
end
|
||||
|
||||
self.tagToActor = {};
|
||||
end
|
||||
end
|
||||
|
||||
local function OnReleased(actorPool, actor)
|
||||
actor:OnReleased();
|
||||
ActorPool_HideAndClearModel(actorPool, actor);
|
||||
end
|
||||
|
||||
function ModelSceneMixin:AcquireActor()
|
||||
if not self.actorPool then
|
||||
self.actorPool = CreateActorPool(self, self.actorTemplate, OnReleased);
|
||||
end
|
||||
return self.actorPool:Acquire();
|
||||
end
|
||||
|
||||
function ModelSceneMixin:ReleaseActor(actor)
|
||||
if not actor or not self.actorPool then
|
||||
return;
|
||||
end
|
||||
|
||||
for tag, taggedActor in pairs(self.tagToActor) do
|
||||
if actor == taggedActor then
|
||||
self.tagToActor[tag] = nil;
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
if actor.dropShadowTexture then
|
||||
self.dropShadowPool:Release(actor.dropShadowTexture);
|
||||
end
|
||||
|
||||
return self.actorPool:Release(actor);
|
||||
end
|
||||
|
||||
function ModelSceneMixin:AcquireAndInitializeActor(actorInfo)
|
||||
local actor = self:AcquireActor();
|
||||
self:InitializeActor(actor, actorInfo);
|
||||
return actor;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:ReleaseAllCameras()
|
||||
self:SetActiveCamera(nil);
|
||||
for i = #self.cameras, 1, -1 do
|
||||
self.cameras[i]:SetOwningScene(nil);
|
||||
self.cameras[i] = nil;
|
||||
end
|
||||
self.tagToCamera = {};
|
||||
end
|
||||
|
||||
function ModelSceneMixin:GetCameraByTag(tag)
|
||||
return self.tagToCamera[tag];
|
||||
end
|
||||
|
||||
function ModelSceneMixin:AddCamera(camera)
|
||||
table.insert(self.cameras, camera);
|
||||
|
||||
camera:SetOwningScene(self);
|
||||
|
||||
if not self:HasActiveCamera() then
|
||||
self:SetActiveCamera(camera);
|
||||
end
|
||||
|
||||
return camera;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:HasActiveCamera()
|
||||
return self.activeCamera ~= nil;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:GetActiveCamera()
|
||||
return self.activeCamera;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:SetActiveCamera(camera)
|
||||
if camera ~= self.activeCamera then
|
||||
if self.activeCamera then
|
||||
self.activeCamera:OnDeactivated();
|
||||
end
|
||||
|
||||
self.activeCamera = camera;
|
||||
|
||||
if self.activeCamera then
|
||||
self.activeCamera:OnActivated();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:IsLeftMouseButtonDown()
|
||||
return self.isLeftButtonDown;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:IsRightMouseButtonDown()
|
||||
return self.isRightButtonDown;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:Transform3DPointTo2D(x, y, z)
|
||||
self:SynchronizeActiveCamera();
|
||||
return self:Project3DPointTo2D(x, y, z);
|
||||
end
|
||||
|
||||
-- "private" functions
|
||||
function ModelSceneMixin:OnUpdate(elapsed)
|
||||
if self.activeCamera then
|
||||
local yawDirection = self.yawDirection;
|
||||
-- ClassicAPI: retail turns by the increment every frame, which spins
|
||||
-- several times faster at this client's uncapped frame rates. The
|
||||
-- increment counts per 1/60 s here: 0.05 is 3 rad/s, the speed of
|
||||
-- 1.12's own rotate buttons (ROTATIONS_PER_SECOND, pi rad/s).
|
||||
local increment = self.increment and self.increment * elapsed * 60;
|
||||
if yawDirection == "left" then
|
||||
self.activeCamera:AdjustYaw(-1, -1, increment);
|
||||
elseif yawDirection == "right" then
|
||||
self.activeCamera:AdjustYaw(1, 1, increment);
|
||||
end
|
||||
|
||||
self.activeCamera:OnUpdate(elapsed);
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:SynchronizeActiveCamera()
|
||||
if self.activeCamera then
|
||||
self.activeCamera:SynchronizeCamera();
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:OnEnter(button)
|
||||
if self.ControlFrame then
|
||||
self.ControlFrame:Show();
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:OnLeave(button)
|
||||
if self.ControlFrame then
|
||||
if not self.ControlFrame:IsMouseOver()then
|
||||
self.ControlFrame:Hide();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:OnMouseDown(button)
|
||||
if button == "LeftButton" then
|
||||
self.isLeftButtonDown = true;
|
||||
elseif button == "RightButton" then
|
||||
self.isRightButtonDown = true;
|
||||
end
|
||||
|
||||
if self.activeCamera then
|
||||
self.activeCamera:OnMouseDown(button);
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:OnMouseUp(button)
|
||||
if button == "LeftButton" then
|
||||
self.isLeftButtonDown = false;
|
||||
elseif button == "RightButton" then
|
||||
self.isRightButtonDown = false;
|
||||
end
|
||||
|
||||
if self.activeCamera then
|
||||
self.activeCamera:OnMouseUp(button);
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:OnMouseWheel(delta)
|
||||
if self.activeCamera then
|
||||
self.activeCamera:OnMouseWheel(delta);
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:AdjustCameraYaw(direction, increment)
|
||||
if self.activeCamera then
|
||||
self.yawDirection = direction;
|
||||
self.increment = increment;
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:StopCameraYaw()
|
||||
self.yawDirection = nil;
|
||||
self.increment = nil;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:InitializeActor(actor, actorInfo)
|
||||
if actorInfo.scriptTag then
|
||||
self.tagToActor[actorInfo.scriptTag] = actor;
|
||||
end
|
||||
|
||||
actor:ApplyFromModelSceneActorInfo(actorInfo);
|
||||
|
||||
actor:Show();
|
||||
end
|
||||
|
||||
function ModelSceneMixin:CreateActorFromScene(actorID)
|
||||
local actorInfo = C_ModelInfo.GetModelSceneActorInfoByID(actorID);
|
||||
return self:AcquireAndInitializeActor(actorInfo);
|
||||
end
|
||||
|
||||
function ModelSceneMixin:CreateOrTransitionActorFromScene(oldTagToActor, actorID)
|
||||
local actorInfo = C_ModelInfo.GetModelSceneActorInfoByID(actorID);
|
||||
local existingActor = oldTagToActor[actorInfo.scriptTag];
|
||||
if existingActor then
|
||||
self:InitializeActor(existingActor, actorInfo);
|
||||
return existingActor;
|
||||
end
|
||||
|
||||
return self:AcquireAndInitializeActor(actorInfo);
|
||||
end
|
||||
|
||||
function ModelSceneMixin:CreateCameraFromScene(modelSceneCameraID)
|
||||
local modelSceneCameraInfo = C_ModelInfo.GetModelSceneCameraInfoByID(modelSceneCameraID);
|
||||
if modelSceneCameraInfo then
|
||||
local camera = CameraRegistry:CreateCameraByType(modelSceneCameraInfo.cameraType);
|
||||
if camera then
|
||||
if modelSceneCameraInfo.scriptTag then
|
||||
self.tagToCamera[modelSceneCameraInfo.scriptTag] = camera;
|
||||
end
|
||||
self:AddCamera(camera);
|
||||
camera:ApplyFromModelSceneCameraInfo(modelSceneCameraInfo, CAMERA_TRANSITION_TYPE_IMMEDIATE, CAMERA_MODIFICATION_TYPE_DISCARD);
|
||||
return camera;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:CreateOrTransitionCameraFromScene(oldTagToCamera, cameraTransitionType, cameraModificationType, modelSceneCameraID)
|
||||
local modelSceneCameraInfo = C_ModelInfo.GetModelSceneCameraInfoByID(modelSceneCameraID);
|
||||
if modelSceneCameraInfo then
|
||||
local existingCamera = oldTagToCamera[modelSceneCameraInfo.scriptTag];
|
||||
if existingCamera and existingCamera:GetCameraType() == modelSceneCameraInfo.cameraType then
|
||||
self.tagToCamera[modelSceneCameraInfo.scriptTag] = existingCamera;
|
||||
|
||||
self:AddCamera(existingCamera);
|
||||
existingCamera:ApplyFromModelSceneCameraInfo(modelSceneCameraInfo, cameraTransitionType, cameraModificationType);
|
||||
return existingCamera;
|
||||
end
|
||||
|
||||
return self:CreateCameraFromScene(modelSceneCameraID);
|
||||
end
|
||||
end
|
||||
|
||||
-- actorSettings = {
|
||||
-- "actorTag" = { startDelay = 0, duration = 0, speed = 1 } -- duration 0 means no automatic stoppage
|
||||
-- }
|
||||
function ModelSceneMixin:ShowAndAnimateActors(actorSettings, onFinishedCallback)
|
||||
self:Show();
|
||||
local totalTime = 0;
|
||||
for actorTag, actorInfo in pairs(actorSettings) do
|
||||
local actor = self:GetActorByTag(actorTag);
|
||||
if actor then
|
||||
local runningTime = actorInfo.startDelay + actorInfo.duration;
|
||||
if actorInfo.startDelay > 0 then
|
||||
actor:SetAnimation(0, 0, 0, 0);
|
||||
C_Timer.After(actorInfo.startDelay,
|
||||
function()
|
||||
actor:SetAnimation(0, 0, actorInfo.speed, 0);
|
||||
end
|
||||
);
|
||||
else
|
||||
actor:SetAnimation(0, 0, actorInfo.speed, 0);
|
||||
end
|
||||
if actorInfo.duration > 0 then
|
||||
C_Timer.After(runningTime,
|
||||
function()
|
||||
actor:SetAnimation(0, 0, 0, 0);
|
||||
end
|
||||
);
|
||||
end
|
||||
if runningTime > totalTime then
|
||||
totalTime = runningTime;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if onFinishedCallback and totalTime > 0 then
|
||||
C_Timer.After(totalTime, onFinishedCallback);
|
||||
end
|
||||
end
|
||||
|
||||
function ModelSceneMixin:GetOrAcquireDropShadow(actor)
|
||||
if actor.dropShadowTexture then
|
||||
return actor.dropShadowTexture;
|
||||
end
|
||||
|
||||
if not self.dropShadowPool then
|
||||
self.dropShadowPool = CreateTexturePool(self, "BORDER", 1, "ModelSceneDropShadowTemplate");
|
||||
end
|
||||
|
||||
actor.dropShadowTexture = self.dropShadowPool:Acquire();
|
||||
return actor.dropShadowTexture;
|
||||
end
|
||||
|
||||
function ModelSceneMixin:AddOrUpdateDropShadow(actor, baseShadowScale)
|
||||
baseShadowScale = baseShadowScale or 1.0;
|
||||
|
||||
local dropShadowTexture = self:GetOrAcquireDropShadow(actor);
|
||||
local positionVector = CreateVector3D(actor:GetPosition());
|
||||
positionVector:ScaleBy(actor:GetScale());
|
||||
local x, y, depthScale = self:Transform3DPointTo2D(positionVector:GetXYZ());
|
||||
|
||||
if not x or not y or not depthScale then
|
||||
dropShadowTexture:Hide();
|
||||
return;
|
||||
end
|
||||
|
||||
dropShadowTexture:ClearAllPoints();
|
||||
depthScale = baseShadowScale * Lerp(.05, 1, ClampedPercentageBetween(depthScale, .8, 1))
|
||||
-- Scales down the texture depending on it's depthScale.
|
||||
dropShadowTexture:SetScale(depthScale);
|
||||
|
||||
-- Need to apply the effective scale to account for UI Scaling.
|
||||
local inverseScale = self:GetEffectiveScale() * depthScale;
|
||||
-- The position of the character can be found by the offset on the screen.
|
||||
dropShadowTexture:SetPoint("CENTER", self, "BOTTOMLEFT", (x / inverseScale) + 2, (y / inverseScale) - 4);
|
||||
dropShadowTexture:Show();
|
||||
end
|
||||
|
||||
|
||||
PanningModelSceneMixin = CreateFromMixins(ModelSceneMixin);
|
||||
function PanningModelSceneMixin:TransitionToModelSceneID(modelSceneID, cameraTransitionType, cameraModificationType, forceEvenIfSame)
|
||||
ModelSceneMixin.TransitionToModelSceneID(self, modelSceneID, cameraTransitionType, cameraModificationType, forceEvenIfSame);
|
||||
|
||||
local camera = self:GetActiveCamera();
|
||||
if camera then
|
||||
camera:SetRightMouseButtonXMode(ORBIT_CAMERA_MOUSE_PAN_HORIZONTAL, true);
|
||||
camera:SetRightMouseButtonYMode(ORBIT_CAMERA_MOUSE_PAN_VERTICAL, true);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
NoCameraControlModelSceneMixin = CreateFromMixins(ModelSceneMixin);
|
||||
function NoCameraControlModelSceneMixin:OnMouseDown(button)
|
||||
self.isLeftButtonDown = false;
|
||||
self.isRightButtonDown = false;
|
||||
end
|
||||
|
||||
function NoCameraControlModelSceneMixin:OnMouseUp(button)
|
||||
self.isLeftButtonDown = false;
|
||||
self.isRightButtonDown = false;
|
||||
end
|
||||
|
||||
function NoCameraControlModelSceneMixin:OnMouseWheel(delta)
|
||||
end
|
||||
NoZoomModelSceneMixin = CreateFromMixins(ModelSceneMixin);
|
||||
function NoZoomModelSceneMixin:OnMouseWheel(delta)
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
UI.xsd">
|
||||
<ModelScene name="NonInteractableModelSceneMixinTemplate" mixin="ModelSceneMixin" virtual="true">
|
||||
<Scripts>
|
||||
<OnLoad method="OnLoad" />
|
||||
<OnUpdate method="OnUpdate" />
|
||||
</Scripts>
|
||||
</ModelScene>
|
||||
<ModelScene name="ModelSceneMixinTemplate" inherits="NonInteractableModelSceneMixinTemplate" virtual="true">
|
||||
<Scripts>
|
||||
<OnEnter method="OnEnter"/>
|
||||
<OnLeave method="OnLeave"/>
|
||||
<OnMouseUp method="OnMouseUp"/>
|
||||
<OnMouseDown method="OnMouseDown"/>
|
||||
<OnMouseWheel method="OnMouseWheel" />
|
||||
</Scripts>
|
||||
</ModelScene>
|
||||
<ModelScene name="StoreModelSceneMixinTemplate" inherits="ModelSceneMixinTemplate" virtual="true">
|
||||
<KeyValues>
|
||||
<!-- CLASS-44103: Automatically allow overlapped models for shop scenes -->
|
||||
<KeyValue key="allowOverlappedModels" value="true" type="boolean"/>
|
||||
</KeyValues>
|
||||
</ModelScene>
|
||||
<ModelScene name="NoZoomModelSceneMixinTemplate" inherits="ModelSceneMixinTemplate" mixin="NoZoomModelSceneMixin" virtual="true"/>
|
||||
<ModelScene name="PanningModelSceneMixinTemplate" inherits="ModelSceneMixinTemplate" mixin="PanningModelSceneMixin" virtual="true" />
|
||||
<ModelScene name="NoCameraControlModelSceneMixinTemplate" inherits="ModelSceneMixinTemplate" mixin="NoCameraControlModelSceneMixin" virtual="true" />
|
||||
<Texture name="ModelSceneDropShadowTemplate" atlas="scoreboard-charactermodels-shadow" useAtlasSize="true" virtual="true"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,640 @@
|
||||
-- Verbatim backport of retail's Blizzard_SharedXML/Shared/ModelSceneCameras/OrbitCameraMixin.lua.
|
||||
|
||||
--[[
|
||||
OrbitCameraMixin - A simple camera that can orbit a target point at a fixed distance.
|
||||
|
||||
The orbit is represented using a distance from a target point in world space and yaw, pitch and roll.
|
||||
The yaw, pitch and roll represent the camera's orientation, or inversely the direction of the camera from the target point.
|
||||
|
||||
Optionally, the target, orientation and zoom can have splines that are added to the respective axes. The splines are fed the zoom percentage to calculate the point on the spline.
|
||||
This is ideal for changing view points based on zoom distance from the target. For example, target model's face on zoom in and center on the model on zoom out.
|
||||
]]
|
||||
|
||||
OrbitCameraMixin = CreateFromMixins(CameraBaseMixin);
|
||||
|
||||
local CAMERA_NAME = "OrbitCamera";
|
||||
CameraRegistry:AddCameraFactoryFromMixin(CAMERA_NAME, OrbitCameraMixin);
|
||||
|
||||
-- "public" functions
|
||||
function OrbitCameraMixin:GetCameraType() -- override
|
||||
return CAMERA_NAME;
|
||||
end
|
||||
|
||||
local function TryCreateZoomSpline(x, y, z, existingSpline)
|
||||
if x and y and z and (x ~= 0 or y ~= 0 or z ~= 0) then
|
||||
local spline = existingSpline or CreateCatmullRomSpline(3);
|
||||
spline:ClearPoints();
|
||||
spline:AddPoint(0, 0, 0);
|
||||
spline:AddPoint(x, y, z);
|
||||
|
||||
return spline;
|
||||
end
|
||||
return nil;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:ApplyFromModelSceneCameraInfo(modelSceneCameraInfo, transitionType, modificationType) -- override
|
||||
self.panningXOffset = 0;
|
||||
self.panningYOffset = 0;
|
||||
|
||||
local transitionalCameraInfo = self:CalculateTransitionalValues(self.modelSceneCameraInfo, modelSceneCameraInfo, modificationType);
|
||||
|
||||
self.modelSceneCameraInfo = modelSceneCameraInfo;
|
||||
|
||||
self:SetTarget(transitionalCameraInfo.target:GetXYZ());
|
||||
self:SetTargetSpline(TryCreateZoomSpline(transitionalCameraInfo.zoomedTargetOffset:GetXYZ()), self:GetTargetSpline());
|
||||
self:SetOrientationSpline(TryCreateZoomSpline(transitionalCameraInfo.zoomedYawOffset, transitionalCameraInfo.zoomedPitchOffset, transitionalCameraInfo.zoomedRollOffset), self:GetOrientationSpline());
|
||||
|
||||
self:SetMinZoomDistance(transitionalCameraInfo.minZoomDistance);
|
||||
self:SetMaxZoomDistance(transitionalCameraInfo.maxZoomDistance);
|
||||
|
||||
self:SetZoomDistance(transitionalCameraInfo.zoomDistance);
|
||||
|
||||
self:SetYaw(transitionalCameraInfo.yaw);
|
||||
self:SetPitch(transitionalCameraInfo.pitch);
|
||||
self:SetRoll(transitionalCameraInfo.roll);
|
||||
|
||||
if transitionType == CAMERA_TRANSITION_TYPE_IMMEDIATE then
|
||||
self:SnapAllInterpolatedValues();
|
||||
end
|
||||
self:UpdateCameraOrientationAndPosition();
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetTarget(x, y, z)
|
||||
self.targetX, self.targetY, self.targetZ = x, y, z;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetTarget(x, y, z)
|
||||
return self.targetX, self.targetY, self.targetZ;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetYaw(yaw)
|
||||
self.yaw = yaw;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetYaw()
|
||||
return self.yaw;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetPitch(pitch)
|
||||
self.pitch = pitch;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetPitch()
|
||||
return self.pitch;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetRoll(roll)
|
||||
self.roll = roll;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetRoll()
|
||||
return self.roll;
|
||||
end
|
||||
|
||||
-- where 100% is fully zoomed out
|
||||
function OrbitCameraMixin:GetZoomPercent()
|
||||
return self.zoomPercent;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetZoomPercent(zoomPercent)
|
||||
self.zoomPercent = Saturate(zoomPercent);
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:ZoomByPercent(percent)
|
||||
self:SetZoomPercent(self:GetZoomPercent() - percent);
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetMaxZoomDistance(distance)
|
||||
self.maxZoomDistance = distance;
|
||||
if self:GetZoomPercent() then
|
||||
self:SetZoomDistance(self:GetZoomDistance());
|
||||
end
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetMaxZoomDistance()
|
||||
return self.maxZoomDistance;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetMinZoomDistance(distance)
|
||||
self.minZoomDistance = distance;
|
||||
if self:GetZoomPercent() then
|
||||
self:SetZoomDistance(self:GetZoomDistance());
|
||||
end
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetMinZoomDistance()
|
||||
return self.minZoomDistance;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetZoomDistance(distance)
|
||||
self:SetZoomPercent(self:CalculateZoomPercentFromDistance(distance));
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:CalculateZoomPercentFromDistance(distance)
|
||||
if self:GetMinZoomDistance() and self:GetMaxZoomDistance() then
|
||||
return PercentageBetween(distance, self:GetMinZoomDistance(), self:GetMaxZoomDistance());
|
||||
end
|
||||
return 0.0;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:CalculateZoomDistanceFromPercent(percent)
|
||||
if self:GetMinZoomDistance() and self:GetMaxZoomDistance() then
|
||||
return Lerp(self:GetMinZoomDistance(), self:GetMaxZoomDistance(), percent);
|
||||
end
|
||||
return 0.0;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetZoomDistance()
|
||||
return self:CalculateZoomDistanceFromPercent(self:GetZoomPercent());
|
||||
end
|
||||
|
||||
-- If min and max values are the same, zoom cannot really happen.
|
||||
function OrbitCameraMixin:GetZoomAvailable()
|
||||
if self:GetMinZoomDistance() and self:GetMaxZoomDistance() then
|
||||
return self:GetMinZoomDistance() ~= self:GetMaxZoomDistance();
|
||||
end
|
||||
return false;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:ZoomBy(distance)
|
||||
self:SetZoomDistance(self:GetZoomDistance() - distance);
|
||||
end
|
||||
|
||||
-- Expects a three dimensional spline where a point of the curve represents a target position
|
||||
function OrbitCameraMixin:SetTargetSpline(targetSpline)
|
||||
self.targetSpline = targetSpline;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetTargetSpline()
|
||||
return self.targetSpline;
|
||||
end
|
||||
|
||||
-- Expects a three dimensional spline where a point of the curve represents the camera yaw, pitch, roll
|
||||
function OrbitCameraMixin:SetOrientationSpline(orientationSpline)
|
||||
self.orientationSpline = orientationSpline;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetOrientationSpline()
|
||||
return self.orientationSpline;
|
||||
end
|
||||
|
||||
-- Expects an one dimensional spline where a point of the curve represents the camera's distance from the target
|
||||
function OrbitCameraMixin:SetZoomSpline(zoomSpline)
|
||||
self.zoomSpline = zoomSpline;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetZoomSpline()
|
||||
return self.zoomSpline;
|
||||
end
|
||||
|
||||
--[[ These return the simple + spline values ]]--
|
||||
function OrbitCameraMixin:GetDerivedTarget()
|
||||
local targetX, targetY, targetZ = self:GetTarget();
|
||||
|
||||
local targetSpline = self:GetTargetSpline();
|
||||
if targetSpline then
|
||||
return Vector3D_Add(targetX, targetY, targetZ, targetSpline:CalculatePointOnGlobalCurve(1.0 - self:GetZoomPercent()));
|
||||
end
|
||||
|
||||
return targetX, targetY, targetZ;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetDerivedOrientation()
|
||||
local yaw, pitch, roll = self:GetYaw(), self:GetPitch(), self:GetRoll();
|
||||
|
||||
local orientationSpline = self:GetOrientationSpline();
|
||||
if orientationSpline then
|
||||
return Vector3D_Add(yaw, pitch, roll, orientationSpline:CalculatePointOnGlobalCurve(1.0 - self:GetZoomPercent()));
|
||||
end
|
||||
|
||||
return yaw, pitch, roll;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetDerivedZoomDistance()
|
||||
local zoomDistance = self:GetZoomDistance();
|
||||
|
||||
local zoomSpline = self:GetZoomSpline();
|
||||
if zoomSpline then
|
||||
return zoomDistance + zoomSpline:CalculatePointOnGlobalCurve(1.0 - self:GetZoomPercent());
|
||||
end
|
||||
|
||||
return zoomDistance;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetAlignLightToOrbitDelta(alignLightToOrbitDelta)
|
||||
if alignLightToOrbitDelta then
|
||||
self.modelSceneCameraInfo.flags = bit.bor(self.modelSceneCameraInfo.flags, Enum.ModelSceneSetting.AlignLightToOrbitDelta);
|
||||
else
|
||||
self.modelSceneCameraInfo.flags = bit.band(self.modelSceneCameraInfo.flags, bit.bnot(Enum.ModelSceneSetting.AlignLightToOrbitDelta));
|
||||
end
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:ShouldAlignLightToOrbitDelta()
|
||||
return bit.band(self.modelSceneCameraInfo.flags, Enum.ModelSceneSetting.AlignLightToOrbitDelta) == Enum.ModelSceneSetting.AlignLightToOrbitDelta;
|
||||
end
|
||||
|
||||
--[[
|
||||
Interpolation API
|
||||
For each API, "amount" is the percentage to approach in an "ideal" frame (60 fps), such that setting the value to >= 1 would snap to the desired target and <= 0 would freeze the interpolation.
|
||||
Setting to nil will disable the interpolation.
|
||||
]]
|
||||
|
||||
function OrbitCameraMixin:SetYawInterpolationAmount(yawInterpolationAmount)
|
||||
self.yawInterpolationAmount = yawInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetYawInterpolationAmount()
|
||||
return self.yawInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetPitchInterpolationAmount(pitchInterpolationAmount)
|
||||
self.pitchInterpolationAmount = pitchInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetPitchInterpolationAmount()
|
||||
return self.pitchInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetRollInterpolationAmount(rollInterpolationAmount)
|
||||
self.rollInterpolationAmount = rollInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetRollInterpolationAmount()
|
||||
return self.rollInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetTargetInterpolationAmount(targetInterpolationAmount)
|
||||
self.targetInterpolationAmount = targetInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetTargetInterpolationAmount()
|
||||
return self.targetInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetZoomInterpolationAmount(zoomInterpolationAmount)
|
||||
self.zoomInterpolationAmount = zoomInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetZoomInterpolationAmount()
|
||||
return self.zoomInterpolationAmount;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetInterpolatedTarget()
|
||||
if self.interpolatedTargetX then
|
||||
return self.interpolatedTargetX, self.interpolatedTargetY, self.interpolatedTargetZ;
|
||||
end
|
||||
return self:GetDerivedTarget();
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetInterpolatedOrientation()
|
||||
if self.interpolatedYaw then
|
||||
return self.interpolatedYaw, self.interpolatedPitch, self.interpolatedRoll;
|
||||
end
|
||||
return self:GetDerivedOrientation();
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetInterpolatedZoomDistance()
|
||||
if self.interpolatedZoomDistance then
|
||||
return self.interpolatedZoomDistance;
|
||||
end
|
||||
return self:GetDerivedZoomDistance();
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SnapToTargetInterpolationYaw()
|
||||
self.interpolatedYaw = nil;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SnapToTargetInterpolationPitch()
|
||||
self.interpolatedPitch = nil;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SnapToTargetInterpolationRoll()
|
||||
self.interpolatedRoll = nil;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SnapToTargetInterpolationZoom()
|
||||
self.interpolatedZoomDistance = nil;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SnapToTargetInterpolationTarget()
|
||||
self.interpolatedTargetX = nil;
|
||||
self.interpolatedTargetY = nil;
|
||||
self.interpolatedTargetZ = nil;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SnapAllInterpolatedValues()
|
||||
self:SnapToTargetInterpolationYaw();
|
||||
self:SnapToTargetInterpolationPitch();
|
||||
self:SnapToTargetInterpolationRoll();
|
||||
|
||||
self:SnapToTargetInterpolationZoom();
|
||||
|
||||
self:SnapToTargetInterpolationTarget();
|
||||
end
|
||||
|
||||
ORBIT_CAMERA_MOUSE_MODE_NOTHING = 0;
|
||||
ORBIT_CAMERA_MOUSE_MODE_YAW_ROTATION = 1;
|
||||
ORBIT_CAMERA_MOUSE_MODE_PITCH_ROTATION = 2;
|
||||
ORBIT_CAMERA_MOUSE_MODE_ROLL_ROTATION = 3;
|
||||
ORBIT_CAMERA_MOUSE_MODE_TARGET_HORIZONTAL = 4;
|
||||
ORBIT_CAMERA_MOUSE_MODE_TARGET_VERTICAL = 5;
|
||||
ORBIT_CAMERA_MOUSE_MODE_ZOOM = 6;
|
||||
ORBIT_CAMERA_MOUSE_PAN_HORIZONTAL = 7;
|
||||
ORBIT_CAMERA_MOUSE_PAN_VERTICAL = 8;
|
||||
|
||||
function OrbitCameraMixin:SetLeftMouseButtonXMode(mouseMode, snap)
|
||||
self.buttonModes.leftX = mouseMode;
|
||||
self.buttonModes.leftXinterpolate = not snap;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetLeftMouseButtonXMode()
|
||||
return self.buttonModes.leftX, not self.buttonModes.leftXinterpolate;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetLeftMouseButtonYMode(mouseMode, snap)
|
||||
self.buttonModes.leftY = mouseMode;
|
||||
self.buttonModes.leftYinterpolate = not snap;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetLeftYMouseButtonYMode()
|
||||
return self.buttonModes.leftY, not self.buttonModes.leftYinterpolate;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetRightMouseButtonXMode(mouseMode, snap)
|
||||
self.buttonModes.rightX = mouseMode;
|
||||
self.buttonModes.rightXinterpolate = not snap;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetRightMouseButtonXMode()
|
||||
return self.buttonModes.rightX, not self.buttonModes.rightXinterpolate;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetRightMouseButtonYMode(mouseMode, snap)
|
||||
self.buttonModes.rightY = mouseMode;
|
||||
self.buttonModes.rightYinterpolate = not snap;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetRightMouseButtonYMode()
|
||||
return self.buttonModes.rightY, not self.buttonModes.rightYinterpolate;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SetMouseWheelMode(mouseMode, snap)
|
||||
self.buttonModes.wheel = mouseMode;
|
||||
self.buttonModes.wheelInterpolate = not snap;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetMouseWheelMode()
|
||||
return self.buttonModes.wheel, not self.buttonModes.wheelInterpolate;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:HandleMouseMovement(mode, delta, snapToValue)
|
||||
if mode == ORBIT_CAMERA_MOUSE_MODE_YAW_ROTATION then
|
||||
self:SetYaw(self:GetYaw() - delta);
|
||||
if snapToValue then
|
||||
self:SnapToTargetInterpolationYaw();
|
||||
end
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_PITCH_ROTATION then
|
||||
self:SetPitch(self:GetPitch() - delta);
|
||||
if snapToValue then
|
||||
self:SnapToTargetInterpolationPitch();
|
||||
end
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_ROLL_ROTATION then
|
||||
self:SetRoll(self:GetRoll() - delta);
|
||||
if snapToValue then
|
||||
self:SnapToTargetInterpolationRoll();
|
||||
end
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_ZOOM then
|
||||
self:ZoomByPercent(delta);
|
||||
if snapToValue then
|
||||
self:SnapToTargetInterpolationZoom();
|
||||
end
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_TARGET_HORIZONTAL then
|
||||
local rightX, rightY, rightZ = Vector3D_ScaleBy(delta, self:GetRightVector());
|
||||
self:SetTarget(Vector3D_Add(rightX, rightY, rightZ, self:GetTarget()));
|
||||
|
||||
if snapToValue then
|
||||
self:SnapToTargetInterpolationTarget();
|
||||
end
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_TARGET_VERTICAL then
|
||||
local upX, upY, upZ = Vector3D_ScaleBy(delta, self:GetUpVector());
|
||||
self:SetTarget(Vector3D_Add(upX, upY, upZ, self:GetTarget()));
|
||||
|
||||
if snapToValue then
|
||||
self:SnapToTargetInterpolationTarget();
|
||||
end
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_PAN_HORIZONTAL then
|
||||
self.panningXOffset = self.panningXOffset + delta;
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_PAN_VERTICAL then
|
||||
self.panningYOffset = self.panningYOffset + delta;
|
||||
end
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:ResetDefaultInputModes()
|
||||
self:SetLeftMouseButtonXMode(ORBIT_CAMERA_MOUSE_MODE_YAW_ROTATION, true);
|
||||
self:SetLeftMouseButtonYMode(ORBIT_CAMERA_MOUSE_MODE_NOTHING);
|
||||
self:SetRightMouseButtonXMode(ORBIT_CAMERA_MOUSE_MODE_NOTHING);
|
||||
self:SetRightMouseButtonYMode(ORBIT_CAMERA_MOUSE_MODE_NOTHING);
|
||||
self:SetMouseWheelMode(ORBIT_CAMERA_MOUSE_MODE_ZOOM, false);
|
||||
end
|
||||
|
||||
-- "private" function
|
||||
function OrbitCameraMixin:OnAdded() -- override
|
||||
self.buttonModes = {};
|
||||
|
||||
self:SetTarget(0, 0, 0);
|
||||
|
||||
local targetSpline = CreateCatmullRomSpline(3);
|
||||
targetSpline:AddPoint(0, 0, 0);
|
||||
targetSpline:AddPoint(0, 0, .5);
|
||||
|
||||
self:SetTargetSpline(targetSpline);
|
||||
|
||||
self:SetMinZoomDistance(6);
|
||||
self:SetMaxZoomDistance(10);
|
||||
|
||||
self:SetZoomDistance(8);
|
||||
|
||||
self:SetYaw(math.pi);
|
||||
self:SetPitch(0);
|
||||
self:SetRoll(0);
|
||||
|
||||
self:SetZoomInterpolationAmount(.15);
|
||||
self:SetYawInterpolationAmount(.15);
|
||||
self:SetPitchInterpolationAmount(.15);
|
||||
self:SetRollInterpolationAmount(.15);
|
||||
self:SetTargetInterpolationAmount(.15);
|
||||
|
||||
self:ResetDefaultInputModes();
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:GetDeltaModifierForCameraMode(mode)
|
||||
if mode == ORBIT_CAMERA_MOUSE_MODE_YAW_ROTATION then
|
||||
return .008;
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_PITCH_ROTATION then
|
||||
return .008;
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_ROLL_ROTATION then
|
||||
return .008;
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_ZOOM then
|
||||
return .1;
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_TARGET_HORIZONTAL then
|
||||
return .05;
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_MODE_TARGET_VERTICAL then
|
||||
return .05;
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_PAN_HORIZONTAL then
|
||||
return 0.93;
|
||||
elseif mode == ORBIT_CAMERA_MOUSE_PAN_VERTICAL then
|
||||
return 0.93;
|
||||
end
|
||||
return 0.0;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:OnUpdate(elapsed) -- override
|
||||
if self:IsLeftMouseButtonDown() then
|
||||
local deltaX, deltaY = InputUtil.GetCursorDelta(GetAppropriateTopLevelParent());
|
||||
self:HandleMouseMovement(self.buttonModes.leftX, deltaX * self:GetDeltaModifierForCameraMode(self.buttonModes.leftX), not self.buttonModes.leftXinterpolate);
|
||||
self:HandleMouseMovement(self.buttonModes.leftY, -deltaY * self:GetDeltaModifierForCameraMode(self.buttonModes.leftY), not self.buttonModes.leftYinterpolate);
|
||||
end
|
||||
|
||||
if self:IsRightMouseButtonDown() then
|
||||
local deltaX, deltaY = InputUtil.GetCursorDelta(GetAppropriateTopLevelParent());
|
||||
self:HandleMouseMovement(self.buttonModes.rightX, deltaX * self:GetDeltaModifierForCameraMode(self.buttonModes.rightX), not self.buttonModes.rightXinterpolate);
|
||||
self:HandleMouseMovement(self.buttonModes.rightY, -deltaY * self:GetDeltaModifierForCameraMode(self.buttonModes.rightY), not self.buttonModes.rightYinterpolate);
|
||||
end
|
||||
|
||||
self:UpdateInterpolationTargets(elapsed);
|
||||
self:SynchronizeCamera();
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:SynchronizeCamera() -- override
|
||||
self:UpdateCameraOrientationAndPosition();
|
||||
self:UpdateLight();
|
||||
end
|
||||
|
||||
local function InterpolateDimension(lastValue, targetValue, amount, elapsed)
|
||||
return lastValue and amount and DeltaLerp(lastValue, targetValue, amount, elapsed) or targetValue;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:UpdateInterpolationTargets(elapsed)
|
||||
local yaw, pitch, roll = self:GetDerivedOrientation();
|
||||
local targetX, targetY, targetZ = self:GetDerivedTarget();
|
||||
local zoomDistance = self:GetDerivedZoomDistance();
|
||||
|
||||
self.interpolatedYaw = InterpolateDimension(self.interpolatedYaw, yaw, self.yawInterpolationAmount, elapsed);
|
||||
self.interpolatedPitch = InterpolateDimension(self.interpolatedPitch, pitch, self.pitchInterpolationAmount, elapsed);
|
||||
self.interpolatedRoll = InterpolateDimension(self.interpolatedRoll, roll, self.rollInterpolationAmount, elapsed);
|
||||
|
||||
self.interpolatedZoomDistance = InterpolateDimension(self.interpolatedZoomDistance, zoomDistance, self.zoomInterpolationAmount, elapsed);
|
||||
|
||||
self.interpolatedTargetX = InterpolateDimension(self.interpolatedTargetX, targetX, self.targetInterpolationAmount, elapsed);
|
||||
self.interpolatedTargetY = InterpolateDimension(self.interpolatedTargetY, targetY, self.targetInterpolationAmount, elapsed);
|
||||
self.interpolatedTargetZ = InterpolateDimension(self.interpolatedTargetZ, targetZ, self.targetInterpolationAmount, elapsed);
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:UpdateCameraOrientationAndPosition()
|
||||
local yaw, pitch, roll = self:GetInterpolatedOrientation();
|
||||
local axisAngleX, axisAngleY, axisAngleZ = Vector3D_CalculateNormalFromYawPitch(yaw, pitch);
|
||||
|
||||
local targetX, targetY, targetZ = self:GetInterpolatedTarget();
|
||||
|
||||
local zoomDistance = self:GetInterpolatedZoomDistance();
|
||||
|
||||
-- Panning start --
|
||||
-- We want the model to move 1-to-1 with the mouse.
|
||||
-- Panning formula: dx / hypotenuse * (zoomDistance - 1 / zoomDistance^3)
|
||||
-- It was experimentally determined that adding the additional fudge factor 1/z^3 resulted in better tracking.
|
||||
|
||||
local width = self:GetOwningScene():GetWidth();
|
||||
local height = self:GetOwningScene():GetHeight();
|
||||
local scaleFactor = (width ~= 0 and height ~= 0) and math.sqrt(width * width + height * height) or 1;
|
||||
local zoomFactor = 1;
|
||||
if zoomDistance > 1 then
|
||||
zoomFactor = zoomDistance - (1 / (zoomDistance * zoomDistance * zoomDistance));
|
||||
if zoomFactor < 1 then
|
||||
zoomFactor = 1;
|
||||
end
|
||||
end
|
||||
|
||||
local rightX, rightY, rightZ = Vector3D_ScaleBy((self.panningXOffset / scaleFactor) * zoomFactor, self:GetRightVector());
|
||||
local upX, upY, upZ = Vector3D_ScaleBy((self.panningYOffset / scaleFactor) * zoomFactor, self:GetUpVector());
|
||||
|
||||
-- Panning end --
|
||||
|
||||
targetX, targetY, targetZ = Vector3D_Add(targetX, targetY, targetZ, rightX, rightY, rightZ);
|
||||
targetX, targetY, targetZ = Vector3D_Add(targetX, targetY, targetZ, upX, upY, upZ);
|
||||
|
||||
self:SetPosition(self:CalculatePositionByDistanceFromTarget(targetX, targetY, targetZ, zoomDistance, axisAngleX, axisAngleY, axisAngleZ));
|
||||
self:GetOwningScene():SetCameraOrientationByYawPitchRoll(yaw, pitch, roll);
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:UpdateLight()
|
||||
if self:ShouldAlignLightToOrbitDelta() then
|
||||
local owningScene = self:GetOwningScene();
|
||||
owningScene:SetLightDirection(self:GetForwardVector());
|
||||
end
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:CalculatePositionByDistanceFromTarget(targetX, targetY, targetZ, zoomDistance, axisAngleX, axisAngleY, axisAngleZ)
|
||||
local towardsPosX, towardsPosY, towardsPosZ = Vector3D_ScaleBy(-zoomDistance, axisAngleX, axisAngleY, axisAngleZ);
|
||||
return Vector3D_Add(towardsPosX, towardsPosY, towardsPosZ, targetX, targetY, targetZ);
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:CalculateTransitionalValues(fromModelSceneCameraInfo, toModelSceneCameraInfo, modificationType)
|
||||
if fromModelSceneCameraInfo and modificationType == CAMERA_MODIFICATION_TYPE_MAINTAIN then
|
||||
local zoomedTargetOffsetX, zoomedTargetOffsetY, zoomedTargetOffsetZ = 0, 0, 0;
|
||||
|
||||
local targetSpline = self:GetTargetSpline();
|
||||
if targetSpline and targetSpline:GetNumPoints() > 0 then
|
||||
zoomedTargetOffsetX, zoomedTargetOffsetY, zoomedTargetOffsetZ = targetSpline:GetPoint(targetSpline:GetNumPoints());
|
||||
end
|
||||
|
||||
local zoomedYawOffset, zoomedPitchOffset, zoomedRollOffset = 0, 0, 0;
|
||||
local orientationSpline = self:GetOrientationSpline();
|
||||
if orientationSpline and orientationSpline:GetNumPoints() > 0 then
|
||||
zoomedYawOffset, zoomedPitchOffset, zoomedRollOffset = orientationSpline:GetPoint(orientationSpline:GetNumPoints());
|
||||
end
|
||||
|
||||
local fromZoomDistancePercent = PercentageBetween(fromModelSceneCameraInfo.zoomDistance, fromModelSceneCameraInfo.minZoomDistance, fromModelSceneCameraInfo.maxZoomDistance);
|
||||
local toZoomDistancePercent = PercentageBetween(toModelSceneCameraInfo.zoomDistance, toModelSceneCameraInfo.minZoomDistance, toModelSceneCameraInfo.maxZoomDistance);
|
||||
-- Maintain any modifications made by applying the scene delta
|
||||
return {
|
||||
target = CreateVector3D(
|
||||
(self.targetX - fromModelSceneCameraInfo.target.x) + toModelSceneCameraInfo.target.x,
|
||||
(self.targetY - fromModelSceneCameraInfo.target.y) + toModelSceneCameraInfo.target.y,
|
||||
(self.targetZ - fromModelSceneCameraInfo.target.z) + toModelSceneCameraInfo.target.z
|
||||
),
|
||||
|
||||
yaw = (self.yaw - fromModelSceneCameraInfo.yaw) + toModelSceneCameraInfo.yaw,
|
||||
pitch = (self.pitch - fromModelSceneCameraInfo.pitch) + toModelSceneCameraInfo.pitch,
|
||||
roll = (self.roll - fromModelSceneCameraInfo.roll) + toModelSceneCameraInfo.roll,
|
||||
|
||||
zoomDistance = Lerp(toModelSceneCameraInfo.minZoomDistance, toModelSceneCameraInfo.maxZoomDistance, (self.zoomPercent - fromZoomDistancePercent) + toZoomDistancePercent),
|
||||
minZoomDistance = (self.minZoomDistance - fromModelSceneCameraInfo.minZoomDistance) + toModelSceneCameraInfo.minZoomDistance,
|
||||
maxZoomDistance = (self.maxZoomDistance - fromModelSceneCameraInfo.maxZoomDistance) + toModelSceneCameraInfo.maxZoomDistance,
|
||||
|
||||
zoomedTargetOffset = CreateVector3D(
|
||||
(zoomedTargetOffsetX - fromModelSceneCameraInfo.zoomedTargetOffset.x) + toModelSceneCameraInfo.zoomedTargetOffset.x,
|
||||
(zoomedTargetOffsetY - fromModelSceneCameraInfo.zoomedTargetOffset.y) + toModelSceneCameraInfo.zoomedTargetOffset.y,
|
||||
(zoomedTargetOffsetZ - fromModelSceneCameraInfo.zoomedTargetOffset.z) + toModelSceneCameraInfo.zoomedTargetOffset.z
|
||||
),
|
||||
|
||||
zoomedYawOffset = (zoomedYawOffset - fromModelSceneCameraInfo.zoomedYawOffset) + toModelSceneCameraInfo.zoomedYawOffset,
|
||||
zoomedPitchOffset = (zoomedPitchOffset - fromModelSceneCameraInfo.zoomedPitchOffset) + toModelSceneCameraInfo.zoomedPitchOffset,
|
||||
zoomedRollOffset = (zoomedRollOffset - fromModelSceneCameraInfo.zoomedRollOffset) + toModelSceneCameraInfo.zoomedRollOffset;
|
||||
};
|
||||
end
|
||||
|
||||
-- Nothing to transition from, just go directly to the values
|
||||
return toModelSceneCameraInfo;
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:OnMouseWheel(delta) -- override
|
||||
self:HandleMouseMovement(self.buttonModes.wheel, delta * self:GetDeltaModifierForCameraMode(self.buttonModes.wheel), not self.buttonModes.wheelInterpolate);
|
||||
end
|
||||
|
||||
function OrbitCameraMixin:AdjustYaw(deltaX, deltaY, rotationScalar)
|
||||
local xRotation = rotationScalar or self:GetDeltaModifierForCameraMode(self.buttonModes.leftX);
|
||||
self:HandleMouseMovement(self.buttonModes.leftX, deltaX * xRotation, not self.buttonModes.leftXinterpolate);
|
||||
|
||||
local yRotatation = rotationScalar or self:GetDeltaModifierForCameraMode(self.buttonModes.leftY);
|
||||
self:HandleMouseMovement(self.buttonModes.leftY, -deltaY * yRotatation, not self.buttonModes.leftYinterpolate);
|
||||
end
|
||||
@@ -107,6 +107,12 @@ function GameTooltip_AddNormalLine(tooltip, text, wrap, leftOffset)
|
||||
GameTooltip_AddColoredLine(tooltip, text, NORMAL_FONT_COLOR, wrap, leftOffset);
|
||||
end
|
||||
|
||||
-- Retail's takes `...` and passes it on; named parameters keep Lua 5.0 from
|
||||
-- building an `arg` table per call.
|
||||
function GameTooltip_AddBodyLine(tooltip, text, wrap, leftOffset)
|
||||
GameTooltip_AddNormalLine(tooltip, text, wrap, leftOffset);
|
||||
end
|
||||
|
||||
function GameTooltip_AddHighlightLine(tooltip, text, wrap, leftOffset)
|
||||
GameTooltip_AddColoredLine(tooltip, text, HIGHLIGHT_FONT_COLOR, wrap, leftOffset);
|
||||
end
|
||||
|
||||
@@ -8,6 +8,12 @@ function InputUtil.GetCursorPosition(parent)
|
||||
return x / scale, y / scale;
|
||||
end
|
||||
|
||||
function InputUtil.GetCursorDelta(parent)
|
||||
local x, y = GetCursorDelta();
|
||||
local scale = parent:GetEffectiveScale();
|
||||
return x / scale, y / scale;
|
||||
end
|
||||
|
||||
function InputUtil.AnchorRegionToCursor(region, point)
|
||||
local parent = GetAppropriateTopLevelParent();
|
||||
local x, y = InputUtil.GetCursorPosition(parent);
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
-- (scrapping, runeforging, soulbinds, keystones, item upgrades,
|
||||
-- recrafting, bank tabs), and the stock bag and character frames never
|
||||
-- ask for it.
|
||||
-- - No ItemUtil.CreateItemTransmogInfo or ItemTransmogInfoMixin: there is
|
||||
-- no transmogrification.
|
||||
-- - ItemTransmogInfoMixin's appearanceID is an item ID and its illusionID a
|
||||
-- SpellItemEnchantment ID, as the ModelSceneActor ItemTransmogInfo
|
||||
-- methods read and write them.
|
||||
-- - ItemUtil.GetItemDetails has no currency path: there are no currencies.
|
||||
-- - No ItemUtil.GetCraftingReagentCount: it picks between the reagent bank
|
||||
-- and the account bank, and neither exists.
|
||||
@@ -95,6 +96,10 @@ function ItemUtil.FilterOwnedItems(itemIDs)
|
||||
return filtered;
|
||||
end
|
||||
|
||||
function ItemUtil.CreateItemTransmogInfo(appearanceID, secondaryAppearanceID, illusionID)
|
||||
return CreateAndInitFromMixin(ItemTransmogInfoMixin, appearanceID, secondaryAppearanceID, illusionID);
|
||||
end
|
||||
|
||||
function ItemUtil.TransformItemIDsToItems(itemIDs)
|
||||
local items = {};
|
||||
for index, itemID in ipairs(itemIDs) do
|
||||
@@ -194,3 +199,42 @@ function ItemUtil.GetEquipSlotTexture(equipSlot)
|
||||
|
||||
return nil;
|
||||
end
|
||||
|
||||
ItemTransmogInfoMixin = {};
|
||||
|
||||
function ItemTransmogInfoMixin:Init(appearanceID, secondaryAppearanceID, illusionID)
|
||||
self.appearanceID = appearanceID;
|
||||
self.secondaryAppearanceID = secondaryAppearanceID or Constants.Transmog.NoTransmogID;
|
||||
self.illusionID = illusionID or Constants.Transmog.NoTransmogID;
|
||||
end
|
||||
|
||||
function ItemTransmogInfoMixin:IsEqual(itemTransmogInfo)
|
||||
if not itemTransmogInfo then
|
||||
return false;
|
||||
end
|
||||
return self.appearanceID == itemTransmogInfo.appearanceID and self.secondaryAppearanceID == itemTransmogInfo.secondaryAppearanceID and self.illusionID == itemTransmogInfo.illusionID;
|
||||
end
|
||||
|
||||
function ItemTransmogInfoMixin:Clear()
|
||||
self.appearanceID = Constants.Transmog.NoTransmogID;
|
||||
self.secondaryAppearanceID = Constants.Transmog.NoTransmogID;
|
||||
self.illusionID = Constants.Transmog.NoTransmogID;
|
||||
end
|
||||
|
||||
-- There is no slot info in ItemTransmogInfo so the following 3 MainHand functions must be used with correct itemTransmogInfo at call site
|
||||
function ItemTransmogInfoMixin:ConfigureSecondaryForMainHand(isLegionArtifact)
|
||||
if isLegionArtifact then
|
||||
self.secondaryAppearanceID = Constants.Transmog.MainHandTransmogIsPairedWeapon;
|
||||
else
|
||||
self.secondaryAppearanceID = Constants.Transmog.MainHandTransmogIsIndividualWeapon;
|
||||
end
|
||||
end
|
||||
|
||||
function ItemTransmogInfoMixin:IsMainHandIndividualWeapon()
|
||||
return self.secondaryAppearanceID == Constants.Transmog.MainHandTransmogIsIndividualWeapon;
|
||||
end
|
||||
|
||||
function ItemTransmogInfoMixin:IsMainHandPairedWeapon()
|
||||
-- paired weapon can be value Constants.Transmog.MainHandTransmogIsPairedWeapon or greater
|
||||
return not self:IsMainHandIndividualWeapon();
|
||||
end
|
||||
|
||||
@@ -224,6 +224,9 @@ end
|
||||
-- extended `GameTooltip:SetHyperlink` (src/tooltip/Hyperlink.cpp), which
|
||||
-- builds the same tooltip `GameTooltip:SetSpellByID` builds, so the handler
|
||||
-- passes the payload through instead of rebuilding the tooltip itself.
|
||||
-- Control-click is retail's dress-up click: DressUpLink previews a mount
|
||||
-- spell, and nothing else happens, as retail's modified click shows no
|
||||
-- tooltip.
|
||||
------------------------------------------------------------------------------
|
||||
LinkUtil.RegisterLinkHandler(LinkTypes.Spell, function(link, text, linkData, contextData)
|
||||
local spellID = tonumber(string.match(link, "^spell:(%d+)"));
|
||||
@@ -234,6 +237,10 @@ LinkUtil.RegisterLinkHandler(LinkTypes.Spell, function(link, text, linkData, con
|
||||
ChatFrameEditBox:Insert(text);
|
||||
return LinkProcessorResponse.Handled;
|
||||
end
|
||||
if IsControlKeyDown() then
|
||||
DressUpLink(text);
|
||||
return LinkProcessorResponse.Handled;
|
||||
end
|
||||
local tooltip = ItemRefTooltip or GameTooltip;
|
||||
tooltip:SetOwner(UIParent, "ANCHOR_PRESERVE");
|
||||
tooltip:SetHyperlink(link);
|
||||
@@ -250,13 +257,14 @@ end);
|
||||
-- links. Route spell links Turtle would drop back through SetItemRef so the
|
||||
-- Spell handler above shows them; Turtle-shaped links
|
||||
-- (`spell:<id>:<bookType>:<player>`) pass through to Turtle's handler
|
||||
-- untouched. On a stock client the original already forwards everything to
|
||||
-- SetItemRef, so both branches behave the same there.
|
||||
-- untouched, except a Control-click, which previews the mount here. On a
|
||||
-- stock client the original already forwards everything to SetItemRef, so
|
||||
-- both branches behave the same there.
|
||||
------------------------------------------------------------------------------
|
||||
local originalChatFrameOnHyperlinkShow = ChatFrame_OnHyperlinkShow;
|
||||
function ChatFrame_OnHyperlinkShow(link, text, button)
|
||||
if type(link) == "string" and strsub(link, 1, 6) == "spell:" and
|
||||
not string.find(link, "^spell:(%d+):(%w+):(%w+)") then
|
||||
(IsControlKeyDown() or not string.find(link, "^spell:(%d+):(%w+):(%w+)")) then
|
||||
SetItemRef(link, text, button);
|
||||
return;
|
||||
end
|
||||
|
||||
@@ -74,6 +74,10 @@ OPTION_TOOLTIP_INTERACT_ICONS = "Legt fest, welche Symbole über NSCs und Gegens
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Automatischen Angriff stoppen, sobald das Ziel gewechselt wird.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<Zum Löschen der Belegung rechtsklicken>";
|
||||
RESET_POSITION = "Zurücksetzen";
|
||||
ROTATE_LEFT = "Linksdrehung";
|
||||
ROTATE_RIGHT = "Rechtsdrehung";
|
||||
ROTATE_TOOLTIP = "Linksklicken und ziehen, um den Charakter zu drehen.";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Tastaturbelegung für \"%s\" zuweisen oder mit %s abbrechen";
|
||||
SET_FOCUS = "Fokus setzen";
|
||||
FOCUSTARGET = "Ziel als Fokus festlegen";
|
||||
|
||||
@@ -17,7 +17,7 @@ CLASSICAPI_AUTO_DISMOUNT_TOOLTIP = "Dismount when you cast a spell, use an objec
|
||||
CLASSICAPI_AUTO_STAND = "Auto Stand";
|
||||
CLASSICAPI_AUTO_STAND_TOOLTIP = "Stand up when you cast a spell, loot, or open the flight map while you sit.";
|
||||
CLASSICAPI_AUTO_UNSHIFT = "Auto Cancel Form";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "Leave your form when you cast a spell that you cannot cast in it, talk to an NPC, use an object or an item, or open the flight map.";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "Leave your form when you cast a spell that you cannot cast in it, use an item, or open the flight map. Also lets you talk to NPCs and use objects in druid forms and Ghost Wolf.";
|
||||
CLASSICAPI_BAGS_AND_ITEMS = "Bags and Items";
|
||||
CLASSICAPI_CASTING_AND_ATTACKING = "Casting and Attacking";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION = "Combat Log Retention";
|
||||
@@ -164,6 +164,10 @@ OPTION_TOOLTIP_INTERACT_ICONS = "Controls which icons appear above NPCs and obje
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Stop auto attacking when you switch targets.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<Right click to unbind>";
|
||||
RESET_POSITION = "Reset Position";
|
||||
ROTATE_LEFT = "Rotate Left";
|
||||
ROTATE_RIGHT = "Rotate Right";
|
||||
ROTATE_TOOLTIP = "Left-click on character and drag to rotate.";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Assign Binding for \"%s\" or Press %s to Cancel";
|
||||
SET_FOCUS = "Set Focus";
|
||||
FOCUS_TOKEN_NOT_FOUND = "<no focus>";
|
||||
|
||||
@@ -74,6 +74,10 @@ OPTION_TOOLTIP_INTERACT_ICONS = "Controla los iconos que aparecen sobre los PNJ
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Detiene el ataque automático al cambiar de objetivo.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<Haz clic derecho para quitar la asignación>";
|
||||
RESET_POSITION = "Restablecer posición";
|
||||
ROTATE_LEFT = "Rotar a la izquierda";
|
||||
ROTATE_RIGHT = "Rotar a la derecha";
|
||||
ROTATE_TOOLTIP = "Haz clic izquierdo en el personaje y arrastra para rotar.";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Asignar atajo para \"%s\" o pulsar %s para cancelar";
|
||||
SET_FOCUS = "Establecer foco";
|
||||
FOCUSTARGET = "Establecer objetivo";
|
||||
|
||||
@@ -74,6 +74,10 @@ OPTION_TOOLTIP_INTERACT_ICONS = "Contrôle les icônes qui s’affichent au-dess
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Interrompt l'attaque automatique lorsque vous changez de cible.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<Clic droit pour retirer>";
|
||||
RESET_POSITION = "Position par défaut";
|
||||
ROTATE_LEFT = "Pivoter à gauche";
|
||||
ROTATE_RIGHT = "Pivoter à droite";
|
||||
ROTATE_TOOLTIP = "Cliquez sur le personnage et faites glisser la souris pour le faire pivoter.";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Assigner un raccourci pour « %s » ou appuyer sur %s pour annuler";
|
||||
SET_FOCUS = "Définir la focalisation";
|
||||
FOCUSTARGET = "Définir la cible de focalisation";
|
||||
|
||||
@@ -5,6 +5,10 @@ DECIMAL_SEPERATOR = ".";
|
||||
FIRST_NUMBER = "천";
|
||||
FIRST_NUMBER_CAP = "만";
|
||||
FIRST_NUMBER_CAP_NO_SPACE = "만";
|
||||
RESET_POSITION = "위치 초기화";
|
||||
ROTATE_LEFT = "왼쪽으로 회전";
|
||||
ROTATE_RIGHT = "오른쪽으로 회전";
|
||||
ROTATE_TOOLTIP = "마우스 왼쪽 버튼으로 클릭 후 끌어 옮기면 캐릭터를 회전할 수 있습니다.";
|
||||
SECOND_NUMBER = "만";
|
||||
SECOND_NUMBER_CAP = "억";
|
||||
SECOND_NUMBER_CAP_NO_SPACE = "억";
|
||||
|
||||
@@ -4,17 +4,109 @@ ACTION_BUTTON_USE_KEY_DOWN = "Способности при нажатии кл
|
||||
ACTION_TARGETING_OPTION = "Разрешить динамическое выделение";
|
||||
ADDON_MEM_KB_ABBR = "(%.0f КБ) %s";
|
||||
ADDON_MEM_MB_ABBR = "(%.2f МБ) %s";
|
||||
ADDON_OPTIONS = "Параметры модификаций";
|
||||
BINDING_NAME_AOELOOT = "Сбор добычи по области";
|
||||
BINDING_NAME_FOCUSTARGET = "Запомненная цель";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "Взаимодействовать указателем мыши";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "Взаимодействовать с объектом под курсором";
|
||||
BINDING_NAME_INTERACTTARGET = "Взаимодействовать с целью";
|
||||
BINDING_NAME_TARGETFOCUS = "Восстановить цель";
|
||||
CLASSICAPI_ACTION_BARS = "Панели команд";
|
||||
CLASSICAPI_ACTION_TARGETING = "Динамическое выделение";
|
||||
CLASSICAPI_ADVANCED = "Дополнительно";
|
||||
CLASSICAPI_ALL_SOFT_TARGET_ICONS = "Значки для всех мягких целей";
|
||||
CLASSICAPI_AUTO_DISMOUNT = "Автоматически спешиваться";
|
||||
CLASSICAPI_AUTO_DISMOUNT_TOOLTIP = "Спешиваться при применении заклинания, использовании объекта, сборе добычи, атаке или открытии карты полетов, если вы едете верхом.";
|
||||
CLASSICAPI_AUTO_STAND = "Автоматически вставать";
|
||||
CLASSICAPI_AUTO_STAND_TOOLTIP = "Вставать при применении заклинания, сборе добычи или открытии карты полетов, если вы сидите.";
|
||||
CLASSICAPI_AUTO_UNSHIFT = "Автоматически снимать облик";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "Покидать свой облик при применении заклинания, которое в нем недоступно, использовании предмета, а также при открытии карты полетов. Также позволяет говорить с NPC и использовать объекты в обликах друида и призрачного волка.";
|
||||
CLASSICAPI_BAGS_AND_ITEMS = "Сумки и предметы";
|
||||
CLASSICAPI_CASTING_AND_ATTACKING = "Заклинания и атаки";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION = "Хранение журнала боя";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION_TOOLTIP = "Сколько секунд журнал боя хранит записи для модификаций.";
|
||||
CLASSICAPI_CURSOR = "Курсор";
|
||||
CLASSICAPI_DIRECTION_ANY = "Любое направление";
|
||||
CLASSICAPI_DIRECTION_CONE = "В пределах 30 градусов";
|
||||
CLASSICAPI_DIRECTION_FRONT = "Прямо перед собой";
|
||||
CLASSICAPI_ENEMIES = "Противники";
|
||||
CLASSICAPI_ENEMY_DIRECTION = "Направление на противника";
|
||||
CLASSICAPI_ENEMY_ICON = "Показывать значок противника";
|
||||
CLASSICAPI_ENEMY_ICON_TOOLTIP = "Показывает значок над противником, которого выбирает динамическое выделение.";
|
||||
CLASSICAPI_ENEMY_RANGE = "Дистанция до противников";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "Показывать расширенные курсоры";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "При наведении на NPC или объект курсор показывает, что с ним можно сделать. Например, наковальню для ремонта, ! или ? для заданий и крючок для вашего поплавка.";
|
||||
CLASSICAPI_FRIEND_DIRECTION = "Направление на союзника";
|
||||
CLASSICAPI_FRIEND_ICON = "Показывать значок союзника";
|
||||
CLASSICAPI_FRIEND_ICON_TOOLTIP = "Показывает значок над дружественной целью, которую выбирает дружественное динамическое выделение.";
|
||||
CLASSICAPI_FRIEND_RANGE = "Дистанция до союзников";
|
||||
CLASSICAPI_FRIEND_TARGETING = "Включить дружественное динамическое выделение";
|
||||
CLASSICAPI_FRIEND_TARGETING_TOOLTIP = "Выбирает ближайшую дружественную цель в поле зрения. Если у вас нет цели, полезные заклинания применяются к ней.";
|
||||
CLASSICAPI_FRIENDS = "Союзники";
|
||||
CLASSICAPI_GAME_OBJECT_ICON = "Показывать значок взаимодействия на объектах";
|
||||
CLASSICAPI_GAME_OBJECT_ICON_TOOLTIP = "Также показывает значок взаимодействия над объектами, которые нельзя выбрать целью, например почтовыми ящиками и сундуками.";
|
||||
CLASSICAPI_HIDE_EQUIPMENT_SETS = "Скрывать комплекты экипировки на предметах";
|
||||
CLASSICAPI_HIDE_EQUIPMENT_SETS_TOOLTIP = "Убирает строку с комплектами экипировки из подсказок предметов.";
|
||||
CLASSICAPI_ICON_SIZE = "Размер значков";
|
||||
CLASSICAPI_ICON_SIZE_TOOLTIP = "Размер значков взаимодействия, противников и союзников.";
|
||||
CLASSICAPI_IGNORE_BACKPACK = "Не сортировать рюкзак";
|
||||
CLASSICAPI_IGNORE_BACKPACK_TOOLTIP = "При сортировке сумок предметы в рюкзаке остаются на своих местах.";
|
||||
CLASSICAPI_IGNORE_BANK = "Не сортировать основной банк";
|
||||
CLASSICAPI_IGNORE_BANK_TOOLTIP = "При сортировке банка предметы в основных ячейках банка остаются на своих местах.";
|
||||
CLASSICAPI_INTERACT_DIRECTION = "Направление взаимодействия";
|
||||
CLASSICAPI_INTERACT_DIRECTION_TOOLTIP = "Где должен находиться NPC или объект, чтобы его выбрала клавиша взаимодействия. Направление определяется камерой, а не персонажем.";
|
||||
CLASSICAPI_INTERACT_ICON = "Показывать значок взаимодействия";
|
||||
CLASSICAPI_INTERACT_ICON_TOOLTIP = "Показывает значок над NPC или объектом, которые использует клавиша взаимодействия.";
|
||||
CLASSICAPI_INTERACT_KEY = "Клавиша взаимодействия";
|
||||
CLASSICAPI_INTERACT_RANGE = "Дистанция взаимодействия";
|
||||
CLASSICAPI_INTERFACE = "Интерфейс";
|
||||
CLASSICAPI_KEEP_ACTION_TARGETING = "Сохранять динамическое выделение";
|
||||
CLASSICAPI_KEEP_ACTION_TARGETING_TOOLTIP = "Динамическое выделение продолжает выбирать противников и союзников, пока у вас есть цель, выбранная вручную.";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS = "За кем следует обычная цель";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_ENEMY = "За мягкой целью противника";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_FRIEND = "За мягкой целью союзника";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_NONE = "Ни за кем";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_TOOLTIP = "Цель, выбранная динамическим выделением, меняется вместе с мягкой целью противника или союзника. Цель, выбранная вручную, сохраняется.";
|
||||
CLASSICAPI_LOW_PRIORITY_ICONS = "Показывать значки низкого приоритета";
|
||||
CLASSICAPI_LOW_PRIORITY_ICONS_TOOLTIP = "Также показывает значок над целью, с которой нельзя ничего сделать, например над членом группы. Значок выглядит как обычный указатель.";
|
||||
CLASSICAPI_MATCH_INSIDE_NAMES = "Учитывать правки внутри имени";
|
||||
CLASSICAPI_MATCH_INSIDE_NAMES_TOOLTIP = "Когда вы вводите текст в середине имени, предлагаемые имена также учитывают буквы после курсора.";
|
||||
CLASSICAPI_MATCH_THE_COMMAND = "Учитывать команду";
|
||||
CLASSICAPI_MATCH_THE_COMMAND_TOOLTIP = "Предлагает только имена, подходящие для команды, например участников гильдии для /gpromote. Если выключено, предлагаются все известные вам имена.";
|
||||
CLASSICAPI_MOTION = "Движение";
|
||||
CLASSICAPI_NAME_SUGGESTIONS = "Подсказки имен";
|
||||
CLASSICAPI_NAMEPLATES = "Таблички с именами";
|
||||
CLASSICAPI_NUMBERS = "Числа";
|
||||
CLASSICAPI_OPTIONS_ABOUT = "Настройки возможностей, которые ClassicAPI добавляет в игру. Каждый параметр меняет одну настройку, и изменение применяется сразу.";
|
||||
CLASSICAPI_OTHER_NAMEPLATE_ALPHA = "Непрозрачность чужих табличек";
|
||||
CLASSICAPI_OTHER_NAMEPLATE_ALPHA_TOOLTIP = "Непрозрачность табличек с именами целей, которые не являются вашей целью.";
|
||||
CLASSICAPI_PREFER_YOUR_TARGET = "Отдавать предпочтение вашей цели";
|
||||
CLASSICAPI_PREFER_YOUR_TARGET_TOOLTIP = "Если выбранную вами цель можно взять, динамическое выделение выбирает ее. Если параметр выключен, всегда выбирается ближайшая цель.";
|
||||
CLASSICAPI_RANGE_TOOLTIP = "Максимальное расстояние в ярдах. Оно не может превышать значение параметра targetNearestDistance.";
|
||||
CLASSICAPI_RECENT_WHISPERS_FIRST = "Сначала недавние шепоты";
|
||||
CLASSICAPI_RECENT_WHISPERS_FIRST_TOOLTIP = "Предлагаемые имена начинаются с игроков, которым вы шептали совсем недавно.";
|
||||
CLASSICAPI_REMEMBER_PLAYERS = "Запоминать игроков";
|
||||
CLASSICAPI_REMEMBER_PLAYERS_TOOLTIP = "Сохраняет имя, класс, расу и пол встречавшихся вам игроков, чтобы модификации могли показывать их в следующих сеансах. Данные общие для всех ваших персонажей на этом сервере.";
|
||||
CLASSICAPI_SCRIPT_PROFILE = "Профилирование скриптов";
|
||||
CLASSICAPI_SCRIPT_PROFILE_TOOLTIP = "Измеряет время процессора, которое используют модификации. Изменение вступает в силу после перезагрузки интерфейса.";
|
||||
CLASSICAPI_SEPARATE_LARGE_NUMBERS = "Разделять большие числа";
|
||||
CLASSICAPI_SEPARATE_LARGE_NUMBERS_TOOLTIP = "Ставит разделитель между группами из трех цифр, когда модификации форматируют числа с помощью BreakUpLargeNumbers.";
|
||||
CLASSICAPI_SORT_BAGS_RIGHT_TO_LEFT = "Сортировать сумки справа налево";
|
||||
CLASSICAPI_SORT_BAGS_RIGHT_TO_LEFT_TOOLTIP = "При сортировке предметы перемещаются в конец последней сумки, а хлам — в начало рюкзака.";
|
||||
CLASSICAPI_STRICT_INTERACT_RANGE = "Строгая дистанция взаимодействия";
|
||||
CLASSICAPI_STRICT_INTERACT_RANGE_TOOLTIP = "Никогда не выбирает NPC или объект за пределами дистанции взаимодействия. Если параметр выключен, объект, доступный для использования сейчас, выбирается даже на большем расстоянии.";
|
||||
CLASSICAPI_TARGET_NAMEPLATE_ALPHA = "Непрозрачность таблички цели";
|
||||
CLASSICAPI_TARGET_NAMEPLATE_ALPHA_TOOLTIP = "Непрозрачность таблички с именем вашей цели.";
|
||||
CLASSICAPI_UI_SHAKE_STRENGTH = "Сила дрожания интерфейса";
|
||||
CLASSICAPI_UI_SHAKE_STRENGTH_TOOLTIP = "Определяет, насколько сильно эффекты дрожания смещают рамки. 0 отключает дрожание.";
|
||||
CLASSICAPI_UNIT_DIRECTION_TOOLTIP = "Где должна находиться цель, чтобы ее выбрало динамическое выделение. Направление определяется камерой, а не персонажем.";
|
||||
CLASSICAPI_WHEN_YOU_HAVE_A_TARGET = "Когда у вас есть цель";
|
||||
COMBAT_LABEL = "Бой";
|
||||
COUNTDOWN_FOR_COOLDOWNS_TEXT = "Отсчет времени восстановления";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "Вы действительно хотите удалить комплект экипировки \"%s\"?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "Хотите сохранить комплект экипировки \"%s\"?";
|
||||
COPPER_AMOUNT = "%d |4медная монета:медные монеты:медных монет;";
|
||||
COPPER_AMOUNT_SYMBOL = "м";
|
||||
COPPER_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:2:3:0:1|t";
|
||||
DEBUFF_SYMBOL_CURSE = "Пр";
|
||||
DEBUFF_SYMBOL_DISEASE = "Бз";
|
||||
DEBUFF_SYMBOL_MAGIC = "Мг";
|
||||
@@ -24,74 +116,70 @@ ENABLE_INTERACT_TEXT = "Включить клавишу взаимодейств
|
||||
EQUIPMENT_MANAGER = "Комплекты экипировки";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "Пропустить ячейку";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "Управление экипировкой отключено.";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(предмет для ячейки \"%d\" отсутствует)|r";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(не хватает: %d)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "Поместить в сумку";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "Учитывать ячейку";
|
||||
EQUIPMENT_SETS = "Комплекты экипировки: |cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "Это название уже используется для другого комплекта.";
|
||||
EQUIPMENT_SETS_TOO_MANY = "Вы не можете создавать новые комплекты экипировки.";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Выбрать специализацию:";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Назначить специализации:";
|
||||
EQUIPMENT_SET_EDIT = "Изменить название/значок";
|
||||
EQUIPMENT_SET_SETTINGS = "Параметры";
|
||||
EQUIPSET_EQUIP = "Надеть";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Не удалось заменить комплект обмундирования. Инвентарь переполнен.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Некоторые предметы из набора \"%s\" не будут экипированы, поскольку вы участвуете в бою.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Недостает одного или более предметов из набора \"%s\".";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Не удалось сменить экипировку. Инвентарь переполнен.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Некоторые предметы из комплекта \"%s\" нельзя сменить, поскольку вы участвуете в бою.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Один или несколько предметов из комплекта \"%s\" отсутствуют и не были надеты.";
|
||||
EVENTTRACE_APPLY_DEFAULT_FILTER = "Применить фильтр по умолчанию";
|
||||
EVENTTRACE_ARG_FMT = "Арг. %d:";
|
||||
EVENTTRACE_BUTTON_DISABLE_FILTERS = "Снять выделение";
|
||||
EVENTTRACE_ARG_FMT = "Аргумент %d:";
|
||||
EVENTTRACE_BUTTON_DISABLE_FILTERS = "Снять все";
|
||||
EVENTTRACE_BUTTON_DISCARD_FILTER = "Сбросить все";
|
||||
EVENTTRACE_BUTTON_ENABLE_FILTERS = "Выделить все";
|
||||
EVENTTRACE_BUTTON_ENABLE_FILTERS = "Выбрать все";
|
||||
EVENTTRACE_BUTTON_MARKER = "Маркер";
|
||||
EVENTTRACE_BUTTON_PAUSE = "Пауза";
|
||||
EVENTTRACE_BUTTON_PLAY = "Играть";
|
||||
EVENTTRACE_BUTTON_PLAY = "Продолжить";
|
||||
EVENTTRACE_FILTER_HEADER = "Фильтр";
|
||||
EVENTTRACE_HEADER = "Журнал события";
|
||||
EVENTTRACE_LOG_CR_EVENTS = "Отображать глобальные события по регистрации";
|
||||
EVENTTRACE_HEADER = "Журнал событий";
|
||||
EVENTTRACE_LOG_CR_EVENTS = "Показывать события глобального реестра";
|
||||
EVENTTRACE_LOG_DISCARD = "Журнал сброшен";
|
||||
EVENTTRACE_LOG_HEADER = "Журнал";
|
||||
EVENTTRACE_LOG_PAUSE = "Регистрация участников события приостановлена";
|
||||
EVENTTRACE_LOG_PAUSE_WHILE_HIDDEN = "Фиксация событий прекращается в свернутом состоянии";
|
||||
EVENTTRACE_LOG_START = "Регистрация участников события началась";
|
||||
EVENTTRACE_LOG_WHEN_HIDDEN = "Фиксировать события в свернутом состоянии";
|
||||
EVENTTRACE_LOG_PAUSE = "Запись событий приостановлена";
|
||||
EVENTTRACE_LOG_PAUSE_WHILE_HIDDEN = "Запись событий приостановлена, пока окно скрыто";
|
||||
EVENTTRACE_LOG_START = "Запись событий начата";
|
||||
EVENTTRACE_LOG_WHEN_HIDDEN = "Записывать события, когда окно скрыто";
|
||||
EVENTTRACE_MARKER = "Маркер";
|
||||
EVENTTRACE_MESSAGE_FORMAT = "--- %s ---";
|
||||
EVENTTRACE_OPTIONS = "Параметры";
|
||||
EVENTTRACE_RESULTS = "Результаты: %d";
|
||||
EVENTTRACE_SHOW_ARGUMENTS = "Отображать аргументы события";
|
||||
EVENTTRACE_SHOW_TIMESTAMP = "Отображать метку времени события";
|
||||
EVENTTRACE_RESULTS = "Результатов: %d";
|
||||
EVENTTRACE_SHOW_ARGUMENTS = "Показывать аргументы событий";
|
||||
EVENTTRACE_SHOW_TIMESTAMP = "Показывать время событий";
|
||||
EVENTTRACE_TIMESTAMP = "Время:";
|
||||
CLEAR_FOCUS = "Отменить запоминание цели";
|
||||
INTERACT_ICONS_DEFAULT = "Только NPC (по умолчанию)";
|
||||
INTERACT_ICONS_OPTION = "Значки клавиш взаимодействия";
|
||||
INTERACT_ICONS_SHOW_ALL = "Показать все";
|
||||
INTERACT_ICONS_OPTION = "Значки клавиши взаимодействия";
|
||||
INTERACT_ICONS_SHOW_ALL = "Показывать все";
|
||||
INTERACT_ICONS_SHOW_NONE = "Не показывать";
|
||||
OPTION_TOOLTIP_ACTION_BUTTON_USE_KEY_DOWN = "Способности будут задействоваться, когда пользователь нажимает, а не отпускает клавишу.";
|
||||
OPTION_TOOLTIP_ACTION_TARGETING = "Система захвата целей, динамически выбирающая цели в поле зрения. Работает параллельно со стандартной системой наведения.";
|
||||
OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Отображение обратного отсчета для времени восстановления на панели команд.";
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Позволяет взаимодействовать с NPC и объектами по нажатию клавиши.";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Определяет, какие значки появляются над NPC и объектами, если включен параметр клавиши взаимодействия.";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Прекращение автоматической атаки при смене цели.";
|
||||
OPTION_TOOLTIP_ACTION_BUTTON_USE_KEY_DOWN = "Способности на панели команд срабатывают при нажатии клавиши, а не при ее отпускании.";
|
||||
OPTION_TOOLTIP_ACTION_TARGETING = "Система выделения, которая динамически выбирает противников в зависимости от направления взгляда. Работает вместе со стандартной системой выделения.";
|
||||
OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Показывать числовой отсчет времени восстановления на панели команд.";
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Позволяет взаимодействовать с NPC и объектами нажатием клавиши.";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Определяет, какие значки появляются над NPC и объектами, когда включена клавиша взаимодействия.";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Прекращать автоатаку при смене цели.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<ПКМ, чтобы отвязать>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Назначьте клавишу \"%s\"или нажмите %s для отмены";
|
||||
KEY_BINDING_TOOLTIP = "<ПКМ, чтобы снять привязку>";
|
||||
RESET_POSITION = "Сброс позиции";
|
||||
ROTATE_LEFT = "Вращение влево";
|
||||
ROTATE_RIGHT = "Вращение вправо";
|
||||
ROTATE_TOOLTIP = "Зажмите ЛКМ и тащите курсор, чтобы вращать изображение.";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Назначьте клавишу для \"%s\" или нажмите %s для отмены";
|
||||
SET_FOCUS = "Запомнить цель";
|
||||
FOCUS_TOKEN_NOT_FOUND = "<фокус отсутствует>";
|
||||
FOCUSTARGET = "Запомнить цель";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4предмет:предмета:предметов;";
|
||||
ITEMS_EQUIPPED = "%d |4предмет экипирован:предмета экипировано:предметов экипировано;";
|
||||
ITEMS_IN_INVENTORY = "%d |4предмет:предмета:предметов; в сумках";
|
||||
ITEM_SLOTS_IGNORED = "%d |4ячейка пропущена:ячейки пропущены:ячейки пропущены;";
|
||||
ITEM_MISSING = "Предмет для ячейки %s отсутствует";
|
||||
ITEMS_NOT_IN_INVENTORY = "%d |4предмет отсутствует:предмета отсутствует:предметов отсутствуют;";
|
||||
STOP_AUTO_ATTACK = "Прекращение автоатаки";
|
||||
TARGETFOCUS = "Восстановить цель";
|
||||
-- BINDING_NAME_AOELOOT = "AoE Loot";
|
||||
COPPER_AMOUNT = "%d |4медная монета:медные монеты:медных монет;";
|
||||
COPPER_AMOUNT_SYMBOL = "м";
|
||||
GEARSETS_POPUP_TEXT = "Введите название комплекта (не более 16 символов):";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT = "%d |4золотая:золотые:золотых;";
|
||||
GOLD_AMOUNT_SYMBOL = "з";
|
||||
GOLD_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:0:1:0:1|t";
|
||||
GOLD_AMOUNT_TEXTURE_STRING = "%s|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:0:1:0:1|t";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "Все значки";
|
||||
ICON_FILTER_ITEM = "Предметы";
|
||||
@@ -109,17 +197,17 @@ ITEM_MOD_ATTACK_POWER_SHORT = "к силе атаки";
|
||||
ITEM_MOD_RANGED_ATTACK_POWER_SHORT = "к силе атаки дальнего боя";
|
||||
ITEM_MOD_CRIT_MELEE_RATING_SHORT = "к показателю критического удара в ближнем бою";
|
||||
ITEM_MOD_CRIT_RANGED_RATING_SHORT = "к показателю критического удара в дальнем бою";
|
||||
ITEM_MOD_HIT_MELEE_RATING_SHORT = "к меткости в ближнем бою";
|
||||
ITEM_MOD_HIT_MELEE_RATING_SHORT = "к меткости в ближнем бою";
|
||||
ITEM_MOD_HIT_RANGED_RATING_SHORT = "к меткости в дальнем бою";
|
||||
ITEM_MOD_HIT_SPELL_RATING_SHORT = "к меткости заклинаний";
|
||||
ITEM_MOD_CRIT_SPELL_RATING_SHORT = "к критическому эффекту заклинаний";
|
||||
ITEM_MOD_SPELL_DAMAGE_DONE_SHORT = "к дополнительному урону";
|
||||
ITEM_MOD_SPELL_HEALING_DONE_SHORT = "к дополнительному исцелению";
|
||||
ITEM_MOD_MANA_REGENERATION_SHORT = "к восполнению маны";
|
||||
ITEM_MOD_DEFENSE_SKILL_RATING_SHORT = "Защита";
|
||||
ITEM_MOD_DODGE_RATING_SHORT = "Уклонение";
|
||||
ITEM_MOD_PARRY_RATING_SHORT = "Парирование";
|
||||
ITEM_MOD_BLOCK_RATING_SHORT = "Блок";
|
||||
ITEM_MOD_DEFENSE_SKILL_RATING_SHORT = "к защите";
|
||||
ITEM_MOD_DODGE_RATING_SHORT = "к уклонению";
|
||||
ITEM_MOD_PARRY_RATING_SHORT = "к парированию";
|
||||
ITEM_MOD_BLOCK_RATING_SHORT = "к блокированию";
|
||||
ITEM_MOD_BLOCK_VALUE_SHORT = "к показателю блокирования";
|
||||
ITEM_MOD_HOLY_SPELL_DAMAGE_DONE_SHORT = "к урону от магии света";
|
||||
ITEM_MOD_FIRE_SPELL_DAMAGE_DONE_SHORT = "к урону от магии огня";
|
||||
@@ -131,6 +219,12 @@ ITEM_MOD_SPELL_PENETRATION_SHORT = "к проникающей способнос
|
||||
ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT = "к пробиванию брони";
|
||||
ITEM_MOD_HEALTH_REGEN_SHORT = "к восполнению здоровья";
|
||||
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "к урону в секунду";
|
||||
ITEMS_EQUIPPED = "%d |4предмет экипирован:предмета экипировано:предметов экипировано;";
|
||||
ITEMS_IN_INVENTORY = "%d |4предмет:предмета:предметов; в инвентаре";
|
||||
ITEMS_NOT_IN_INVENTORY = "%d |4предмет отсутствует:предмета отсутствует:предметов отсутствует;";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4предмет:предмета:предметов;";
|
||||
ITEM_MISSING = "%s отсутствует";
|
||||
ITEM_SLOTS_IGNORED = "%d |4ячейка пропущена:ячейки пропущены:ячеек пропущено;";
|
||||
LARGE_NUMBER_SEPERATOR = "";
|
||||
DECIMAL_SEPERATOR = ",";
|
||||
FIRST_NUMBER = "тыс.";
|
||||
@@ -141,14 +235,18 @@ SECOND_NUMBER_CAP = "М";
|
||||
SECOND_NUMBER_CAP_NO_SPACE = "М";
|
||||
THIRD_NUMBER = "млрд";
|
||||
FOURTH_NUMBER = "трлн";
|
||||
LUA_ERROR = "Ошибка Lua";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "Комплекты экипировки";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "Новый комплект";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SET_FRIENDNOTE_LABEL = "Заметка о |3-5(%s):";
|
||||
PLAYER_LIST_DELIMITER = ", ";
|
||||
RELOADUI = "Перезагрузить интерфейс";
|
||||
SET_FRIENDNOTE_LABEL = "Задать заметку для %s:";
|
||||
SET_NOTE = "Добавить заметку";
|
||||
SILVER_AMOUNT = "%d |4серебряная:серебряные:серебряных;";
|
||||
SILVER_AMOUNT_SYMBOL = "с";
|
||||
SILVER_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:1:2:0:1|t";
|
||||
SLASH_CANCELAURA1 = "/снятьауру";
|
||||
SLASH_CANCELAURA2 = "/cancelaura";
|
||||
SLASH_CANCELFORM1 = "/нормоблик";
|
||||
@@ -222,10 +320,14 @@ SLASH_TARGET_NEAREST_RAID2 = "/targetraid";
|
||||
SLASH_USE1 = "/use";
|
||||
SLASH_USE2 = "/использовать";
|
||||
SOCIAL_LABEL = "Общение";
|
||||
LINK_TRADESKILL_TOOLTIP = "Щелкните здесь, чтобы создать ссылку на вашу профессию.";
|
||||
TEXT_TO_SPEECH = "Текст в речь";
|
||||
TEXT_TO_SPEECH_ADJUST_RATE = "Изменить скорость речи.";
|
||||
STOP_AUTO_ATTACK = "Прекращение автоатаки";
|
||||
SYMBOLIC_NEGATIVE_NUMBER = "-%s";
|
||||
SYMBOLIC_POSITIVE_NUMBER = "+%s";
|
||||
TARGETFOCUS = "Восстановить цель";
|
||||
LINK_TRADESKILL_TOOLTIP = "Щелкните здесь, чтобы создать ссылку на свою профессию.";
|
||||
TEXT_TO_SPEECH = "Синтез речи";
|
||||
TEXT_TO_SPEECH_ADJUST_RATE = "Изменить скорость речи";
|
||||
TEXT_TO_SPEECH_ADJUST_VOLUME = "Громкость речи";
|
||||
TEXT_TO_SPEECH_VOICE_OPTION = "Параметры голоса";
|
||||
TOTAL_MEM_KB_ABBR = "Память: %.0f КБ";
|
||||
TOTAL_MEM_MB_ABBR = "Модификации занимают в памяти: %.2f МБ";
|
||||
TEXT_TO_SPEECH_VOICE_OPTION = "Выбор голоса";
|
||||
TOTAL_MEM_KB_ABBR = "Память модификаций: %.0f КБ";
|
||||
TOTAL_MEM_MB_ABBR = "Память модификаций: %.2f МБ";
|
||||
|
||||
@@ -19,7 +19,7 @@ CLASSICAPI_AUTO_DISMOUNT_TOOLTIP = "骑乘状态下施放法术、使用物体
|
||||
CLASSICAPI_AUTO_STAND = "自动站起";
|
||||
CLASSICAPI_AUTO_STAND_TOOLTIP = "坐下状态下施放法术、拾取或打开飞行地图时自动站起。";
|
||||
CLASSICAPI_AUTO_UNSHIFT = "自动取消形态";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "施放当前形态无法施放的法术、与NPC对话、使用物体或物品、或打开飞行地图时自动取消该形态。";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "施放当前形态无法施放的法术、使用物品或打开飞行地图时自动取消该形态。同时允许在德鲁伊形态和幽魂之狼形态下与NPC对话和使用物体。";
|
||||
CLASSICAPI_BAGS_AND_ITEMS = "背包与物品";
|
||||
CLASSICAPI_CASTING_AND_ATTACKING = "施法与攻击";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION = "战斗记录保留时间";
|
||||
@@ -163,6 +163,10 @@ OPTION_TOOLTIP_INTERACT_ICONS = "控制交互按键启用后NPC和物体上方
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "当你切换目标时停止自动攻击。";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s(%s)";
|
||||
KEY_BINDING_TOOLTIP = "<右键解除键位>";
|
||||
RESET_POSITION = "重置位置";
|
||||
ROTATE_LEFT = "左转";
|
||||
ROTATE_RIGHT = "右转";
|
||||
ROTATE_TOOLTIP = "左键点击并拖曳以旋转角色";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "设置 \"%s\"的快捷键,或者按 %s 取消";
|
||||
SET_FOCUS = "设置焦点";
|
||||
FOCUSTARGET = "设定焦点目标";
|
||||
|
||||
@@ -74,6 +74,10 @@ OPTION_TOOLTIP_INTERACT_ICONS = "在啟用互動鍵功能後,控制出現在NP
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "當你切換目標時停止自動攻擊。";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s(%s)";
|
||||
KEY_BINDING_TOOLTIP = "<右鍵點擊以解除綁定>";
|
||||
RESET_POSITION = "重置位置";
|
||||
ROTATE_LEFT = "向左旋轉";
|
||||
ROTATE_RIGHT = "向右旋轉";
|
||||
ROTATE_TOOLTIP = "滑鼠左鍵點擊角色並拖曳以旋轉角度。";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "為\"%s\"指配綁定或是按下%s以取消";
|
||||
SET_FOCUS = "設定專注目標";
|
||||
FOCUSTARGET = "設定鎖定目標";
|
||||
|
||||
Binary file not shown.
@@ -217,8 +217,7 @@ void Register() {
|
||||
{"Standard", static_cast<int>(Variant::Standard)},
|
||||
{"UrlSafe", static_cast<int>(Variant::UrlSafe)},
|
||||
};
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "Base64Variant",
|
||||
kBase64Variant, 2);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "Base64Variant", kBase64Variant);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -305,8 +305,7 @@ void Register() {
|
||||
{"Zlib", static_cast<int>(Method::Zlib)},
|
||||
{"Gzip", static_cast<int>(Method::Gzip)},
|
||||
};
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "CompressionMethod",
|
||||
kCompressionMethod, 3);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "CompressionMethod", kCompressionMethod);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -224,9 +224,7 @@ static void RegisterLuaFunctions() {
|
||||
// 4.3.4's switch for the item-tooltip line; dedups by name on /reload.
|
||||
g_hideSetsCVar = CVar::Factory::Register("dontShowEquipmentSetsOnItems", "Don't show which equipment sets an item is associated with", Offsets::CVAR_CATEGORY_GAME, "0");
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY),
|
||||
g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+88
-38
@@ -19,6 +19,7 @@
|
||||
#include "frame/ScriptArgs.h"
|
||||
#include "frame/UnitEvent.h"
|
||||
#include "profile/ScriptTime.h"
|
||||
#include "storm/Memory.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -55,6 +56,10 @@ struct Reservation {
|
||||
int slot; // -1 until claimed
|
||||
const Game::Doc::Event *doc; // API documentation; nullptr = undocumented
|
||||
int fixedId; // an `EngineName`'s engine id (its slot for good); -1 = claimed
|
||||
// The SStrDup'd name `Claim` wrote into `slot`'s entry; nullptr while
|
||||
// unclaimed. `RetryClaims` compares it against the live entry to notice
|
||||
// that the engine rebuilt the table underneath a cached slot.
|
||||
const char *claimed;
|
||||
};
|
||||
Reservation g_reserved[MAX_RESERVED];
|
||||
int g_reservedCount = 0;
|
||||
@@ -63,7 +68,7 @@ bool g_writesEnabled = false;
|
||||
// claim) is logged once per session instead of silently dropping the
|
||||
// event — the failure mode that would otherwise rot unnoticed. Re-armed on
|
||||
// `/reload` (the table is rebuilt fresh). In practice this never triggers:
|
||||
// the engine leaves ~115 claimable gaps below 549 (see `TryClaim`) against
|
||||
// the engine leaves ~115 claimable gaps below 549 (see `Claim`) against
|
||||
// our ~77 reservations, and `Grow` adds room past that. The log is
|
||||
// purely a backstop if some future install somehow drains the gap pool.
|
||||
bool g_warnedTableFull = false;
|
||||
@@ -90,23 +95,6 @@ char *SStrDup(const char *s) {
|
||||
return fn(s, __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
// Storm allocator/free — the same pair the engine uses for the event-table
|
||||
// buffer (see RebuildEventTable), so its reload teardown `SMemFree`s our
|
||||
// grown buffer exactly as its own. Both `__stdcall`, 4 args (`ret 0x10`).
|
||||
// SMemAlloc flag bit 8 zero-fills (matching SetEventCount's fresh entries).
|
||||
void *SMemAlloc(unsigned size) {
|
||||
using SMemAlloc_t = void *(__stdcall *)(unsigned size, const char *file,
|
||||
int line, int flags);
|
||||
auto fn = reinterpret_cast<SMemAlloc_t>(Offsets::FUN_STORM_SMEM_ALLOC);
|
||||
return fn(size, __FILE__, __LINE__, 8 /* zero-fill */);
|
||||
}
|
||||
void SMemFree(void *ptr) {
|
||||
using SMemFree_t = int(__stdcall *)(void *ptr, const char *file, int line,
|
||||
int flags);
|
||||
auto fn = reinterpret_cast<SMemFree_t>(Offsets::FUN_STORM_SMEM_FREE);
|
||||
fn(ptr, __FILE__, __LINE__, 0);
|
||||
}
|
||||
|
||||
// True when slot `id` of the live table can take one of our names: its name
|
||||
// is NULL (no one owns it) AND the engine never signals it by number. A NULL
|
||||
// name alone is not enough — the glue band (ids below GLUE_EVENT_COUNT) and
|
||||
@@ -147,23 +135,39 @@ int ClaimDemand() {
|
||||
// tail. Below 549 only nampower writes, at 369 and 540, both before any Lua
|
||||
// runs, so skip-occupied steps over them. Claiming low keeps us out of
|
||||
// everyone's way and independent of their clamp / hook order.
|
||||
int TryClaim(const char *eventName) {
|
||||
//
|
||||
// Claims only into the IN-GAME table. `CGGameUI::Initialize` (0x0048FE00)
|
||||
// calls `RegisterScriptFunctions` (our `EnableWrites` hook) and only THEN
|
||||
// `SetScriptEventTable(0xBE1198, 0x225)`, so between the two the live table
|
||||
// is still glue's 26-entry one. A `Frame::RegisterEvent` in that window
|
||||
// (a third-party DLL registering a frame from its own LoadScriptFunctions
|
||||
// hook — issue #134) used to grow the glue table and claim into it; the
|
||||
// engine then freed that buffer and our cached ids silently aliased engine
|
||||
// events in the real table. The in-game build always passes 0x225 names
|
||||
// (clamps by other DLLs only enlarge it), so a smaller count is not it.
|
||||
bool GameTableInstalled(int count) {
|
||||
return count >= Offsets::EVENT_NAME_TABLE_STATIC_COUNT;
|
||||
}
|
||||
|
||||
void Claim(Reservation &r) {
|
||||
if (!g_writesEnabled)
|
||||
return -1;
|
||||
return;
|
||||
auto *base = Game::Read<uint8_t *>(Offsets::VAR_EVENT_TABLE_BASE_PTR);
|
||||
const int count = Game::Read<int>(Offsets::VAR_EVENT_TABLE_COUNT);
|
||||
if (base == nullptr || count <= 0)
|
||||
return -1;
|
||||
if (base == nullptr || !GameTableInstalled(count))
|
||||
return;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (Claimable(base, i)) {
|
||||
auto **namePtr = Game::Ptr<const char *>(
|
||||
base + i * Offsets::EVENT_ENTRY_STRIDE,
|
||||
Offsets::OFF_EVENT_ENTRY_NAME);
|
||||
char *copy = SStrDup(eventName);
|
||||
char *copy = SStrDup(r.name);
|
||||
if (copy == nullptr)
|
||||
return -1;
|
||||
return;
|
||||
*namePtr = copy;
|
||||
return i;
|
||||
r.slot = i;
|
||||
r.claimed = copy;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Writes are enabled and the table is valid, but every slot is taken —
|
||||
@@ -173,9 +177,39 @@ int TryClaim(const char *eventName) {
|
||||
Debug::Log::Printf(
|
||||
"[event] table full (%d slots, no NULL left) — cannot claim '%s'"
|
||||
" and any further custom events; even Grow couldn't make room.",
|
||||
count, eventName ? eventName : "?");
|
||||
count, r.name);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// True when a claimed reservation's slot no longer holds its name: the
|
||||
// engine rebuilt the table (every entry re-`SStrDup`'d at a fresh buffer)
|
||||
// without our `PrepareForReload` seeing it. The pointer test is the cheap
|
||||
// common case; the strcmp covers an allocator handing the engine our freed
|
||||
// block for a different name.
|
||||
bool Stale(const Reservation &r) {
|
||||
if (r.claimed == nullptr)
|
||||
return false;
|
||||
auto *base = Game::Read<uint8_t *>(Offsets::VAR_EVENT_TABLE_BASE_PTR);
|
||||
const int count = Game::Read<int>(Offsets::VAR_EVENT_TABLE_COUNT);
|
||||
if (base == nullptr || r.slot >= count)
|
||||
return true;
|
||||
const char *live = Game::Read<const char *>(
|
||||
base + r.slot * Offsets::EVENT_ENTRY_STRIDE, Offsets::OFF_EVENT_ENTRY_NAME);
|
||||
if (live == r.claimed)
|
||||
return false;
|
||||
return live == nullptr || std::strcmp(live, r.name) != 0;
|
||||
}
|
||||
|
||||
// Forget every claimed slot: the table they were claimed in is gone. The
|
||||
// engine freed our name copies with its per-entry teardown, so nothing to
|
||||
// release here. Re-arms the per-build one-shots.
|
||||
void ForgetClaims() {
|
||||
for (int i = 0; i < g_reservedCount; ++i) {
|
||||
g_reserved[i].slot = g_reserved[i].fixedId;
|
||||
g_reserved[i].claimed = nullptr;
|
||||
}
|
||||
g_warnedTableFull = false;
|
||||
g_growLatched = false;
|
||||
}
|
||||
|
||||
// On-demand event-table grow. Correctly TIMED: called once per in-game
|
||||
@@ -205,12 +239,14 @@ int TryClaim(const char *eventName) {
|
||||
void Grow() {
|
||||
if (g_growLatched || !g_writesEnabled)
|
||||
return;
|
||||
g_growLatched = true; // one attempt per build, whether or not it grows
|
||||
|
||||
const int count = Game::Read<int>(Offsets::VAR_EVENT_TABLE_COUNT);
|
||||
auto *base = Game::Read<uint8_t *>(Offsets::VAR_EVENT_TABLE_BASE_PTR);
|
||||
if (base == nullptr || !GameTableInstalled(count))
|
||||
return; // still glue's table — the in-game build hasn't run yet
|
||||
g_growLatched = true; // one attempt per build, whether or not it grows
|
||||
|
||||
const int demand = ClaimDemand();
|
||||
if (base == nullptr || count <= 0 || demand <= 0)
|
||||
if (demand <= 0)
|
||||
return;
|
||||
|
||||
int nulls = 0;
|
||||
@@ -242,8 +278,12 @@ void Grow() {
|
||||
constexpr int kMargin = 16;
|
||||
const int deficit = (nulls < demand) ? (demand - nulls) : 0;
|
||||
const int newCount = count + deficit + kMargin;
|
||||
// Storm's heap, like the engine's own table buffer (see RebuildEventTable),
|
||||
// so its reload teardown SMemFrees ours the same way; zero-filled, like
|
||||
// SetEventCount's fresh entries.
|
||||
auto *newBuf = static_cast<uint8_t *>(
|
||||
SMemAlloc(static_cast<unsigned>(newCount) * Offsets::EVENT_ENTRY_STRIDE));
|
||||
Storm::Alloc(static_cast<size_t>(newCount) * Offsets::EVENT_ENTRY_STRIDE, __FILE__,
|
||||
__LINE__, true));
|
||||
if (newBuf == nullptr)
|
||||
return; // alloc failed — keep the old table; claiming still works
|
||||
|
||||
@@ -271,7 +311,7 @@ void Grow() {
|
||||
Game::Ref<uint8_t *>(Offsets::VAR_EVENT_TABLE_BASE_PTR) = newBuf;
|
||||
Game::Ref<int>(Offsets::VAR_EVENT_TABLE_CAP) = newCount;
|
||||
Game::Ref<int>(Offsets::VAR_EVENT_TABLE_COUNT) = newCount;
|
||||
SMemFree(base);
|
||||
Storm::Free(base, __FILE__, __LINE__);
|
||||
|
||||
Debug::Log::Printf("[event] grew table %d -> %d (%d NULL, %d reserved%s)",
|
||||
count, newCount, nulls, demand,
|
||||
@@ -320,6 +360,7 @@ AutoReserve::AutoReserve(const char *name, const Game::Doc::Event *doc) {
|
||||
g_reserved[g_reservedCount].slot = -1;
|
||||
g_reserved[g_reservedCount].doc = doc;
|
||||
g_reserved[g_reservedCount].fixedId = -1;
|
||||
g_reserved[g_reservedCount].claimed = nullptr;
|
||||
index_ = g_reservedCount;
|
||||
++g_reservedCount;
|
||||
}
|
||||
@@ -339,7 +380,7 @@ EngineName::EngineName(int id, const char *name, const Game::Doc::Event *doc) {
|
||||
if (*slot != nullptr)
|
||||
return; // the engine names it; never take an engine event's slot
|
||||
*slot = name;
|
||||
g_reserved[g_reservedCount] = {name, id, doc, id};
|
||||
g_reserved[g_reservedCount] = {name, id, doc, id, nullptr};
|
||||
++g_reservedCount;
|
||||
}
|
||||
|
||||
@@ -428,6 +469,18 @@ void FireValues(int slot, void *L, int firstArgIdx, int nargs) {
|
||||
}
|
||||
|
||||
void RetryClaims() {
|
||||
// A table rebuilt behind our back (no FrameScript_Initialize in between)
|
||||
// leaves every cached slot pointing at whatever engine event now sits at
|
||||
// that id. Check the claims against the live table and start over if any
|
||||
// moved — we run before the original registers, so no frame has joined a
|
||||
// chain in the new table yet.
|
||||
for (int i = 0; i < g_reservedCount; ++i) {
|
||||
if (Stale(g_reserved[i])) {
|
||||
Debug::Log::Printf("[event] table rebuilt under cached slots — re-claiming");
|
||||
ForgetClaims();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Grow the table first if the low gap pool is short (dormant valve).
|
||||
// Runs at most once per table build; on the first call every chain is
|
||||
// still empty (this is the first registration), which is the only safe
|
||||
@@ -435,18 +488,15 @@ void RetryClaims() {
|
||||
Grow();
|
||||
for (int i = 0; i < g_reservedCount; ++i) {
|
||||
if (g_reserved[i].slot < 0)
|
||||
g_reserved[i].slot = TryClaim(g_reserved[i].name);
|
||||
Claim(g_reserved[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void EnableWrites() { g_writesEnabled = true; }
|
||||
|
||||
void PrepareForReload() {
|
||||
for (int i = 0; i < g_reservedCount; ++i)
|
||||
g_reserved[i].slot = g_reserved[i].fixedId;
|
||||
ForgetClaims();
|
||||
g_writesEnabled = false;
|
||||
g_warnedTableFull = false; // fresh table after the rebuild — re-arm
|
||||
g_growLatched = false; // re-arm the one-shot grow for the new table
|
||||
}
|
||||
|
||||
static const Game::ReloadAutoRegister _reloadReg{&PrepareForReload};
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ inline void FireIdSuccess(int eventID, int id, bool success) {
|
||||
// catch any reservations that couldn't claim earlier.
|
||||
void RetryClaims();
|
||||
|
||||
// Internal: permit `TryClaim` to write to the event table. Held
|
||||
// Internal: permit `Claim` to write to the event table. Held
|
||||
// closed until `LoadScriptFunctions_h` returns, so the boot phase
|
||||
// (during which the engine and SuperWoWhook fire many internal
|
||||
// `RegisterEvent` calls) can't trigger our writes. Writing during
|
||||
|
||||
@@ -39,19 +39,8 @@ namespace {
|
||||
|
||||
// FUN_FONTSTRING_FONT_HEIGHT — __thiscall(fs, scaled-on-stack); dummy-EDX form.
|
||||
using FsFontHeight_t = float(__fastcall *)(void *fs, void *edx, int scaled);
|
||||
using GetScaledScreenAspect_t = float(__cdecl *)();
|
||||
// Declared double so the quotient is read from ST0 with `fstp qword`, as
|
||||
// GetFont stores it, not narrowed to float.
|
||||
using DivideByScreenScaleX_t = double(__stdcall *)(float);
|
||||
|
||||
// GetFont's push (0x0079D4A5..0x0079D4C4). `aspect * 1024` is exact, so the
|
||||
// float product rounds once, as GetFont's `fstp dword` does.
|
||||
double ToUiHeight(float internal) {
|
||||
const float aspect =
|
||||
reinterpret_cast<GetScaledScreenAspect_t>(Offsets::FUN_COORD_GET_SCALED_SCREEN_ASPECT)();
|
||||
const float scaled = aspect * static_cast<float>(Offsets::UI_COORD_SCALE_UNIT) * internal;
|
||||
return reinterpret_cast<DivideByScreenScaleX_t>(Offsets::FUN_COORD_DIVIDE_BY_SCREEN_SCALE_X)(scaled);
|
||||
}
|
||||
// GetFont's push (0x0079D4A5..0x0079D4C4).
|
||||
double ToUiHeight(float internal) { return Game::InternalToPixel(internal); }
|
||||
|
||||
void *FontStringSelf(void *L) { return Game::Lua::ResolveFontString(L); }
|
||||
void *FontSelf(void *L) { return Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_FONT_LUA_TYPE_ID); }
|
||||
@@ -144,10 +133,11 @@ const Game::Doc::Method kFontDocs[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY),
|
||||
kFontStringMethods, 2, kFontStringDocs, 2);
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONT_METHOD_REGISTRY), kFontMethods,
|
||||
2, kFontDocs, 2);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kFontStringMethods,
|
||||
kFontStringDocs);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONT_METHOD_REGISTRY), kFontMethods, kFontDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -181,8 +181,8 @@ const Game::Doc::Method kHtmlDocs[] = {
|
||||
{"GetIndentedWordWrap", &kHtmlGet},
|
||||
};
|
||||
|
||||
template <typename P> void Register(uintptr_t registry, const Game::Doc::Method *docs) {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), P::methods, 2, docs, 2);
|
||||
template <typename P> void Register(uintptr_t registry, Game::Doc::MethodList docs) {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), P::methods, docs);
|
||||
}
|
||||
|
||||
// A font string takes the bit from its font only when its settable mask
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "frame/Layout.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -24,9 +25,6 @@ namespace {
|
||||
|
||||
// FUN_HANDLE_RELEASE — DecRef a ref-counted handle. __fastcall(handle).
|
||||
using HandleRelease_t = void(__fastcall *)(void *handle);
|
||||
// FUN_LAYOUT_RESIZE — region layout resize (0 = invalidate). __thiscall(region,
|
||||
// int); dummy-EDX form.
|
||||
using LayoutInvalidate_t = void(__fastcall *)(void *region, void *edx, int arg);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -39,8 +37,7 @@ void Invalidate(void *fs) {
|
||||
Game::Ref<void *>(fs, Offsets::OFF_FONTSTRING_TEXT_BLOCK) = nullptr;
|
||||
}
|
||||
Game::Ref<uint32_t>(fs, Offsets::OFF_FONTSTRING_DIRTY_FLAGS) &= ~1u;
|
||||
void *region = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(fs) + 0x24);
|
||||
reinterpret_cast<LayoutInvalidate_t>(Offsets::FUN_LAYOUT_RESIZE)(region, nullptr, 0);
|
||||
Frame::Layout::Resize(fs, false);
|
||||
}
|
||||
|
||||
} // namespace FontString::Layout
|
||||
|
||||
@@ -378,9 +378,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -96,8 +96,8 @@ const Game::Doc::Method kDocs[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kMethods, 2,
|
||||
kDocs, 2);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kMethods, kDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+21
-37
@@ -54,6 +54,8 @@
|
||||
#include "Offsets.h"
|
||||
#include "frame/ClipChildren.h"
|
||||
#include "frame/Destruction.h"
|
||||
#include "frame/Layout.h"
|
||||
#include "gx/Device.h"
|
||||
#include "texture/Load.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -63,19 +65,8 @@ namespace Frame::AlphaGradient {
|
||||
|
||||
namespace {
|
||||
|
||||
using Matrix_t = void(__fastcall *)(float *m);
|
||||
using SetProjection_t = void(__fastcall *)(const float *m);
|
||||
using GetViewport_t = void(__fastcall *)(float *minX, float *maxX, float *minY, float *maxY, float *minZ,
|
||||
float *maxZ);
|
||||
using SetViewport_t = void(__stdcall *)(float minX, float maxX, float minY, float maxY, float minZ, float maxZ);
|
||||
using Convert_t = void(__fastcall *)(float *outX, float *outY, float x, float y);
|
||||
using GetRect_t = int(__fastcall *)(void *layout, void *edx, float *out);
|
||||
using GetRenderable_t = void *(__fastcall *)(void *hTex, int force, void *);
|
||||
using RsSet_t = void(__fastcall *)(int selector, int value);
|
||||
using RsSetPtr_t = void(__fastcall *)(int selector, const void *value);
|
||||
using StateOp_t = void(__fastcall *)();
|
||||
using TexMtxLoad_t = void(__fastcall *)(int unit, const float *m);
|
||||
using TexMtxOp_t = void(__fastcall *)(int unit);
|
||||
using SetFrameFlag_t = void(__fastcall *)(void *frame, void *edx, uint32_t mask, int on);
|
||||
|
||||
// A rect: {bottom, left, top, right}.
|
||||
@@ -135,7 +126,7 @@ void *RampTexture() {
|
||||
if (g_ramp == nullptr)
|
||||
g_ramp = Texture::CreateFromPixels("ClassicAPI\\AlphaGradient", 2, 2, kRampTexels, Texture::DefaultFlags());
|
||||
return g_ramp != nullptr
|
||||
? reinterpret_cast<GetRenderable_t>(Offsets::FUN_TEXTURE_GET_RENDERABLE)(g_ramp, 1, nullptr)
|
||||
? Texture::DeviceTexture(g_ramp)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
@@ -155,13 +146,12 @@ void RampColumn(const Stage &s, int axis, float vMin, float vMax, float col[4])
|
||||
}
|
||||
|
||||
void Load(const Stage &s) {
|
||||
float p[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
|
||||
reinterpret_cast<Matrix_t>(Offsets::FUN_GX_GET_PROJECTION)(p);
|
||||
float minX, maxX, minY, maxY, minZ, maxZ;
|
||||
reinterpret_cast<GetViewport_t>(Offsets::FUN_GX_GET_VIEWPORT)(&minX, &maxX, &minY, &maxY, &minZ, &maxZ);
|
||||
Gx::Matrix p;
|
||||
Gx::GetProjection(p);
|
||||
const Gx::Viewport vp = Gx::GetViewport();
|
||||
float u[4], v[4];
|
||||
RampColumn(s, 0, minX, maxX, u);
|
||||
RampColumn(s, 1, minY, maxY, v);
|
||||
RampColumn(s, 0, vp.minX, vp.maxX, u);
|
||||
RampColumn(s, 1, vp.minY, vp.maxY, v);
|
||||
float m[16];
|
||||
for (int r = 0; r < 4; ++r) {
|
||||
const float *row = p + r * 4;
|
||||
@@ -170,7 +160,7 @@ void Load(const Stage &s) {
|
||||
m[r * 4 + 2] = row[3];
|
||||
m[r * 4 + 3] = 0.0f;
|
||||
}
|
||||
reinterpret_cast<TexMtxLoad_t>(Offsets::FUN_GX_TEXMTX_LOAD)(s.unit, m);
|
||||
Gx::SetTransform(s.unit, m);
|
||||
}
|
||||
|
||||
// Appends the stages of `frame`'s gradient at g_stages[g_count + n]. Returns
|
||||
@@ -180,11 +170,9 @@ int AddStages(const void *frame, int n, int lastUnit) {
|
||||
if (it == g_gradients.end())
|
||||
return n;
|
||||
float rect[4];
|
||||
void *layout = const_cast<uint8_t *>(static_cast<const uint8_t *>(frame) + Offsets::OFF_REGION_ANCHOR);
|
||||
if (reinterpret_cast<GetRect_t>(Offsets::FUN_REGION_GET_RECT)(layout, nullptr, rect) == 0)
|
||||
if (!Frame::Layout::GetRect(frame, rect))
|
||||
return n;
|
||||
const float scale = Game::Read<float>(frame, Offsets::OFF_REGION_EFFECTIVE_SCALE);
|
||||
const auto toNdc = reinterpret_cast<Convert_t>(Offsets::FUN_COORD_DDC_TO_NDC);
|
||||
for (int index = 0; index < 2; ++index) {
|
||||
const float lx = it->second.x[index], ly = it->second.y[index];
|
||||
if (!(lx > 0.0f) && !(ly > 0.0f))
|
||||
@@ -198,8 +186,8 @@ int AddStages(const void *frame, int n, int lastUnit) {
|
||||
const float dy = Game::PixelToInternal(ly * scale) * (index == 0 ? -1.0f : 1.0f);
|
||||
Stage &s = g_stages[g_count + n];
|
||||
s.unit = 1 + g_count + n;
|
||||
toNdc(&s.p0[0], &s.p0[1], ex, ey);
|
||||
toNdc(&s.p1[0], &s.p1[1], ex + dx, ey + dy);
|
||||
Frame::Layout::DdcToNdc(&s.p0[0], &s.p0[1], ex, ey);
|
||||
Frame::Layout::DdcToNdc(&s.p1[0], &s.p1[1], ex + dx, ey + dy);
|
||||
s.on[0] = lx > 0.0f && s.p1[0] != s.p0[0];
|
||||
s.on[1] = ly > 0.0f && s.p1[1] != s.p0[1];
|
||||
if (s.on[0] || s.on[1])
|
||||
@@ -324,9 +312,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
@@ -357,16 +343,14 @@ int Begin(const void *frame, bool ancestors) {
|
||||
void *ramp = RampTexture();
|
||||
if (ramp == nullptr)
|
||||
return 0;
|
||||
const auto rs = reinterpret_cast<RsSet_t>(Offsets::FUN_GX_RS_SET);
|
||||
const auto rsPtr = reinterpret_cast<RsSetPtr_t>(Offsets::FUN_GX_RS_SET_PTR);
|
||||
reinterpret_cast<StateOp_t>(Offsets::FUN_GX_STATE_PUSH)();
|
||||
Gx::PushStates();
|
||||
for (int i = g_count; i < g_count + n; ++i) {
|
||||
const int unit = g_stages[i].unit;
|
||||
rsPtr(Offsets::GXRS_TEXTURE0 + unit, ramp);
|
||||
rs(Offsets::GXRS_COMBINER0 + unit, Offsets::GXRS_COMBINE_MODULATE);
|
||||
rs(Offsets::GXRS_TEXGEN0 + unit, Offsets::GXRS_TEXGEN_CAMERA_POS);
|
||||
rs(Offsets::GXRS_TEXXFORM0 + unit, Offsets::GXRS_TEXXFORM_PROJECTED);
|
||||
reinterpret_cast<TexMtxOp_t>(Offsets::FUN_GX_TEXMTX_PUSH)(unit);
|
||||
Gx::SetState(Offsets::GXRS_TEXTURE0 + unit, ramp);
|
||||
Gx::SetState(Offsets::GXRS_COMBINER0 + unit, Offsets::GXRS_COMBINE_MODULATE);
|
||||
Gx::SetState(Offsets::GXRS_TEXGEN0 + unit, Offsets::GXRS_TEXGEN_CAMERA_POS);
|
||||
Gx::SetState(Offsets::GXRS_TEXXFORM0 + unit, Offsets::GXRS_TEXXFORM_PROJECTED);
|
||||
Gx::PushTransform(unit);
|
||||
Load(g_stages[i]);
|
||||
}
|
||||
g_count += n;
|
||||
@@ -377,9 +361,9 @@ void End(int stages) {
|
||||
if (stages == 0)
|
||||
return;
|
||||
for (int i = g_count - 1; i >= g_count - stages; --i)
|
||||
reinterpret_cast<TexMtxOp_t>(Offsets::FUN_GX_TEXMTX_POP)(g_stages[i].unit);
|
||||
Gx::PopTransform(g_stages[i].unit);
|
||||
g_count -= stages;
|
||||
reinterpret_cast<StateOp_t>(Offsets::FUN_GX_STATE_POP)();
|
||||
Gx::PopStates();
|
||||
}
|
||||
|
||||
int StagesInUse() { return g_count; }
|
||||
|
||||
@@ -1165,9 +1165,7 @@ const Game::Lua::FrameMethodEntry g_frameMethods[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY),
|
||||
g_frameMethods,
|
||||
static_cast<int>(sizeof(g_frameMethods) / sizeof(g_frameMethods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "frame/Layout.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include <cstdint>
|
||||
@@ -43,20 +44,12 @@ struct Rect {
|
||||
float bottom, left, top, right;
|
||||
};
|
||||
|
||||
using GetRect_t = int(__fastcall *)(void *layout, void *edx, Rect *out);
|
||||
using LayoutDirty_t = int(__fastcall *)(void *layout);
|
||||
using Resize_t = void(__fastcall *)(void *layout, void *edx, int force);
|
||||
|
||||
void *LayoutOf(void *region) { return static_cast<uint8_t *>(region) + Offsets::OFF_REGION_ANCHOR; }
|
||||
|
||||
// Grows `rect` by a region's own rect, resolving a pending layout first.
|
||||
void AddRegionRect(void *region, Rect &rect) {
|
||||
void *layout = LayoutOf(region);
|
||||
if (reinterpret_cast<LayoutDirty_t>(Offsets::FUN_REGION_LAYOUT_DIRTY)(layout) != 0)
|
||||
reinterpret_cast<Resize_t>(Offsets::FUN_LAYOUT_RESIZE)(layout, nullptr, 1);
|
||||
Rect own{};
|
||||
if (reinterpret_cast<GetRect_t>(Offsets::FUN_REGION_GET_RECT)(layout, nullptr, &own) == 0)
|
||||
float r[4];
|
||||
if (!Frame::Layout::GetResolvedRect(region, r))
|
||||
return;
|
||||
const Rect own{r[0], r[1], r[2], r[3]};
|
||||
rect.left = own.left < rect.left ? own.left : rect.left;
|
||||
rect.right = own.right > rect.right ? own.right : rect.right;
|
||||
rect.bottom = own.bottom < rect.bottom ? own.bottom : rect.bottom;
|
||||
@@ -117,9 +110,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "frame/Layout.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
@@ -54,10 +55,7 @@ std::unordered_map<void *, Insets> g_insets;
|
||||
using CalcRect_t = int(__thiscall *)(void *layout, float *rect);
|
||||
CalcRect_t g_origCalcRect = nullptr;
|
||||
|
||||
using Invalidate_t = void(__thiscall *)(void *layout, int arg);
|
||||
|
||||
void *LayoutOf(void *frame) { return static_cast<uint8_t *>(frame) + Offsets::OFF_REGION_ANCHOR; }
|
||||
void *FrameOf(void *layout) { return static_cast<uint8_t *>(layout) - Offsets::OFF_REGION_ANCHOR; }
|
||||
|
||||
// A length in the frame's own units (what GetLeft returns) in layout units:
|
||||
// the inverse of Script GetLeft's conversion.
|
||||
@@ -70,7 +68,7 @@ int __fastcall CalcRect_h(void *layout, void * /*edx*/, float *rect) {
|
||||
const uint32_t flags = Game::Read<uint32_t>(layout, Offsets::OFF_LAYOUT_FLAGS);
|
||||
if ((flags & Offsets::LAYOUT_FLAG_CLAMPED) == 0 || g_insets.empty())
|
||||
return g_origCalcRect(layout, rect);
|
||||
void *frame = FrameOf(layout);
|
||||
void *frame = Frame::Layout::OwnerOf(layout);
|
||||
const auto it = g_insets.find(frame);
|
||||
if (it == g_insets.end())
|
||||
return g_origCalcRect(layout, rect);
|
||||
@@ -144,7 +142,7 @@ int __fastcall Script_SetClampRectInsets(void *L) {
|
||||
else
|
||||
g_insets[frame] = in;
|
||||
// Re-lay the frame out, as SetClampedToScreen does after changing the clamp.
|
||||
reinterpret_cast<Invalidate_t>(Offsets::FUN_LAYOUT_RESIZE)(LayoutOf(frame), 0);
|
||||
Frame::Layout::Resize(frame, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -186,9 +184,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -102,8 +102,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+12
-30
@@ -66,10 +66,13 @@
|
||||
#include "Common.h"
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "frame/Layout.h"
|
||||
#include "frame/AlphaGradient.h"
|
||||
#include "frame/ClipChildren.h"
|
||||
#include "frame/Destruction.h"
|
||||
#include "frame/LayerChange.h"
|
||||
#include "frame/ParentChange.h"
|
||||
#include "gx/Device.h"
|
||||
#include "xml/FrameLoad.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -85,14 +88,8 @@ using BatchOp_t = void(__fastcall *)(void *batch);
|
||||
using Callback_t = void(__fastcall *)(void *arg);
|
||||
using AddCallback_t = void(__fastcall *)(void *batch, void *edx, Callback_t fn, void *arg);
|
||||
using SetFrameFlag_t = void(__fastcall *)(void *frame, void *edx, uint32_t mask, int on);
|
||||
using NotifyLayerChanged_t = void(__fastcall *)(void *frame, void *edx, int layer);
|
||||
using HitTest_t = int(__fastcall *)(void *frame, void *edx, const float *point);
|
||||
using Matrix_t = void(__fastcall *)(float *m);
|
||||
using GetViewport_t = void(__fastcall *)(float *minX, float *maxX, float *minY, float *maxY, float *minZ,
|
||||
float *maxZ);
|
||||
using SetViewport_t = void(__stdcall *)(float minX, float maxX, float minY, float maxY, float minZ, float maxZ);
|
||||
using SetupOrtho_t = void(__fastcall *)(const float *rect, const float *point, int unused);
|
||||
using Convert_t = void(__fastcall *)(float *outX, float *outY, float x, float y);
|
||||
|
||||
// A hit rect: {bottom, left, top, right}.
|
||||
enum { BOTTOM, LEFT, TOP, RIGHT };
|
||||
@@ -218,18 +215,11 @@ bool ClipRect(const void *root, float rect[4]) {
|
||||
// The ScrollFrame's draw (0x00787220) with `rect` in place of its hit rect
|
||||
// and `draw(arg)` in place of its scroll child's.
|
||||
void DrawInside(float rect[4], Callback_t draw, void *arg) {
|
||||
float projection[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
|
||||
float view[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
|
||||
reinterpret_cast<Matrix_t>(Offsets::FUN_GX_GET_PROJECTION)(projection);
|
||||
reinterpret_cast<Matrix_t>(Offsets::FUN_GX_GET_VIEW)(view);
|
||||
float vpMinX, vpMaxX, vpMinY, vpMaxY, vpMinZ, vpMaxZ;
|
||||
reinterpret_cast<GetViewport_t>(Offsets::FUN_GX_GET_VIEWPORT)(&vpMinX, &vpMaxX, &vpMinY, &vpMaxY, &vpMinZ,
|
||||
&vpMaxZ);
|
||||
|
||||
const auto toDdc = reinterpret_cast<Convert_t>(Offsets::FUN_COORD_NDC_TO_DDC);
|
||||
const Gx::SavedTransforms saved;
|
||||
const Gx::Viewport &vp = saved.SavedViewport();
|
||||
float minX = 0, minY = 0, maxX = 0, maxY = 0;
|
||||
toDdc(&minX, &minY, vpMinX, vpMinY);
|
||||
toDdc(&maxX, &maxY, vpMaxX, vpMaxY);
|
||||
Frame::Layout::NdcToDdc(&minX, &minY, vp.minX, vp.minY);
|
||||
Frame::Layout::NdcToDdc(&maxX, &maxY, vp.maxX, vp.maxY);
|
||||
if (minX > rect[LEFT])
|
||||
rect[LEFT] = minX;
|
||||
if (maxX < rect[RIGHT])
|
||||
@@ -241,9 +231,8 @@ void DrawInside(float rect[4], Callback_t draw, void *arg) {
|
||||
|
||||
const float point[2] = {0, 0};
|
||||
reinterpret_cast<SetupOrtho_t>(Offsets::FUN_SETUP_ORTHO_TRANSFORMS)(rect, point, 0);
|
||||
const auto toNdc = reinterpret_cast<Convert_t>(Offsets::FUN_COORD_DDC_TO_NDC);
|
||||
toNdc(&rect[LEFT], &rect[BOTTOM], rect[LEFT], rect[BOTTOM]);
|
||||
toNdc(&rect[RIGHT], &rect[TOP], rect[RIGHT], rect[TOP]);
|
||||
Frame::Layout::DdcToNdc(&rect[LEFT], &rect[BOTTOM], rect[LEFT], rect[BOTTOM]);
|
||||
Frame::Layout::DdcToNdc(&rect[RIGHT], &rect[TOP], rect[RIGHT], rect[TOP]);
|
||||
if (rect[LEFT] < 0.0f) {
|
||||
rect[RIGHT] -= rect[LEFT];
|
||||
rect[LEFT] = 0.0f;
|
||||
@@ -261,14 +250,9 @@ void DrawInside(float rect[4], Callback_t draw, void *arg) {
|
||||
rect[TOP] = 1.0f;
|
||||
}
|
||||
if (rect[LEFT] < rect[RIGHT] && rect[BOTTOM] < rect[TOP]) {
|
||||
reinterpret_cast<SetViewport_t>(Offsets::FUN_GX_SET_VIEWPORT)(rect[LEFT], rect[RIGHT], rect[BOTTOM],
|
||||
rect[TOP], 0.0f, 1.0f);
|
||||
Gx::SetViewport({rect[LEFT], rect[RIGHT], rect[BOTTOM], rect[TOP], 0.0f, 1.0f});
|
||||
draw(arg);
|
||||
}
|
||||
|
||||
reinterpret_cast<Matrix_t>(Offsets::FUN_GX_SET_PROJECTION)(projection);
|
||||
reinterpret_cast<Matrix_t>(Offsets::FUN_GX_SET_VIEW)(view);
|
||||
reinterpret_cast<SetViewport_t>(Offsets::FUN_GX_SET_VIEWPORT)(vpMinX, vpMaxX, vpMinY, vpMaxY, vpMinZ, vpMaxZ);
|
||||
}
|
||||
|
||||
void __fastcall DrawClipped(void *arg) {
|
||||
@@ -345,7 +329,7 @@ const Game::HookAutoRegister _hitTestHook{Offsets::FUN_FRAME_HIT_TEST, reinterpr
|
||||
|
||||
void MarkLayers(void *frame) {
|
||||
for (int layer = 0; layer < Offsets::FRAME_LAYER_COUNT; ++layer)
|
||||
reinterpret_cast<NotifyLayerChanged_t>(Offsets::FUN_FRAME_NOTIFY_LAYER_CHANGED)(frame, nullptr, layer);
|
||||
Frame::LayerChange::NotifyFrame(frame, layer);
|
||||
}
|
||||
|
||||
// Marks every layer of each frame under `frame` changed.
|
||||
@@ -453,9 +437,7 @@ void RegisterLuaFunctions() {
|
||||
Common::ReplaceWithCall(Offsets::CALL_FRAME_DRAW_SELF_CHILD, kDrawChildCall, sizeof kDrawChildCall,
|
||||
reinterpret_cast<const void *>(&DrawChildThunk));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+56
-167
@@ -83,9 +83,12 @@
|
||||
#include "api/Documentation.h"
|
||||
#include "cvar/Factory.h"
|
||||
#include "frame/ExtraScripts.h"
|
||||
#include "frame/LayerChange.h"
|
||||
#include "frame/Layout.h"
|
||||
#include "frame/Region.h"
|
||||
#include "frame/RenderBatch.h"
|
||||
#include "frame/ScriptArgs.h"
|
||||
#include "frame/Subclass.h"
|
||||
#include "text/NumericFormatter.h"
|
||||
#include "texture/Load.h"
|
||||
#include "time/Clock.h"
|
||||
@@ -185,9 +188,7 @@ struct Rect {
|
||||
};
|
||||
const Rect &RectOf(void *frame) { return *Game::Ptr<Rect>(frame, Offsets::OFF_REGION_RECT); }
|
||||
|
||||
void *FrameOfLayout(void *layout) {
|
||||
return static_cast<uint8_t *>(layout) - Offsets::OFF_FRAME_LAYOUT_SUBOBJECT;
|
||||
}
|
||||
using Frame::Layout::OwnerOf;
|
||||
|
||||
// The alpha the frame draws with — 4.3.4's m_alpha * m_parentAlpha / 255.
|
||||
// 1.12's alpha setter copies one byte down the tree instead of multiplying.
|
||||
@@ -224,28 +225,29 @@ uint32_t ColorByte(double c) {
|
||||
return static_cast<uint32_t>(c * 255.0 + 0.5);
|
||||
}
|
||||
|
||||
// ---- vtables ----------------------------------------------------------------
|
||||
// ---- the class ----------------------------------------------------------------
|
||||
|
||||
// An MSVC vtable with its RTTI locator in front, as the engine's are laid out.
|
||||
template <size_t N>
|
||||
struct VTable {
|
||||
const void *locator;
|
||||
const void *slots[N];
|
||||
};
|
||||
|
||||
VTable<Offsets::VTBL_SIMPLEFRAME_SLOTS> g_vtable;
|
||||
VTable<Offsets::VTBL_SIMPLEFRAME_LAYOUT_SLOTS> g_layoutVtable;
|
||||
|
||||
// CSimpleFrame's own slot, for the base call of an override.
|
||||
template <typename Fn>
|
||||
Fn Base(uintptr_t slot) {
|
||||
return Game::Read<Fn>(reinterpret_cast<const void *>(Offsets::VTBL_SIMPLEFRAME), slot);
|
||||
}
|
||||
template <typename Fn>
|
||||
Fn LayoutBase(uintptr_t slot) {
|
||||
return Game::Read<Fn>(reinterpret_cast<const void *>(Offsets::VTBL_SIMPLEFRAME_LAYOUT), slot);
|
||||
// CGCooldown::~CGCooldown, before CSimpleFrame's deleting destructor.
|
||||
void Destroy(void *frame) {
|
||||
Fields &f = F(frame);
|
||||
for (Slot &s : f.slots) {
|
||||
Texture::ReleaseHandle(s.texture);
|
||||
s.texture = nullptr;
|
||||
}
|
||||
Text::NumericFormatter::Release(f.countdownFormatter);
|
||||
f.countdownFormatter = nullptr;
|
||||
delete f.countdownText;
|
||||
f.countdownText = nullptr;
|
||||
}
|
||||
|
||||
Frame::Subclass::Class<Offsets::VTBL_SIMPLEFRAME_SLOTS, Offsets::VTBL_SIMPLEFRAME_LAYOUT_SLOTS>
|
||||
g_class{"Cooldown",
|
||||
Offsets::VTBL_SIMPLEFRAME,
|
||||
Offsets::VTBL_SIMPLEFRAME_LAYOUT,
|
||||
Offsets::FUN_SIMPLEFRAME_CONSTRUCTOR,
|
||||
kObjectSize,
|
||||
&Destroy};
|
||||
|
||||
// 1.12's CSimpleFrame::Show / Hide, as Script Show / Hide make them.
|
||||
void Show(void *frame) {
|
||||
using Slot_t = void(__fastcall *)(void *self, void *edx);
|
||||
@@ -259,10 +261,7 @@ void Hide(void *frame) {
|
||||
Game::Read<Slot_t>(Game::Read<void *>(frame, 0), Offsets::VTBL_FRAME_HIDE)(frame, nullptr);
|
||||
}
|
||||
|
||||
void NotifyLayerChanged(void *frame, int layer) {
|
||||
using Fn_t = void(__fastcall *)(void *self, void *edx, int layer);
|
||||
reinterpret_cast<Fn_t>(Offsets::FUN_FRAME_NOTIFY_LAYER_CHANGED)(frame, nullptr, layer);
|
||||
}
|
||||
using Frame::LayerChange::NotifyFrame;
|
||||
|
||||
// ---- textures ---------------------------------------------------------------
|
||||
|
||||
@@ -371,8 +370,6 @@ void MapStarTexCoords(Fields &f, const Rect &r) {
|
||||
void AnimateVerts(void *frame) {
|
||||
Fields &f = F(frame);
|
||||
const Rect &r = RectOf(frame);
|
||||
const float cx = (r.minX + r.maxX) * 0.5f;
|
||||
const float cy = (r.minY + r.maxY) * 0.5f;
|
||||
|
||||
if (!f.flashing) {
|
||||
const float elapsed = static_cast<float>(f.elapsed);
|
||||
@@ -482,7 +479,7 @@ void SetSlotTexture(void *frame, TextureSlot slot, const char *file, const uint3
|
||||
if (base != nullptr)
|
||||
s.base = *base;
|
||||
f.texturesSet |= 1u << slot;
|
||||
NotifyLayerChanged(frame, kSlotLayer[slot]);
|
||||
NotifyFrame(frame, kSlotLayer[slot]);
|
||||
UpdateColors(frame);
|
||||
}
|
||||
|
||||
@@ -633,25 +630,12 @@ void UpdateCountdown(void *frame) {
|
||||
|
||||
// ---- the overrides ------------------------------------------------------------
|
||||
|
||||
using DeletingDestructor_t = void *(__fastcall *)(void *self, void *edx, uint32_t flags);
|
||||
using GetScriptMethod_t = int(__fastcall *)(void *self, void *edx, void *L, const char *name);
|
||||
using IsA_t = bool(__fastcall *)(void *self, void *edx, int typeId);
|
||||
using IsObjectType_t = bool(__fastcall *)(void *self, void *edx, const char *name);
|
||||
using SetFrameAlpha_t = void(__fastcall *)(void *self, void *edx, uint8_t alpha);
|
||||
using OnFrameUpdate_t = void(__fastcall *)(void *self, void *edx, float elapsedSec);
|
||||
using EmitLayer_t = void(__fastcall *)(void *self, void *edx, void *batch, int layer);
|
||||
using Frame::Subclass::EmitLayer_t;
|
||||
using Frame::Subclass::LoadXML_t;
|
||||
using Frame::Subclass::OnFrameUpdate_t;
|
||||
using Frame::Subclass::SetFrameAlpha_t;
|
||||
using OnFrameSizeChanged_t = void(__fastcall *)(void *layout, void *edx, const float *oldRect);
|
||||
using LoadXML_t = uintptr_t(__fastcall *)(void *layout, void *edx, const void *node,
|
||||
void *status);
|
||||
using PushMethod_t = int(__fastcall *)(void *L, const char *name, void *hash);
|
||||
|
||||
// The class's method hash and type id (Offsets.h "Per-class method hashes").
|
||||
uint32_t g_hash[Offsets::METHOD_HASH_SIZE / 4];
|
||||
int g_typeId = 0;
|
||||
|
||||
uintptr_t TypeIdVar() { return reinterpret_cast<uintptr_t>(&g_typeId); }
|
||||
|
||||
const Frame::ExtraScripts::Script g_onCooldownDone{"oncooldowndone", TypeIdVar()};
|
||||
const Frame::ExtraScripts::Script g_onCooldownDone{"oncooldowndone", g_class.TypeIdVar()};
|
||||
|
||||
// From the update walk, where the engine runs OnUpdate.
|
||||
void RunOnCooldownDone(void *frame) {
|
||||
@@ -664,52 +648,15 @@ void RunOnCooldownDone(void *frame) {
|
||||
Frame::ScriptArgs::RunSlotWithValues(L, slot, frame, Game::Lua::GetTop(L) + 1, 0);
|
||||
}
|
||||
|
||||
// CGCooldown::~CGCooldown, then CSimpleFrame's deleting destructor.
|
||||
void *__fastcall DeletingDestructor(void *self, void *, uint32_t flags) {
|
||||
Fields &f = F(self);
|
||||
for (Slot &s : f.slots) {
|
||||
Texture::ReleaseHandle(s.texture);
|
||||
s.texture = nullptr;
|
||||
}
|
||||
Text::NumericFormatter::Release(f.countdownFormatter);
|
||||
f.countdownFormatter = nullptr;
|
||||
delete f.countdownText;
|
||||
f.countdownText = nullptr;
|
||||
return Base<DeletingDestructor_t>(Offsets::VTBL_FRAME_DELETING_DESTRUCTOR)(self, nullptr,
|
||||
flags);
|
||||
}
|
||||
|
||||
void *__fastcall LayoutDeletingDestructor(void *layout, void *, uint32_t flags) {
|
||||
return DeletingDestructor(FrameOfLayout(layout), nullptr, flags);
|
||||
}
|
||||
|
||||
int __fastcall GetScriptMethod(void *self, void *, void *L, const char *name) {
|
||||
if (reinterpret_cast<PushMethod_t>(Offsets::FUN_FRAMESCRIPT_PUSH_METHOD)(L, name, g_hash))
|
||||
return 1;
|
||||
return Base<GetScriptMethod_t>(Offsets::VTBL_FRAME_GET_SCRIPT_METHOD)(self, nullptr, L, name);
|
||||
}
|
||||
|
||||
bool __fastcall IsA(void *self, void *, int typeId) {
|
||||
return (g_typeId != 0 && typeId == g_typeId) ||
|
||||
Base<IsA_t>(Offsets::VTBL_FRAME_IS_A)(self, nullptr, typeId);
|
||||
}
|
||||
|
||||
bool __fastcall IsObjectType(void *self, void *, const char *name) {
|
||||
return (name != nullptr && _stricmp(name, "Cooldown") == 0) ||
|
||||
Base<IsObjectType_t>(Offsets::VTBL_FRAME_IS_OBJECT_TYPE)(self, nullptr, name);
|
||||
}
|
||||
|
||||
const char *__fastcall GetObjectType(void *, void *) { return "Cooldown"; }
|
||||
|
||||
// OFFSET (4.3.4): 0x009FF570 (UpdateAlpha)
|
||||
void __fastcall SetFrameAlpha(void *self, void *, uint8_t alpha) {
|
||||
Base<SetFrameAlpha_t>(Offsets::VTBL_FRAME_SET_FRAME_ALPHA)(self, nullptr, alpha);
|
||||
g_class.Base<SetFrameAlpha_t>(Offsets::VTBL_FRAME_SET_FRAME_ALPHA)(self, nullptr, alpha);
|
||||
UpdateColors(self);
|
||||
}
|
||||
|
||||
// OFFSET (4.3.4): 0x009FF5A0 (OnLayerUpdate)
|
||||
void __fastcall OnFrameUpdate(void *self, void *, float elapsedSec) {
|
||||
Base<OnFrameUpdate_t>(Offsets::VTBL_FRAME_ON_FRAME_UPDATE)(self, nullptr, elapsedSec);
|
||||
g_class.Base<OnFrameUpdate_t>(Offsets::VTBL_FRAME_ON_FRAME_UPDATE)(self, nullptr, elapsedSec);
|
||||
Fields &f = F(self);
|
||||
// 4.3.4: `now <= start ? 0 : now - start`, compared wrap-safe. A paused
|
||||
// frame reads the time it was paused at.
|
||||
@@ -757,7 +704,7 @@ void __fastcall OnFrameUpdate(void *self, void *, float elapsedSec) {
|
||||
|
||||
// OFFSET (4.3.4): 0x009FE830 (OnFrameRender)
|
||||
void __fastcall EmitLayer(void *self, void *, void *batch, int layer) {
|
||||
Base<EmitLayer_t>(Offsets::VTBL_FRAME_EMIT_LAYER)(self, nullptr, batch, layer);
|
||||
g_class.Base<EmitLayer_t>(Offsets::VTBL_FRAME_EMIT_LAYER)(self, nullptr, batch, layer);
|
||||
Fields &f = F(self);
|
||||
using RenderBatch::QueueStrip;
|
||||
void *swipe = f.slots[SLOT_SWIPE].texture;
|
||||
@@ -777,9 +724,9 @@ void __fastcall EmitLayer(void *self, void *, void *batch, int layer) {
|
||||
|
||||
// OFFSET (4.3.4): 0x009FF580
|
||||
void __fastcall OnFrameSizeChanged(void *layout, void *, const float *oldRect) {
|
||||
LayoutBase<OnFrameSizeChanged_t>(Offsets::VTBL_LAYOUT_ON_FRAME_SIZE_CHANGED)(layout, nullptr,
|
||||
g_class.LayoutBase<OnFrameSizeChanged_t>(Offsets::VTBL_LAYOUT_ON_FRAME_SIZE_CHANGED)(layout, nullptr,
|
||||
oldRect);
|
||||
ResetVerts(FrameOfLayout(layout));
|
||||
ResetVerts(OwnerOf(layout));
|
||||
}
|
||||
|
||||
// The stock CooldownFrameTemplate: a Model template (Cooldown.xml, 1.12).
|
||||
@@ -800,16 +747,15 @@ void LoadModernTemplate(void *layout) {
|
||||
nullptr);
|
||||
reinterpret_cast<SetAllPoints_t>(Offsets::FUN_LAYOUT_SET_ALL_POINTS)(layout, nullptr, parent,
|
||||
1);
|
||||
Hide(FrameOfLayout(layout));
|
||||
Hide(OwnerOf(layout));
|
||||
}
|
||||
|
||||
// A texture element's <Color> child into `color`, read as CSimpleTexture::
|
||||
// LoadXML reads one (it passes its load status too); false when it has none.
|
||||
bool XmlTextureColor(const void *node, void *status, uint32_t &color) {
|
||||
using ReadColor_t = int(__fastcall *)(const void *node, uint32_t *bgra, void *status);
|
||||
for (const void *c = Xml::FirstChild(node); c != nullptr; c = Xml::NextSibling(c)) {
|
||||
if (Xml::TagIs(c, "Color")) {
|
||||
reinterpret_cast<ReadColor_t>(Offsets::FUN_XML_READ_COLOR)(c, &color, status);
|
||||
Xml::ReadColor(c, color, status);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -826,10 +772,10 @@ uintptr_t __fastcall LoadXML(void *layout, void *, const void *node, void *statu
|
||||
return 0;
|
||||
}
|
||||
const uintptr_t result =
|
||||
LayoutBase<LoadXML_t>(Offsets::VTBL_XML_NODE_LOADER)(layout, nullptr, node, status);
|
||||
g_class.LayoutBase<LoadXML_t>(Offsets::VTBL_XML_NODE_LOADER)(layout, nullptr, node, status);
|
||||
if (node == nullptr)
|
||||
return result;
|
||||
Fields &f = F(FrameOfLayout(layout));
|
||||
Fields &f = F(OwnerOf(layout));
|
||||
if (const char *reverse = Xml::Attr(node, "reverse"))
|
||||
f.reverse = Game::StringToFlag(reverse);
|
||||
if (const char *drawEdge = Xml::Attr(node, "drawEdge"))
|
||||
@@ -853,7 +799,7 @@ uintptr_t __fastcall LoadXML(void *layout, void *, const void *node, void *statu
|
||||
if (!Xml::TagIs(c, kSlotTag[slot]))
|
||||
continue;
|
||||
uint32_t color = 0;
|
||||
SetSlotTexture(FrameOfLayout(layout), static_cast<TextureSlot>(slot),
|
||||
SetSlotTexture(OwnerOf(layout), static_cast<TextureSlot>(slot),
|
||||
Xml::Attr(c, "file"),
|
||||
XmlTextureColor(c, status, color) ? &color : nullptr);
|
||||
}
|
||||
@@ -862,44 +808,23 @@ uintptr_t __fastcall LoadXML(void *layout, void *, const void *node, void *statu
|
||||
}
|
||||
|
||||
void BuildVTables() {
|
||||
if (g_vtable.slots[0] != nullptr)
|
||||
if (!Frame::Subclass::BuildVTables<g_class>())
|
||||
return;
|
||||
std::memcpy(&g_vtable, reinterpret_cast<const void *>(Offsets::VTBL_SIMPLEFRAME - 4),
|
||||
sizeof(g_vtable));
|
||||
std::memcpy(&g_layoutVtable,
|
||||
reinterpret_cast<const void *>(Offsets::VTBL_SIMPLEFRAME_LAYOUT - 4),
|
||||
sizeof(g_layoutVtable));
|
||||
auto set = [](const void **slots, uintptr_t slot, const void *fn) { slots[slot / 4] = fn; };
|
||||
const void **v = g_vtable.slots;
|
||||
set(v, Offsets::VTBL_FRAME_DELETING_DESTRUCTOR, reinterpret_cast<const void *>(&DeletingDestructor));
|
||||
set(v, Offsets::VTBL_FRAME_GET_SCRIPT_METHOD, reinterpret_cast<const void *>(&GetScriptMethod));
|
||||
set(v, Offsets::VTBL_FRAME_IS_A, reinterpret_cast<const void *>(&IsA));
|
||||
set(v, Offsets::VTBL_FRAME_IS_OBJECT_TYPE, reinterpret_cast<const void *>(&IsObjectType));
|
||||
set(v, Offsets::VTBL_FRAME_GET_OBJECT_TYPE, reinterpret_cast<const void *>(&GetObjectType));
|
||||
set(v, Offsets::VTBL_FRAME_SET_FRAME_ALPHA, reinterpret_cast<const void *>(&SetFrameAlpha));
|
||||
set(v, Offsets::VTBL_FRAME_ON_FRAME_UPDATE, reinterpret_cast<const void *>(&OnFrameUpdate));
|
||||
set(v, Offsets::VTBL_FRAME_EMIT_LAYER, reinterpret_cast<const void *>(&EmitLayer));
|
||||
const void **l = g_layoutVtable.slots;
|
||||
set(l, Offsets::VTBL_LAYOUT_ON_FRAME_SIZE_CHANGED,
|
||||
reinterpret_cast<const void *>(&OnFrameSizeChanged));
|
||||
set(l, Offsets::VTBL_LAYOUT_DELETING_DESTRUCTOR,
|
||||
reinterpret_cast<const void *>(&LayoutDeletingDestructor));
|
||||
set(l, Offsets::VTBL_XML_NODE_LOADER, reinterpret_cast<const void *>(&LoadXML));
|
||||
auto fn = [](auto f) { return reinterpret_cast<const void *>(f); };
|
||||
g_class.vtable.Set(Offsets::VTBL_FRAME_SET_FRAME_ALPHA, fn(&SetFrameAlpha));
|
||||
g_class.vtable.Set(Offsets::VTBL_FRAME_ON_FRAME_UPDATE, fn(&OnFrameUpdate));
|
||||
g_class.vtable.Set(Offsets::VTBL_FRAME_EMIT_LAYER, fn(&EmitLayer));
|
||||
g_class.layoutVtable.Set(Offsets::VTBL_LAYOUT_ON_FRAME_SIZE_CHANGED, fn(&OnFrameSizeChanged));
|
||||
g_class.layoutVtable.Set(Offsets::VTBL_XML_NODE_LOADER, fn(&LoadXML));
|
||||
}
|
||||
|
||||
// OFFSET (4.3.4): 0x008C6390 (CGCooldown::Create) — the "Cooldown" factory.
|
||||
void *__fastcall CreateCooldown(void *parent) {
|
||||
using SMemAlloc_t = void *(__stdcall *)(size_t size, const char *file, int line, int flags);
|
||||
using Constructor_t = void(__fastcall *)(void *self, void *edx, void *parent);
|
||||
BuildVTables();
|
||||
// Zero-filled: 4.3.4's constructor zeroes every CGCooldown field.
|
||||
void *frame = reinterpret_cast<SMemAlloc_t>(Offsets::FUN_STORM_SMEM_ALLOC)(
|
||||
kObjectSize, __FILE__, __LINE__, 0x08);
|
||||
void *frame = Frame::Subclass::New<g_class>(parent);
|
||||
if (frame == nullptr)
|
||||
return nullptr;
|
||||
reinterpret_cast<Constructor_t>(Offsets::FUN_SIMPLEFRAME_CONSTRUCTOR)(frame, nullptr, parent);
|
||||
Game::Ref<const void *>(frame, 0) = g_vtable.slots;
|
||||
Game::Ref<const void *>(frame, Offsets::OFF_FRAME_LAYOUT_SUBOBJECT) = g_layoutVtable.slots;
|
||||
Fields &f = F(frame);
|
||||
f.modRate = 1.0f;
|
||||
f.drawSwipe = 1;
|
||||
@@ -917,7 +842,7 @@ void *__fastcall CreateCooldown(void *parent) {
|
||||
|
||||
// ---- the methods (CGCooldown_Scripts.cpp, then the later clients') -------------------
|
||||
|
||||
void *ResolveCooldown(void *L) { return Game::Lua::ResolveTypedObject(L, 1, TypeIdVar()); }
|
||||
void *ResolveCooldown(void *L) { return Game::Lua::ResolveTypedObject(L, 1, g_class.TypeIdVar()); }
|
||||
|
||||
// Seconds to ms on the GetTime() epoch, wrapping like the tick does.
|
||||
uint32_t Ms(double seconds) { return static_cast<uint32_t>(static_cast<int64_t>(seconds * 1000.0)); }
|
||||
@@ -1046,7 +971,7 @@ int __fastcall Script_SetDrawSwipe(void *L) {
|
||||
const uint32_t drawSwipe = Game::Lua::ToFlag(L, 2, true);
|
||||
if (drawSwipe != f.drawSwipe) {
|
||||
f.drawSwipe = drawSwipe;
|
||||
NotifyLayerChanged(frame, kSlotLayer[SLOT_SWIPE]);
|
||||
NotifyFrame(frame, kSlotLayer[SLOT_SWIPE]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1308,7 +1233,7 @@ int __fastcall Script_SetDrawEdge(void *L) {
|
||||
const uint32_t drawEdge = Game::Lua::ToFlag(L, 2, true);
|
||||
if (drawEdge != f.drawEdge) {
|
||||
f.drawEdge = drawEdge;
|
||||
NotifyLayerChanged(frame, kSlotLayer[SLOT_EDGE]);
|
||||
NotifyFrame(frame, kSlotLayer[SLOT_EDGE]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1561,57 +1486,21 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
{"GetCountdownFormatter", &kGetCountdownFormatter},
|
||||
};
|
||||
|
||||
// The engine's static initializer for a class hash (Model's 0x0076F5F0).
|
||||
void InitMethodHash() {
|
||||
if (g_hash[0] != 0)
|
||||
return;
|
||||
g_hash[0] = Offsets::VTBL_METHOD_HASH_ALLOCATOR;
|
||||
g_hash[1] = 0xC; // node link offset
|
||||
g_hash[2] = reinterpret_cast<uintptr_t>(&g_hash[2]);
|
||||
g_hash[3] = reinterpret_cast<uintptr_t>(&g_hash[2]) | 1;
|
||||
g_hash[9] = 0xFFFFFFFF; // no buckets yet
|
||||
}
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
using RegisterFactory_t = int(__fastcall *)(const char *type, void *(__fastcall *)(void *),
|
||||
int unique);
|
||||
// The engine's frame-type table outlives the Lua state, so on a /reload
|
||||
// the name is already there and the registrar keeps the first one. This
|
||||
// pass runs before FrameXML loads (CGGameUI::Initialize calls
|
||||
// This pass runs before FrameXML loads (CGGameUI::Initialize calls
|
||||
// LoadScriptFunctions first), like the engine's own game-UI list
|
||||
// FUN_00495940, where 4.3.4 registers "Cooldown".
|
||||
InitMethodHash();
|
||||
g_countdownForCooldowns = CVar::Factory::Register(
|
||||
"countdownForCooldowns",
|
||||
"Whether to use number countdown instead of radial swipe for action button cooldowns or not.",
|
||||
Offsets::CVAR_CATEGORY_GAME, "0");
|
||||
reinterpret_cast<RegisterFactory_t>(Offsets::FUN_FRAMEXML_REGISTER_FACTORY)(
|
||||
"Cooldown", &CreateCooldown, 0);
|
||||
Api::Documentation::NameWidgetSystem(g_hash, "FrameAPICooldown");
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
g_hash, g_methods, static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])),
|
||||
g_methodDocs, static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
Frame::Subclass::RegisterFactory(g_class.name, &CreateCooldown);
|
||||
Api::Documentation::NameWidgetSystem(g_class.hash.Registry(), "FrameAPICooldown");
|
||||
Game::Lua::RegisterFrameMethods(g_class.hash.Registry(), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
// The hash's functions are references into the game Lua state, so they are
|
||||
// released with the engine's own class hashes, at the end of the game UI's
|
||||
// unregister pass while that state is still open.
|
||||
using UnregisterScriptFunctions_t = void(__cdecl *)();
|
||||
using UnregisterMethods_t = void(__fastcall *)(void *hash);
|
||||
UnregisterScriptFunctions_t UnregisterScriptFunctions_o = nullptr;
|
||||
|
||||
void __cdecl UnregisterScriptFunctions_h() {
|
||||
UnregisterScriptFunctions_o();
|
||||
reinterpret_cast<UnregisterMethods_t>(Offsets::FUN_FRAMESCRIPT_UNREGISTER_METHODS)(g_hash);
|
||||
}
|
||||
|
||||
const Game::HookAutoRegister _unregisterHook{
|
||||
Offsets::FUN_GAMEUI_UNREGISTER_SCRIPT_FUNCTIONS,
|
||||
reinterpret_cast<void *>(&UnregisterScriptFunctions_h),
|
||||
reinterpret_cast<void **>(&UnregisterScriptFunctions_o)};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Frame::Cooldown
|
||||
|
||||
@@ -53,9 +53,6 @@ using SetAttr_t = void(__thiscall *)(void *node, const char *name, const char *v
|
||||
using Build_t = void *(__fastcall *)(const void *node, void *parent, void *status);
|
||||
using StatusReset_t = void(__thiscall *)(void *self);
|
||||
|
||||
// LUA_TNONE: lua_type of an index past the top.
|
||||
constexpr int kTypeNone = -1;
|
||||
|
||||
// 4.3.4's usage line; vanilla's (0x00872F60) has no `id`.
|
||||
constexpr const char *kUsage =
|
||||
"Usage: CreateFrame(\"frameType\" [, \"name\"] [, parent] [, \"template\"] [, id])";
|
||||
@@ -102,7 +99,7 @@ int __fastcall CreateFrame_h(void *L) {
|
||||
|
||||
const int parentType = Type(L, 3);
|
||||
if (!IsString(L, 1) ||
|
||||
(parentType != kTypeNone && parentType != TYPE_NIL && parentType != TYPE_TABLE)) {
|
||||
(parentType != TYPE_NONE && parentType != TYPE_NIL && parentType != TYPE_TABLE)) {
|
||||
Error(L, kUsage);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -111,9 +111,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -221,9 +221,8 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -67,9 +67,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -113,9 +113,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -108,10 +108,11 @@ const Game::Doc::Method kFontStringDocs[] = {{"SetFormattedText", &kFontStringDo
|
||||
const Game::Doc::Method kButtonDocs[] = {{"SetFormattedText", &kButtonDoc}};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY),
|
||||
kFontStringMethods, 1, kFontStringDocs, 1);
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kButtonMethods,
|
||||
1, kButtonDocs, 1);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kFontStringMethods,
|
||||
kFontStringDocs);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kButtonMethods, kButtonDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -198,8 +198,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -95,8 +95,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
void RegisterLuaFunctions() {
|
||||
for (const uintptr_t registry : {static_cast<uintptr_t>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY),
|
||||
static_cast<uintptr_t>(Offsets::VAR_SIMPLEHTML_METHOD_REGISTRY)}) {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), g_methods);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,9 +139,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
// The engine's two draw-layer invalidates: what to call after a change to
|
||||
// what a frame or a region draws, so the next batch rebuild picks it up.
|
||||
|
||||
#include "Offsets.h"
|
||||
|
||||
namespace Frame::LayerChange {
|
||||
|
||||
// CSimpleFrame::NotifyLayerChanged (FUN_FRAME_NOTIFY_LAYER_CHANGED): one of
|
||||
// the frame's own draw layers.
|
||||
inline void NotifyFrame(void *frame, int layer) {
|
||||
using Fn_t = void(__fastcall *)(void *frame, void *edx, int layer);
|
||||
reinterpret_cast<Fn_t>(Offsets::FUN_FRAME_NOTIFY_LAYER_CHANGED)(frame, nullptr, layer);
|
||||
}
|
||||
|
||||
// CScriptRegion::NotifyLayerChanged (FUN_REGION_NOTIFY_LAYER_CHANGED): the
|
||||
// layer the region draws in.
|
||||
inline void NotifyRegion(void *region) {
|
||||
using Fn_t = void(__fastcall *)(void *region);
|
||||
reinterpret_cast<Fn_t>(Offsets::FUN_REGION_NOTIFY_LAYER_CHANGED)(region);
|
||||
}
|
||||
|
||||
} // namespace Frame::LayerChange
|
||||
@@ -0,0 +1,82 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
// The layout sub-object every region and frame has (4.3.4's CLayoutFrame, at
|
||||
// OFF_REGION_ANCHOR, which is OFF_FRAME_LAYOUT_SUBOBJECT for a frame) and the
|
||||
// engine calls on it. Each call here takes the region or frame itself.
|
||||
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Frame::Layout {
|
||||
|
||||
static_assert(Offsets::OFF_REGION_ANCHOR == Offsets::OFF_FRAME_LAYOUT_SUBOBJECT,
|
||||
"a frame's layout sub-object is where a region's is");
|
||||
|
||||
// The layout sub-object: `this` for SetPoint and the calls below.
|
||||
inline void *Of(const void *object) {
|
||||
return const_cast<uint8_t *>(static_cast<const uint8_t *>(object) + Offsets::OFF_REGION_ANCHOR);
|
||||
}
|
||||
|
||||
// Layout units (DDC, the space rects are stored in) to the device's 0..1
|
||||
// (NDC) and back: each axis scaled by the screen's (VAR_COORD_DDC_*_SCALE).
|
||||
// Either out pointer may be null, and may alias its input.
|
||||
inline void DdcToNdc(float *outX, float *outY, float x, float y) {
|
||||
using Fn = void(__fastcall *)(float *, float *, float, float);
|
||||
reinterpret_cast<Fn>(Offsets::FUN_COORD_DDC_TO_NDC)(outX, outY, x, y);
|
||||
}
|
||||
inline void NdcToDdc(float *outX, float *outY, float x, float y) {
|
||||
using Fn = void(__fastcall *)(float *, float *, float, float);
|
||||
reinterpret_cast<Fn>(Offsets::FUN_COORD_NDC_TO_DDC)(outX, outY, x, y);
|
||||
}
|
||||
|
||||
// The region or frame a layout sub-object belongs to, for the layout
|
||||
// vtable's overrides and hooks.
|
||||
inline void *OwnerOf(void *layout) {
|
||||
return static_cast<uint8_t *>(layout) - Offsets::OFF_REGION_ANCHOR;
|
||||
}
|
||||
|
||||
// CLayoutFrame::GetRect (FUN_REGION_GET_RECT): {bottom, left, top, right} in
|
||||
// layout units. False while the object has no valid rect.
|
||||
inline bool GetRect(const void *object, float (&rect)[4]) {
|
||||
using Fn = int(__thiscall *)(void *, float *);
|
||||
return reinterpret_cast<Fn>(Offsets::FUN_REGION_GET_RECT)(Of(object), rect) != 0;
|
||||
}
|
||||
|
||||
// IsResizePending (FUN_REGION_LAYOUT_DIRTY): a layout change waits for the
|
||||
// layout pass.
|
||||
inline bool IsResizePending(const void *object) {
|
||||
using Fn = int(__thiscall *)(void *);
|
||||
return reinterpret_cast<Fn>(Offsets::FUN_REGION_LAYOUT_DIRTY)(Of(object)) != 0;
|
||||
}
|
||||
|
||||
// CLayoutFrame::Resize (FUN_LAYOUT_RESIZE): `now` lays the object out at
|
||||
// once; otherwise it is marked to be laid out later, the path SetPoint takes.
|
||||
inline void Resize(void *object, bool now) {
|
||||
using Fn = void(__thiscall *)(void *, int);
|
||||
reinterpret_cast<Fn>(Offsets::FUN_LAYOUT_RESIZE)(Of(object), now ? 1 : 0);
|
||||
}
|
||||
|
||||
// The edge getters' read (Script GetLeft 0x007A1A19): a pending resize run
|
||||
// now, then the rect. A hidden object, which the layout pass skips, gets a
|
||||
// rect this way.
|
||||
inline bool GetResolvedRect(void *object, float (&rect)[4]) {
|
||||
if (IsResizePending(object))
|
||||
Resize(object, true);
|
||||
return GetRect(object, rect);
|
||||
}
|
||||
|
||||
} // namespace Frame::Layout
|
||||
+8
-24
@@ -72,6 +72,7 @@
|
||||
#include "Game.h"
|
||||
#include "HookSecureFunc.h"
|
||||
#include "Offsets.h"
|
||||
#include "frame/Layout.h"
|
||||
#include "frame/ScriptBindings.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -104,13 +105,10 @@ int __fastcall Script_SetSize(void *L) {
|
||||
}
|
||||
|
||||
using LayoutExtent_t = float(__thiscall *)(void *layout);
|
||||
using LayoutDirty_t = int(__fastcall *)(void *layout);
|
||||
using LayoutResize_t = void(__fastcall *)(void *layout, void *edx, int force);
|
||||
using LayoutGetRect_t = int(__fastcall *)(void *layout, void *edx, float *out);
|
||||
|
||||
// The explicit size of `self` in layout `slot`; 0 when none is set.
|
||||
float ExplicitExtent(void *self, int slot) {
|
||||
void *layout = static_cast<uint8_t *>(self) + Offsets::OFF_REGION_ANCHOR;
|
||||
void *layout = Frame::Layout::Of(self);
|
||||
auto *vtable = Game::Read<uintptr_t *>(layout, 0);
|
||||
return reinterpret_cast<LayoutExtent_t>(vtable[slot / sizeof(uintptr_t)])(layout);
|
||||
}
|
||||
@@ -145,13 +143,10 @@ void PushUnscaledExtent(void *L, bool ignoreRect, int slot, bool vertical) {
|
||||
void *self = Game::Lua::ResolveRegion(L);
|
||||
if (self == nullptr)
|
||||
return;
|
||||
void *layout = static_cast<uint8_t *>(self) + Offsets::OFF_REGION_ANCHOR;
|
||||
float extent = ExplicitExtent(self, slot);
|
||||
if (extent == 0.0f && !ignoreRect) {
|
||||
if (reinterpret_cast<LayoutDirty_t>(Offsets::FUN_REGION_LAYOUT_DIRTY)(layout) != 0)
|
||||
reinterpret_cast<LayoutResize_t>(Offsets::FUN_LAYOUT_RESIZE)(layout, nullptr, 1);
|
||||
float rect[4] = {0.0f, 0.0f, 0.0f, 0.0f}; // {bottom, left, top, right}
|
||||
if (reinterpret_cast<LayoutGetRect_t>(Offsets::FUN_REGION_GET_RECT)(layout, nullptr, rect) != 0)
|
||||
if (Frame::Layout::GetResolvedRect(self, rect))
|
||||
extent = (vertical ? rect[2] - rect[0] : rect[3] - rect[1]) /
|
||||
Game::Read<float>(self, Offsets::OFF_REGION_EFFECTIVE_SCALE);
|
||||
}
|
||||
@@ -569,26 +564,15 @@ const Game::Lua::FrameMethodEntry g_fontStringMethods[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY),
|
||||
g_regionMethods,
|
||||
static_cast<int>(sizeof(g_regionMethods) / sizeof(g_regionMethods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_regionMethods);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY),
|
||||
g_frameMethods,
|
||||
static_cast<int>(sizeof(g_frameMethods) / sizeof(g_frameMethods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY),
|
||||
g_buttonMethods,
|
||||
static_cast<int>(sizeof(g_buttonMethods) / sizeof(g_buttonMethods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), g_buttonMethods);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_TEXTURE_METHOD_REGISTRY),
|
||||
g_textureMethods,
|
||||
static_cast<int>(sizeof(g_textureMethods) / sizeof(g_textureMethods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_TEXTURE_METHOD_REGISTRY), g_textureMethods);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY),
|
||||
g_fontStringMethods,
|
||||
static_cast<int>(sizeof(g_fontStringMethods) /
|
||||
sizeof(g_fontStringMethods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), g_fontStringMethods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -380,9 +380,8 @@ void RegisterLuaFunctions() {
|
||||
reinterpret_cast<const void *>(&RemovalThunk));
|
||||
Common::ReplaceWithCall(Offsets::ADDR_UI_FOCUS_PASS_PROBE, kProbe, sizeof kProbe,
|
||||
reinterpret_cast<const void *>(&ProbeThunk));
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
Game::Lua::RegisterGlobalFunction("GetMouseFoci", &Script_GetMouseFoci, &kGetMouseFoci);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user