ya2 · news · projects · code · about

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