ya2 · news · projects · code · about

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