ya2 · news · projects · code · about

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