ya2 · news · projects · code · about

external testing process
[pmachines.git] / game / app.py
1 import argparse
2 import simplepbr
3 #import gltf
4 from glob import glob
5 from importlib import import_module
6 from inspect import isclass
7 from sys import platform, argv, exit
8 from logging import info, debug
9 from os.path import exists
10 from os import makedirs
11 from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
12 Texture, WindowProperties, LVector2i, TextNode
13 from panda3d.bullet import BulletWorld, BulletDebugNode
14 from direct.showbase.ShowBase import ShowBase
15 from direct.gui.OnscreenText import OnscreenText
16 from direct.fsm.FSM import FSM
17 from game.music import MusicMgr
18 from game.items.background import Background
19 from game.menu import Menu
20 from game.scene import Scene
21 from game.scenes.scene_basketball import SceneBasketBall
22 from game.scenes.scene_box import SceneBox
23 from game.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
24 from game.scenes.scene_domino_box import SceneDominoBox
25 from game.scenes.scene_domino import SceneDomino
26 from game.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
27 from game.scenes.scene_teeter_tooter import SceneTeeterTooter
28 from lib.dictfile import DctFile
29 from lib.lib.p3d.p3d import LibP3d
30 from lib.engine.lang import LangMgr
31 from lib.engine.log import LogMgr
32 from lib.engine.functional import FunctionalTest
33
34
35 class MainFsm(FSM):
36
37 def __init__(self, pmachines):
38 super().__init__('Main FSM')
39 self._pmachines = pmachines
40
41 def enterMenu(self):
42 self._pmachines.on_menu_enter()
43
44 def exitMenu(self):
45 self._pmachines.on_menu_exit()
46
47 def enterScene(self, cls):
48 self._pmachines.on_scene_enter(cls)
49
50 def exitScene(self):
51 self._pmachines.on_scene_exit()
52
53
54 class PmachinesApp:
55
56 scenes = [
57 SceneDomino,
58 SceneBox,
59 SceneDominoBox,
60 SceneBasketBall,
61 SceneDominoBoxBasketball,
62 SceneTeeterTooter,
63 SceneTeeterDominoBoxBasketball]
64
65 def __init__(self):
66 info('platform: %s' % platform)
67 info('exists main.py: %s' % exists('main.py'))
68 args = self._parse_args()
69 self._configure(args)
70 self.base = ShowBase()
71 self._pipeline = None
72 self.updating = args.update
73 self.version = args.version
74 self.log_mgr = LogMgr.init_cls()(self)
75 self._prepare_window(args)
76 if args.update:
77 return
78 if args.functional_test:
79 self._options['settings']['volume'] = 0
80 self._music = MusicMgr(self._options['settings']['volume'])
81 self.lang_mgr = LangMgr(self._options['settings']['language'],
82 'pmachines',
83 'assets/locale/')
84 self._fsm = MainFsm(self)
85 if args.screenshot:
86 cls = [cls for cls in self.scenes if cls.__name__ == args.screenshot][0]
87 scene = cls(BulletWorld(), None, True, False, lambda: None, self.scenes)
88 scene.screenshot()
89 scene.destroy()
90 exit()
91 elif self._options['development']['auto_start']:
92 mod_name = 'game.scenes.scene_' + self._options['development']['auto_start']
93 for member in import_module(mod_name).__dict__.values():
94 if isclass(member) and issubclass(member, Scene) and \
95 member != Scene:
96 cls = member
97 self._fsm.demand('Scene', cls)
98 else:
99 self._fsm.demand('Menu')
100 if args.functional_test or args.functional_ref:
101 FunctionalTest(args.functional_ref)
102
103 def on_menu_enter(self):
104 self._menu_bg = Background()
105 self._menu = Menu(
106 self._fsm, self.lang_mgr, self._options, self._music,
107 self._pipeline, self.scenes)
108
109 def on_home(self):
110 self._fsm.demand('Menu')
111
112 def on_menu_exit(self):
113 self._menu_bg.destroy()
114 self._menu.destroy()
115
116 def on_scene_enter(self, cls):
117 self._set_physics()
118 self._scene = cls(
119 self.world, self.on_home,
120 self._options['development']['auto_close_instructions'],
121 self._options['development']['debug_items'],
122 self.reload,
123 self.scenes)
124
125 def on_scene_exit(self):
126 self._unset_physics()
127 self._scene.destroy()
128
129 def reload(self, cls):
130 self._fsm.demand('Scene', cls)
131
132 def _configure(self, args):
133 load_prc_file_data('', 'window-title pmachines')
134 load_prc_file_data('', 'framebuffer-srgb true')
135 load_prc_file_data('', 'sync-video true')
136 if args.functional_test or args.functional_ref:
137 load_prc_file_data('', 'win-size 1360 768')
138 # otherwise it is not centered in exwm
139 # load_prc_file_data('', 'threading-model Cull/Draw')
140 # it freezes when you go to the next scene
141 if args.screenshot:
142 load_prc_file_data('', 'window-type offscreen')
143 load_prc_file_data('', 'audio-library-name null')
144
145 def _parse_args(self):
146 parser = argparse.ArgumentParser()
147 parser.add_argument('--update', action='store_true')
148 parser.add_argument('--version', action='store_true')
149 parser.add_argument('--optfile')
150 parser.add_argument('--screenshot')
151 parser.add_argument('--functional-test', action='store_true')
152 parser.add_argument('--functional-ref', action='store_true')
153 cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
154 args = parser.parse_args(cmd_line)
155 return args
156
157 def _prepare_window(self, args):
158 data_path = ''
159 if (platform.startswith('win') or platform.startswith('linux')) and (
160 not exists('main.py') or __file__.startswith('/app/bin/')):
161 # it is the deployed version for windows
162 data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
163 home = '/home/flavio' # we must force this for wine
164 if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
165 data_path = home + '/.wine/drive_' + data_path[1:]
166 info('creating dirs: %s' % data_path)
167 makedirs(data_path, exist_ok=True)
168 optfile = args.optfile if args.optfile else 'options.ini'
169 info('data path: %s' % data_path)
170 info('option file: %s' % optfile)
171 info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
172 default_opt = {
173 'settings': {
174 'volume': 1,
175 'language': 'en',
176 'fullscreen': 1,
177 'resolution': '',
178 'antialiasing': 1,
179 'shadows': 1},
180 'development': {
181 'simplepbr': 1,
182 'verbose_log': 0,
183 'physics_debug': 0,
184 'auto_start': 0,
185 'auto_close_instructions': 0,
186 'show_buffers': 0,
187 'debug_items': 0,
188 'mouse_coords': 0,
189 'fps': 0}}
190 opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
191 opt_exists = exists(opt_path)
192 self._options = DctFile(
193 LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
194 default_opt)
195 if not opt_exists:
196 self._options.store()
197 res = self._options['settings']['resolution']
198 if res:
199 res = LVector2i(*[int(_res) for _res in res.split('x')])
200 else:
201 d_i = base.pipe.get_display_information()
202 def _res(idx):
203 return d_i.get_display_mode_width(idx), \
204 d_i.get_display_mode_height(idx)
205 resolutions = [
206 _res(idx) for idx in range(d_i.get_total_display_modes())]
207 res = sorted(resolutions)[-1]
208 fullscreen = self._options['settings']['fullscreen']
209 props = WindowProperties()
210 if args.functional_test or args.functional_ref:
211 fullscreen = False
212 else:
213 props.set_size(res)
214 props.set_fullscreen(fullscreen)
215 props.set_icon_filename('assets/images/icon/pmachines.ico')
216 if not args.screenshot and not self.version:
217 base.win.request_properties(props)
218 #gltf.patch_loader(base.loader)
219 if self._options['development']['simplepbr'] and not self.version:
220 self._pipeline = simplepbr.init(
221 use_normal_maps=True,
222 use_emission_maps=False,
223 use_occlusion_maps=True,
224 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
225 enable_shadows=int(self._options['settings']['shadows']))
226 debug(f'msaa: {self._pipeline.msaa_samples}')
227 debug(f'shadows: {self._pipeline.enable_shadows}')
228 render.setAntialias(AntialiasAttrib.MAuto)
229 self.base.set_background_color(0, 0, 0, 1)
230 self.base.disable_mouse()
231 if self._options['development']['show_buffers']:
232 base.bufferViewer.toggleEnable()
233 if self._options['development']['fps']:
234 base.set_frame_rate_meter(True)
235 #self.base.accept('window-event', self._on_win_evt)
236 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
237 if self._options['development']['mouse_coords']:
238 coords_txt = OnscreenText(
239 '', parent=base.a2dTopRight, scale=0.04,
240 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
241 def update_coords(task):
242 txt = '%s %s' % (int(base.win.get_pointer(0).x),
243 int(base.win.get_pointer(0).y))
244 coords_txt['text'] = txt
245 return task.cont
246 taskMgr.add(update_coords, 'update_coords')
247
248
249 def _set_physics(self):
250 if self._options['development']['physics_debug']:
251 debug_node = BulletDebugNode('Debug')
252 debug_node.show_wireframe(True)
253 debug_node.show_constraints(True)
254 debug_node.show_bounding_boxes(True)
255 debug_node.show_normals(True)
256 self._debug_np = render.attach_new_node(debug_node)
257 self._debug_np.show()
258 self.world = BulletWorld()
259 self.world.set_gravity((0, 0, -9.81))
260 if self._options['development']['physics_debug']:
261 self.world.set_debug_node(self._debug_np.node())
262 def update(task):
263 dt = globalClock.get_dt()
264 self.world.do_physics(dt, 10, 1/180)
265 return task.cont
266 self._phys_tsk = taskMgr.add(update, 'update')
267
268 def _unset_physics(self):
269 if self._options['development']['physics_debug']:
270 self._debug_np.remove_node()
271 self.world = None
272 taskMgr.remove(self._phys_tsk)
273
274 def _on_aspect_ratio_changed(self):
275 if self._fsm.state == 'Scene':
276 self._scene.on_aspect_ratio_changed()