diff --git a/LoreHound.fla b/LoreHound.fla index 97536af..ee807ca 100644 Binary files a/LoreHound.fla and b/LoreHound.fla differ diff --git a/README.md b/README.md index e78db63..6c1d0d3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Each category can independently have notifications output to either chat (System + Instance IDs: Identifier values for the particular pickup object in the world. + Occasionally useful if a single lore seems to be getting spammed to determine that they are actually multiple objects. -To improve efficiency and reduce false-positives, it only checks against the existing list of known/suspected lore. If it fails to detect something that it should, particularly if the developers have added new content, the "New Game Content" option will activate additional testing on objects that would otherwise be ignored. +To improve efficiency and reduce false-positives, it only checks against the existing list of known/suspected lore. If it fails to detect something that it should have the "Extra Testing" option will activate additional testing on objects that would otherwise be ignored. All settings are saved account wide and will be shared across characters. If you'd rather have different settings for each character, renaming the file "LoginPrefs.xml" to "CharPrefs.xml" when installing/upgrading the mod should work without any problems. A clean install of the mod is recommended if doing this, as it will be unable to transfer existing settings anyway. @@ -38,9 +38,13 @@ The update system *should* carry forward settings from v0.4.0-beta onwards. Howe If upgrading from v0.1.1-alpha, a clean reinstall is recommended. Remove the existing mod entirely and login to the game to clear any existing settings before installing a more recent version. ## Change Log -Version next +Version 1.1.0 + Setting migration from versions prior to v1.0.0 no longer supported ++ Onscreen flags at detected lore locations! + + It's a rather ugly hack at the moment, the occasional ak'ab may request hugs + Tooltip now provides listing of tracked lore and pending reports ++ Fixed a bug with auto report setting corruption that was causing the system to fail + + During the update a chat message will be issued if you were affected, and that setting will be reset Version 1.0.0 + An actual release version! @@ -95,23 +99,20 @@ Version 0.1.1-alpha The following issues are known to exist in the most recent release: + There appear to be seven uncategorized lore IDs somewhere in the game + Three of these are believed to be event related and unavailable at the moment -+ Misses lore pickups already within the detection range when zoning into a new map ++ Sometimes misses lore pickups already within the detection range when zoning into a new map + "Fixing" this causes cascading strange behaviours as it detects things halfway through loading the map. While these can, mostly, be corrected, I'm not convinced it's worth the time. + Long label customizations are truncated by the GUI + If I get any translations, I'll likely have to expand the space to fit them -+ An empty archive is left in the settings file after standardizing setting names - + This should have no effect other than being slightly untidy and will go away after v1.1.0 ++ A brief lag may be observed after reloading the ui, where the full size icon is displayed rather than attached to the topbar + + This is intentional and reduces the occurence of bugs related to other mods integrating with the topbar ## Testing and Further Developments -This release is currently feature complete, but some things may arrive in subsequent releases: -+ Actual localization would be nice, but I'm not going to rely on Google and my limited knowledge of French to be at all accurate. Somebody else will have to provide me with translations. -+ Am finding the coordinate based reporting slightly difficult to parse at times, will be looking into some form of onscreen waypoint or flag. (1.1.0+) -+ A second project is in the preliminary stages of development, and may have some integration possibilities with this one. -+ Maintenance updates will be made as needed to fix bugs or add updates to the data files. -+ Some form of whitelisting is a possibility: (1.1.0+) +I'm relatively satisfied with the features that exist, and will continue providing basic support for bug fixes and updates to files if needed. However, due to the current situation, I am unlikely to get around to implementing the remaining features or attempting to port to SWL until more information is available: ++ Some form of whitelisting to further filter the accepted values: + The *easy* version would be one that simply works on loreIDs after initial filtering, as a global white list. + More complicated systems (intelligent per-category whitelists, random drops only, etc.) would require additional information to be saved about each lore entry. -+ It's still a bit spammy with messages. Perhaps an extension of the lore tracking system would be useful to limit how frequently a particular dynel is detected? (1.1.0) ++ Actual localization would be nice, but I'm not going to rely on Google and my limited knowledge of French to be at all accurate. Somebody else will have to provide me with translations, if there is sufficient interest. ++ My previously mentioned second project is currently on hold. While I still think it has value, I am not likely to develop it further until I have had a chance to look at SWL and decide which, if either, I will be playing. A feature for helping with The Abandoned lore was found to be unworkable. Lore.IsLockedForChar either does not work as advertised, or requires GM permissions. diff --git a/config/Strings.xml b/config/Strings.xml index 289fca6..8f538d4 100644 --- a/config/Strings.xml +++ b/config/Strings.xml @@ -75,6 +75,8 @@ en="Ignore Unclaimed Lore" /> + + + + diff --git a/efd/LoreHound/LoreHound.as b/efd/LoreHound/LoreHound.as index 2c17d4b..93592b0 100644 --- a/efd/LoreHound/LoreHound.as +++ b/efd/LoreHound/LoreHound.as @@ -2,15 +2,21 @@ // Released under the terms of the MIT License // https://github.com/Earthfiredrake/TSW-LoreHound +import flash.geom.Point; + import gfx.utils.Delegate; import com.GameInterface.Dynels; +import com.GameInterface.Game.Character; import com.GameInterface.Game.Dynel; import com.GameInterface.Lore; import com.GameInterface.LoreNode; import com.GameInterface.MathLib.Vector3; import com.GameInterface.VicinitySystem; -import com.GameInterface.WaypointInterface; // Playfield change notifications +import com.GameInterface.Waypoint; +import com.GameInterface.WaypointInterface; +import GUI.Waypoints.CustomWaypoint; +import com.Utils.Archive; //DEPRECIATED(v1.1.0.alpha): Required for bugfix that corrects forgetting AutoReport settings import com.Utils.ID32; import com.Utils.LDBFormat; @@ -24,7 +30,7 @@ class efd.LoreHound.LoreHound extends Mod { // Debug settings at top so that commenting out leaves no hanging ',' // Trace : true, Name : "LoreHound", - Version : "1.1.0.alpha", + Version : "1.1.0", Type : e_ModType_Reactive, MinUpgradableVersion : "1.0.0", IconData : { UpdateState : UpdateIcon, @@ -64,7 +70,9 @@ class efd.LoreHound.LoreHound extends Mod { DetailStatMode = 2; // Defaulting to mode 2 based on repeated comments in game source that it is somehow "full" SystemsLoaded.CategoryIndex = false; + RecentLore = new Object(); TrackedLore = new Object(); + WaypointSystem = _root.waypoints; WaypointInterface.SignalPlayfieldChanged.Connect(ClearTracking, this); var arConfig:ConfigWrapper = AutoReport.Initialize(ModName, Version, DevName); @@ -83,7 +91,9 @@ class efd.LoreHound.LoreHound extends Mod { Config.NewSetting("IgnoreUnclaimedLore", true); // Ignore lore if the player hasn't picked it up already Config.NewSetting("IgnoreOffSeasonLore", true); // Ignore event lore if the event isn't running (TODO: Test this when a game event is running) Config.NewSetting("TrackDespawns", true); // Track lore drops for when they despawn - Config.NewSetting("CheckNewContent", false); // Does extra tests to detect lore that isn't on the index list at all yet (ie: new content!) + Config.NewSetting("ShowWaypoints", true); // Show onscreen waypoints for any reported lores + Config.NewSetting("CheckNewContent", false); // DEPRECIATED(v1.1.0.alpha): Renamed + Config.NewSetting("ExtraTesting", false); // Does extra tests to detect lore that isn't on the index list at all yet (ie: new content!) // Extended information, regardless of this setting: // - Is always ommitted from Fifo notifications, to minimize spam @@ -108,7 +118,18 @@ class efd.LoreHound.LoreHound extends Mod { private function ConfigChanged(setting:String, newValue, oldValue):Void { switch(setting) { case "TrackDespawns": - if (!newValue) { ClearTracking(); } + if (!newValue) { ClearDespawnTracking(); } + break; + case "ShowWaypoints": + if (newValue) { + for (var key:String in RecentLore) { + var dynel:Dynel = RecentLore[key]; + // TODO: This is a bit of a hack, it'll do slightly odd things if people toggle it around incomplete dynels (like inactive event lore) + // Still is quicker than doing a full identification and reasonably accurate + CreateWaypoint(dynel, AttemptIdentification(dynel.GetStat(e_Stats_LoreId, 2))); + } + } + else { ClearWaypoints(); } break; default: super.ConfigChanged(setting, newValue, oldValue); @@ -161,10 +182,25 @@ class efd.LoreHound.LoreHound extends Mod { // Version specific updates // Some upgrades may reflect unreleased builds, for consistency on develop branch + if (CompareVersions("1.1.0.alpha", oldVersion) >= 0) { + // Renaming setting due to recent events + Config.SetValue("ExtraTesting", Config.GetValue("CheckNewContent")); + // May have lost the config settings for the auto report system :( + if (Config.GetValue("AutoReport") instanceof Archive) { + Config.SetValue("AutoReport", Config.GetDefault("AutoReport")); + ChatMsg(LocaleManager.GetString("Patch", "AutoReportRepair")); + } + } + } + + private function LoadComplete():Void { + super.LoadComplete(); + Config.DeleteSetting("CheckNewContent"); // DEPRECIATED(v1.1.0.alpha): Renamed } private function Activate():Void { AutoReport.IsEnabled = true; // Only updates this component's view of the mod state + HostMovie.onEnterFrame = Delegate.create(this, UpdateWaypoints); VicinitySystem.SignalDynelEnterVicinity.Connect(LoreSniffer, this); Dynels.DynelGone.Connect(LoreDespawned, this); } @@ -176,14 +212,10 @@ class efd.LoreHound.LoreHound extends Mod { // Detection notices between the deactivate-activate pair have a strange habit of providing the correct LoreId, but being unable to link to an actual lore object Dynels.DynelGone.Disconnect(LoreDespawned, this); VicinitySystem.SignalDynelEnterVicinity.Disconnect(LoreSniffer, this); + delete HostMovie.onEnterFrame; AutoReport.IsEnabled = false; // Only updates this component's view of the mod state } - private function TopbarRegistered():Void { - // Topbar icon does not copy custom state variable, so needs explicit refresh - // Icon.UpdateState(ef_IconState_Report, AutoReport.HasReportsPending); - } - private function UpdateIcon():Void { Icon.RefreshTooltip(); if (Config.GetValue("Enabled")) { // If game disables mod, it isn't visible at all, so only user disables matter @@ -232,9 +264,10 @@ class efd.LoreHound.LoreHound extends Mod { // category: The string table to pull from? Constantly 50200 whenever I've checked // key: Some sort of hash? May vary by id# or string content (shrouded and regular lore have different values), but seems constant within a group // knubot: No idea at all. Always seems to be 0 for what it's worth. - // GetID() - The ID type seems to be constant (51320) for all lore, but is shared with a wide variety of other props + // GetID() - The ID type seems to be constant (51320) for all lore, but is shared with a wide variety of other props (simple dynels) // Other types: // 50000 - used by all creatures (players, pets, npcs, monsters, etc.) + // 51321 - destructibles (according to global enums) // 51322 - loot bags // As a note for later, the other category uses a different GetName() system (is pre-localized, rather than the xml tag used by objects) // Instance ids of fixed lore may vary slightly between sessions, seemingly depending on load orders or caching of map info. @@ -265,7 +298,8 @@ class efd.LoreHound.LoreHound extends Mod { /// Lore detection and sorting private function LoreSniffer(dynelId:ID32):Void { - if (dynelId.m_Type != e_DynelType_Object) { return; } // Dynel is not of supertype associated with lore + if (dynelId.m_Type != _global.Enums.TypeID.e_Type_GC_SimpleDynel) { return; } // Dynel is not of supertype associated with lore + if (RecentLore[dynelId.toString()]) { return; } // Lore has already been reported recently var dynel:Dynel = Dynel.GetDynel(dynelId); var dynelName:String = dynel.GetName(); @@ -278,7 +312,7 @@ class efd.LoreHound.LoreHound extends Mod { // Categorize the detected item var loreType:Number = ClassifyID(categorizationId); if (loreType == ef_LoreType_None) { - if (Config.GetValue("CheckNewContent") && ExpandedDetection(dynel)) { loreType = ef_LoreType_Unknown; } // It's so new it hasn't been added to the index list yet + if (Config.GetValue("ExtraTesting") && ExpandedDetection(dynel)) { loreType = ef_LoreType_Unknown; } // It's so new it hasn't been added to the index list yet else { return; } } @@ -289,6 +323,15 @@ class efd.LoreHound.LoreHound extends Mod { private function ProcessAndNotify(dynel:Dynel, loreType:Number, categorizationId:Number, repeat:Number):Void { if (ProcessLore(dynel, loreType, categorizationId, repeat)) { SendLoreNotifications(loreType, categorizationId, dynel); + var dynelId:ID32 = dynel.GetID(); + RecentLore[dynelId.toString()] = dynel; + // Used to register for RecentLore system... + // _global.enums.Property.e_ObjScreenPos seems like it would be more useful, and returns the same value :( + // Can't tell if it actually updates the value on a regular basis... as lore doesn't move + Dynels.RegisterProperty(dynelId.m_Type, dynelId.m_Instance, _global.enums.Property.e_ObjPos); + if (Config.GetValue("ShowWaypoints")) { + CreateWaypoint(dynel, AttemptIdentification(dynel.GetStat(e_Stats_LoreId, 2), loreType, categorizationId)); + } } } @@ -353,20 +396,35 @@ class efd.LoreHound.LoreHound extends Mod { } UpdateIcon(); } + if (RecentLore[despawnedId]) { + if (Config.GetValue("ShowWaypoints")) { + delete WaypointSystem.m_CurrentPFInterface.m_Waypoints[despawnedId]; + WaypointSystem.m_CurrentPFInterface.SignalWaypointRemoved.Emit(new ID32(type, instance)); + } + delete RecentLore[despawnedId]; + } } private function ClearTracking():Void { - for (var key:String in TrackedLore) { - var id:Array = key.split(":"); - // Probably don't *have* to unregister, the dynel is most likely about to be destroyed anyway - // This is more for cleaning up my end of things - Dynels.UnregisterProperty(id[0], id[1], _global.enums.Property.e_ObjPos); - } + ClearDespawnTracking(); + // Don't need to clear waypoints, as the playfield change will reset the waypoint interface + delete RecentLore; + RecentLore = new Object(); + } + + private function ClearDespawnTracking():Void { delete TrackedLore; TrackedLore = new Object(); UpdateIcon(); } + private function ClearWaypoints():Void { + for (var key:String in RecentLore) { + delete WaypointSystem.m_CurrentPFInterface.m_Waypoints[key]; + WaypointSystem.m_CurrentPFInterface.SignalWaypointRemoved.Emit(RecentLore[key].GetID()); + } + } + /// Lore identification // Much of the primary categorization info is now contained in the xml data file private function ClassifyID(categorizationId:Number):Number { @@ -386,7 +444,6 @@ class efd.LoreHound.LoreHound extends Mod { } /// Notification and message formatting - private function SendLoreNotifications(loreType:Number, categorizationId:Number, dynel:Dynel):Void { var messageStrings:Array = GetMessageStrings(loreType, dynel.GetStat(e_Stats_LoreId, 2), dynel, categorizationId); var detailStrings:Array = GetDetailStrings(loreType, dynel); @@ -570,8 +627,53 @@ class efd.LoreHound.LoreHound extends Mod { } } + /// Waypoint rendering + private function CreateWaypoint(dynel:Dynel, loreName:String):Void { + var waypoint:Waypoint = new Waypoint(); + waypoint.m_Id = dynel.GetID(); + waypoint.m_WaypointType = _global.Enums.WaypointType.e_RMWPScannerBlip; + waypoint.m_WaypointState = _global.Enums.QuestWaypointState.e_WPStateActive; + waypoint.m_Label = loreName; + waypoint.m_IsScreenWaypoint = true; + waypoint.m_IsStackingWaypoint = true; + waypoint.m_Radius = 0; + waypoint.m_Color = 0xF6D600; + waypoint.m_WorldPosition = dynel.GetPosition(0); + var scrPos:Point = dynel.GetScreenPosition(); + waypoint.m_ScreenPositionX = scrPos.x; + waypoint.m_ScreenPositionY = scrPos.y; + waypoint.m_CollisionOffsetX = 0; + waypoint.m_CollisionOffsetY = 0; + waypoint.m_DistanceToCam = dynel.GetCameraDistance(0); + waypoint.m_MinViewDistance = 0; + waypoint.m_MaxViewDistance = 50; + + WaypointSystem.m_CurrentPFInterface.m_Waypoints[waypoint.m_Id.toString()] = waypoint; + WaypointSystem.m_CurrentPFInterface.SignalWaypointAdded.Emit(waypoint.m_Id); + } + + // Ugly, but I don't really see any alternative to doing a per/frame update + private function UpdateWaypoints():Void { + if (Config.GetValue("ShowWaypoints")) { + for (var key:String in RecentLore) { + // The waypoints that are added to the PFInterface are constantly stomped by the C++ side. + // So I'm updating the data held by the rendered copy, and then forcing it to redo the layout before it gets stomped again. + // As long as the mod is updated after the main interface, this should work. + // To do this properly, I'd have to implement my own waypoint system, which just isn't worth it at this point. + var dynel:Dynel = RecentLore[key]; + var scrPos:Point = dynel.GetScreenPosition(); + var waypoint:CustomWaypoint = WaypointSystem.m_RenderedWaypoints[key]; + waypoint.m_Waypoint.m_ScreenPositionX = scrPos.x; + waypoint.m_Waypoint.m_ScreenPositionY = scrPos.y; + waypoint.m_Waypoint.m_DistanceToCam = dynel.GetCameraDistance(0); + + waypoint.Update(Stage.visibleRect.width); + waypoint = undefined; + } + } + } + /// Variables - private static var e_DynelType_Object:Number = 51320; // All known lore shares this dynel type with a wide variety of other props private static var e_Stats_LoreId:Number = 2000560; // Most lore dynels seem to store the LoreId at this stat index, those that don't are either not fully loaded, or event related private static var c_ShroudedLoreCategory:Number = 7993128; // Keep ending up with special cases for this particular one @@ -585,5 +687,7 @@ class efd.LoreHound.LoreHound extends Mod { private var IndexFile:XML; private var CategoryIndex:Array; + private var RecentLore:Object; private var TrackedLore:Object; + private var WaypointSystem:Object; } diff --git a/efd/LoreHound/gui/ConfigWindowContent.as b/efd/LoreHound/gui/ConfigWindowContent.as index 5fc6c09..20f451b 100644 --- a/efd/LoreHound/gui/ConfigWindowContent.as +++ b/efd/LoreHound/gui/ConfigWindowContent.as @@ -20,9 +20,10 @@ class efd.LoreHound.gui.ConfigWindowContent extends WindowComponentContent { CBModEnabled.disableFocus = true; CBIgnoreUnclaimedLore.disableFocus = true; CBIgnoreOffSeasonLore.disableFocus = true; + CBWaypoints.disableFocus = true; CBTrackDespawns.disableFocus = true; CBErrorReports.disableFocus = true; - CBNewContent.disableFocus = true; + CBExtraTests.disableFocus = true; CBDetailTimestamp.disableFocus = true; CBDetailLocation.disableFocus = true; CBDetailCategory.disableFocus = true; @@ -37,9 +38,10 @@ class efd.LoreHound.gui.ConfigWindowContent extends WindowComponentContent { LocaleManager.ApplyLabel(LBEnable); LocaleManager.ApplyLabel(LBUnclaimed); LocaleManager.ApplyLabel(LBInactive); + LocaleManager.ApplyLabel(LBWaypoints); LocaleManager.ApplyLabel(LBDespawn); LocaleManager.ApplyLabel(LBAutoReport); - LocaleManager.ApplyLabel(LBNewContent); + LocaleManager.ApplyLabel(LBExtraTests); } public function AttachConfig(config:ConfigWrapper):Void { @@ -52,9 +54,10 @@ class efd.LoreHound.gui.ConfigWindowContent extends WindowComponentContent { CBModEnabled.addEventListener("select", this, "CBModEnabled_Select"); CBIgnoreUnclaimedLore.addEventListener("select", this, "CBIgnoreUnclaimedLore_Select"); CBIgnoreOffSeasonLore.addEventListener("select", this, "CBIgnoreOffSeasonLore_Select"); + CBWaypoints.addEventListener("select", this, "CBWaypoints_Select"); CBTrackDespawns.addEventListener("select", this, "CBTrackDespawns_Select"); CBErrorReports.addEventListener("select", this, "CBErrorReports_Select"); - CBNewContent.addEventListener("select", this, "CBNewContent_Select"); + CBExtraTests.addEventListener("select", this, "CBExtraTests_Select"); CBDetailTimestamp.addEventListener("select", this, "CBDetailTimestamp_Select"); CBDetailLocation.addEventListener("select", this, "CBDetailLocation_Select"); CBDetailCategory.addEventListener("select", this, "CBDetailCategory_Select"); @@ -77,11 +80,14 @@ class efd.LoreHound.gui.ConfigWindowContent extends WindowComponentContent { if (setting == "IgnoreOffSeasonLore" || setting == undefined) { CBIgnoreOffSeasonLore.selected = Config.GetValue("IgnoreOffSeasonLore"); } + if (setting == "ShowWaypoints" || setting == undefined) { + CBWaypoints.selected = Config.GetValue("ShowWaypoints"); + } if (setting == "TrackDespawns" || setting == undefined) { CBTrackDespawns.selected = Config.GetValue("TrackDespawns"); } - if (setting == "CheckNewContent" || setting == undefined) { - CBNewContent.selected = Config.GetValue("CheckNewContent"); + if (setting == "ExtraTesting" || setting == undefined) { + CBExtraTests.selected = Config.GetValue("ExtraTesting"); } if (setting == "Details" || setting == undefined) { var details = Config.GetValue("Details"); @@ -111,6 +117,10 @@ class efd.LoreHound.gui.ConfigWindowContent extends WindowComponentContent { Config.SetValue("IgnoreOffSeasonLore", event.selected); } + private function CBWaypoints_Select(event:Object):Void { + Config.SetValue("ShowWaypoints", event.selected); + } + private function CBTrackDespawns_Select(event:Object):Void { Config.SetValue("TrackDespawns", event.selected); } @@ -119,8 +129,8 @@ class efd.LoreHound.gui.ConfigWindowContent extends WindowComponentContent { Config.GetValue("AutoReport").SetValue("Enabled", event.selected); } - private function CBNewContent_Select(event:Object):Void { - Config.SetValue("CheckNewContent", event.selected); + private function CBExtraTests_Select(event:Object):Void { + Config.SetValue("ExtraTesting", event.selected); } private function CBDetailTimestamp_Select(event:Object):Void { @@ -149,17 +159,19 @@ class efd.LoreHound.gui.ConfigWindowContent extends WindowComponentContent { private var LBEnable:TextField; private var LBUnclaimed:TextField; private var LBInactive:TextField; + private var LBWaypoints:TextField; private var LBDespawn:TextField; private var LBAutoReport:TextField; - private var LBNewContent:TextField; + private var LBExtraTests:TextField; // Checkboxes private var CBModEnabled:CheckBox; private var CBIgnoreUnclaimedLore:CheckBox; private var CBIgnoreOffSeasonLore:CheckBox; + private var CBWaypoints:CheckBox; private var CBTrackDespawns:CheckBox; private var CBErrorReports:CheckBox; - private var CBNewContent:CheckBox; + private var CBExtraTests:CheckBox; private var CBDetailTimestamp:CheckBox; private var CBDetailLocation:CheckBox; diff --git a/efd/LoreHound/lib/ConfigWrapper.as b/efd/LoreHound/lib/ConfigWrapper.as index f611574..056e5c8 100644 --- a/efd/LoreHound/lib/ConfigWrapper.as +++ b/efd/LoreHound/lib/ConfigWrapper.as @@ -141,7 +141,7 @@ class efd.LoreHound.lib.ConfigWrapper { } public function SaveConfig():Archive { - if (IsDirty) { + if (IsDirty || !IsLoaded) { UpdateCachedArchive(); if (ArchiveName != undefined) { DistributedValue.SetDValue(ArchiveName, CurrentArchive); } } @@ -211,6 +211,7 @@ class efd.LoreHound.lib.ConfigWrapper { DirtyFlag = false; } else { CurrentArchive = new Archive(); // Nothing to load, but we tried + CurrentArchive.AddEntry("ArchiveType", "Config"); // Remember to flag this as a config archive, in case we get saved as an invalid setting } SignalConfigLoaded.Emit(); }