ya2 · news · projects · code · about

904a7a6e77412c0faea887befec4dbb9b04fc773
[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 ya2.utils.dictfile import DctFile
29 from ya2.p3d.p3d import LibP3d
30 from ya2.utils.lang import LangMgr
31 from ya2.utils.log import LogMgr
32 from ya2.utils.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 self._args = 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 = 'logics.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, self._args.functional_test or self._args.functional_ref)
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 resolutions = []
202 if not self.version:
203 d_i = base.pipe.get_display_information()
204 def _res(idx):
205 return d_i.get_display_mode_width(idx), \
206 d_i.get_display_mode_height(idx)
207 resolutions = [
208 _res(idx) for idx in range(d_i.get_total_display_modes())]
209 res = sorted(resolutions)[-1]
210 fullscreen = self._options['settings']['fullscreen']
211 props = WindowProperties()
212 if args.functional_test or args.functional_ref:
213 fullscreen = False
214 else:
215 props.set_size(res)
216 props.set_fullscreen(fullscreen)
217 props.set_icon_filename('assets/images/icon/pmachines.ico')
218 if not args.screenshot and not self.version:
219 base.win.request_properties(props)
220 #gltf.patch_loader(base.loader)
221 if self._options['development']['simplepbr'] and not self.version:
222 self._pipeline = simplepbr.init(
223 use_normal_maps=True,
224 use_emission_maps=False,
225 use_occlusion_maps=True,
226 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
227 enable_shadows=int(self._options['settings']['shadows']))
228 debug(f'msaa: {self._pipeline.msaa_samples}')
229 debug(f'shadows: {self._pipeline.enable_shadows}')
230 render.setAntialias(AntialiasAttrib.MAuto)
231 self.base.set_background_color(0, 0, 0, 1)
232 self.base.disable_mouse()
233 if self._options['development']['show_buffers']:
234 base.bufferViewer.toggleEnable()
235 if self._options['development']['fps']:
236 base.set_frame_rate_meter(True)
237 #self.base.accept('window-event', self._on_win_evt)
238 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
239 if self._options['development']['mouse_coords']:
240 coords_txt = OnscreenText(
241 '', parent=base.a2dTopRight, scale=0.04,
242 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
243 def update_coords(task):
244 txt = '%s %s' % (int(base.win.get_pointer(0).x),
245 int(base.win.get_pointer(0).y))
246 coords_txt['text'] = txt
247 return task.cont
248 taskMgr.add(update_coords, 'update_coords')
249
250 def _set_physics(self):
251 if self._options['development']['physics_debug']:
252 debug_node = BulletDebugNode('Debug')
253 debug_node.show_wireframe(True)
254 debug_node.show_constraints(True)
255 debug_node.show_bounding_boxes(True)
256 debug_node.show_normals(True)
257 self._debug_np = render.attach_new_node(debug_node)
258 self._debug_np.show()
259 self.world = BulletWorld()
260 self.world.set_gravity((0, 0, -9.81))
261 if self._options['development']['physics_debug']:
262 self.world.set_debug_node(self._debug_np.node())
263 def update(task):
264 dt = globalClock.get_dt()
265 self.world.do_physics(dt, 10, 1/180)
266 return task.cont
267 self._phys_tsk = taskMgr.add(update, 'update')
268
269 def _unset_physics(self):
270 if self._options['development']['physics_debug']:
271 self._debug_np.remove_node()
272 self.world = None
273 taskMgr.remove(self._phys_tsk)
274
275 def _on_aspect_ratio_changed(self):
276 if self._fsm.state == 'Scene':
277 self._scene.on_aspect_ratio_changed()