diff --git a/README.md b/README.md index 553fb19..2379f54 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,40 @@ # SWL-FreeHUD -Free floating ability markers for easier cooldown tracking +Free-floating ability icons for easier cooldown tracking ## Overview +Mirrors the active ability and gadget slots from the ability bar as individual free-floating icons that can be customized using the standard GUI edit mode (lock icon at the right end of the topbar, drag to place, scroll-wheel to resize). Abilities with no possible cooldown are hidden automatically, and the others can be set to only display when on cooldown with `/setoption efdFreeHUDHideReady true`. +Each icon links to one ability slot, layout may need adjustment between builds. ## Installation Any packaged release should be unzipped (including the internal folder) into the appropriate folder and the client restarted. -
TSW: [TSW Directory]\Data\Gui\Customized\Flash.
SWL: [SWL Directory]\Data\Gui\Custom\Flash. -The safest method for upgrading (required for installing) is to have the client closed and delete any existing .bxml files in the Cartographer directory. Hotpatching (using /reloadui) works as long as neither Modules.xml or LoginPrefs.xml have changed. +The safest method for upgrading (required for installing) is to have the client closed and delete any existing .bxml files in the Cartographer directory. Hotpatching (using /reloadui) works as long as none of Modules.xml, LoginPrefs.xml, or CharPrefs.xml have changed. -I intend to permit setting migration from the first public beta to v1.0.x, but this may be subject to change. As with my other mods, this update compatibility window will occasionally be shifted to reduce legacy code clutter. +Upgrading should retain settings as much as possible. All settings are currently saved per character. ## Change Log -Version Initial - +Version 0.0.1-beta ++ Initial release ++ Active ability and gadget icons ++ Customizable layout (on ability slots, one per character) ++ Option to only show running cooldowns (/setoption efdFreeHUDHideReady true) ## Known Issues -This is an early version of this mod. There are many issues, some of them are known. -I'm always open to hearing comments and suggestions as well, easier to start with good ideas than rewrite from bad ones. +This is an early version of this mod, so there's likely to be a few issues discovered: ++ Abilities (mostly weapon gimmicks) may not accurately reflect the disabled state when first loaded ++ Default layout doesn't match the ability bar (Won't fix: whole point is to rearrange them anyway) As always, defect reports, suggestions, and contributions are welcome. They can be sent to Peloprata in SWL (by mail or pm), via the github issues system, or @Peloprata in #modding on discord. ## Wishlist -+ Let GEM mode reveal hidden icons so they can be positioned without swapping slots -+ Option to hide icon when not on cooldown, and to hide cooldown clock -+ Gadget (maybe pot) cooldowns ++ Option to hide when going into cooldown (opposite behaviour to HideReady) ++ Option to hide cooldown clock display ++ Pot cooldowns? + Work with action swap outs (Shotgun ammo, maybe fist/ele? what other weapons behave like this?) + Integrate with build manager and BooBuilds(?) so that icon placement can be customized on a per build basis @@ -37,14 +42,14 @@ As always, defect reports, suggestions, and contributions are welcome. They can Source Repository: https://github.com/Earthfiredrake/SWL-FreeHUD -Curse Mirror: TBD +Curse Mirror: https://www.curseforge.com/swlegends/tswl-mods/freehud ## Building from Source Requires copies of the SWL and Scaleform CLIK APIs. Existing project files are configured for Flash Pro CS5.5. Master/Head is the most recent packaged release. Develop/Head is usually a commit or two behind my current test build. As much as possible I try to avoid regressions or unbuildable commits but new features may be incomplete and unstable and there may be additional debug code that will be removed or disabled prior to release. -Once built, 'FreeHUD.swf' and the contents of 'config' should be copied to the directory 'FreeHUD' in the game's mod directory. '/reloadui' is sufficient to force the game to load an updated swf or mod data file, but changes to the game config files (LoginPrefs.xml and Modules.xml) will require a restart of the client and possible deletion of .bxml caches from the mod directory. +Once built, 'FreeHUD.swf' and the contents of 'config' should be copied to the directory 'FreeHUD' in the game's mod directory. '/reloadui' is sufficient to force the game to load an updated swf or mod data file, but changes to the game config files (*Prefs.xml and Modules.xml) will require a restart of the client and possible deletion of .bxml caches from the mod directory. ## License and Attribution Copyright (c) 2018 Earthfiredrake
@@ -58,9 +63,10 @@ https://github.com/eltorqiro/TSW-UITweaks TSW, SWL, the related API, and most graphics elements are copyright (c) 2012 Funcom GmBH
Used under the terms of the Funcom UI License
+Curseforge icon based off of game assets and: +https://commons.wikimedia.org/wiki/File:Simpleicons_Interface_unlocked-padlock.svg (CC Attribution 3.0) Special Thanks to:
-The TSW modding community for neglecting to properly secure important intel in their faction vaults
+The usual suspects
Shivvies for the idea
Everyone who provided suggestions, testing and feedback
- diff --git a/efd/FreeHUD/FreeHUD.as b/efd/FreeHUD/FreeHUD.as index dad24b3..b7be630 100644 --- a/efd/FreeHUD/FreeHUD.as +++ b/efd/FreeHUD/FreeHUD.as @@ -2,8 +2,13 @@ // Released under the terms of the MIT License // https://github.com/Earthfiredrake/SWL-FreeHUD +import com.GameInterface.DistributedValue; +import com.GameInterface.Game.Character; import com.GameInterface.Game.Shortcut; import com.GameInterface.Game.ShortcutData; +import com.GameInterface.Inventory; +import com.GameInterface.Utils; +import com.Utils.ID32; import efd.FreeHUD.lib.Mod; import efd.FreeHUD.lib.sys.ConfigManager; @@ -14,7 +19,7 @@ class efd.FreeHUD.FreeHUD extends Mod { // Debug flag at top so that commenting out leaves no hanging ',' // Debug : true, Name : "FreeHUD", - Version : "0.0.1.alpha", + Version : "0.0.1.beta", Subsystems : { Config : { Init : ConfigManager.Create @@ -25,7 +30,7 @@ class efd.FreeHUD.FreeHUD extends Mod { // LeftMouseInfo : IconMouse_ToggleConfigWindow, // RightMouseInfo : IconMouse_ToggleUserEnabled } - }, + }, LinkVTIO : { Init : VTIOHelper.Create, InitObj : { @@ -39,16 +44,26 @@ class efd.FreeHUD.FreeHUD extends Mod { public function FreeHUD(hostMovie:MovieClip) { super(GetModInfo(), hostMovie); Config.NewSetting("CooldownLayout", GetDefaultLayout()); + Config.NewSetting("HideReady", false); + + Equipment = new Inventory(new ID32(_global.Enums.InvType.e_Type_GC_WeaponContainer, Character.GetClientCharID().GetInstance())); } private function Activate():Void { if (!CooldownViews) { - CooldownViews = new Object; + CooldownViews = new Array; var layoutSettings:Array = Config.GetValue("CooldownLayout"); - for (var i:Number = 0; i < AbilityCount; ++i) { + var hideReady:Boolean = Config.GetValue("HideReady"); + for (var i:Number = 0; i < CooldownCount; ++i) { var layout:Object = layoutSettings[i]; - CooldownViews[i] = HostClip.attachMovie("efdFreeHUDCooldownDisplay", "CooldownDisplay" + i, HostClip.getNextHighestDepth(), - {_x : layout.x, _y : layout.y, _xscale : layout.scale, _yscale : layout.scale, SlotID : i + AbilityOffset}); + var cooldown:MovieClip = HostClip.attachMovie("efdFreeHUDCooldownDisplay", "CooldownDisplay" + i, HostClip.getNextHighestDepth(), + {_x : layout.x, _y : layout.y, _xscale : layout.scale, _yscale : layout.scale, + SlotID : (i == GadgetIndex ? 0 : i + AbilityOffset), + HideReady : hideReady}); + cooldown.ChangeAbility(i < AbilityCount ? + Shortcut.m_ShortcutList[i + AbilityOffset] : + Equipment.GetItemAt(_global.Enums.ItemEquipLocation.e_Aegis_Talisman_1)); + CooldownViews.push(cooldown); } } Shortcut.SignalShortcutAdded.Connect(AbilityChanged, this); @@ -56,21 +71,34 @@ class efd.FreeHUD.FreeHUD extends Mod { Shortcut.SignalShortcutMoved.Connect(AbilityMoved, this); Shortcut.SignalShortcutEnabled.Connect(EnableAbility, this); Shortcut.SignalCooldownTime.Connect(AbilityCooldown, this); + + Equipment.SignalItemAdded.Connect(ItemChanged, this); + Equipment.SignalItemLoaded.Connect(ItemChanged, this); + Equipment.SignalItemRemoved.Connect(ItemChanged, this); + Equipment.SignalItemCooldown.Connect(ItemCooldown, this); + Equipment.SignalItemCooldownRemoved.Connect(ItemCooldown, this); } - + private function Deactivate():Void { var layout = new Array(); - for (var i:Number = 0; i < AbilityCount; ++i) { + for (var i:Number = 0; i < CooldownCount; ++i) { var clip:MovieClip = CooldownViews[i]; layout.push({x : clip._x, y : clip._y, scale : clip._xscale}); } Config.SetValue("CooldownLayout", layout); } - + + private function LoadComplete():Void { + VisibilityBehaviourDV = DistributedValue.Create(DVPrefix + ModName + "HideReady"); + VisibilityBehaviourDV.SetValue(Config.GetValue("HideReady")); + VisibilityBehaviourDV.SignalChanged.Connect(VisibilityBehaviourChanged, this); + super.LoadComplete(); + } + private function ConfigChanged(setting:String, newValue, oldValue):Void { switch(setting) { case "CooldownLayout": { - for (var i:Number = 0; i < AbilityCount; ++i) { + for (var i:Number = 0; i < CooldownCount; ++i) { var clip:MovieClip = CooldownViews[i]; var layout = newValue[i]; clip._x = layout.x; @@ -80,33 +108,53 @@ class efd.FreeHUD.FreeHUD extends Mod { } break; } - default: super.ConfigChanged(setting, newValue, oldValue); + case "HideReady": { + for (var i:Number = 0; i < CooldownCount; ++i) { + CooldownViews[i].SetVisibilityBehaviour(newValue); + } + break; + } + default: super.ConfigChanged(setting, newValue, oldValue); + } + } + + private function VisibilityBehaviourChanged(dv:DistributedValue):Void { + Config.SetValue("HideReady", dv.GetValue()); + } + + private function UpdateMod(newVersion:String, oldVersion:String):Void { + var layouts:Array = Config.GetValue("CooldownLayout"); + var defaultLayouts:Array = Config.GetDefault("CooldownLayout"); + if (layouts.length < defaultLayouts.length) { + for (var i:Number = layouts.length; i < defaultLayouts.length; ++i) { + layouts.push(defaultLayouts[i]); + } } } - + private function GetDefaultLayout():Array { var layout = new Array(); - for (var i:Number = 0; i < AbilityCount; ++i) { + for (var i:Number = 0; i < CooldownCount; ++i) { layout.push({x : 575 + i * 50, y : 450, scale : 100}); } return layout; } - + // Despite documentation to the contrary, SignalShortcutAdded only emits the first parameter private function AbilityChanged(pos:Number) { - CooldownViews[pos-AbilityOffset].ChangeAbility(); + CooldownViews[pos-AbilityOffset].ChangeAbility(Shortcut.m_ShortcutList[pos]); } - + // Triggers once only private function AbilityMoved(oldPos:Number, newPos:Number) { AbilityChanged(oldPos); AbilityChanged(newPos); } - + private function EnableAbility(pos:Number, enabled:Boolean):Void { CooldownViews[pos-AbilityOffset].EnableAbility(enabled); } - + private function AbilityCooldown(pos:Number, start:Number, end:Number, type:Number) { var remains:Number = end - start; if (type > 0 && remains > 0) { @@ -115,8 +163,31 @@ class efd.FreeHUD.FreeHUD extends Mod { CooldownViews[pos-AbilityOffset].RemoveCooldown(); } } - - private var CooldownViews:Object; + + private function ItemChanged(inventoryID:com.Utils.ID32, itemPos:Number):Void { + if (itemPos == _global.Enums.ItemEquipLocation.e_Aegis_Talisman_1) { + CooldownViews[GadgetIndex].ChangeAbility(Equipment.GetItemAt(_global.Enums.ItemEquipLocation.e_Aegis_Talisman_1)); + } + } + + private function ItemCooldown(inventoryID:com.Utils.ID32, itemPos:Number, seconds:Number):Void { + if (itemPos == _global.Enums.ItemEquipLocation.e_Aegis_Talisman_1) { + if (seconds) { + var now:Number = Utils.GetGameTime(); + CooldownViews[GadgetIndex].AddCooldown(now, now + seconds, 0); + } else { + Debug.TraceMsg("Cooldown force removed"); + CooldownViews[GadgetIndex].RemoveCooldown(); + } + } + } + private static var AbilityOffset:Number = 100; private static var AbilityCount:Number = 6; + private static var CooldownCount:Number = AbilityCount + 1; + private static var GadgetIndex = AbilityCount; + + private var CooldownViews:Array; + private var VisibilityBehaviourDV:DistributedValue; + private var Equipment:Inventory; } diff --git a/efd/FreeHUD/gui/CooldownDisplay.as b/efd/FreeHUD/gui/CooldownDisplay.as index a475dbd..1abe472 100644 --- a/efd/FreeHUD/gui/CooldownDisplay.as +++ b/efd/FreeHUD/gui/CooldownDisplay.as @@ -4,6 +4,7 @@ import com.GameInterface.Game.Shortcut; import com.GameInterface.Game.ShortcutData; +import com.GameInterface.InventoryItem; import com.GameInterface.Tooltip.TooltipDataProvider; import com.GameInterface.Utils; import com.Utils.Colors; @@ -11,6 +12,7 @@ import com.Utils.GlobalSignal; import com.Utils.Signal; import GUI.HUD.AbilityCooldown; +import efd.FreeHUD.lib.DebugUtils; import efd.FreeHUD.lib.Mod; import efd.FreeHUD.lib.etu.GemController; @@ -18,72 +20,56 @@ class efd.FreeHUD.gui.CooldownDisplay extends MovieClip { public function CooldownDisplay() { super(); - + Clear(); m_Gloss._visible = false; + Colors.ApplyColor(m_Background.background, 0); + Colors.ApplyColor(m_Background.highlight, 4276545); AbilityIconLoader = new MovieClipLoader(); GlobalSignal.SignalSetGUIEditMode.Connect(ManageGEM, this); SignalGeometryChanged = new Signal(); + } - ChangeAbility(); - UpdateVisuals(); - } - - - public function ChangeAbility():Void { - var data:ShortcutData = Shortcut.m_ShortcutList[SlotID]; - SetColor(data.m_ColorLine); + public function ChangeAbility(data:Object):Void { if (data.m_Icon) { SetIcon(Utils.CreateResourceString(data.m_Icon)); - SpellID = data.m_SpellId; - _visible = TooltipDataProvider.GetSpellTooltip(SpellID, 0).m_RecastTime > 0; - } else { - Clear(); - _visible = false; - } + if (SlotID > 0 && !data.m_Enabled) { DebugUtils.TraceMsgS("Ability disabled on creation"); } + } else { Clear(); } + UpdateVisuals(); } - + public function EnableAbility(enabled:Boolean):Void { Enabled = enabled; UpdateVisuals(); } - + + public function SetVisibilityBehaviour(hideReady:Boolean):Void { + HideReady = hideReady; + UpdateVisuals(); + } + private function Clear():Void { - SpellID = 0; Enabled = true; if (IsLoaded) { AbilityIconLoader.unloadClip(AbilityIcon); IsLoaded = false; } - } - - private function SetColor(colorLine:Number):Void { - m_ColorObject = Colors.GetColorlineColors(colorLine); - SetBackgroundColor(true); } private function SetIcon(path:String):Void { if (IsLoaded) { AbilityIconLoader.unloadClip(AbilityIcon); } IsLoaded = AbilityIconLoader.loadClip(path, AbilityIcon); - + AbilityIcon._xscale = m_Background._width; AbilityIcon._yscale = m_Background._height; } - - public function SetBackgroundColor(show:Boolean):Void { - m_Background._visible = show - if (show) { - Colors.ApplyColor( m_Background.background, m_ColorObject.background ); - Colors.ApplyColor( m_Background.highlight, m_ColorObject.highlight ); - } - } /// GUI Edit Mode - private function ManageGEM(unlock:Boolean):Void { - if (unlock && _visible && !GemManager) { + private function ManageGEM(unlock:Boolean):Void { + if (unlock && !GemManager) { GemManager = GemController.create("GuiEditModeInterface" + SlotID, _parent, _parent.getNextHighestDepth(), this); GemManager.lockAxis(0); GemManager.addEventListener("scrollWheel", this, "ChangeScale"); @@ -93,12 +79,11 @@ class efd.FreeHUD.gui.CooldownDisplay extends MovieClip { GemManager.removeMovieClip(); GemManager = null; } - } - - private function ChangePosition(event:Object):Void { - SignalGeometryChanged.Emit(); + UpdateVisuals(); } + private function ChangePosition(event:Object):Void { SignalGeometryChanged.Emit(); } + private function ChangeScale(event:Object):Void { var newScale:Number = _xscale + event.delta * 5; newScale = Math.min(200, Math.max(30, newScale)); @@ -110,90 +95,84 @@ class efd.FreeHUD.gui.CooldownDisplay extends MovieClip { /// Cooldowns public function AddCooldown(cooldownStart:Number, cooldownEnd:Number, cooldownFlags:Number):Void { if (cooldownFlags & _global.Enums.TemplateLock.e_GlobalCooldown) { return; } - // update visuals now, to avoid being forever stuck in the wrong state if other abilities are spammed - ForceUpdateVisuals(); + // Unneeded? update visuals now, to avoid being forever stuck in the wrong state if other abilities are spammed + // UpdateVisualState(); AbilityIcon._alpha = 35; // Start or update cooldown if (CooldownOverlay == undefined) { - CooldownOverlay = new AbilityCooldown(this, cooldownStart, cooldownEnd, cooldownFlags, SpellID); + CooldownOverlay = new AbilityCooldown(this, cooldownStart, cooldownEnd, cooldownFlags); CooldownOverlay.SignalDone.Connect(RemoveCooldown, this); } else { CooldownOverlay.OverwriteCooldown(cooldownStart, cooldownEnd, cooldownFlags); } + UpdateVisuals(); } - private function RemoveCooldown(spellId:Number):Void { + public function RemoveCooldown():Void { if(CooldownOverlay != undefined) { - m_OuterLine._visible = true; - - Colors.ApplyColor(m_OuterLine, Colors.e_ColorBlack); - AbilityIcon._alpha = 100; CooldownOverlay.SignalDone.Disconnect(RemoveCooldown, this); - + CooldownOverlay.RemoveCooldown(); CooldownOverlay = undefined; UpdateVisuals(); } } - + /// Display Adjustments private function SetOffCD():Void { m_CooldownLine._visible = false; m_OuterLine._visible = true; Colors.ApplyColor(m_OuterLine, Colors.e_ColorBlack); } - + public function SetDisabled():Void { SetOffCD(); m_Background._visible = false; - + AbilityIcon._alpha = 50; } - + public function SetAvailable():Void { SetOffCD(); - SetBackgroundColor(true); + m_Background._visible = true; AbilityIcon._alpha = 100; - m_Background._alpha = 100; + m_Background._alpha = 100; } - + private function UpdateVisuals():Void { + _visible = GemManager || (!HideReady || CooldownOverlay) && IsLoaded && (SlotID == 0 || TooltipDataProvider.GetSpellTooltip(Shortcut.m_ShortcutList[SlotID].m_SpellId, 0).m_RecastTime > 0); if (CooldownOverlay != undefined) { return; } - ForceUpdateVisuals(); + UpdateVisualState(); } - - private function ForceUpdateVisuals():Void { + + private function UpdateVisualState():Void { if (Enabled) { SetAvailable(); } else { SetDisabled(); } } - -/// vars - private var m_ColorObject:Object; - - private var m_OuterLine:MovieClip; - private var m_BackgroundGradient:MovieClip; - +/// vars private var AbilityIcon:MovieClip; private var AbilityIconLoader:MovieClipLoader; private var IsLoaded:Boolean = false; - + + private var HideReady:Boolean = false; private var Enabled:Boolean; - + private var GemManager:GemController; private var SignalGeometryChanged:Signal; private var SlotID:Number; - private var SpellID:Number; - + private var CooldownOverlay:AbilityCooldown = undefined; - + // Library object elements - + private var m_OuterLine:MovieClip; + private var m_BackgroundGradient:MovieClip; + // Referenced from AbilityCooldown library class, don't rename private var m_CooldownLine:MovieClip; private var m_Gloss:MovieClip;