1#!/usr/bin/env python3
2"""Translates map coordinate to map file and folder and vice-versa
3Run this script with -h for full usage information.
4
5Info about output:
6     Map: In game map coordinate number
7    File: Where coordinate is found on the maps folder
8  Folder: Range of submaps the folder can have
9Overmaps: Number of overmaps the folder contains
10
11Examples with map format:
12
13    %(prog)s 6.1.-3.map
14    %(prog)s -4.8.1.map
15    %(prog)s -9.-9.17.map
16
17Examples using folder format:
18
19    %(prog)s 1.0.0
20    %(prog)s -7.3.5
21    %(prog)s 8.-9.-1
22
23Examples with map coordinate format:
24
25    %(prog)s "5'107 3'146"
26    %(prog)s "7'98 -7'137 5"
27    %(prog)s "-1'0 0'4 -8"
28
29Examples of INVALID map coordinate format:
30
31    %(prog)s "3'180 3'17"    -> Number after the ' must be between 0 and 179
32    %(prog)s "7'31  5'-17 1" -> Number after the ' must be positive
33    %(prog)s 1'13 3'17 1   -> Quotation marks are needed (")
34                              to prevent string escaping
35
36Examples with range:
37
38    %(prog)s "0'31 0'0" --range "0'32 0'0"
39    %(prog)s "1'5 4'3 5" --range "-7'1 -9'179 -7"
40    %(prog)s 7.3.0.map --range -1.73.1.map
41"""
42
43import sys
44import argparse
45import re
46
47
48def print_info() -> None:
49    print(
50        """
51    The game map is divided in overmaps
52    Each overmap is divided in overmap terrain tiles (OTT) in a 180x180 grid
53    Each OTT is 24x24 tiles divided into 4 submaps ( terrain )
54    Each OTT is a `.map` file
55    Each map folder is 32x32 OTTs
56    """
57    )
58
59
60def info_map(format: str) -> list:
61    mx, cx = [int(n) for n in format.split()[0].split("'")]
62    my, cy = [int(n) for n in format.split()[1].split("'")]
63
64    dx = mx * 180 + cx
65    dy = my * 180 + cy
66    dz = 0
67
68    if len(format.split()) == 3:
69        dz = int(format.split()[2])
70
71    return (dx, dy, dz)
72
73
74def info_file(format: str) -> list:
75    cx, cy, cz = [int(n) for n in format.split(".")[:3]]
76    return (cx, cy, cz)
77
78
79def info_folder(format: str) -> None:
80    cx, cy, cz = [int(n) for n in format.split(".")]
81    min_cx, max_cx = cx * 32, cx * 32 + 31
82    min_cy, max_cy = cy * 32, cy * 32 + 31
83
84    o1 = f"{min_cx//180} {min_cy//180}"
85    o2 = f"{min_cx//180} {max_cy//180}"
86    o3 = f"{max_cx//180} {min_cy//180}"
87    o4 = f"{max_cx//180} {max_cy//180}"
88    ol = [o1]
89    if o2 not in ol:
90        ol.append(o2)
91    if o3 not in ol:
92        ol.append(o3)
93    if o4 not in ol:
94        ol.append(o4)
95
96    print(f"  Folder:  {min_cx}.{min_cy}.{cz}.map -> "
97          f"{max_cx}.{max_cy}.{cz}.map")
98    print("Overmaps:  ", end="")
99    for k, v in enumerate(ol):
100        if k == len(ol) - 1:
101            print(v)
102        else:
103            print(v, end=", ")
104
105
106def get_range_list(value1: int, value2: int) -> list:
107    min_value = min(value1, value2)
108    max_value = max(value1, value2)
109
110    if min_value == max_value:
111        return [min_value]
112    else:
113        return list(range(min_value, max_value + 1))
114
115
116def info_range(r1: tuple, r2: tuple) -> None:
117
118    lvx = get_range_list(r1[0], r2[0])
119    lvy = get_range_list(r1[1], r2[1])
120    lvz = get_range_list(r1[2], r2[2])
121
122    for z in lvz:
123        for y in lvy:
124            for x in lvx:
125                print(f"{x//32}.{y//32}.{z}/{x}.{y}.{z}.map", end=" ")
126    print("")
127
128
129if __name__ == "__main__":
130
131    if len(sys.argv) > 1:
132        for key, arg in enumerate(sys.argv[1:]):
133            if arg[0] == "-" and arg[1].isdigit():
134                argvCopy = " " + arg
135                sys.argv[key + 1] = argvCopy
136
137    parser = argparse.ArgumentParser(
138        description=__doc__,
139        formatter_class=argparse.RawTextHelpFormatter,
140    )
141    parser.add_argument(
142        "format",
143        type=str,
144        nargs="?",
145        help="""coordinate format
146Accepted formats:
147Folders:    x.y.z
148Maps:       x.y.z.map
149Coordinate: \"x'(0->179) y'(0->179)\" or \"x'(0->179) y'(0->179) z\"
150    Default for z is 0
151        """,
152    )
153    parser.add_argument("--info", action="store_true",
154                        help="Print info about maps")
155    parser.add_argument(
156        "--range", help="Print maps in range using maps or coordinate format"
157    )
158    args = parser.parse_args(sys.argv[1:])
159
160    if not args.format:
161        print("Use --help to view usage")
162        sys.exit(1)
163
164    args.format = args.format.strip()
165
166    if args.info:
167        print_info()
168
169    elif args.range:
170        args.range = args.range.strip()
171        matchFile = re.compile(r"^-?\d+\.-?\d+\.-?\d+\.map$")
172        matchCoord = re.compile(r"^-?\d'-?\d+\s+-?\d+'-?\d+(\s+-?\d+)?$")
173
174        if re.match(matchFile, args.format):
175            if re.match(matchFile, args.range):
176                retInfoR = info_file(args.range)
177
178            elif re.match(matchCoord, args.range):
179                retInfoR = info_map(args.range)
180
181            else:
182                print("Invalid format for range, use --help to view usage")
183                sys.exit(1)
184
185            retInfoF = info_file(args.format)
186
187        elif re.match(matchCoord, args.format):
188            if re.match(matchFile, args.range):
189                retInfoR = info_file(args.range)
190
191            elif re.match(matchCoord, args.range):
192                retInfoR = info_map(args.range)
193
194            else:
195                print("Invalid format for range, use --help  to view usage")
196                sys.exit(1)
197
198            retInfoF = info_map(args.format)
199
200        else:
201            print("Invalid format, print help to view usage")
202            sys.exit(1)
203
204        info_range(retInfoF, retInfoR)
205
206    else:
207        args.format = args.format.strip()
208
209        retInfo = False
210
211        # .map file pattern
212        if re.match(r"^-?\d+\.-?\d+\.-?\d+\.map$", args.format):
213            retInfo = info_file(args.format)
214
215        # .map folder pattern
216        elif re.match(r"^-?\d+\.-?\d+\.-?\d+$", args.format):
217            info_folder(args.format)
218
219        # map coordinate pattern
220        elif re.match(
221            r"^-?\d'(\d{0,2}|1[0-7]\d)\s+-?\d+'(\d{0,2}|1[0-7]\d)(\s+-?\d+)?$",
222            args.format,
223        ):
224            retInfo = info_map(args.format)
225        elif re.match(r"^-?\d'-?\d+\s+-?\d+'-?\d+(\s+-?\d+)?$", args.format):
226            print(
227                "Invalid range for map coordination,"
228                " a'b where b is positive from 0 to 179"
229            )
230        else:
231            print("Invalid format, print help to view usage")
232
233        if retInfo:
234            cx, cy, cz = retInfo
235
236            print(
237                f"     Map:  {cx//180}'{cx - 180 * (cx//180)}"
238                f"  {cy//180}'{cy- 180 * (cy//180)}  {cz}"
239            )
240
241            print(f"    File:  {cx//32}.{cy//32}.{cz}/{cx}.{cy}.{cz}.map")
242
243            info_folder(f"{cx//32}.{cy//32}.{cz}")
244