-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstarmap.py
339 lines (274 loc) · 10.6 KB
/
starmap.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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
import struct
import pygame
QTY_STARS = 42010
SCREEN_SIZE = 800
class Vector3:
def __init__(self, x, y, z):
self.x = x
self.y = -y
self.z = z
def __str__(self):
return f'({self.x}, {self.y}, {self.z})'
def __repr__(self):
return f'Vector3({self.x!r}, {self.y!r}, {self.z!r})'
class cStarRecord:
def __init__(self, key, name, unk, flags, position, type, planet_count):
self.key = key
self.name = name
self.unk = unk
self.flags = flags
self.position = position
self.type = type
self.planet_count = planet_count
def csv(self):
csv = '{},{},{},{},{},{},{}\n'.format(
self.key,
self.name,
self.unk,
self.flags,
self.position.x, self.position.y, self.position.z,
self.type,
self.planet_count
)
class ViewMatrix:
def __init__(self, x=0.0, y=0.0, scale=0.5, translation_factor=100):
self.x = x
self.y = y
self.scale = scale
self.translation_factor = translation_factor
def translate(self, dx, dy):
# Increase the translation amount as the zoom increases
self.x += dx * self.translation_factor
self.y += dy * self.translation_factor
def zoom(self, factor):
# Translate the view matrix so that the center of the screen is at the origin
center_x = SCREEN_SIZE / self.scale / 2 + self.x
center_y = SCREEN_SIZE / self.scale / 2 + self.y
self.translate(-center_x, -center_y)
# Perform the zoom
self.scale *= factor
# Translate the view matrix back
self.translate(center_x, center_y)
# Adjust the translation factor as the zoom level changes
self.translation_factor /= factor
def set_offset(self, x, y):
self.x = x
self.y = y
def read_stars_from_file(filename):
# Init empty stars array
star_list = []
# Read all stars from file into star list
with open(filename, 'rb') as star_file:
for i in range(QTY_STARS):
key_bytes = star_file.read(4)
mKey = int.from_bytes(key_bytes, 'big')
mKey = key_bytes # temp
# Skip 156 bytes
star_file.read(132)
# Read star position (3 floats)
float_bytes = star_file.read(3 * 4)
# Unpack the floats using the struct module
# <-- '<fff' specifies little-endian, 3 floats
x, y, z = struct.unpack('>fff', float_bytes)
mPosition = Vector3(x, y, z)
# Read unknown value
unk = star_file.read(4)
# Read star flags (4 bytes)
flag_bytes = star_file.read(4)
mFlags = int.from_bytes(flag_bytes, 'big')
# Read name length (4-byte int)
name_len_bytes = star_file.read(4)
name_len = int.from_bytes(name_len_bytes, 'big')
# Initialize empty string for star name
mName = ''
# Read the specified number of bytes for the name
string_bytes = star_file.read(name_len*2)
# Decode the bytes to a string and add it to the result string
mName += string_bytes.decode('utf-16le')
# Skip more bytes
star_file.read(20)
# Read star type (4 byte int)
type_bytes = star_file.read(4)
mType = int.from_bytes(type_bytes, 'big')
# Skip more bytes
star_file.read(52)
# Read planet count (1 byte)
planet_count_byte = star_file.read(1)
mPlanetCount = int.from_bytes(planet_count_byte, 'big')
# Print the star data
star_list.append(cStarRecord(mKey, mName, unk, mFlags, mPosition, mType, mPlanetCount))
return star_list
def write_stars_to_csv(stars, ofilename="stars.csv"):
csv = "key,name,unk,flags,position,type,planet_count\n"
for star in stars:
csv += star.csv()
with open(ofilename, 'w+') as ofile:
ofile.write(csv)
def draw_stars(screen, stars, view=ViewMatrix()):
# Init empty list of stars to draw laset
deferred = []
# Iterate over the array of stars
for star in stars:
# Defer drawing special stars until the end
if (star.name in ['Sol', 'Galactic Core', 'Treus-8']):
deferred.append(star)
# Draw star on screen
draw_star(screen, star, view)
# Finally, draw all deferred stars
for star in deferred:
draw_star(screen, star, view)
def draw_star(screen, star, view=ViewMatrix()):
# Get the name and position of the star
name = star.name
pos = star.position
# Get the size of the screen
screen_width, screen_height = screen.get_size()
# Calculate the screen position of the star
x = int((pos.x - view.x) * view.scale + screen_width / 2)
y = int((pos.y - view.y) * view.scale + screen_width / 2)
# Determine if star is off screen, and if so, skip
if x < 0 or y < 0 or x > screen_width or y > screen_height:
return
# Set default the color and size of the star to draw
color = (255, 255, 255)
color2 = (255, 255, 255)
size = 1
# Set color based on star type
if (star.type == 1): # galactic core
color = (255, 255, 255)
elif (star.type == 2): # black hole
color = (0, 0, 255)
elif (star.type == 3): # proto-planetary disks
color = (143, 143, 255)
elif (star.type == 6): # red
color = (211, 105, 86)
elif (star.type == 5): # blue
color = (100, 179, 252)
elif (star.type == 4): # yellow
color = (229, 189, 114)
elif (star.type == 12): # red-red
color = (242, 80, 48)
color2 = (242, 80, 48)
elif (star.type == 7): # blue-blue
color = (143, 255, 255)
color2 = (143, 255, 255)
elif (star.type == 10): # yellow-yellow
color = (215, 178, 56)
color2 = (215, 178, 56)
elif (star.type == 8): # blue-red
color = (143, 255, 255)
color2 = (242, 80, 48)
elif (star.type == 9): # blue-yellow
color = (143, 255, 255)
color2 = (215, 178, 56)
elif (star.type == 11): # yellow-red
color = (215, 178, 56)
color2 = (242, 80, 48)
# Draw star at it's screen position with varying detail based on viewport scale
if view.scale >= 8:
# Draw primary star
pygame.draw.circle(screen, color, (x, y), int(size * view.scale / 8))
if (view.scale >= 16):
# Draw secondary star if binary system
if star.type in [12, 7, 10, 8, 9, 11]:
pygame.draw.circle(screen, color2, (x-6, y), int(size * view.scale / 8))
if (view.scale >= 32):
font = pygame.font.Font(None, int(4 * view.scale/10))
# Draw star name to bottom-right of circle
text = font.render(name, 1, (255, 255, 255))
screen.blit(text, (x, y))
else:
screen.set_at((x, y), color)
# Prominently redraw special star systems
if (view.scale < 32):
if (star.name in ['Sol', 'Treus-8']):
color = (255, 255, 255)
size = 2
pygame.draw.circle(screen, color, (x, y), size)
font = pygame.font.Font(None, 14)
text = font.render(star.name, 1, color)
screen.blit(text, (x+2, y+2))
elif (star.name == 'Galactic Core'):
size = 3
pygame.draw.circle(screen, color, (x, y), size)
font = pygame.font.Font(None, 14)
text = font.render(star.name, 1, color)
screen.blit(text, (x+3, y+3))
#pygame.display.flip()
def main(stars_file="stars.bin"):
stars = read_stars_from_file(stars_file)
# Initialize view matrix
view = ViewMatrix()
# Initialize Pygame
pygame.init()
pygame.display.set_caption("Star Map Prototype")
# Create a Pygame screen
screen = pygame.display.set_mode((SCREEN_SIZE, SCREEN_SIZE))
# Set the screen background color
screen.fill((0, 0, 0))
# Draw background image
image = pygame.image.load('bg.png')
# Run the game loop
running = True
do_redraw = True
while running:
if do_redraw:
# Clear the display
screen.fill((0, 0, 0))
# Blit the bg image onto the screen only if it will fit perfectly
if ((0.45 < view.scale < 0.55) and (-1 < view.x < 1) and (-1 < view.y < 1)):
screen.blit(pygame.transform.scale(image, screen.get_size()), (0, 0))
# Draw the stars on the screen
draw_stars(screen, stars, view)
# Draw UI
font = pygame.font.Font(None, 16)
text = 'Position: ({}, {}) Zoom: {}x'.format(
round(view.x, 2), round(view.y, 2), round(view.scale, 2))
blot = font.render(text, 1, (255, 255, 255))
screen.blit(blot, (10, 10))
blot = font.render('Prototype', 1, (255, 255, 255))
screen.blit(blot, (10, 26))
# Update the display
pygame.display.flip()
# Done updating
do_redraw = False
# Handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_EQUALS:
view.zoom(1.1)
do_redraw = True
elif event.key == pygame.K_MINUS:
view.zoom(1/1.1)
do_redraw = True
elif event.key == pygame.K_UP:
view.translate(0, -1)
do_redraw = True
elif event.key == pygame.K_DOWN:
view.translate(0, 1)
do_redraw = True
elif event.key == pygame.K_LEFT:
view.translate(-1, 0)
do_redraw = True
elif event.key == pygame.K_RIGHT:
view.translate(1, 0)
do_redraw = True
elif event.key == pygame.K_SPACE:
view.scale = 0.5
view.x = 0
view.y = 0
do_redraw = True
if event.type == pygame.MOUSEWHEEL:
#todo not working?
if event.y == 1:
view.zoom(1.1)
elif event.y == -1:
view.zoom(1/1.1)
# Quit Pygame
pygame.quit()
if __name__ == "__main__":
main()