-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathislandtocsv.py
229 lines (149 loc) · 5.7 KB
/
islandtocsv.py
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
"""Code flow to retrieve island infos.
Needed for each island:
1) Size
2) Region,
3) difficulty
4) id/type
5) gamemode
assets.xml:
Retrieve all RandomIsland entries. Each entry has:
FilePath: To a7m file.
IslandRegion: Moderate, ...
IslandDifficulty: Normal;Hard
IslandType: Normal;Starter;Decoration;ThirdParty;PirateIsland
IslandBaseName: Is the same for river and nonriver variant (optional)
AllowedGameType: SandboxSingleplayer;SandboxMultilayer;CampaignMode (optional, default value: all three)
Now we have 2,3,4,5.
assets.xml:
Retrieve .//MapGenerator/IslandSizes
This tells us the xsize and ysize that corresponds that allow us to sort islands into the three lists:
0) Small, 1) Medium, 2) Large
Islands do NOT tell just tell us their size directly. We must use this.
Open up the a7minfo for each island (next to the a7m) and retrieve the ActiveMapRect (x0,y0,x1,x2):
xsize=x1-x0
ysize=y1-y0
for xlim,ylim in IslandSizes:
if xsize<=xlim and ysize<=ylim:
Remember this size for this island.
Now we have everything.
Sort by name/path and save as raw.csv for further processing.
Further processing:
Split into 3 lists depending on size.
Filter so that we only keep id<=3.
Binarize the enums into bitmasks.
Save as csv.
"""
import sys,os,zlib, shutil
from binascii import hexlify, unhexlify
from struct import pack,unpack
import pandas as pd
import xml.etree.ElementTree as ET
import matplotlib.pyplot as plt
from io import StringIO
root = ".." # Path to all extracted files.
ASSET_PATH = root+"/data/config/export/main/asset/assets.xml"
pd.options.display.max_colwidth = 100
pd.options.display.width = 0
pd.options.display.max_rows = 200
try: os.mkdir("islands")
except: pass
def SaveRaw():
"""Create the raw.csv file with all islands in one place."""
# Retrieve RandomIsland data.
assets = ET.parse(ASSET_PATH)
node = assets.getroot()
islands = node.findall(".//RandomIsland")
df = pd.DataFrame()
for island in islands:
df2 = pd.read_xml(StringIO(ET.tostring(island).decode()),xpath=".")
df = pd.concat([df, df2])
df = df.set_index("FilePath")
# Get the IslandSizes from the mapgenerator.
mapgenerator = node.findall(".//MapGenerator")
assert len(mapgenerator) == 1, "Multiple map generators found."
mapgenerator = mapgenerator[0]
def Df(node, xpath="."):
"""Same as a normal pd.read_xml except that we include rownames."""
data = ET.tostring(node).decode()
df = pd.read_xml(StringIO(data),xpath=xpath+"/*")
df.index = pd.read_xml(StringIO(data), xpath=xpath).columns
return df
islandsizes = Df(mapgenerator, "IslandSizes")
# Now go through the a7minfo files.
sizes = []
for path in df.index:
a7minfo = root +"/"+path+"info"
assert os.path.exists(a7minfo), f"Cannot find {a7minfo}"
cmd = os.path.normpath(r"../FileDBReader/FileDBReader.exe decompress -y -f "+a7minfo)
rv = os.system(cmd)
if rv:
raise Exception("Could not run cmd. FileDBReader not found?")
node = ET.parse(a7minfo[:-8]+".xml").getroot()
rectangle = node.find("ActiveMapRect")
if rectangle is None:
print("No rectangle data:",a7minfo)
continue
x0,y0,x1,y1 = unpack("IIII",unhexlify(rectangle.text))
xsize = x1-x0
ysize = y1-y0
# Find the right size.
for sz,(xlim,ylim) in islandsizes.iterrows():
if xsize<=xlim and ysize<=ylim:
sizes.append(sz)
break
else:
sizes.append("Very large") # Unused.
df["sz"] = sizes
df = df.sort_index()
df.to_csv("islands/raw.csv")
SaveRaw()
# Now work a bit with that island data.
# We need to split it into three lists.
def ShortName(name, size):
"""E.g.
data/sessions/islands/pool/moderate/moderate_l_01/moderate_l_01.a7m => L1
data/sessions/islands/pool/colony01/colony01_l_01/colony01_l_01_river_01.a7m => L1R
data/sessions/islands/pool/moderate/community_island/community_island.a7m => CI
"""
if name=="Pirate" or name.startswith("NPC"): return name
sz = "SML"[size]
name = name.split(".")[0].split("/")[-1]
if "_river" in name:
assert "_river_01" in name
river = "R"
name = name.replace("_river_01","")
else:
river = ""
if name.startswith("community_island"): return "CI"+river
suffix = ""
try:
num = str(int(name.split("_")[-1]))
except ValueError:
num = str(int(name.split("_")[-2]))
suffix = name.split("_")[-1]
return sz+num+river+suffix
df = pd.read_csv("islands/raw.csv", index_col = 0)
from util import SIZES, REGIONS, DIFFS, GAMEMODES, ISLAND_TYPE
def BitMask(s, table, defaultval=0):
"""Return the bitmask of s, using table (dictionary).
E.g. BitMask("Normal;Hard", DIFFS) => 3
"""
if pd.isna(s):
return defaultval
mask = 0
for word in s.split(";"):
mask += table[word]
return mask
df["id"] = df.IslandType.apply(BitMask, args=[ISLAND_TYPE])
df["diff"] = df.IslandDifficulty.apply(BitMask, args=[DIFFS])
df["region"] = df.IslandRegion.apply(BitMask, args=[REGIONS])
df["gamemode"] = df.AllowedGameType.apply(BitMask, args=[GAMEMODES, 7])
for sz in ["Small","Medium","Large"]:
data = df[(df.sz == sz) & (df.id<=3)].copy()
intsize = SIZES.index(sz)
data["shortname"] = data.index.map(lambda x:ShortName(x,intsize))
# Needed columns:
# path, region, three, diff, id, gamemode, shortname.
data = data.loc[:,["region","diff","id","gamemode","shortname"]]
data.index.name = "path"
data.to_csv(f"islands/{sz}.csv")