LocOps
LocOps - Location Operations - The yellow pages of Skyrim.
Get information about the area you're in. The system is data driven and can be expanded via Json. I've already got one for JK's Skyrim. Multiple jsons are 'rolled together' for a complete picture.
Highlights:
- Look up people by roles/location, Jarl's, Stewards, House carls, Court wizards, Inn keepers, blacksmiths, shop keepers, etc.
- Respects civil war status.
- Location agnostic. You don't need to know where the player is. MGF will figure that out for you.
- Layered area, hold, world scripts/functions.
- Slaverun status.
- Vanilla home ownership.
- Thane status.
- Custom roles. You can expand the lookup with custom roles (not Jarl, Blacksmith etc) via Json. Eg: 'Tailor'. Again duplicate roles will be rolled together.
- Coming soon: Get the closest whatever - Example in LocOpsWorld.
2 'Points Of Interest' quests - (Area / Hold) with aliases that are automatically populated on area/hold change:
Some of these I'm still cleaning up.
LocOpsArea:
Spoiler
Scriptname _MGF_LocOpsArea extends Quest ; The yellow pages for Skyrim (local area edition) ;======================================================================================== ; User Functions ======================================================================== ;======================================================================================== ; Should probably stick to calling 'user functions' only. ; AreaStateName = Primary Location Editor ID. See LocationMap.json. Anywhere in Riverwood = 'RiverwoodLocation' ; Walled cities are the exception. ; Inside city walls = Primary location editor ID. Eg: Anywhere inside Whiterun = 'WhiterunLocation' ; Outside city walls are '<City>Outskirts'. Eg: 'WhiterunOutskirts' Import _MGF_ArrayCreate Import _MGF_ArrayEdit Import _MGF_ArrayQuery Function UpdatePointsOfInterest() BeginPoiUpdate(Store.LocTrack.AreaStateName) EndFunction Bool Function GetIsSlaverunFreeTownByString(String AreaStateName) ; True - free ; False - enslaved _MGF_LocOpsArea_Base Handler = GetHandler(AreaStateName) If !Handler ; No dedicated handler for this AreaStateName. ; Fall back to the hold's overall enslavement status rather than defaulting straight to "free". If AreaStateName != "Wilderness" Return LocOpsHold.GetIsSlaverunFreeHoldByString(Store.LocTrack.GetHoldStateNameForArea(AreaStateName)) EndIf Handler = DefaultHandler EndIf Return Handler.IsSlaverunFreeTown() EndFunction Bool Function GetIsSlaverunFreeTownByIndex(Int Index) Return GetIsSlaverunFreeTownByString(Store.LocTrack.GetAreaStateNameByIndex(Index)) EndFunction Bool Function GetIsSlaverunFreeTownHere() Return GetIsSlaverunFreeTownByString(Store.LocTrack.AreaStateName) EndFunction Bool Function GetOwnsVanillaHomeInArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).OwnsVanillaHomeHere() EndFunction Bool Function GetOwnsVanillaHomeHere() Return GetOwnsVanillaHomeInArea(Store.LocTrack.AreaStateName) EndFunction Actor Function GetLeaderForArea(String AreaStateName) ; Returns the guy in charge. The big cheese. The head honcho. The main cahone. ; Main towns - this is always going to be the jarl. ; Orc strongholds - The orc chief Return GetHandlerOrDefault(AreaStateName).GetLeader(AreaStateName) EndFunction Actor Function GetLeaderHere() Return GetLeaderForArea(Store.LocTrack.AreaStateName) EndFunction Actor Function GetStewardForArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).GetSteward(AreaStateName) EndFunction Actor Function GetStewardHere() Return GetStewardForArea(Store.LocTrack.AreaStateName) EndFunction Actor Function GetCourtWizardForArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).GetCourtWizard(AreaStateName) EndFunction Actor Function GetCourtWizardHere() Return GetCourtWizardForArea(Store.LocTrack.AreaStateName) EndFunction Actor Function GetHousecarlForArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).GetHousecarl(AreaStateName) EndFunction Actor Function GetHousecarlHere() Return GetHousecarlForArea(Store.LocTrack.AreaStateName) EndFunction Actor Function GetHostlerForArea(String AreaStateName) ; The guy that sells horses. Return GetHandlerOrDefault(AreaStateName).GetHostler(AreaStateName) EndFunction Actor Function GetHostlerHere() ; The guy that sells horses. Return GetHostlerForArea(Store.LocTrack.AreaStateName) EndFunction Actor Function GetLicenceOfficerForArea(String AreaStateName) Return Store.ForkLicence.GetLicenceOfficerForArea(AreaStateName) EndFunction Actor Function GetLicenceOfficerHere() Return GetLicenceOfficerForArea(Store.LocTrack.AreaStateName) EndFunction Actor Function GetDriverForArea(String AreaStateName) ; Carriage driver Return GetHandlerOrDefault(AreaStateName).GetDriver(AreaStateName) EndFunction Actor Function GetDriverHere() ; Carriage driver Return GetDriverForArea(Store.LocTrack.AreaStateName) EndFunction Actor[] Function GetInnkeepersForArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).GetInnkeepers(AreaStateName) EndFunction Actor[] Function GetInnkeepersHere() Return GetInnkeepersForArea(Store.LocTrack.AreaStateName) EndFunction Actor[] Function GetBlacksmithsForArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).GetBlacksmiths(AreaStateName) EndFunction Actor[] Function GetBlacksmithsHere() Return GetBlacksmithsForArea(Store.LocTrack.AreaStateName) EndFunction Actor[] Function GetGeneralTradersForArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).GetGeneralTraders(AreaStateName) EndFunction Actor[] Function GetGeneralTradersHere() Return GetGeneralTradersForArea(Store.LocTrack.AreaStateName) EndFunction Actor[] Function GetAlchemistsForArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).GetAlchemists(AreaStateName) EndFunction Actor[] Function GetAlchemistsHere() Return GetAlchemistsForArea(Store.LocTrack.AreaStateName) EndFunction Actor[] Function GetPriestsForArea(String AreaStateName) Return GetHandlerOrDefault(AreaStateName).GetPriests(AreaStateName) EndFunction Actor[] Function GetPriestsHere() Return GetPriestsForArea(Store.LocTrack.AreaStateName) EndFunction ObjectReference[] Function GetCustomRoleForArea(String AreaStateName, String RoleName) ; Custom role lookup for third-party mods - see _MGF_LocOpsArea_Base.GetCustomRole() for details. ; Any role name works, e.g. GetCustomRoleForArea("WindhelmLocation", "Tailor"). ; No changes to this script ever required. Wrapped in ValidateRefs() as a safety net. ; If two mods use the same role for the same location (but different jsons obviously) then all actors ; from both mods will be rolled into the same role. ; Deliberately ObjectReference[] typed to allow the return of 'things' and not just actors. Return _MGF_Util.ValidateRefs(GetHandlerOrDefault(AreaStateName).GetCustomRole(AreaStateName, RoleName)) EndFunction ObjectReference[] Function GetCustomRoleHere(String RoleName) Return GetCustomRoleForArea(Store.LocTrack.AreaStateName, RoleName) EndFunction ; ======================================================================================= ; Internal Stuff ======================================================================== ; ======================================================================================= ; Handler Dispatch ====================================================================== ; Replaced the old state machine mechanics which had the potential to mix up requests ; if LocOps ever got clobbered with simultaneous requests. _MGF_LocOpsArea_Base Function GetHandler(String AreaStateName) ; Returns None if Index is out of range (AreaStateName not in the table) Int Index = AreaStateNames.Find(AreaStateName) If Index >= 0 Return GetNthAlias(Index) as _MGF_LocOpsArea_Base EndIf Return None EndFunction _MGF_LocOpsArea_Base Function GetHandlerOrDefault(String AreaStateName) _MGF_LocOpsArea_Base Handler = GetHandler(AreaStateName) If Handler Return Handler EndIf Return DefaultHandler EndFunction Event OnInit() GenerateAreaStateNames() EndEvent Function GenerateAreaStateNames() ; Dynamically generates AreaStateNames (all areas with a special handler script) via the alias names. ; This forms the 1:1 AreaStateName to Alias index handler map. See GetHandler() AreaStateNames = _MGF_Util.GetAliasNames(Self, 1, False) ; Last alias should always be the default (base) handler. Not an actual area - Pluck. AreaStateNames = ArrayStringPluck(AreaStateNames, AreaStateNames.Length - 1) EndFunction Function BeginPoiUpdate(String AreaStateName) ; Fills the PointsOfInterest quest with AreaStateName's POIs. ; Or clears them if AreaStateName is not mapped. If AreaStateName == "" ; unknown ClearPointsOfInterest() Else FillPointsOfInterest(AreaStateName) EndIf EndFunction Function FillPointsOfInterest(String AreaStateName) _MGF_LocOpsArea_Base Handler = GetHandlerOrDefault(AreaStateName) FillAlias(Store.PoiAliasesArea[0], Handler.GetLeader(AreaStateName)) FillAlias(Store.PoiAliasesArea[1], Handler.GetSteward(AreaStateName)) FillAlias(Store.PoiAliasesArea[2], Handler.GetCourtWizard(AreaStateName)) FillAlias(Store.PoiAliasesArea[3], Handler.GetHousecarl(AreaStateName)) FillAlias(Store.PoiAliasesArea[4], Handler.GetHostler(AreaStateName)) FillAlias(Store.PoiAliasesArea[5], Store.ForkLicence.GetLicenceOfficerForArea(AreaStateName)) ; GetInnkeepers/Blacksmiths/GeneralTraders/Alchemists/Priests already return Actor[] (filtered for disabled/dead). FillAliasArray(Store.PoiAliasesArea[6], ActorsToRefs(Handler.GetInnkeepers(AreaStateName))) FillAliasArray(Store.PoiAliasesArea[7], ActorsToRefs(Handler.GetBlacksmiths(AreaStateName))) FillAliasArray(Store.PoiAliasesArea[8], ActorsToRefs(Handler.GetGeneralTraders(AreaStateName))) FillAliasArray(Store.PoiAliasesArea[9], ActorsToRefs(Handler.GetAlchemists(AreaStateName))) FillAliasArray(Store.PoiAliasesArea[10], ActorsToRefs(Handler.GetPriests(AreaStateName))) EndFunction ; ======================================================================================= ; Helpers =============================================================================== ; ======================================================================================= Function ClearPointsOfInterest() Int i = 0 While i < Store.PoiAliasesArea.Length Store.PoiAliasesArea[i].Clear() i += 1 EndWhile EndFunction Function FillAlias(ReferenceAlias akAlias, ObjectReference ObjRef) If ObjRef akAlias.ForceRefTo(ObjRef) Else akAlias.Clear() EndIf EndFunction Function FillAliasArray(ReferenceAlias akAlias, ObjectReference[] POIs) If POIs.Length akAlias.ForceRefTo(POIs[POIs.Length - 1]) ; Prefer last element - Primary location POI Else akAlias.Clear() EndIf EndFunction String[] Function GetSpecialAreas() ; Every AreaStateName with a dedicated handler script. ; i.e. exactly what GetHandler() can resolve to something other than DefaultHandler. Return AreaStateNames EndFunction String[] Function GetBuiltInRoles() ; The fixed set of built-in role names. ; See the property comment on _MGF_LocOpsArea_PropertyStore.BuiltInRoles for the canonical list. Return Store.BuiltInRoles EndFunction ; ======================================================================================= ; Debug ================================================================================= ; ======================================================================================= Function DumpHandlers() ; Debug utility - walks AreaStateNames and prints what each alias index actually resolves to. Debug.Trace("[_MGF_LocOpsArea] ==== DumpHandlers: " + AreaStateNames.Length + " entries ====") Int i = 0 While i < AreaStateNames.Length _MGF_LocOpsArea_Base Handler = GetNthAlias(i) as _MGF_LocOpsArea_Base String Status = "" If !Handler Status = " <<< NONE - no script attached to this alias, or wrong script type" EndIf Debug.Trace("[_MGF_LocOpsArea] [" + i + "] " + AreaStateNames[i] + " -> " + Handler + Status) i += 1 EndWhile Debug.Trace("[_MGF_LocOpsArea] DefaultHandler -> " + DefaultHandler) Debug.Trace("[_MGF_LocOpsArea] ==== DumpHandlers: done ====") EndFunction Function DumpDirectoryFull() ; Debug utility - cycles through every AreaStateName and calls the real accessor functions ; (GetLeaderForArea, GetInnkeepersForArea, etc.) - the same path an actual caller takes. Debug.Trace("[_MGF_LocOpsArea] ==== DumpDirectoryFull: " + AreaStateNames.Length + " entries ====") Int i = 0 While i < AreaStateNames.Length String AreaStateName = AreaStateNames[i] _MGF_LocOpsArea_Base Handler = GetNthAlias(i) as _MGF_LocOpsArea_Base If !Handler Debug.Trace("[_MGF_LocOpsArea] [" + i + "] " + AreaStateName + " <<< NONE - no script attached to this alias, or wrong script type") Else Debug.Trace("[_MGF_LocOpsArea] [" + i + "] " + AreaStateName + " (" + Handler + ")") Debug.Trace("[_MGF_LocOpsArea] IsSlaverunFreeTown: " + GetIsSlaverunFreeTownByString(AreaStateName)) Debug.Trace("[_MGF_LocOpsArea] OwnsVanillaHome: " + GetOwnsVanillaHomeInArea(AreaStateName)) Debug.Trace("[_MGF_LocOpsArea] Leader: " + _MGF_Util.GetActorName(GetLeaderForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] Steward: " + _MGF_Util.GetActorName(GetStewardForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] CourtWizard: " + _MGF_Util.GetActorName(GetCourtWizardForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] Housecarl: " + _MGF_Util.GetActorName(GetHousecarlForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] Hostler: " + _MGF_Util.GetActorName(GetHostlerForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] Driver: " + _MGF_Util.GetActorName(GetDriverForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] LicenceOfficer: " + _MGF_Util.GetActorName(GetLicenceOfficerForArea(AreaStateName))) TraceActorList("Innkeepers", GetInnkeepersForArea(AreaStateName)) TraceActorList("Blacksmiths", GetBlacksmithsForArea(AreaStateName)) TraceActorList("GeneralTraders", GetGeneralTradersForArea(AreaStateName)) TraceActorList("Alchemists", GetAlchemistsForArea(AreaStateName)) TraceActorList("Priests", GetPriestsForArea(AreaStateName)) EndIf i += 1 EndWhile Debug.Trace("[_MGF_LocOpsArea] DefaultHandler -> " + DefaultHandler) Debug.Trace("[_MGF_LocOpsArea] ==== DumpDirectoryFull: done ====") EndFunction Function DumpDirectoryFromJson() ; Debug utility - reads the FULL location list straight from the authoritative LocationMap.json "locationstates" array ; ( every settlement/camp/cabin the game tracks), rather than the AreaStateNames property (only the locations with a ; dedicated handler script). ; Exercises GetHandlerOrDefault() and the default-fallback StorageUtil path for EVERY known area, not just the ; ones with a handler String JsonPath = "Data/SKSE/Plugins/StorageUtilData/Mcgufin/Internal/LocationMap.json" String[] AllAreaStates = _MGF_File.JsonStringListGet(JsonPath, "locationstates") Debug.Trace("[_MGF_LocOpsArea] ==== DumpDirectoryFromJson: " + AllAreaStates.Length + " entries from LocationMap.json ====") Int i = 0 While i < AllAreaStates.Length String AreaStateName = AllAreaStates[i] Debug.Trace("[_MGF_LocOpsArea] [" + i + "] " + AreaStateName) Debug.Trace("[_MGF_LocOpsArea] IsSlaverunFreeTown: " + GetIsSlaverunFreeTownByString(AreaStateName)) Debug.Trace("[_MGF_LocOpsArea] OwnsVanillaHome: " + GetOwnsVanillaHomeInArea(AreaStateName)) Debug.Trace("[_MGF_LocOpsArea] Leader: " + _MGF_Util.GetActorName(GetLeaderForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] Steward: " + _MGF_Util.GetActorName(GetStewardForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] CourtWizard: " + _MGF_Util.GetActorName(GetCourtWizardForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] Housecarl: " + _MGF_Util.GetActorName(GetHousecarlForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] Hostler: " + _MGF_Util.GetActorName(GetHostlerForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] Driver: " + _MGF_Util.GetActorName(GetDriverForArea(AreaStateName))) Debug.Trace("[_MGF_LocOpsArea] LicenceOfficer: " + _MGF_Util.GetActorName(GetLicenceOfficerForArea(AreaStateName))) TraceActorList("Innkeepers", GetInnkeepersForArea(AreaStateName)) TraceActorList("Blacksmiths", GetBlacksmithsForArea(AreaStateName)) TraceActorList("GeneralTraders", GetGeneralTradersForArea(AreaStateName)) TraceActorList("Alchemists", GetAlchemistsForArea(AreaStateName)) TraceActorList("Priests", GetPriestsForArea(AreaStateName)) i += 1 EndWhile Debug.Trace("[_MGF_LocOpsArea] ==== DumpDirectoryFromJson: done ====") EndFunction Function TraceActorList(String Label, Actor[] akActors) ; Helper for DumpDirectoryFull()/DumpDirectoryFromJson(). ; Joins an Actor[] into one readable, comma-separated log line via ; _MGF_Util.GetActorNames() + _MGF_ArrayCreate.ArrayStringJoin(). String[] Names = _MGF_Util.GetActorNames(akActors) If Names.Length == 0 Debug.Trace("[_MGF_LocOpsArea] " + Label + ": (none)") Else Debug.Trace("[_MGF_LocOpsArea] " + Label + ": " + ArrayStringJoin(Names, ", ")) EndIf EndFunction ;================================================================================================== ; Properties ======================================================================================= ;================================================================================================== _MGF_LocOpsArea_PropertyStore Property Store Auto _MGF_LocOpsHold Property LocOpsHold Auto ; Attached to its own alias, OUTSIDE the 0-25 indexed range below (e.g. ; alias 26) - just the plain _MGF_LocOpsArea_Base script, no subclass. ; Used for any AreaStateName with no dedicated handler (mostly "Wilderness"), ; and as the fallback when a real handler's alias is wired wrong. _MGF_LocOpsArea_Base Property DefaultHandler Auto ; AreaStateNames is computed automatically at OnInit from the alias ; names themselves (see GenerateAreaStateNames() above) - nothing to ; fill here directly. What actually needs doing in the CK is naming ; each alias to match the list below exactly, in order. Walled-city ; pairs (e.g. Windhelm / WindhelmOutskirts) get two ALIASES both ; hosting the same handler script - see the earlier conversation on ; why duplicate aliases beat a parallel Int[] index-mapping array here. ; 26 area-state aliases total (indices 0-25), mapping to 21 distinct ; handler scripts: ; ; 0 WindhelmLocation ; 1 WindhelmOutskirts ; 2 WinterholdLocation ; 3 SolitudeLocation ; 4 SolitudeOutskirts ; 5 MarkarthLocation ; 6 MarkarthOutskirts ; 7 RiftenLocation ; 8 RiftenOutskirts ; 9 WhiterunLocation ; 10 WhiterunOutskirts ; 11 FalkreathLocation ; 12 BYOHHouse1Location (Lakeview Manor) ; 13 MorthalLocation ; 14 BYOHHouse2Location (Windstad Manor) ; 15 DawnstarLocation ; 16 BYOHHouse3Location (Heljarchen Hall) ; 17 NarzulburLocation ; 18 DushnikhYalLocation ; 19 MorKhazgurLocation ; 20 LargashburLocation ; 21 OldHroldanLocation ; 22 IvarsteadLocation ; 23 ShorsStoneLocation ; 24 RiverwoodLocation ; 25 RoriksteadLocation ; ; Alias 26 (DefaultHandler) needs no name matching anything here - it's ; excluded from this array entirely (see GenerateAreaStateNames()). ; ; Adding a new area later: attach its handler script to a new alias at ; the next free index and name that alias to match - nothing else to ; update, GenerateAreaStateNames() picks it up automatically on next load. String[] Property AreaStateNames Auto Hidden
LocOpsHold:
Spoiler
Scriptname _MGF_LocOpsHold extends Quest ; The yellow pages for Skyrim (Hold-wide edition) ;================================================================================================== ; User Functions ================================================================================== ;================================================================================================== ; Should probably stick to calling 'user functions' only. ; HoldStateName = The name of the hold with any whitespace removed. See LocationMap.json for details. ; Egs: ; 'Whiterun' ; 'ThePale' ; 'TheRift' Import _MGF_ArrayCreate Import _MGF_ArrayEdit Import _MGF_ArrayQuery Function UpdatePointsOfInterest() BeginPoiUpdate(Store.LocTrack.HoldStateName) EndFunction Bool Function GetIsSlaverunFreeHoldByString(String HoldStateName) ; True - free ; False - enslaved Return GetHandlerOrDefault(HoldStateName).IsSlaverunFreeHold() EndFunction Bool Function GetIsSlaverunFreeHoldHere() Return GetIsSlaverunFreeHoldByString(Store.LocTrack.HoldStateName) EndFunction Bool Function GetOwnsVanillaHomeInHold(String HoldStateName) Return GetHandlerOrDefault(HoldStateName).OwnsVanillaHomeInHold() EndFunction Bool Function GetOwnsVanillaHomeHere() Return GetOwnsVanillaHomeInHold(Store.LocTrack.HoldStateName) EndFunction Bool Function GetIsThaneOfHold(String HoldStateName) ; Is the player a thane of this hold Return GetHandlerOrDefault(HoldStateName).IsThaneOfHold() EndFunction Bool Function GetIsThaneOfHoldHere() ; Should work anywhere within the hold borders Return GetIsThaneOfHold(Store.LocTrack.HoldStateName) EndFunction Actor Function GetJarlForHold(String HoldStateName) Return GetHandlerOrDefault(HoldStateName).GetJarl() EndFunction Actor Function GetJarlHere() Return GetJarlForHold(Store.LocTrack.HoldStateName) EndFunction Actor Function GetStewardForHold(String HoldStateName) Return GetHandlerOrDefault(HoldStateName).GetSteward() EndFunction Actor Function GetStewardHere() Return GetStewardForHold(Store.LocTrack.HoldStateName) EndFunction Actor Function GetCourtWizardForHold(String HoldStateName) Return GetHandlerOrDefault(HoldStateName).GetCourtWizard() EndFunction Actor Function GetCourtWizardHere() Return GetCourtWizardForHold(Store.LocTrack.HoldStateName) EndFunction Actor Function GetHousecarlForHold(String HoldStateName) Return GetHandlerOrDefault(HoldStateName).GetHousecarl() EndFunction Actor Function GetHousecarlHere() Return GetHousecarlForHold(Store.LocTrack.HoldStateName) EndFunction Actor Function GetHostlerForHold(String HoldStateName) Return GetHandlerOrDefault(HoldStateName).GetHostler() EndFunction Actor Function GetHostlerHere() Return GetHostlerForHold(Store.LocTrack.HoldStateName) EndFunction Actor Function GetLicenceOfficerForHold(String HoldStateName) ; Never went through the State/GoToState system in the original - not ; hold-dependent handler logic, just a direct delegation. Return Store.ForkLicence.GetLicenceOfficerForHold(HoldStateName) EndFunction Actor Function GetLicenceOfficerHere() Return GetLicenceOfficerForHold(Store.LocTrack.HoldStateName) EndFunction ; -- Hold-level aggregation -- ; Aggregates the plural POI categories across every area within a hold ; (e.g. GetBlacksmithsForHold("Whiterun") merges Whiterun + Riverwood + ; Rorikstead + every other area GetAreaStateNamesForHold returns). ; Deliberately NOT applied to the singular leadership roles above ; (Jarl/Steward/CourtWizard/Housecarl/Hostler) - those are tied ; specifically to the hold's capital, and "aggregate all jarls in the ; hold" doesn't mean anything when only the capital has one. ; ; The "primary POI last in the array" convention within a single area's ; list doesn't carry any special meaning here - each area's own primary ; just ends up wherever that area's results land in the merge order. Actor[] Function GetInnkeepersForHold(String HoldStateName) Actor[] Result String[] Areas = Store.LocTrack.GetAreaStateNamesForHold(HoldStateName) Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetInnkeepersForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetInnkeepersHere() Return GetInnkeepersForHold(Store.LocTrack.HoldStateName) EndFunction Actor[] Function GetBlacksmithsForHold(String HoldStateName) Actor[] Result String[] Areas = Store.LocTrack.GetAreaStateNamesForHold(HoldStateName) Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetBlacksmithsForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetBlacksmithsHere() Return GetBlacksmithsForHold(Store.LocTrack.HoldStateName) EndFunction Actor[] Function GetGeneralTradersForHold(String HoldStateName) Actor[] Result String[] Areas = Store.LocTrack.GetAreaStateNamesForHold(HoldStateName) Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetGeneralTradersForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetGeneralTradersHere() Return GetGeneralTradersForHold(Store.LocTrack.HoldStateName) EndFunction Actor[] Function GetAlchemistsForHold(String HoldStateName) Actor[] Result String[] Areas = Store.LocTrack.GetAreaStateNamesForHold(HoldStateName) Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetAlchemistsForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetAlchemistsHere() Return GetAlchemistsForHold(Store.LocTrack.HoldStateName) EndFunction Actor[] Function GetPriestsForHold(String HoldStateName) Actor[] Result String[] Areas = Store.LocTrack.GetAreaStateNamesForHold(HoldStateName) Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetPriestsForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetPriestsHere() Return GetPriestsForHold(Store.LocTrack.HoldStateName) EndFunction ; Custom role aggregation - same pattern, ObjectReference[] rather than ; Actor[] since GetCustomRoleForArea can return non-Actor entries ; (shrines, etc.). ObjectReference[] Function GetCustomRoleForHold(String HoldStateName, String RoleName) ObjectReference[] Result String[] Areas = Store.LocTrack.GetAreaStateNamesForHold(HoldStateName) Int i = 0 While i < Areas.Length Result = ArrayRefMerge(Result, LocOpsArea.GetCustomRoleForArea(Areas[i], RoleName)) i += 1 EndWhile Return Result EndFunction ObjectReference[] Function GetCustomRoleHere(String RoleName) Return GetCustomRoleForHold(Store.LocTrack.HoldStateName, RoleName) EndFunction ;================================================================================================== ; Handler Dispatch ================================================================================ ;================================================================================================== ; Same design as _MGF_LocOpsArea - alias order on this quest IS the ; lookup table. GetNthAlias(i) returns the alias, cast to ; _MGF_LocOpsHold_Base resolves to whichever specific handler script is ; attached to it. No registration, no array property, nothing to ; bootstrap at runtime - only the CK-side alias ordering to keep correct ; (see the property doc comment below). ; ; Returns None if HoldStateName isn't in the table, or if the alias at ; that index has no matching script attached. Most callers should use ; GetHandlerOrDefault() below. _MGF_LocOpsHold_Base Function GetHandler(String HoldStateName) Int Index = HoldStateNames.Find(HoldStateName) If Index >= 0 Return GetNthAlias(Index) as _MGF_LocOpsHold_Base EndIf Return None EndFunction _MGF_LocOpsHold_Base Function GetHandlerOrDefault(String HoldStateName) _MGF_LocOpsHold_Base Handler = GetHandler(HoldStateName) If Handler Return Handler EndIf Return DefaultHandler EndFunction ; Every HoldStateName with a dedicated handler script - i.e. exactly ; what GetHandler() can resolve to something other than DefaultHandler. String[] Function GetSpecialHolds() Return HoldStateNames EndFunction ; Debug utility - walks HoldStateNames and prints what each alias index ; actually resolves to, so a CK wiring mistake (wrong script on an ; alias, or an alias left empty) shows up as a visible None in the log ; rather than a silent wrong-handler bug later. Call from console via ; "cgf _MGF_LocOpsHold DumpDirectory" (adjust to your actual quest ; editor ID) or from another debug script holding a reference to this ; quest. ; Debug utility - walks HoldStateNames and prints what each alias index ; actually resolves to, so a CK wiring mistake (wrong script on an ; alias, or an alias left empty) shows up as a visible None in the log ; rather than a silent wrong-handler bug later. Cheap - just resolves ; the handler, doesn't touch StorageUtil/aliases. See DumpDirectoryFull() ; below for a full data dump. Call from console via ; "cgf _MGF_LocOpsHold DumpHandlers" (adjust to your actual quest editor ; ID) or from another debug script holding a reference to this quest. Function DumpHandlers() Debug.Trace("[_MGF_LocOpsHold] ==== DumpHandlers: " + HoldStateNames.Length + " entries ====") Int i = 0 While i < HoldStateNames.Length _MGF_LocOpsHold_Base Handler = GetNthAlias(i) as _MGF_LocOpsHold_Base String Status = "" If !Handler Status = " <<< NONE - no script attached to this alias, or wrong script type" EndIf Debug.Trace("[_MGF_LocOpsHold] [" + i + "] " + HoldStateNames[i] + " -> " + Handler + Status) i += 1 EndWhile Debug.Trace("[_MGF_LocOpsHold] DefaultHandler -> " + DefaultHandler) Debug.Trace("[_MGF_LocOpsHold] ==== DumpHandlers: done ====") EndFunction ; Debug utility - cycles through every HoldStateName and calls the real ; accessor functions (GetJarlForHold, GetStewardForHold, etc.) - the ; same path an actual caller takes, not just alias resolution - printing ; readable names via _MGF_Util.GetActorName instead of raw object ; references. Call from console via "cgf _MGF_LocOpsHold DumpDirectoryFull". Function DumpDirectoryFull() Debug.Trace("[_MGF_LocOpsHold] ==== DumpDirectoryFull: " + HoldStateNames.Length + " entries ====") Int i = 0 While i < HoldStateNames.Length String HoldStateName = HoldStateNames[i] _MGF_LocOpsHold_Base Handler = GetNthAlias(i) as _MGF_LocOpsHold_Base If !Handler Debug.Trace("[_MGF_LocOpsHold] [" + i + "] " + HoldStateName + " <<< NONE - no script attached to this alias, or wrong script type") Else Debug.Trace("[_MGF_LocOpsHold] [" + i + "] " + HoldStateName + " (" + Handler + ")") Debug.Trace("[_MGF_LocOpsHold] IsSlaverunFreeHold: " + GetIsSlaverunFreeHoldByString(HoldStateName)) Debug.Trace("[_MGF_LocOpsHold] OwnsVanillaHome: " + GetOwnsVanillaHomeInHold(HoldStateName)) Debug.Trace("[_MGF_LocOpsHold] IsThaneOfHold: " + GetIsThaneOfHold(HoldStateName)) Debug.Trace("[_MGF_LocOpsHold] Jarl: " + _MGF_Util.GetActorName(GetJarlForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] Steward: " + _MGF_Util.GetActorName(GetStewardForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] CourtWizard: " + _MGF_Util.GetActorName(GetCourtWizardForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] Housecarl: " + _MGF_Util.GetActorName(GetHousecarlForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] Hostler: " + _MGF_Util.GetActorName(GetHostlerForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] LicenceOfficer: " + _MGF_Util.GetActorName(GetLicenceOfficerForHold(HoldStateName))) TraceActorList("Innkeepers (aggregate)", GetInnkeepersForHold(HoldStateName)) TraceActorList("Blacksmiths (aggregate)", GetBlacksmithsForHold(HoldStateName)) TraceActorList("GeneralTraders (aggregate)", GetGeneralTradersForHold(HoldStateName)) TraceActorList("Alchemists (aggregate)", GetAlchemistsForHold(HoldStateName)) TraceActorList("Priests (aggregate)", GetPriestsForHold(HoldStateName)) EndIf i += 1 EndWhile Debug.Trace("[_MGF_LocOpsHold] DefaultHandler -> " + DefaultHandler) Debug.Trace("[_MGF_LocOpsHold] ==== DumpDirectoryFull: done ====") EndFunction ; Helper for DumpDirectoryFull()/DumpDirectoryFromJson() - joins an ; Actor[] into one readable, comma-separated log line via ; _MGF_Util.GetActorNames() + _MGF_ArrayQuery.ArrayStringJoin(). Function TraceActorList(String Label, Actor[] akActors) String[] Names = _MGF_Util.GetActorNames(akActors) If Names.Length == 0 Debug.Trace("[_MGF_LocOpsHold] " + Label + ": (none)") Else Debug.Trace("[_MGF_LocOpsHold] " + Label + ": " + ArrayStringJoin(Names, ", ")) EndIf EndFunction ; Debug utility - reads the full hold list straight from the ; authoritative LocationMap.json "holdstates" array (10 entries - ; "Wilderness" plus the 9 real holds) rather than our own HoldStateNames ; property. For Hold this covers the same ground either way (no holds ; are missing a dedicated handler the way DragonBridgeLocation was on ; the Area side), but keeps this dump automatically in sync with the ; JSON source if a hold is ever added, rather than needing a hardcoded ; list update. Note the aggregate categories can get large for ; "Wilderness" specifically - GetAreaStateNamesForHold("Wilderness") ; returns every area not assigned to one of the 9 real holds, so the ; merged Innkeepers/Blacksmiths/etc lists here cover a lot more ground ; than a real hold's would. Call from console via ; "cgf _MGF_LocOpsHold DumpDirectoryFromJson". Function DumpDirectoryFromJson() String JsonPath = "Data/SKSE/Plugins/StorageUtilData/Mcgufin/Internal/LocationMap.json" String[] AllHoldStates = _MGF_File.JsonStringListGet(JsonPath, "holdstates") Debug.Trace("[_MGF_LocOpsHold] ==== DumpDirectoryFromJson: " + AllHoldStates.Length + " entries from LocationMap.json ====") Int i = 0 While i < AllHoldStates.Length String HoldStateName = AllHoldStates[i] Debug.Trace("[_MGF_LocOpsHold] [" + i + "] " + HoldStateName) Debug.Trace("[_MGF_LocOpsHold] IsSlaverunFreeHold: " + GetIsSlaverunFreeHoldByString(HoldStateName)) Debug.Trace("[_MGF_LocOpsHold] OwnsVanillaHome: " + GetOwnsVanillaHomeInHold(HoldStateName)) Debug.Trace("[_MGF_LocOpsHold] IsThaneOfHold: " + GetIsThaneOfHold(HoldStateName)) Debug.Trace("[_MGF_LocOpsHold] Jarl: " + _MGF_Util.GetActorName(GetJarlForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] Steward: " + _MGF_Util.GetActorName(GetStewardForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] CourtWizard: " + _MGF_Util.GetActorName(GetCourtWizardForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] Housecarl: " + _MGF_Util.GetActorName(GetHousecarlForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] Hostler: " + _MGF_Util.GetActorName(GetHostlerForHold(HoldStateName))) Debug.Trace("[_MGF_LocOpsHold] LicenceOfficer: " + _MGF_Util.GetActorName(GetLicenceOfficerForHold(HoldStateName))) TraceActorList("Innkeepers (aggregate)", GetInnkeepersForHold(HoldStateName)) TraceActorList("Blacksmiths (aggregate)", GetBlacksmithsForHold(HoldStateName)) TraceActorList("GeneralTraders (aggregate)", GetGeneralTradersForHold(HoldStateName)) TraceActorList("Alchemists (aggregate)", GetAlchemistsForHold(HoldStateName)) TraceActorList("Priests (aggregate)", GetPriestsForHold(HoldStateName)) i += 1 EndWhile Debug.Trace("[_MGF_LocOpsHold] ==== DumpDirectoryFromJson: done ====") EndFunction ;================================================================================================== ; Helper Functions ================================================================================ ;================================================================================================== Event OnInit() GenerateHoldStateNames() EndEvent ; Same reasoning as _MGF_LocOpsArea's GenerateAreaStateNames() - see ; that comment for the full explanation of GetAliasNames' parameters ; and why SkipUnfilled=False matters. Alias 9 (DefaultHandler) has no ; corresponding HoldStateName and is excluded the same way. Function GenerateHoldStateNames() HoldStateNames = _MGF_Util.GetAliasNames(Self, 1, False) HoldStateNames = ArrayStringPluck(HoldStateNames, HoldStateNames.Length - 1) EndFunction Function BeginPoiUpdate(String HoldStateName) If HoldStateName == "" ; unknown ClearPointsOfInterest() Else FillPointsOfInterest(HoldStateName) EndIf EndFunction Function FillPointsOfInterest(String HoldStateName) ; Single GetHandler() resolution for the whole update, same reasoning ; as _MGF_LocOpsArea's version - one lookup instead of five separate ; ForHold() calls each re-resolving the handler independently. _MGF_LocOpsHold_Base Handler = GetHandlerOrDefault(HoldStateName) FillAlias(Store.PoiAliasesHold[0], Handler.GetJarl()) FillAlias(Store.PoiAliasesHold[1], Handler.GetSteward()) FillAlias(Store.PoiAliasesHold[2], Handler.GetCourtWizard()) FillAlias(Store.PoiAliasesHold[3], Handler.GetHousecarl()) FillAlias(Store.PoiAliasesHold[4], Handler.GetHostler()) FillAlias(Store.PoiAliasesHold[5], Store.ForkLicence.GetLicenceOfficerForHold(HoldStateName)) EndFunction Function ClearPointsOfInterest() Int i = 0 While i < Store.PoiAliasesHold.Length Store.PoiAliasesHold[i].Clear() i += 1 EndWhile EndFunction Function FillAlias(ReferenceAlias akAlias, ObjectReference ObjRef) If ObjRef akAlias.ForceRefTo(ObjRef) Else akAlias.Clear() EndIf EndFunction ;================================================================================================== ; Properties ======================================================================================= ;================================================================================================== _MGF_LocOpsArea_PropertyStore Property Store Auto ; Needed for Hold-level POI aggregation - Hold calls Area's own ; GetXForArea() accessors for every area within a hold and merges the ; results. _MGF_LocOpsArea Property LocOpsArea Auto ; Attached to its own alias, OUTSIDE the 0-8 indexed range below (e.g. ; alias 9) - just the plain _MGF_LocOpsHold_Base script, no subclass. ; Used for any HoldStateName with no dedicated handler, and as the ; fallback when a real handler's alias is wired wrong. _MGF_LocOpsHold_Base Property DefaultHandler Auto ; HoldStateNames is computed automatically at OnInit from the alias ; names themselves (see GenerateHoldStateNames() above) - nothing to ; fill here directly. What actually needs doing in the CK is naming ; each alias to match the list below exactly, in order. No ; Outskirts-style pairing here - every hold gets exactly one alias: ; ; 0 Eastmarch ; 1 Falkreath ; 2 Haafingar ; 3 Hjaalmarch ; 4 ThePale ; 5 TheReach ; 6 TheRift ; 7 Whiterun ; 8 Winterhold ; ; Alias 9 (DefaultHandler) needs no name matching anything here - it's ; excluded from this array entirely (see GenerateHoldStateNames()). ; ; Adding a new hold later: attach its handler script to a new alias at ; the next free index and name that alias to match - nothing else to ; update, GenerateHoldStateNames() picks it up automatically on next load. String[] Property HoldStateNames Auto Hidden
LocOpsWorld:
Spoiler
Scriptname _MGF_LocOpsWorld extends Quest ; The yellow pages for Skyrim (world edition) Import _MGF_ArrayCreate Import _MGF_ArrayEdit Import _MGF_ArrayQuery Actor[] Function GetAllJarls() Actor[] Result String[] Holds = Store.LocTrack.GetAllHolds() Int i = 0 While i < Holds.Length Actor Jarl = LocOpsHold.GetJarlForHold(Holds[i]) If Jarl Result = ArrayActorAppend(Result, Jarl) EndIf i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllJarlsByProximity() Actor[] akActors = GetAllJarls() ObjectReference[] akRefs = _MGF_Util.SortRefsByAnchoredDistance(Store.PlayerRef, ActorsToRefs(akActors), DropUnknowns = true) Return RefsToActors(akRefs) EndFunction Actor[] Function GetAllStewards() Actor[] Result String[] Holds = Store.LocTrack.GetAllHolds() Int i = 0 While i < Holds.Length Actor Steward = LocOpsHold.GetStewardForHold(Holds[i]) If Steward Result = ArrayActorAppend(Result, Steward) EndIf i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllCourtWizards() Actor[] Result String[] Holds = Store.LocTrack.GetAllHolds() Int i = 0 While i < Holds.Length Actor CourtWizard = LocOpsHold.GetCourtWizardForHold(Holds[i]) If CourtWizard Result = ArrayActorAppend(Result, CourtWizard) EndIf i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllHousecarls() Actor[] Result String[] Holds = Store.LocTrack.GetAllHolds() Int i = 0 While i < Holds.Length Actor Housecarl = LocOpsHold.GetHousecarlForHold(Holds[i]) If Housecarl Result = ArrayActorAppend(Result, Housecarl) EndIf i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllHostlers() Actor[] Result String[] Holds = Store.LocTrack.GetAllHolds() Int i = 0 While i < Holds.Length Actor Hostler = LocOpsHold.GetHostlerForHold(Holds[i]) If Hostler Result = ArrayActorAppend(Result, Hostler) EndIf i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllLicenceOfficers() Actor[] Result String[] Holds = Store.LocTrack.GetAllHolds() Int i = 0 While i < Holds.Length Actor LicenceOfficer = LocOpsHold.GetLicenceOfficerForHold(Holds[i]) If LicenceOfficer Result = ArrayActorAppend(Result, LicenceOfficer) EndIf i += 1 EndWhile Return Result EndFunction ; -- Flat (world) aggregation -- ; Aggregates the plural POI categories across EVERY area in the game, ; not just one hold - GetAllBlacksmiths() is the union of every ; GetBlacksmithsForHold() call across GetAllHolds(). Goes straight ; through LocTrack.GetAllAreas() rather than nesting through ; GetAllHolds()/GetAreaStateNamesForHold() - the Hold grouping means ; nothing to a "give me everything" caller, so there's no reason to ; pay for the extra tier of dispatch. ; ; Deliberately NOT cached. Several _MGF_LocOpsArea_Base subclasses ; override the plural getters with alias reads that swap on actor ; death or civil war status (see that script's GetCustomRole() warning ; about per-town alias overrides) - a cache only rebuilt on ; LocTrack.ReloadJsons() would go stale exactly when those swaps ; happen, which defeats the point of the alias-dispatch system. If a ; real caller later proves this walk is too expensive to run live, ; invalidate on whatever event/state-change the alias swaps themselves ; react to - not on JSON reload, which is the wrong signal for this ; data. ; ; Scope note: GetAllAreas() is backed by LocationMap.json's ; "locationstates", which today only covers the Skyrim worldspace (9 ; holds + Wilderness). Sovngarde/The Soul Cairn/Solstheim exist one ; tier up in "worldstates" but have no area-level breakdown yet, so ; there is currently no meaningfully different "every worldspace" ; variant to build - it would return identical results to these. Only ; add one if/when those worlds get their own area entries in the JSON. Actor[] Function GetAllInnkeepers() Actor[] Result String[] Areas = Store.LocTrack.GetAllAreas() Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetInnkeepersForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllBlacksmiths() Actor[] Result String[] Areas = Store.LocTrack.GetAllAreas() Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetBlacksmithsForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllGeneralTraders() Actor[] Result String[] Areas = Store.LocTrack.GetAllAreas() Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetGeneralTradersForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllAlchemists() Actor[] Result String[] Areas = Store.LocTrack.GetAllAreas() Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetAlchemistsForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction Actor[] Function GetAllPriests() Actor[] Result String[] Areas = Store.LocTrack.GetAllAreas() Int i = 0 While i < Areas.Length Result = ArrayActorMerge(Result, LocOpsArea.GetPriestsForArea(Areas[i])) i += 1 EndWhile Return Result EndFunction ObjectReference[] Function GetAllCustomRole(String RoleName) ObjectReference[] Result String[] Areas = Store.LocTrack.GetAllAreas() Int i = 0 While i < Areas.Length Result = ArrayRefMerge(Result, LocOpsArea.GetCustomRoleForArea(Areas[i], RoleName)) i += 1 EndWhile Return Result EndFunction ;================================================================================================== ; Properties ======================================================================================= ;================================================================================================== _MGF_LocOpsArea_PropertyStore Property Store Auto _MGF_LocOpsArea Property LocOpsArea Auto _MGF_LocOpsHold Property LocOpsHold Auto
Edited by Monoman1
1
0 Comments
Recommended Comments
There are no comments to display.