-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathMapNameHelper.cs
103 lines (93 loc) · 4.6 KB
/
MapNameHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// This file is part of Screenshot Location and Metadata.
//
// Screenshot Location and Metadata 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.
//
// Screenshot Location and Metadata 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
// Screenshot Location and Metadata. If not, see https://www.gnu.org/licenses/.
using System.Linq;
using NetScriptFramework.SkyrimSE;
namespace ScreenshotLocationAndMetadata
{
static class MapNameHelper
{
/// <summary>
/// Attempts to get the cell name as displayed on the map, load doors, and save games. For interior cells, this
/// is the cell name itself. For exterior worldspace cells, the game seems to look at all of the cell's Regions
/// for the Region Data Entry of type Map with the highest Priority and uses its Map Name. The Override flag
/// seems to be irrelevant. The order of Regions also does not matter (xEdit simply sorts them by form id).
///
/// <para />
///
/// For most places this ends up being BorderRegionSkyrim which has a Map entry named "Skyrim". With Unique
/// Region Names installed, hold-specific Regions are added to every worldspace cell that override the former
/// with names like "Haafingar" or "Whiterun Hold".
///
/// <para />
///
/// Note that this is different from using CurrentLocation, as that refers to the Location record which serves
/// other purposes. As an example, if that were used, the Winterhold "Hall of Countenance" would become
/// "Dormitory" (that being the name of its LCTN record). On the other hand, KatlasFarmExterior (the Solitude
/// stables) has the Location "Katla's Farm," while a save game in that cell, and the door leaving the stables
/// building, simply says "Haafingar" (or "Skyrim").
/// </summary>
/// <remarks>
/// The local map appears to always show the worldspace name when outside (always "Skyrim"), not the region name
/// even with Unique Region Names. For outside saves to use the region name, Regional Save Names is required, as
/// apparently the naming behavior was changed in SSE.
/// </remarks>
/// <param name="cell">The cell for which to get the appropriate map name.</param>
public static string GetMapName(TESObjectCELL cell)
{
if (cell is null)
{
return null;
}
// For interior cells, the name used is simply the cell name
if (cell.IsInterior)
{
return cell.Name;
}
// Look for the cell record's list of Regions
TESRegionList regionList = FindRegionList(cell.ExtraDataList);
if (regionList is null)
{
return cell.Name;
}
// Find the Map data with the highest priority
TESRegionDataMap mapData = regionList
.SelectMany(r => MemoryLinkedList<TESRegionData>.FromAddress(r.DataList))
.OfType<TESRegionDataMap>()
.OrderBy(d => d.Header.Priority)
.LastOrDefault();
// Return its map name, or fall back again to cell name if there are no regions with map data
return mapData?.MapName.Text ?? cell.Name;
}
/// <summary>
/// Searches the ExtraDataList for the list of Regions. For some reason this isn't just a property on
/// TESObjectCell but hidden within a linked list of various data types which we need to traverse by hand.
/// </summary>
/// <param name="linkedList">The <see cref="TESObjectCELL.ExtraDataList"/>.</param>
/// <returns>The <see cref="TESRegionList"/>, or null if none was found.</returns>
private static TESRegionList FindRegionList(BSExtraDataList linkedList)
{
var item = linkedList.First;
while (!(item is null))
{
if (item is ExtraRegionList regionListItem)
{
return regionListItem.RegionList;
}
item = item.Next;
}
return null;
}
}
}