1 | # |
||
2 | # Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/] |
||
3 | # |
||
4 | # This file is part of Outer Space. |
||
5 | # |
||
6 | # Outer Space is free software; you can redistribute it and/or modify |
||
7 | # it under the terms of the GNU General Public License as published by |
||
8 | # the Free Software Foundation; either version 2 of the License, or |
||
9 | # (at your option) any later version. |
||
10 | # |
||
11 | # Outer Space is distributed in the hope that it will be useful, |
||
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
14 | # GNU General Public License for more details. |
||
15 | # |
||
16 | # You should have received a copy of the GNU General Public License |
||
17 | # along with Outer Space; if not, write to the Free Software |
||
18 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
||
19 | # |
||
20 | |||
21 | # some default values as a workaround [midpoint] |
||
22 | import binascii |
||
23 | import os, os.path |
||
24 | import random |
||
25 | import re |
||
26 | import time |
||
27 | import sys |
||
28 | |||
29 | """ |
||
30 | To save people trouble with installing PyGame, which might be a bit of |
||
31 | a challenge for non-technical users, we will install it ourselves. |
||
32 | Even though it is mentioned in the Installation guide, we want to make |
||
33 | sure user agrees with the installation. |
||
34 | """ |
||
35 | try: |
||
36 | import pygame |
||
37 | except ImportError: |
||
38 | import pip |
||
39 | print("Outer Space client requires PyGame to work properly.") |
||
40 | print("PyGame will be installed in 10 seconds") |
||
41 | print("You can cancel the installation by pressing CTRL+C or by closing this window") |
||
42 | for i in xrange(10,0,-1): |
||
43 | print i |
||
44 | time.sleep(1) |
||
45 | pip.main(['install','pygame','--user']) |
||
46 | # reload needs to happen, so we can import right away |
||
47 | import site |
||
48 | reload(site) |
||
49 | import pygame |
||
50 | |||
51 | import pygame.image, pygame.ftfont, pygame.time, pygame.version |
||
52 | import pygame.transform |
||
53 | |||
54 | from osci.config import Config |
||
55 | import osci.gdata as gdata |
||
56 | import ige.version |
||
57 | from ige import log |
||
58 | import osci |
||
59 | import resources |
||
60 | import osci.res |
||
61 | |||
62 | |||
63 | |||
64 | def defineBackground(): |
||
65 | surface = pygame.Surface.copy(gdata.screen) |
||
66 | image = random.choice([ |
||
67 | resources.get('bck1_1024x768.jpg'), |
||
68 | resources.get('bck2_1024x768.jpg'), |
||
69 | resources.get('bck3_1024x768.jpg'), |
||
70 | resources.get('bck4_1024x768.jpg'), |
||
71 | resources.get('bck5_1024x768.jpg'), |
||
72 | ]) |
||
73 | background = pygame.image.load(image).convert_alpha() |
||
74 | backgroundOffset = ( |
||
75 | (surface.get_width() - background.get_width()) / 2, |
||
76 | (surface.get_height() - background.get_height()) / 2, |
||
77 | ) |
||
78 | try: |
||
79 | font = pygame.ftfont.Font(resources.get('fonts/DejaVuLGCSans.ttf'), 12) |
||
80 | except IOError: |
||
81 | # this can happen on windows during update process, when directory |
||
82 | # is moved already |
||
83 | # TODO: proper fix is to use pygameui and caching |
||
84 | font = pygame.ftfont.Font(None, 12) |
||
85 | font.set_bold(1) |
||
86 | color = 0x40, 0x70, 0x40 |
||
87 | # |
||
88 | surface.blit(background, backgroundOffset) |
||
89 | # screen.fill((0x00, 0x00, 0x00)) |
||
90 | # OSCI version |
||
91 | img = font.render(_('Outer Space %s') % ige.version.versionString, 1, color) |
||
92 | surface.blit(img, (5, surface.get_height() - 4 * img.get_height() - 5)) |
||
93 | # Pygame version |
||
94 | img = font.render(_('Pygame %s') % pygame.version.ver, 1, color) |
||
95 | surface.blit(img, (5, surface.get_height() - 3 * img.get_height() - 5)) |
||
96 | # Python version |
||
97 | img = font.render(_('Python %s') % sys.version, 1, color) |
||
98 | surface.blit(img, (5, surface.get_height() - 2 * img.get_height() - 5)) |
||
99 | # Video driver |
||
100 | w, h = pygame.display.get_surface().get_size() |
||
101 | d = pygame.display.get_surface().get_bitsize() |
||
102 | img = font.render(_('Video Driver: %s [%dx%dx%d]') % (pygame.display.get_driver(), w, h, d), 1, color) |
||
103 | surface.blit(img, (5, surface.get_height() - 1 * img.get_height() - 5)) |
||
104 | return surface |
||
105 | |||
106 | # update function |
||
107 | def update(): |
||
108 | rects = gdata.app.draw(gdata.screen) |
||
109 | if gdata.cmdInProgress: |
||
110 | img = osci.res.cmdInProgressImg |
||
111 | wx, wy = gdata.screen.get_size() |
||
112 | x, y = img.get_size() |
||
113 | gdata.screen.blit(img, (wx - x, 0)) |
||
114 | rects.append(pygame.Rect(wx - x, 0, img.get_width(), img.get_height())) |
||
115 | pygame.display.update(rects) |
||
116 | pygame.event.pump() |
||
117 | |||
118 | def setDefaults(gdata, options): |
||
119 | if gdata.config.client.language == None: |
||
120 | gdata.config.client.language = 'en' |
||
121 | if gdata.config.defaults.minfleetsymbolsize == None: |
||
122 | gdata.config.defaults.minfleetsymbolsize = 4 |
||
123 | if gdata.config.defaults.minplanetsymbolsize == None: |
||
124 | gdata.config.defaults.minplanetsymbolsize = 5 |
||
125 | if gdata.config.defaults.maxfleetsymbolsize == None: |
||
126 | gdata.config.defaults.maxfleetsymbolsize = 0 |
||
127 | if gdata.config.defaults.maxplanetsymbolsize == None: |
||
128 | gdata.config.defaults.maxplanetsymbolsize = 0 |
||
129 | if gdata.config.game.screenshot_dir is None: |
||
130 | gdata.config.game.screenshot_dir = os.path.join(options.configDir, 'screenshots') |
||
131 | try: |
||
132 | os.makedirs(gdata.config.game.screenshot_dir) |
||
133 | except OSError: |
||
134 | pass |
||
135 | # read Highlights |
||
136 | if gdata.config.defaults.colors != None: |
||
137 | for coldef in gdata.config.defaults.colors.split(' '): |
||
138 | m = re.match('(\d+):(0[xX].*?),(0[xX].*?),(0[xX].*)',coldef) |
||
139 | if m != None : |
||
140 | id = int(m.group(1)) |
||
141 | red = min(int(m.group(2),16),255) |
||
142 | green = min(int(m.group(3),16),255) |
||
143 | blue = min(int(m.group(4),16),255) |
||
144 | gdata.playersHighlightColors[id] = (red,green,blue) |
||
145 | else: |
||
146 | log.warning('OSCI','Unrecognized highlight definition :',coldef) |
||
147 | # read Object Keys |
||
148 | if gdata.config.defaults.objectkeys != None: |
||
149 | for objectkey in gdata.config.defaults.objectkeys.split(' '): |
||
150 | m = re.match('(\d+):(\d+)',objectkey) |
||
151 | if m != None : |
||
152 | key = int(m.group(1)) |
||
153 | objid = int(m.group(2)) |
||
154 | gdata.objectFocus[key] = objid |
||
155 | else: |
||
156 | log.warning('OSCI','Unrecognized object key definition :',objectkey) |
||
157 | |||
158 | def setSkinTheme(gdata, ui): |
||
159 | theme = "green" |
||
160 | if gdata.config.client.theme != None: |
||
161 | theme = gdata.config.client.theme |
||
162 | ui.SkinableTheme.enableMusic(gdata.config.defaults.music == "yes") |
||
163 | ui.SkinableTheme.enableSound(gdata.config.defaults.sound == "yes") |
||
164 | ui.SkinableTheme.setSkin(os.path.join(resources.get("themes"), theme)) |
||
165 | ui.SkinableTheme.loadMusic(gdata.config.defaults.mymusic) |
||
166 | if gdata.config.defaults.musicvolume: |
||
167 | ui.SkinableTheme.setMusicVolume(float(gdata.config.defaults.musicvolume)/ 100.0) |
||
168 | if gdata.config.defaults.soundvolume: |
||
169 | ui.SkinableTheme.setVolume(float(gdata.config.defaults.soundvolume) / 100.0) |
||
170 | |||
171 | gdata.sevColors[gdata.CRI] = (ui.SkinableTheme.themeCritical) |
||
172 | gdata.sevColors[gdata.MAJ] = (ui.SkinableTheme.themeMajor) |
||
173 | gdata.sevColors[gdata.MIN] = (ui.SkinableTheme.themeMinor) |
||
174 | gdata.sevColors[gdata.NONE] = (ui.SkinableTheme.themeNone) |
||
175 | gdata.sevColors[gdata.DISABLED] = (ui.SkinableTheme.themeDisabled) |
||
176 | |||
177 | def runClient(options): |
||
178 | |||
179 | # log initialization |
||
180 | log.message("Starting Outer Space Client", ige.version.versionString) |
||
181 | log.debug("sys.path =", sys.path) |
||
182 | log.debug("os.name =", os.name) |
||
183 | log.debug("sys.platform =", sys.platform) |
||
184 | log.debug("os.getcwd() =", os.getcwd()) |
||
185 | log.debug("sys.frozen =", getattr(sys, "frozen", None)) |
||
186 | |||
187 | # create required directories |
||
188 | if not os.path.exists(options.configDir): |
||
189 | os.makedirs(options.configDir) |
||
190 | log.debug("options.configDir =", options.configDir) |
||
191 | |||
192 | running = 1 |
||
193 | first = True |
||
194 | #while running: |
||
195 | if not first: |
||
196 | reload(osci) |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||
197 | # parse configuration |
||
198 | if first: |
||
199 | import osci.gdata as gdata |
||
200 | else: |
||
201 | reload(gdata) |
||
202 | |||
203 | gdata.config = Config(os.path.join(options.configDir, options.configFilename)) |
||
204 | gdata.config.game.server = options.server |
||
205 | |||
206 | setDefaults(gdata, options) |
||
207 | |||
208 | language = gdata.config.client.language |
||
209 | import gettext |
||
210 | log.debug('OSCI', 'Installing translation for:', language) |
||
211 | if language == 'en': |
||
212 | log.debug('OSCI', 'English is native - installing null translations') |
||
213 | tran = gettext.NullTranslations() |
||
214 | else: |
||
215 | try: |
||
216 | tran = gettext.translation('OSPACE', resources.get('translations'), languages = [language]) |
||
217 | except IOError: |
||
218 | log.warning('OSCI', 'Cannot find catalog for', language) |
||
219 | log.message('OSCI', 'Installing null translations') |
||
220 | tran = gettext.NullTranslations() |
||
221 | |||
222 | tran.install(unicode = 1) |
||
223 | |||
224 | |||
225 | #initialize pygame and prepare screen |
||
226 | if (gdata.config.defaults.sound == "yes") or (gdata.config.defaults.music == "yes"): |
||
227 | pygame.mixer.pre_init(44100, -16, 2, 4096) |
||
228 | |||
229 | os.environ['SDL_VIDEO_ALLOW_SCREENSAVER'] = '1' |
||
230 | os.environ['SDL_DEBUG'] = '1' |
||
231 | pygame.init() |
||
232 | |||
233 | flags = pygame.SWSURFACE |
||
234 | |||
235 | DEFAULT_SCRN_SIZE = (800, 600) |
||
236 | gdata.scrnSize = DEFAULT_SCRN_SIZE |
||
237 | if gdata.config.display.resolution == "FULLSCREEN": |
||
238 | gdata.scrnSize = (0, 0) |
||
239 | flags |= pygame.FULLSCREEN |
||
240 | elif gdata.config.display.resolution is not None: |
||
241 | width, height = gdata.config.display.resolution.split('x') |
||
242 | gdata.scrnSize = (int(width), int(height)) |
||
243 | |||
244 | if gdata.config.display.depth == None: |
||
245 | # guess best depth |
||
246 | bestdepth = pygame.display.mode_ok(gdata.scrnSize, flags) |
||
247 | else: |
||
248 | bestdepth = int(gdata.config.display.depth) |
||
249 | |||
250 | # initialize screen |
||
251 | try: |
||
252 | screen = pygame.display.set_mode(gdata.scrnSize, flags, bestdepth) |
||
253 | # gdata.scrnSize is used everywhere to setup windows |
||
254 | gdata.scrnSize = screen.get_size() |
||
255 | except pygame.error: |
||
256 | # for example if fullscreen is selected with resolution bigger than display |
||
257 | # TODO: as of now, fullscreen has automatic resolution |
||
258 | gdata.scrnSize = DEFAULT_SCRN_SIZE |
||
259 | screen = pygame.display.set_mode(gdata.scrnSize, flags, bestdepth) |
||
260 | gdata.screen = screen |
||
261 | log.debug('OSCI', 'Driver:', pygame.display.get_driver()) |
||
262 | log.debug('OSCI', 'Using depth:', bestdepth) |
||
263 | log.debug('OSCI', 'Display info:', pygame.display.Info()) |
||
264 | |||
265 | pygame.mouse.set_visible(1) |
||
266 | |||
267 | pygame.display.set_caption(_('Outer Space %s') % ige.version.versionString) |
||
268 | |||
269 | # set icon |
||
270 | pygame.display.set_icon(pygame.image.load(resources.get('icon48.png')).convert_alpha()) |
||
271 | |||
272 | # UI stuff |
||
273 | if first: |
||
274 | import pygameui as ui |
||
275 | else: |
||
276 | reload(ui) |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
277 | |||
278 | setSkinTheme(gdata, ui) |
||
279 | |||
280 | app = ui.Application(update, theme = ui.SkinableTheme) |
||
281 | app.background = defineBackground() |
||
282 | app.draw(gdata.screen) |
||
283 | app.windowSurfaceFlags = pygame.SWSURFACE | pygame.SRCALPHA |
||
284 | gdata.app = app |
||
285 | |||
286 | pygame.event.clear() |
||
287 | |||
288 | # resources |
||
289 | import osci.res |
||
290 | |||
291 | osci.res.initialize() |
||
292 | |||
293 | # load resources |
||
294 | import osci.dialog |
||
295 | dlg = osci.dialog.ProgressDlg(gdata.app) |
||
296 | osci.res.loadResources(dlg) |
||
297 | dlg.hide() |
||
298 | osci.res.prepareUIIcons(ui.SkinableTheme.themeIcons) |
||
299 | |||
300 | |||
301 | while running: |
||
302 | if first: |
||
303 | import osci.client, osci.handler |
||
304 | from igeclient.IClient import IClientException |
||
305 | else: |
||
306 | reload(osci.client) |
||
307 | reload(osci.handler) |
||
308 | osci.client.initialize(gdata.config.game.server, osci.handler, options) |
||
309 | |||
310 | # create initial dialogs |
||
311 | if first: |
||
312 | import osci.dialog |
||
313 | else: |
||
314 | reload(osci.dialog) |
||
315 | gdata.savePassword = gdata.config.game.lastpasswordcrypted != None |
||
316 | |||
317 | if options.login and options.password: |
||
318 | gdata.config.game.lastlogin = options.login |
||
319 | gdata.config.game.lastpassword = options.password |
||
320 | gdata.config.game.lastpasswordcrypted = binascii.b2a_base64(options.password).strip() |
||
321 | gdata.config.game.autologin = 'yes' |
||
322 | gdata.savePassword = 'no' |
||
323 | |||
324 | loginDlg = osci.dialog.LoginDlg(gdata.app) |
||
325 | updateDlg = osci.dialog.UpdateDlg(gdata.app) |
||
326 | |||
327 | # event loop |
||
328 | update() |
||
329 | |||
330 | lastSave = time.clock() |
||
331 | # set counter to -1 to trigger Update dialog (see "if" below) |
||
332 | counter = -1 |
||
333 | needsRefresh = False |
||
334 | session = 1 |
||
335 | first = False |
||
336 | while running and session: |
||
337 | try: |
||
338 | counter += 1 |
||
339 | if counter == 0: |
||
340 | # display initial dialog in the very first cycle |
||
341 | updateDlg.display(caller = loginDlg, options = options) |
||
342 | # process as many events as possible before updating |
||
343 | evt = pygame.event.wait() |
||
344 | evts = pygame.event.get() |
||
345 | evts.insert(0, evt) |
||
346 | |||
347 | forceKeepAlive = False |
||
348 | saveDB = False |
||
349 | |||
350 | for evt in evts: |
||
351 | if evt.type == pygame.QUIT: |
||
352 | running = 0 |
||
353 | break |
||
354 | if evt.type == (ui.USEREVENT) and evt.action == "localExit": |
||
355 | session = False |
||
356 | break |
||
357 | if evt.type == pygame.ACTIVEEVENT: |
||
358 | if evt.gain == 1 and evt.state == 6: |
||
359 | # pygame desktop window focus event |
||
360 | needsRefresh = True |
||
361 | if evt.type == pygame.KEYUP and evt.key == pygame.K_F12: |
||
362 | if not pygame.key.get_mods() & pygame.KMOD_CTRL: |
||
363 | running = 0 |
||
364 | break |
||
365 | if evt.type == pygame.KEYUP and evt.key == pygame.K_F9: |
||
366 | forceKeepAlive = True |
||
367 | evt = gdata.app.processEvent(evt) |
||
368 | |||
369 | if gdata.app.needsUpdate() or needsRefresh: |
||
370 | needsRefresh = False |
||
371 | update() |
||
372 | # keep alive connection |
||
373 | osci.client.keepAlive(forceKeepAlive) |
||
374 | |||
375 | # save DB every 4 hours in case of a computer crash |
||
376 | # using "counter" to limit calls to time.clock() to approximately every 10-15 minutes |
||
377 | if counter > 5000: |
||
378 | # set this to zero so we don't display Update dialog |
||
379 | counter = 0 |
||
380 | if time.clock() - lastSave > 14400: |
||
381 | saveDB = True |
||
382 | if saveDB: |
||
383 | osci.client.saveDB() |
||
384 | lastSave = time.clock(); |
||
385 | |||
386 | except IClientException, e: |
||
0 ignored issues
–
show
|
|||
387 | osci.client.reinitialize() |
||
388 | gdata.app.setStatus(e.args[0]) |
||
389 | loginDlg.display(message = e.args[0]) |
||
390 | except Exception, e: |
||
391 | log.warning('OSCI', 'Exception in event loop') |
||
392 | if not isinstance(e, SystemExit) and not isinstance(e, KeyboardInterrupt): |
||
393 | log.debug("Processing exception") |
||
394 | # handle exception |
||
395 | import traceback, StringIO |
||
396 | fh = StringIO.StringIO() |
||
397 | exctype, value, tb = sys.exc_info() |
||
398 | funcs = [entry[2] for entry in traceback.extract_tb(tb)] |
||
399 | faultID = "%06d-%03d" % ( |
||
400 | hash("/".join(funcs)) % 1000000, |
||
401 | traceback.extract_tb(tb)[-1][1] % 1000, |
||
402 | ) |
||
403 | del tb |
||
404 | # high level info |
||
405 | print >>fh, "Exception ID:", faultID |
||
406 | print >>fh |
||
407 | print >>fh, "%s: %s" % (exctype, value) |
||
408 | print >>fh |
||
409 | print >>fh, "--- EXCEPTION DATA ---" |
||
410 | # dump exception |
||
411 | traceback.print_exc(file = fh) |
||
412 | excDlg = osci.dialog.ExceptionDlg(gdata.app) |
||
413 | excDlg.display(faultID, fh.getvalue()) |
||
414 | del excDlg # reference to the dialog holds app's intance |
||
415 | fh.close() |
||
416 | del fh |
||
417 | else: |
||
418 | break |
||
419 | |||
420 | # write configuration |
||
421 | log.debug("Saving configuration.") |
||
422 | # Save highlights |
||
423 | hl = "" |
||
424 | for playerID in gdata.playersHighlightColors.keys(): |
||
425 | color = gdata.playersHighlightColors[playerID] |
||
426 | r = hex(color[0]) |
||
427 | g = hex(color[1]) |
||
428 | b = hex(color[2]) |
||
429 | hl = "%s %s:%s,%s,%s" % (hl,playerID,r,g,b) |
||
430 | gdata.config.defaults.colors = hl |
||
431 | # Save objects |
||
432 | of = "" |
||
433 | for keyNum in gdata.objectFocus.keys(): |
||
434 | objid = gdata.objectFocus[keyNum] |
||
435 | of = "%s %s:%s" % (of,keyNum,objid) |
||
436 | gdata.config.defaults.objectkeys = of |
||
437 | # |
||
438 | if gdata.savePassword == False: |
||
439 | gdata.config.game.lastpasswordcrypted = None |
||
440 | gdata.config.save() |
||
441 | |||
442 | # logout |
||
443 | osci.client.logout() |
||
444 | |||
445 | log.debug("Shut down") |
||
446 | return osci.client |
||
447 |