"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: pmachines/scene.py:106
-msgid "Scene: "
-msgstr "Scena: "
-
-#: pmachines/scene.py:305 pmachines/gui/menu.py:116
-msgid "Exit"
-msgstr "Esci"
-
-#: pmachines/scene.py:306
-msgid "Instructions"
-msgstr "Istruzioni"
-
-#: pmachines/scene.py:307
-msgid "Run"
-msgstr "Vai"
-
-#: pmachines/scene.py:313
-msgid "Editor"
-msgstr "Editor"
-
-#: pmachines/scene.py:636
-msgid "You win!"
-msgstr "Hai vinto!"
-
-#: pmachines/scene.py:716
-msgid "You have failed!"
-msgstr "Hai perso!"
-
#: pmachines/editor/augmented_frame.py:48
msgid "Collapse/Expand"
msgstr "Nascondi/Mostra"
msgid "Credits"
msgstr "Riconoscimenti"
+#: pmachines/gui/menu.py:116 pmachines/scene/scene.py:305
+msgid "Exit"
+msgstr "Esci"
+
#: pmachines/gui/menu.py:126 pmachines/gui/menu.py:129
#: pmachines/gui/menu.py:330
msgid "English"
" \ 1scale\ 1Luisa Tenuta\ 2\n"
" \ 1scale\ 1Damiana Ercolani\ 2"
+#: pmachines/scene/scene.py:106
+msgid "Scene: "
+msgstr "Scena: "
+
+#: pmachines/scene/scene.py:306
+msgid "Instructions"
+msgstr "Istruzioni"
+
+#: pmachines/scene/scene.py:307
+msgid "Run"
+msgstr "Vai"
+
+#: pmachines/scene/scene.py:313
+msgid "Editor"
+msgstr "Editor"
+
+#: pmachines/scene/scene.py:636
+msgid "You win!"
+msgstr "Hai vinto!"
+
+#: pmachines/scene/scene.py:716
+msgid "You have failed!"
+msgstr "Hai perso!"
+
#~ msgid ""
#~ "Goal: the left box must hit the right box\n"
#~ "\n"
if '--version' in argv: GuiTools.no_window()
from os.path import exists
from p3d_appimage import AppImageBuilder
-from pmachines.app import Pmachines
+from pmachines.application.application import Pmachines
from traceback import print_exc
+++ /dev/null
-import argparse
-import simplepbr
-#import gltf
-from json import loads
-from sys import platform, exit, argv
-from platform import node
-from logging import info, debug
-from os.path import exists
-from os import makedirs
-from multiprocessing import cpu_count
-from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
- WindowProperties, LVector2i, TextNode, GraphicsBuffer
-from panda3d.bullet import BulletWorld, BulletDebugNode
-from direct.showbase.ShowBase import ShowBase
-from direct.gui.OnscreenText import OnscreenText
-from direct.fsm.FSM import FSM
-from pmachines.audio.music import MusicMgr
-from pmachines.items.background import Background
-from pmachines.gui.menu import Menu
-from pmachines.scene import Scene
-from pmachines.persistent import Persistent
-from ya2.utils.dictfile import DctFile
-from ya2.utils.logics import LogicsTools
-from ya2.utils.language import LanguageManager
-from ya2.utils.log import WindowedLogManager
-from ya2.utils.functional import FunctionalTest
-from ya2.utils.asserts import Assert
-from ya2.utils.gfx import DirectGuiMixin
-
-
-class MainFsm(FSM):
-
- def __init__(self, pmachines):
- super().__init__('Main FSM')
- self._pmachines = pmachines
- self.accept('new_scene', self.__on_new_scene)
-
- def enterMenu(self):
- self._pmachines.on_menu_enter()
-
- def exitMenu(self):
- self._pmachines.on_menu_exit()
- self.__do_asserts()
- DirectGuiMixin.clear_tooltips()
-
- def enterScene(self, cls):
- self._pmachines.on_scene_enter(cls)
-
- def exitScene(self):
- self._pmachines.on_scene_exit()
- self.__do_asserts()
- DirectGuiMixin.clear_tooltips()
-
- def __on_new_scene(self):
- self.demand('Scene', None)
-
- def __do_asserts(self):
- args = self._pmachines._args
- if not LogicsTools.in_build or args.functional_test or args.functional_ref:
- Assert.assert_threads()
- Assert.assert_tasks()
- Assert.assert_render3d()
- Assert.assert_render2d()
- Assert.assert_aspect2d()
- Assert.assert_events()
- Assert.assert_buffers()
-
- def enterOff(self):
- self.ignore('new_scene')
-
-
-class Pmachines:
-
- @staticmethod
- def scenes():
- with open('assets/scenes/index.json') as f:
- json = loads(f.read())
- return json['list']
-
- def __init__(self):
- info('platform: %s' % platform)
- info('exists main.py: %s' % exists('main.py'))
- self._args = args = self._parse_args()
- self._configure(args)
- self.base = ShowBase()
- self._pipeline = None
- self.is_update_run = args.update
- self.is_version_run = args.version
- self.log_mgr = WindowedLogManager.init_cls()
- self._pos_mgr = {}
- self._prepare_window(args)
- self._fsm = MainFsm(self)
- self._fsm.demand('Start') # otherwise it is Off and cleanup in tests won't work
- if args.update:
- return
- if args.functional_test:
- self._options['settings']['volume'] = 0
- self._music = MusicMgr(self._options['settings']['volume'])
- self.lang_mgr = LanguageManager(self._options['settings']['language'],
- 'pmachines',
- 'assets/locale/')
- if args.functional_test or args.functional_ref:
- FunctionalTest(args.functional_ref, self._pos_mgr, 'pmachines')
- if not LogicsTools.in_build or args.functional_test or args.functional_ref:
- self.__fps_lst = []
- taskMgr.do_method_later(1.0, self.__assert_fps, 'assert_fps')
-
- def start(self):
- if self._args.screenshot:
- #cls = [cls for cls in self.scenes if cls.__name__ == self._args.screenshot][0]
- scene = Scene(BulletWorld(), None, True, False, lambda: None, self.scenes(), self._pos_mgr, None, None, None, self._args.screenshot, None, None)
- scene.screenshot()
- scene.destroy()
- exit()
- elif self._options['development']['auto_start']:
- # mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
- # for member in import_module(mod_name).__dict__.values():
- # if isclass(member) and issubclass(member, Scene) and \
- # member != Scene:
- # cls = member
- self._fsm.demand('Scene', self._options['development']['auto_start'])
- else:
- Scene.scenes_done = self.__persistent.scenes_done
- self._fsm.demand('Menu')
-
- def on_menu_enter(self):
- self._menu_bg = Background()
- self._menu = Menu(
- self._fsm, self.lang_mgr, self._options, self._music,
- self._pipeline, self.scenes(), self._args.functional_test or self._args.functional_ref,
- self._pos_mgr)
-
- def on_home(self):
- Scene.scenes_done = self.__persistent.scenes_done
- self._fsm.demand('Menu')
-
- def on_menu_exit(self):
- self._menu_bg.destroy()
- self._menu.destroy()
-
- def on_scene_enter(self, scene_name):
- self._set_physics()
- self._scene = Scene(
- self.world, self.on_home,
- self._options['development']['auto_close_instructions'],
- self._options['development']['debug_items'],
- self.reload,
- self.scenes(),
- self._pos_mgr,
- self._args.functional_test or self._args.functional_ref,
- self._options['development']['mouse_coords'],
- self.__persistent,
- scene_name,
- self._options['development']['editor'],
- self._options['development']['auto_start_editor'])
-
- def on_scene_exit(self):
- self._unset_physics()
- self._scene.destroy()
-
- def reload(self, cls):
- self._fsm.demand('Scene', cls)
-
- def _configure(self, args):
- load_prc_file_data('', 'window-title pmachines')
- load_prc_file_data('', 'framebuffer-srgb true')
- load_prc_file_data('', 'sync-video true')
- if args.functional_test or args.functional_ref:
- load_prc_file_data('', 'win-size 1360 768')
- # otherwise it is not centered in exwm
- # load_prc_file_data('', 'threading-model Cull/Draw')
- # it freezes when you go to the next scene
- if args.screenshot:
- load_prc_file_data('', 'window-type offscreen')
- load_prc_file_data('', 'audio-library-name null')
-
- def _parse_args(self):
- parser = argparse.ArgumentParser()
- parser.add_argument('--update', action='store_true')
- parser.add_argument('--version', action='store_true')
- parser.add_argument('--optfile')
- parser.add_argument('--screenshot')
- parser.add_argument('--functional-test', action='store_true')
- parser.add_argument('--functional-ref', action='store_true')
- cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
- args = parser.parse_args(cmd_line)
- return args
-
- def _prepare_window(self, args):
- data_path = ''
- if (platform.startswith('win') or platform.startswith('linux')) and (
- not exists('main.py') or __file__.startswith('/app/bin/')):
- # it is the deployed version for windows
- data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
- home = '/home/flavio' # we must force this for wine
- if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
- data_path = home + '/.wine/drive_' + data_path[1:]
- info('creating dirs: %s' % data_path)
- makedirs(data_path, exist_ok=True)
- optfile = args.optfile if args.optfile else 'options.ini'
- info('data path: %s' % data_path)
- info('option file: %s' % optfile)
- info('fixed path: %s' % LogicsTools.platform_specific_path(data_path + '/' + optfile))
- default_opt = {
- 'settings': {
- 'volume': 1,
- 'language': 'en',
- 'fullscreen': 1,
- 'resolution': '',
- 'antialiasing': 1,
- 'shadows': 1},
- 'save': {
- 'scenes_done': []
- },
- 'development': {
- 'simplepbr': 1,
- 'verbose_log': 0,
- 'physics_debug': 0,
- 'auto_start': 0,
- 'auto_close_instructions': 0,
- 'show_buffers': 0,
- 'debug_items': 0,
- 'mouse_coords': 0,
- 'fps': 0,
- 'editor': 0,
- 'auto_start_editor': 0}}
- opt_path = LogicsTools.platform_specific_path(data_path + '/' + optfile) if data_path else optfile
- opt_exists = exists(opt_path)
- self._options = DctFile(
- LogicsTools.platform_specific_path(data_path + '/' + optfile) if data_path else optfile,
- default_opt)
- if not opt_exists:
- self._options.store()
- self.__persistent = Persistent(self._options['save']['scenes_done'], self._options)
- Scene.scenes_done = self.__persistent.scenes_done
- res = self._options['settings']['resolution']
- if res:
- res = LVector2i(*[int(_res) for _res in res.split('x')])
- else:
- resolutions = []
- if not self.is_version_run:
- d_i = base.pipe.get_display_information()
- def _res(idx):
- return d_i.get_display_mode_width(idx), \
- d_i.get_display_mode_height(idx)
- resolutions = [
- _res(idx) for idx in range(d_i.get_total_display_modes())]
- res = sorted(resolutions)[-1]
- fullscreen = self._options['settings']['fullscreen']
- props = WindowProperties()
- if args.functional_test or args.functional_ref:
- fullscreen = False
- elif not self.is_version_run:
- props.set_size(res)
- props.set_fullscreen(fullscreen)
- props.set_icon_filename('assets/images/icon/pmachines.ico')
- if not args.screenshot and not self.is_version_run and base.win and not isinstance(base.win, GraphicsBuffer):
- base.win.request_properties(props)
- #gltf.patch_loader(base.loader)
- if self._options['development']['simplepbr'] and not self.is_version_run and base.win:
- self._pipeline = simplepbr.init(
- use_normal_maps=True,
- use_emission_maps=False,
- use_occlusion_maps=True,
- msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
- enable_shadows=int(self._options['settings']['shadows']))
- debug(f'msaa: {self._pipeline.msaa_samples}')
- debug(f'shadows: {self._pipeline.enable_shadows}')
- render.setAntialias(AntialiasAttrib.MAuto)
- self.base.set_background_color(0, 0, 0, 1)
- self.base.disable_mouse()
- if self._options['development']['show_buffers']:
- base.bufferViewer.toggleEnable()
- if self._options['development']['fps']:
- base.set_frame_rate_meter(True)
- #self.base.accept('window-event', self._on_win_evt)
- self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
- if self._options['development']['mouse_coords']:
- coords_txt = OnscreenText(
- '', parent=base.a2dTopRight, scale=0.04,
- pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
- def update_coords(task):
- txt = '%s %s' % (int(base.win.get_pointer(0).x),
- int(base.win.get_pointer(0).y))
- coords_txt['text'] = txt
- return task.cont
- taskMgr.add(update_coords, 'update_coords')
-
- def _set_physics(self):
- if self._options['development']['physics_debug']:
- debug_node = BulletDebugNode('Debug')
- debug_node.show_wireframe(True)
- debug_node.show_constraints(True)
- debug_node.show_bounding_boxes(True)
- debug_node.show_normals(True)
- self._debug_np = render.attach_new_node(debug_node)
- self._debug_np.show()
- self.world = BulletWorld()
- self.world.set_gravity((0, 0, -9.81))
- if self._options['development']['physics_debug']:
- self.world.set_debug_node(self._debug_np.node())
- def update(task):
- dt = globalClock.get_dt()
- self.world.do_physics(dt, 10, 1/180)
- return task.cont
- self._phys_tsk = taskMgr.add(update, 'update')
-
- def _unset_physics(self):
- if self._options['development']['physics_debug']:
- self._debug_np.remove_node()
- self.world = None
- taskMgr.remove(self._phys_tsk)
-
- def _on_aspect_ratio_changed(self):
- if self._fsm.state == 'Scene':
- self._scene.on_aspect_ratio_changed()
-
- def __assert_fps(self, task):
- if len(self.__fps_lst) > 3:
- self.__fps_lst.pop(0)
- self.__fps_lst += [globalClock.average_frame_rate]
- if len(self.__fps_lst) == 4:
- fps_threshold = 55 if cpu_count() >= 4 and node() != 'localhost.localdomain' else 10 # i.e. it is the builder machine
- assert not all(fps < fps_threshold for fps in self.__fps_lst), 'low fps %s' % self.__fps_lst
- return task.again
-
- def destroy(self):
- self._fsm.cleanup()
- self.base.destroy()
-
- def run(self):
- self.start()
- self.base.run()
--- /dev/null
+import argparse
+import simplepbr
+#import gltf
+from json import loads
+from sys import platform, exit, argv
+from platform import node
+from logging import info, debug
+from os.path import exists
+from os import makedirs
+from multiprocessing import cpu_count
+from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
+ WindowProperties, LVector2i, TextNode, GraphicsBuffer
+from panda3d.bullet import BulletWorld, BulletDebugNode
+from direct.showbase.ShowBase import ShowBase
+from direct.gui.OnscreenText import OnscreenText
+from direct.fsm.FSM import FSM
+from pmachines.audio.music import MusicMgr
+from pmachines.items.background import Background
+from pmachines.gui.menu import Menu
+from pmachines.scene.scene import Scene
+from pmachines.application.persistent import Persistent
+from ya2.utils.dictfile import DctFile
+from ya2.utils.logics import LogicsTools
+from ya2.utils.language import LanguageManager
+from ya2.utils.log import WindowedLogManager
+from ya2.utils.functional import FunctionalTest
+from ya2.utils.asserts import Assert
+from ya2.utils.gfx import DirectGuiMixin
+
+
+class MainFsm(FSM):
+
+ def __init__(self, pmachines):
+ super().__init__('Main FSM')
+ self._pmachines = pmachines
+ self.accept('new_scene', self.__on_new_scene)
+
+ def enterMenu(self):
+ self._pmachines.on_menu_enter()
+
+ def exitMenu(self):
+ self._pmachines.on_menu_exit()
+ self.__do_asserts()
+ DirectGuiMixin.clear_tooltips()
+
+ def enterScene(self, cls):
+ self._pmachines.on_scene_enter(cls)
+
+ def exitScene(self):
+ self._pmachines.on_scene_exit()
+ self.__do_asserts()
+ DirectGuiMixin.clear_tooltips()
+
+ def __on_new_scene(self):
+ self.demand('Scene', None)
+
+ def __do_asserts(self):
+ args = self._pmachines._args
+ if not LogicsTools.in_build or args.functional_test or args.functional_ref:
+ Assert.assert_threads()
+ Assert.assert_tasks()
+ Assert.assert_render3d()
+ Assert.assert_render2d()
+ Assert.assert_aspect2d()
+ Assert.assert_events()
+ Assert.assert_buffers()
+
+ def enterOff(self):
+ self.ignore('new_scene')
+
+
+class Pmachines:
+
+ @staticmethod
+ def scenes():
+ with open('assets/scenes/index.json') as f:
+ json = loads(f.read())
+ return json['list']
+
+ def __init__(self):
+ info('platform: %s' % platform)
+ info('exists main.py: %s' % exists('main.py'))
+ self._args = args = self._parse_args()
+ self._configure(args)
+ self.base = ShowBase()
+ self._pipeline = None
+ self.is_update_run = args.update
+ self.is_version_run = args.version
+ self.log_mgr = WindowedLogManager.init_cls()
+ self._pos_mgr = {}
+ self._prepare_window(args)
+ self._fsm = MainFsm(self)
+ self._fsm.demand('Start') # otherwise it is Off and cleanup in tests won't work
+ if args.update:
+ return
+ if args.functional_test:
+ self._options['settings']['volume'] = 0
+ self._music = MusicMgr(self._options['settings']['volume'])
+ self.lang_mgr = LanguageManager(self._options['settings']['language'],
+ 'pmachines',
+ 'assets/locale/')
+ if args.functional_test or args.functional_ref:
+ FunctionalTest(args.functional_ref, self._pos_mgr, 'pmachines')
+ if not LogicsTools.in_build or args.functional_test or args.functional_ref:
+ self.__fps_lst = []
+ taskMgr.do_method_later(1.0, self.__assert_fps, 'assert_fps')
+
+ def start(self):
+ if self._args.screenshot:
+ #cls = [cls for cls in self.scenes if cls.__name__ == self._args.screenshot][0]
+ scene = Scene(BulletWorld(), None, True, False, lambda: None, self.scenes(), self._pos_mgr, None, None, None, self._args.screenshot, None, None)
+ scene.screenshot()
+ scene.destroy()
+ exit()
+ elif self._options['development']['auto_start']:
+ # mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
+ # for member in import_module(mod_name).__dict__.values():
+ # if isclass(member) and issubclass(member, Scene) and \
+ # member != Scene:
+ # cls = member
+ self._fsm.demand('Scene', self._options['development']['auto_start'])
+ else:
+ Scene.scenes_done = self.__persistent.scenes_done
+ self._fsm.demand('Menu')
+
+ def on_menu_enter(self):
+ self._menu_bg = Background()
+ self._menu = Menu(
+ self._fsm, self.lang_mgr, self._options, self._music,
+ self._pipeline, self.scenes(), self._args.functional_test or self._args.functional_ref,
+ self._pos_mgr)
+
+ def on_home(self):
+ Scene.scenes_done = self.__persistent.scenes_done
+ self._fsm.demand('Menu')
+
+ def on_menu_exit(self):
+ self._menu_bg.destroy()
+ self._menu.destroy()
+
+ def on_scene_enter(self, scene_name):
+ self._set_physics()
+ self._scene = Scene(
+ self.world, self.on_home,
+ self._options['development']['auto_close_instructions'],
+ self._options['development']['debug_items'],
+ self.reload,
+ self.scenes(),
+ self._pos_mgr,
+ self._args.functional_test or self._args.functional_ref,
+ self._options['development']['mouse_coords'],
+ self.__persistent,
+ scene_name,
+ self._options['development']['editor'],
+ self._options['development']['auto_start_editor'])
+
+ def on_scene_exit(self):
+ self._unset_physics()
+ self._scene.destroy()
+
+ def reload(self, cls):
+ self._fsm.demand('Scene', cls)
+
+ def _configure(self, args):
+ load_prc_file_data('', 'window-title pmachines')
+ load_prc_file_data('', 'framebuffer-srgb true')
+ load_prc_file_data('', 'sync-video true')
+ if args.functional_test or args.functional_ref:
+ load_prc_file_data('', 'win-size 1360 768')
+ # otherwise it is not centered in exwm
+ # load_prc_file_data('', 'threading-model Cull/Draw')
+ # it freezes when you go to the next scene
+ if args.screenshot:
+ load_prc_file_data('', 'window-type offscreen')
+ load_prc_file_data('', 'audio-library-name null')
+
+ def _parse_args(self):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--update', action='store_true')
+ parser.add_argument('--version', action='store_true')
+ parser.add_argument('--optfile')
+ parser.add_argument('--screenshot')
+ parser.add_argument('--functional-test', action='store_true')
+ parser.add_argument('--functional-ref', action='store_true')
+ cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
+ args = parser.parse_args(cmd_line)
+ return args
+
+ def _prepare_window(self, args):
+ data_path = ''
+ if (platform.startswith('win') or platform.startswith('linux')) and (
+ not exists('main.py') or __file__.startswith('/app/bin/')):
+ # it is the deployed version for windows
+ data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
+ home = '/home/flavio' # we must force this for wine
+ if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
+ data_path = home + '/.wine/drive_' + data_path[1:]
+ info('creating dirs: %s' % data_path)
+ makedirs(data_path, exist_ok=True)
+ optfile = args.optfile if args.optfile else 'options.ini'
+ info('data path: %s' % data_path)
+ info('option file: %s' % optfile)
+ info('fixed path: %s' % LogicsTools.platform_specific_path(data_path + '/' + optfile))
+ default_opt = {
+ 'settings': {
+ 'volume': 1,
+ 'language': 'en',
+ 'fullscreen': 1,
+ 'resolution': '',
+ 'antialiasing': 1,
+ 'shadows': 1},
+ 'save': {
+ 'scenes_done': []
+ },
+ 'development': {
+ 'simplepbr': 1,
+ 'verbose_log': 0,
+ 'physics_debug': 0,
+ 'auto_start': 0,
+ 'auto_close_instructions': 0,
+ 'show_buffers': 0,
+ 'debug_items': 0,
+ 'mouse_coords': 0,
+ 'fps': 0,
+ 'editor': 0,
+ 'auto_start_editor': 0}}
+ opt_path = LogicsTools.platform_specific_path(data_path + '/' + optfile) if data_path else optfile
+ opt_exists = exists(opt_path)
+ self._options = DctFile(
+ LogicsTools.platform_specific_path(data_path + '/' + optfile) if data_path else optfile,
+ default_opt)
+ if not opt_exists:
+ self._options.store()
+ self.__persistent = Persistent(self._options['save']['scenes_done'], self._options)
+ Scene.scenes_done = self.__persistent.scenes_done
+ res = self._options['settings']['resolution']
+ if res:
+ res = LVector2i(*[int(_res) for _res in res.split('x')])
+ else:
+ resolutions = []
+ if not self.is_version_run:
+ d_i = base.pipe.get_display_information()
+ def _res(idx):
+ return d_i.get_display_mode_width(idx), \
+ d_i.get_display_mode_height(idx)
+ resolutions = [
+ _res(idx) for idx in range(d_i.get_total_display_modes())]
+ res = sorted(resolutions)[-1]
+ fullscreen = self._options['settings']['fullscreen']
+ props = WindowProperties()
+ if args.functional_test or args.functional_ref:
+ fullscreen = False
+ elif not self.is_version_run:
+ props.set_size(res)
+ props.set_fullscreen(fullscreen)
+ props.set_icon_filename('assets/images/icon/pmachines.ico')
+ if not args.screenshot and not self.is_version_run and base.win and not isinstance(base.win, GraphicsBuffer):
+ base.win.request_properties(props)
+ #gltf.patch_loader(base.loader)
+ if self._options['development']['simplepbr'] and not self.is_version_run and base.win:
+ self._pipeline = simplepbr.init(
+ use_normal_maps=True,
+ use_emission_maps=False,
+ use_occlusion_maps=True,
+ msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
+ enable_shadows=int(self._options['settings']['shadows']))
+ debug(f'msaa: {self._pipeline.msaa_samples}')
+ debug(f'shadows: {self._pipeline.enable_shadows}')
+ render.setAntialias(AntialiasAttrib.MAuto)
+ self.base.set_background_color(0, 0, 0, 1)
+ self.base.disable_mouse()
+ if self._options['development']['show_buffers']:
+ base.bufferViewer.toggleEnable()
+ if self._options['development']['fps']:
+ base.set_frame_rate_meter(True)
+ #self.base.accept('window-event', self._on_win_evt)
+ self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
+ if self._options['development']['mouse_coords']:
+ coords_txt = OnscreenText(
+ '', parent=base.a2dTopRight, scale=0.04,
+ pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
+ def update_coords(task):
+ txt = '%s %s' % (int(base.win.get_pointer(0).x),
+ int(base.win.get_pointer(0).y))
+ coords_txt['text'] = txt
+ return task.cont
+ taskMgr.add(update_coords, 'update_coords')
+
+ def _set_physics(self):
+ if self._options['development']['physics_debug']:
+ debug_node = BulletDebugNode('Debug')
+ debug_node.show_wireframe(True)
+ debug_node.show_constraints(True)
+ debug_node.show_bounding_boxes(True)
+ debug_node.show_normals(True)
+ self._debug_np = render.attach_new_node(debug_node)
+ self._debug_np.show()
+ self.world = BulletWorld()
+ self.world.set_gravity((0, 0, -9.81))
+ if self._options['development']['physics_debug']:
+ self.world.set_debug_node(self._debug_np.node())
+ def update(task):
+ dt = globalClock.get_dt()
+ self.world.do_physics(dt, 10, 1/180)
+ return task.cont
+ self._phys_tsk = taskMgr.add(update, 'update')
+
+ def _unset_physics(self):
+ if self._options['development']['physics_debug']:
+ self._debug_np.remove_node()
+ self.world = None
+ taskMgr.remove(self._phys_tsk)
+
+ def _on_aspect_ratio_changed(self):
+ if self._fsm.state == 'Scene':
+ self._scene.on_aspect_ratio_changed()
+
+ def __assert_fps(self, task):
+ if len(self.__fps_lst) > 3:
+ self.__fps_lst.pop(0)
+ self.__fps_lst += [globalClock.average_frame_rate]
+ if len(self.__fps_lst) == 4:
+ fps_threshold = 55 if cpu_count() >= 4 and node() != 'localhost.localdomain' else 10 # i.e. it is the builder machine
+ assert not all(fps < fps_threshold for fps in self.__fps_lst), 'low fps %s' % self.__fps_lst
+ return task.again
+
+ def destroy(self):
+ self._fsm.cleanup()
+ self.base.destroy()
+
+ def run(self):
+ self.start()
+ self.base.run()
--- /dev/null
+import json
+
+
+class Persistent:
+
+ def __init__(self, scenes_done, opt_file):
+ self.__scenes_done = scenes_done
+ self.__fix_ini_parsing()
+ self.__opt_file = opt_file
+
+ def __fix_ini_parsing(self):
+ #if len(self.__scenes_done) == 1 and not self.__scenes_done[0]:
+ # self.__scenes_done = []
+ #print(self.__scenes_done)
+ #self.__scenes_done = self.__scenes_done[0]
+ if self.__scenes_done:
+ if not isinstance(self.__scenes_done, list): # empty list: []
+ self.__scenes_done = self.__scenes_done.strip("'")
+ if self.__scenes_done:
+ if not isinstance(self.__scenes_done, list):
+ self.__scenes_done = json.loads(self.__scenes_done)
+
+ def save_scene(self, name, version):
+ scenes = []
+ scenes = [scene for scene in self.__scenes_done if scene[0] != name]
+ self.__scenes_done = scenes + [(name, version)]
+ self.__opt_file['save']['scenes_done'] = "'%s'" % json.dumps(self.__scenes_done)
+ self.__opt_file.store()
+
+ @property
+ def scenes_done(self):
+ return self.__scenes_done
'frameColor': (1, 1, 1, .8),
'text_scale': .64}
left = - (dx := .8) * (min(4, len(self._scenes)) - 1) / 2
- from pmachines.scene import Scene
+ from pmachines.scene.scene import Scene
for i, scene_name in enumerate(self._scenes):
top = .1 if len(self._scenes) < 5 else .6
row = 0 if i < 4 else 1
+++ /dev/null
-import json
-
-
-class Persistent:
-
- def __init__(self, scenes_done, opt_file):
- self.__scenes_done = scenes_done
- self.__fix_ini_parsing()
- self.__opt_file = opt_file
-
- def __fix_ini_parsing(self):
- #if len(self.__scenes_done) == 1 and not self.__scenes_done[0]:
- # self.__scenes_done = []
- #print(self.__scenes_done)
- #self.__scenes_done = self.__scenes_done[0]
- if self.__scenes_done:
- if not isinstance(self.__scenes_done, list): # empty list: []
- self.__scenes_done = self.__scenes_done.strip("'")
- if self.__scenes_done:
- if not isinstance(self.__scenes_done, list):
- self.__scenes_done = json.loads(self.__scenes_done)
-
- def save_scene(self, name, version):
- scenes = []
- scenes = [scene for scene in self.__scenes_done if scene[0] != name]
- self.__scenes_done = scenes + [(name, version)]
- self.__opt_file['save']['scenes_done'] = "'%s'" % json.dumps(self.__scenes_done)
- self.__opt_file.store()
-
- @property
- def scenes_done(self):
- return self.__scenes_done
+++ /dev/null
-from os.path import exists
-from os import makedirs
-from logging import info
-from json import loads
-from collections import namedtuple
-from panda3d.core import AmbientLight, Texture, TextPropertiesManager, \
- TextNode, Spotlight, PerspectiveLens, BitMask32
-from panda3d.bullet import BulletPlaneShape, BulletGhostNode
-from direct.gui.OnscreenImage import OnscreenImage
-from direct.gui.OnscreenText import OnscreenText
-from direct.gui.DirectGui import DirectButton, DirectFrame
-from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
-from direct.showbase.DirectObject import DirectObject
-from direct.interval.IntervalGlobal import Sequence, Func
-from direct.interval.LerpInterval import LerpFunctionInterval
-from pmachines.items.background import Background
-from pmachines.gui.sidepanel import SidePanel
-from pmachines.items.box import Box, HitStrategy
-from pmachines.items.basketball import Basketball
-from pmachines.items.domino import Domino, DownStrategy
-from pmachines.items.shelf import Shelf
-from pmachines.items.teetertooter import TeeterTooter
-from pmachines.editor.scene import SceneEditor
-from ya2.utils.cursor import MouseCursor
-from ya2.utils.gfx import GfxTools, DirectGuiMixin
-from ya2.utils.gui import GuiTools
-
-
-class Scene(DirectObject):
-
- json_files = {}
- scenes_done = []
-
- def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes, pos_mgr, testing, mouse_coords, persistent, json_name, editor, auto_start_editor):
- super().__init__()
- self._world = world
- self._exit_cb = exit_cb
- self._testing = testing
- self._mouse_coords = mouse_coords
- self._dbg_items = dbg_items
- self._reload_cb = reload_cb
- self._pos_mgr = pos_mgr
- self._pos_mgr = {}
- self._scenes = scenes
- self._start_evt_time = None
- self._enforce_result = ''
- self.__persistent = persistent
- self.__json_name = json_name
- self.__editor = editor
- self.__scene_editor = None
- self.json = {}
- self.accept('enforce_result', self.enforce_result)
- self._set_camera()
- self._cursor = MouseCursor(
- 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
- (.01, .01), testing)
- self.__set_font()
- self._set_gui()
- self._set_lights()
- self._set_input()
- self._set_mouse_plane()
- self.__items = []
- self._test_items = []
- self.reset()
- self._state = 'init'
- self._paused = False
- self._item_active = None
- if auto_close_instr:
- self.__store_state()
- self.__restore_state()
- else:
- self._set_instructions()
- self._bg = Background()
- self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.__items)
- self._scene_tsk = taskMgr.add(self.on_frame, 'scene_on_frame')
- if auto_start_editor:
- self._set_editor()
- self.accept('editor-inspector-delete', self.__on_inspector_delete)
-
- @classmethod
- def filename(cls, scene_name):
- return f'assets/scenes/{scene_name}.json'
-
- @classmethod
- def name(cls, scene_name):
- if not scene_name in cls.json_files:
- with open(cls.filename(scene_name)) as f:
- cls.json_files[scene_name] = loads(f.read())
- return _(cls.json_files[scene_name]['name'])
-
- @classmethod
- def version(cls, scene_name):
- if not scene_name: return ''
- if not scene_name in cls.json_files:
- with open(cls.filename(scene_name)) as f:
- cls.json_files[scene_name] = loads(f.read())
- return cls.json_files[scene_name]['version']
-
- @classmethod
- def is_done(cls, scene_name):
- if not cls.scenes_done or len(cls.scenes_done) == 1 and not cls.scenes_done[0]:
- return False
- return bool([(name, version) for name, version in cls.scenes_done if scene_name == name and cls.version(scene_name) == version])
-
- def _instr_txt(self):
- txt = _('Scene: ') + self.name(self.__json_name) + '\n\n'
- txt += _(self.__process_json_escape(self.__class__.json_files[self.__json_name]['instructions']))
- return txt
-
- def __process_json_escape(self, string):
- return bytes(string, 'utf-8').decode('unicode-escape')
-
- @property
- def items(self):
- items = self.__items[:]
- if self.__scene_editor:
- items += self.__scene_editor.test_items
- return items
-
- def _set_items(self):
- if not self.__json_name: return
- self.__items = []
- self._test_items = []
- if not self.json:
- with open(f'assets/scenes/{self.__json_name}.json') as f:
- self.json = loads(f.read())
- for item in self.json['start_items']:
- args = {
- 'world': self._world,
- 'plane_node': self._mouse_plane_node,
- 'cb_inst': self.cb_inst,
- 'curr_bottom': self.current_bottom,
- 'repos': self.repos,
- 'count': item['count'],
- 'json': item}
- if 'mass' in item:
- args['mass'] = item['mass']
- if 'friction' in item:
- args['friction'] = item['friction']
- self.__items += [self.__code2class(item['class'])(**args)]
- for item in self.json['items']:
- args = {
- 'world': self._world,
- 'plane_node': self._mouse_plane_node,
- 'cb_inst': self.cb_inst,
- 'curr_bottom': self.current_bottom,
- 'repos': self.repos,
- 'json': item}
- args['pos'] = tuple(item['position'])
- if 'mass' in item:
- args['mass'] = item['mass']
- if 'friction' in item:
- args['friction'] = item['friction']
- if 'roll' in item:
- args['r'] = item['roll']
- if 'model_scale' in item:
- args['model_scale'] = item['model_scale']
- if 'restitution' in item:
- args['restitution'] = item['restitution']
- if 'friction' in item:
- args['friction'] = item['friction']
- self.__items += [self.__code2class(item['class'])(**args)]
- if 'strategy' in item:
- match item['strategy']:
- case 'DownStrategy':
- self.__items[-1].set_strategy(self.__code2class(item['strategy'])(self.__items[-1]._np, *item['strategy_args']))
- case 'HitStrategy':
- self.__items[-1].set_strategy(self.__code2class(item['strategy'])(self.__item_with_id(item['strategy_args'][0]), self.items[-1].node, self.__items[-1]._world))
-
- def __code2class(self, code):
- return {
- 'Box': Box,
- 'Basketball': Basketball,
- 'Domino': Domino,
- 'Shelf': Shelf,
- 'TeeterTooter': TeeterTooter,
- 'DownStrategy': DownStrategy,
- 'HitStrategy': HitStrategy
- }[code]
-
- def __item_with_id(self, id):
- for item in self.__items:
- if 'id' in item.json and item.json['id'] == id:
- return item
-
- def screenshot(self, task=None):
- tex = Texture('screenshot')
- buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
- cam = base.make_camera(buffer)
- cam.reparent_to(render)
- cam.node().get_lens().set_fov(base.camLens.get_fov())
- cam.set_pos(0, -20, 0)
- cam.look_at(0, 0, 0)
- import simplepbr
- simplepbr.init(
- window=buffer,
- camera_node=cam,
- use_normal_maps=True,
- use_emission_maps=False,
- use_occlusion_maps=True,
- msaa_samples=4,
- enable_shadows=True)
- base.graphicsEngine.renderFrame()
- base.graphicsEngine.renderFrame()
- fname = self.__json_name
- if not exists('assets/images/scenes'):
- makedirs('assets/images/scenes')
- buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
- # img = DirectButton(
- # frameTexture=buffer.get_texture(), relief=FLAT,
- # frameSize=(-.2, .2, -.2, .2))
- return buffer.get_texture()
-
- def current_bottom(self):
- curr_bottom = 1
- for item in self.__items:
- if item.repos_done:
- curr_bottom = min(curr_bottom, item.get_bottom())
- return curr_bottom
-
- def reset(self):
- [itm.destroy() for itm in self.__items]
- [itm.remove_node() for itm in self._test_items]
- self.__items = []
- self._test_items = []
- self._set_items()
- self._set_test_items()
- self._state = 'init'
- self._commands = []
- self._command_idx = 0
- self._start_evt_time = None
- if hasattr(self, '_success_txt'):
- self._success_txt.destroy()
- del self._success_txt
- self.__right_btn['state'] = NORMAL
-
- def enforce_result(self, val):
- self._enforce_result = val
- info('enforce result: ' + val)
-
- def destroy(self):
- self.__intro_sequence.finish()
- self.ignore('enforce_result')
- self._unset_gui()
- self._unset_lights()
- self._unset_input()
- self._unset_mouse_plane()
- [itm.destroy() for itm in self.__items]
- [itm.remove_node() for itm in self._test_items]
- self._bg.destroy()
- self._side_panel.destroy()
- self._cursor.destroy()
- taskMgr.remove(self._scene_tsk)
- if hasattr(self, '_success_txt'):
- self._success_txt.destroy()
- self.ignore('editor-inspector-delete')
- if self.__scene_editor: self.__scene_editor.destroy()
-
- def _set_camera(self):
- base.camera.set_pos(0, -20, 0)
- base.camera.look_at(0, 0, 0)
- def camera_ani(t):
- start_v = (1, -5, 1)
- end_v = (0, -20, 0)
- curr_pos = (
- start_v[0] + (end_v[0] - start_v[0]) * t,
- start_v[1] + (end_v[1] - start_v[1]) * t,
- start_v[2] + (end_v[2] - start_v[2]) * t)
- base.camera.set_pos(*curr_pos)
- self.repos()
- camera_interval = LerpFunctionInterval(
- camera_ani,
- 1.2,
- 0,
- 1,
- blendType='easeInOut')
- self.__intro_sequence = Sequence(
- camera_interval,
- Func(self.repos))
- self.__intro_sequence.start()
-
- def __load_img_btn(self, path, col):
- img = OnscreenImage('assets/images/buttons/%s.dds' % path)
- img.set_transparency(True)
- img.set_color(col)
- img.detach_node()
- return img
-
- def _set_gui(self):
- def load_images_btn(path, col):
- colors = {
- 'gray': [
- (.6, .6, .6, 1), # ready
- (1, 1, 1, 1), # press
- (.8, .8, .8, 1), # rollover
- (.4, .4, .4, .4)],
- 'green': [
- (.1, .68, .1, 1),
- (.1, 1, .1, 1),
- (.1, .84, .1, 1),
- (.4, .1, .1, .4)]}[col]
- return [self.__load_img_btn(path, col) for col in colors]
- abl, abr = base.a2dBottomLeft, base.a2dBottomRight
- btn_info = [
- ('home', self.on_home, NORMAL, abl, 'gray', _('Exit'), 'right'),
- ('information', self._set_instructions, NORMAL, abl, 'gray', _('Instructions'), 'right'),
- ('right', self.on_play, NORMAL, abr, 'green', _('Run'), 'left'),
- #('next', self.on_next, DISABLED, abr, 'gray'),
- #('previous', self.on_prev, DISABLED, abr, 'gray'),
- #('rewind', self.reset, NORMAL, abr, 'gray')
- ]
- if self.__editor:
- btn_info.insert(2, ('wrench', self._set_editor, NORMAL, abl, 'gray', _('Editor'), 'right'))
- num_l = num_r = 0
- btns = []
- tooltip_args = self.__font, .05, (.93, .93, .93, 1)
- for binfo in btn_info:
- imgs = load_images_btn(binfo[0], binfo[4])
- if binfo[3] == base.a2dBottomLeft:
- sign, num = 1, num_l
- num_l += 1
- else:
- sign, num = -1, num_r
- num_r += 1
- fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
- btn = DirectButton(
- image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
- parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
- frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- btn.set_transparency(True)
- t = tooltip_args + (binfo[6],)
- btn.set_tooltip(binfo[5], *t)
- self._pos_mgr[binfo[0]] = btn.pos_pixel()
- btns += [btn]
- if self.__editor:
- self.__home_btn, self.__info_btn, self.__editor_btn, self.__right_btn = btns
- else:
- self.__home_btn, self.__info_btn, self.__right_btn = btns
- # , self.__next_btn, self.__prev_btn, self.__rewind_btn
- if self._dbg_items:
- self._info_txt = OnscreenText(
- '', parent=base.a2dTopRight, scale=0.04,
- pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
- if self._mouse_coords:
- self._coords_txt = OnscreenText(
- '', parent=base.a2dTopRight, scale=0.04,
- pos=(-.03, -.12), fg=(.9, .9, .9, 1), align=TextNode.A_right)
- def update_coords(task):
- pos = None
- for hit in self._get_hits():
- if hit.get_node() == self._mouse_plane_node:
- pos = hit.get_hit_pos()
- if pos:
- txt = '%s %s' % (round(pos.x, 3),
- round(pos.z, 3))
- self._coords_txt['text'] = txt
- return task.cont
- self._coords_tsk = taskMgr.add(update_coords, 'update_coords')
-
- def _unset_gui(self):
- btns = [
- self.__home_btn, self.__info_btn, self.__right_btn
- #self.__next_btn, self.__prev_btn, self.__rewind_btn
- ]
- if self.__editor: btns += [self.__editor_btn]
- [btn.destroy() for btn in btns]
- if self._dbg_items:
- self._info_txt.destroy()
- if self._mouse_coords:
- taskMgr.remove(self._coords_tsk)
- self._coords_txt.destroy()
-
- def _set_spotlight(self, name, pos, look_at, color, shadows=False):
- light = Spotlight(name)
- if shadows:
- light.setLens(PerspectiveLens())
- light_np = render.attach_new_node(light)
- light_np.set_pos(pos)
- light_np.look_at(look_at)
- light.set_color(color)
- render.set_light(light_np)
- return light_np
-
- def _set_lights(self):
- alight = AmbientLight('alight') # for ao
- alight.set_color((.15, .15, .15, 1))
- self._alnp = render.attach_new_node(alight)
- render.set_light(self._alnp)
- self._key_light = self._set_spotlight(
- 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
- self._shadow_light = self._set_spotlight(
- 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
- self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
- self._shadow_light.node().get_lens().set_film_size(2048, 2048)
- self._shadow_light.node().get_lens().set_near_far(1, 256)
- self._shadow_light.node().set_camera_mask(BitMask32(0x01))
-
- def _unset_lights(self):
- for light in [self._alnp, self._key_light, self._shadow_light]:
- render.clear_light(light)
- light.remove_node()
-
- def _set_input(self):
- self.accept('mouse1', self.on_click_l)
- self.accept('mouse1-up', self.on_release)
- self.accept('mouse3', self.on_click_r)
- self.accept('mouse3-up', self.on_release)
-
- def _unset_input(self):
- for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
- self.ignore(evt)
-
- def _set_mouse_plane(self):
- shape = BulletPlaneShape((0, -1, 0), 0)
- #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
- self._mouse_plane_node = BulletGhostNode('mouse plane')
- self._mouse_plane_node.addShape(shape)
- #np = render.attachNewNode(self._mouse_plane_node)
- #self._world.attachRigidBody(self._mouse_plane_node)
- self._world.attach_ghost(self._mouse_plane_node)
-
- def _unset_mouse_plane(self):
- self._world.remove_ghost(self._mouse_plane_node)
-
- def _get_hits(self):
- if not base.mouseWatcherNode.has_mouse(): return []
- p_from, p_to = GuiTools.get_mouse().from_to_points()
- return self._world.ray_test_all(p_from, p_to).get_hits()
-
- def _update_info(self, item):
- txt = ''
- if item:
- txt = '%.3f %.3f\n%.3f°' % (
- item._np.get_x(), item._np.get_z(), item._np.get_r())
- self._info_txt['text'] = txt
-
- def _on_click(self, method):
- if self._paused:
- return
- for hit in self._get_hits():
- if hit.get_node() == self._mouse_plane_node:
- pos = hit.get_hit_pos()
- for hit in self._get_hits():
- for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
- if not self._item_active:
- self._item_active = item
- if item not in self.__items:
- method = 'on_click_l'
- getattr(item, method)(pos)
- img = 'move' if method == 'on_click_l' else 'rotate'
- if not (img == 'rotate' and not item._instantiated):
- self._cursor.set_image('assets/images/buttons/%s.dds' % img)
-
- def on_click_l(self):
- self._on_click('on_click_l')
-
- def on_click_r(self):
- self._on_click('on_click_r')
-
- def on_release(self):
- if self._item_active and not self._item_active._first_command:
- self._commands = self._commands[:self._command_idx]
- self._commands += [self._item_active]
- self._command_idx += 1
- #self.__prev_btn['state'] = NORMAL
- #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
- #self.__prev_btn['frameColor'] = fcols[0]
- #if self._item_active._command_idx == len(self._item_active._commands) - 1:
- # self.__next_btn['state'] = DISABLED
- # self.__next_btn['frameColor'] = fcols[1]
- self._item_active = None
- [item.on_release() for item in self.__items]
- self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
-
- def repos(self):
- for item in self.__items:
- item.repos_done = False
- self.__items = sorted(self.__items, key=lambda itm: itm.__class__.__name__)
- [item.on_aspect_ratio_changed() for item in self.__items]
- self._side_panel.update(self.__items)
- max_x = -float('inf')
- for item in self.__items:
- if not item._instantiated:
- max_x = max(item._np.get_x(), max_x)
- for item in self.__items:
- if not item._instantiated:
- item.repos_x(max_x)
-
- def on_aspect_ratio_changed(self):
- self.repos()
-
- def _win_condition(self):
- return all(itm.strategy.win_condition() for itm in self.__items) and not self._paused
-
- def _fail_condition(self):
- return all(itm.fail_condition() for itm in self.__items) and not self._paused and self._state == 'playing'
-
- def on_frame(self, task):
- hits = self._get_hits()
- pos = None
- for hit in self._get_hits():
- if hit.get_node() == self._mouse_plane_node:
- pos = hit.get_hit_pos()
- hit_nodes = [hit.get_node() for hit in hits]
- if self._item_active:
- items_hit = [self._item_active]
- else:
- items_hit = [itm for itm in self.items if itm.node in hit_nodes]
- items_no_hit = [itm for itm in self.items if itm not in items_hit]
- [itm.on_mouse_on() for itm in items_hit]
- [itm.on_mouse_off() for itm in items_no_hit]
- if pos and self._item_active:
- self._item_active.on_mouse_move(pos)
- if self._dbg_items:
- self._update_info(items_hit[0] if items_hit else None)
- if not self.__scene_editor and self._win_condition():
- self._start_evt_time = None
- self._set_fail() if self._enforce_result == 'fail' else self._set_win()
- elif self._state == 'playing' and self._fail_condition():
- self._start_evt_time = None
- self._set_win() if self._enforce_result == 'win' else self._set_fail()
- elif self._testing and self._start_evt_time and globalClock.getFrameTime() - self._start_evt_time > 5.0:
- self._start_evt_time = None
- self._set_win() if self._enforce_result == 'win' else self._set_fail()
- if any(itm._overlapping for itm in self.items):
- self._cursor.set_color((.9, .1, .1, 1))
- else:
- self._cursor.set_color((.9, .9, .9, 1))
- return task.cont
-
- def cb_inst(self, item):
- self.__items += [item]
-
- def on_play(self):
- self._state = 'playing'
- #self.__prev_btn['state'] = DISABLED
- #self.__next_btn['state'] = DISABLED
- self.__right_btn['state'] = DISABLED
- [itm.play() for itm in self.__items]
- self._start_evt_time = globalClock.getFrameTime()
-
- def on_next(self):
- self._commands[self._command_idx].redo()
- self._command_idx += 1
- #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
- #self.__prev_btn['state'] = NORMAL
- #self.__prev_btn['frameColor'] = fcols[0]
- #more_commands = self._command_idx < len(self._commands)
- #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
- #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
-
- def on_prev(self):
- self._command_idx -= 1
- self._commands[self._command_idx].undo()
- #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
- #self.__next_btn['state'] = NORMAL
- #self.__next_btn['frameColor'] = fcols[0]
- #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
- #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
-
- def on_home(self):
- self._exit_cb()
-
- def __set_font(self):
- self.__font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
- self.__font.clear()
- self.__font.set_pixels_per_unit(60)
- self.__font.set_minfilter(Texture.FTLinearMipmapLinear)
- self.__font.set_outline((0, 0, 0, 1), .8, .2)
-
-
- def _set_instructions(self):
- self._paused = True
- self.__store_state()
- mgr = TextPropertiesManager.get_global_ptr()
- for name in ['mouse_l', 'mouse_r']:
- graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
- graphic.set_scale(.5)
- graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
- graphic.get_texture().set_anisotropic_degree(2)
- mgr.set_graphic(name, graphic)
- graphic.set_z(-.2)
- graphic.set_transparency(True)
- graphic.detach_node()
- frm = DirectFrame(frameColor=(.4, .4, .4, .06),
- frameSize=(-.6, .6, -.3, .3))
- self._txt = OnscreenText(
- self._instr_txt(), parent=frm, font=self.__font, scale=0.06,
- fg=(.9, .9, .9, 1), align=TextNode.A_left)
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
- btn_scale = .05
- mar = .06 # margin
- z = h / 2 - self.__font.get_line_height() * self._txt['scale'][1]
- z += (btn_scale + 2 * mar) / 2
- self._txt['pos'] = -w / 2, z
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
- fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
- frm['frameSize'] = fsz
- colors = [
- (.6, .6, .6, 1), # ready
- (1, 1, 1, 1), # press
- (.8, .8, .8, 1), # rollover
- (.4, .4, .4, .4)]
- imgs = [self.__load_img_btn('exitRight', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- btn.set_transparency(True)
- self._pos_mgr['close_instructions'] = btn.pos_pixel()
-
- def _set_win(self):
- self.__persistent.save_scene(self.__json_name, self.version(self.__json_name))
- loader.load_sfx('assets/audio/sfx/success.ogg').play()
- self._paused = True
- self.__store_state()
- frm = DirectFrame(frameColor=(.4, .4, .4, .06),
- frameSize=(-.6, .6, -.3, .3))
- font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
- font.clear()
- font.set_pixels_per_unit(60)
- font.set_minfilter(Texture.FTLinearMipmapLinear)
- font.set_outline((0, 0, 0, 1), .8, .2)
- self._txt = OnscreenText(
- _('You win!'),
- parent=frm,
- font=font, scale=0.2,
- fg=(.9, .9, .9, 1))
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
- h = u_l[2] - l_r[2]
- btn_scale = .05
- mar = .06 # margin
- z = h / 2 - font.get_line_height() * self._txt['scale'][1]
- z += (btn_scale + 2 * mar) / 2
- self._txt['pos'] = 0, z
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
- fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
- frm['frameSize'] = fsz
- colors = [
- (.6, .6, .6, 1), # ready
- (1, 1, 1, 1), # press
- (.8, .8, .8, 1), # rollover
- (.4, .4, .4, .4)]
- imgs = [self.__load_img_btn('home', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_end_home, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- btn.set_transparency(True)
- self._pos_mgr['home_win'] = btn.pos_pixel()
- imgs = [self.__load_img_btn('rewind', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(0, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_restart, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['replay'] = btn.pos_pixel()
- btn.set_transparency(True)
- if self.__json_name:
- enabled = self._scenes.index(self.__json_name) < len(self._scenes) - 1
- if enabled:
- next_scene = self._scenes[self._scenes.index(self.__json_name) + 1]
- else:
- next_scene = None
- else:
- next_scene = None
- enabled = False
- imgs = [self.__load_img_btn('right', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_next_scene,
- extraArgs=[frm, next_scene], relief=FLAT,
- frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- btn['state'] = NORMAL if enabled else DISABLED
- self._pos_mgr['next'] = btn.pos_pixel()
- btn.set_transparency(True)
-
- def _set_fail(self):
- loader.load_sfx('assets/audio/sfx/success.ogg').play()
- self._paused = True
- self.__store_state()
- frm = DirectFrame(frameColor=(.4, .4, .4, .06),
- frameSize=(-.6, .6, -.3, .3))
- font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
- font.clear()
- font.set_pixels_per_unit(60)
- font.set_minfilter(Texture.FTLinearMipmapLinear)
- font.set_outline((0, 0, 0, 1), .8, .2)
- self._txt = OnscreenText(
- _('You have failed!'),
- parent=frm,
- font=font, scale=0.2,
- fg=(.9, .9, .9, 1))
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
- h = u_l[2] - l_r[2]
- btn_scale = .05
- mar = .06 # margin
- z = h / 2 - font.get_line_height() * self._txt['scale'][1]
- z += (btn_scale + 2 * mar) / 2
- self._txt['pos'] = 0, z
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
- fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
- frm['frameSize'] = fsz
- colors = [
- (.6, .6, .6, 1), # ready
- (1, 1, 1, 1), # press
- (.8, .8, .8, 1), # rollover
- (.4, .4, .4, .4)]
- imgs = [self.__load_img_btn('home', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_end_home, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['home_win'] = btn.pos_pixel()
- btn.set_transparency(True)
- imgs = [self.__load_img_btn('rewind', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(0, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_restart, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['replay'] = btn.pos_pixel()
- btn.set_transparency(True)
-
- def _on_restart(self, frm):
- self.__on_close_instructions(frm)
- self.reset()
-
- def _on_end_home(self, frm):
- self.__on_close_instructions(frm)
- self.on_home()
-
- def _on_next_scene(self, frm, scene):
- self.__on_close_instructions(frm)
- self._reload_cb(scene)
-
- def __store_state(self):
- btns = [
- self.__home_btn, self.__info_btn, self.__right_btn,
- #self.__next_btn, self.__prev_btn, self.__rewind_btn
- ]
- if self.__editor: btns += [self.__editor_btn]
- self.__btn_state = [btn['state'] for btn in btns]
- for btn in btns:
- btn['state'] = DISABLED
- [itm.store_state() for itm in self.__items]
-
- def __restore_state(self):
- btns = [
- self.__home_btn, self.__info_btn, self.__right_btn,
- #self.__next_btn, self.__prev_btn, self.__rewind_btn
- ]
- if self.__editor: btns += [self.__editor_btn]
- for btn, state in zip(btns, self.__btn_state):
- btn['state'] = state
- [itm.restore_state() for itm in self.__items]
- self._paused = False
-
- def __on_close_instructions(self, frm):
- frm.remove_node()
- self.__restore_state()
-
- def _set_test_items(self):
- def frame_after(task):
- self._define_test_items()
- for itm in self._test_items:
- self._pos_mgr[itm.name] = itm.pos2d_pixel()
- taskMgr.doMethodLater(1.4, frame_after, 'frame after') # after the intro sequence
-
- def _define_test_items(self):
- if not self.__json_name: return
- if not self.__json_name in self.__class__.json_files:
- with open(self.__class__.filename(self.__json_name)) as f:
- self.__class__.json_files[self.__json_name] = loads(f.read())
- for item in self.__class__.json_files[self.__json_name]['test_items']['pixel_space']:
- self._pos_mgr[item['id']] = tuple(item['position'])
- for item in self.__class__.json_files[self.__json_name]['test_items']['world_space']:
- self._set_test_item(item['id'], tuple(item['position']))
-
- def _set_test_item(self, name, pos):
- self._test_items += [GfxTools.build_empty_node(name)]
- self._test_items[-1].set_pos(pos[0], 0, pos[1])
-
- def add_item(self, item):
- self.__items += [item]
-
- def _set_editor(self):
- fields = ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
- SceneContext = namedtuple('SceneContext', fields)
- context = SceneContext(
- self._world,
- self._mouse_plane_node,
- self.cb_inst,
- self.current_bottom,
- self.repos,
- {})
- self.__scene_editor = SceneEditor(self.json, self.__json_name, context, self.add_item, self.__items, self._world, self._mouse_plane_node)
-
- def __on_inspector_delete(self, item):
- self.__items.remove(item)
- self.json['items'].remove(item.json)
- item.destroy()
--- /dev/null
+from os.path import exists
+from os import makedirs
+from logging import info
+from json import loads
+from collections import namedtuple
+from panda3d.core import AmbientLight, Texture, TextPropertiesManager, \
+ TextNode, Spotlight, PerspectiveLens, BitMask32
+from panda3d.bullet import BulletPlaneShape, BulletGhostNode
+from direct.gui.OnscreenImage import OnscreenImage
+from direct.gui.OnscreenText import OnscreenText
+from direct.gui.DirectGui import DirectButton, DirectFrame
+from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
+from direct.showbase.DirectObject import DirectObject
+from direct.interval.IntervalGlobal import Sequence, Func
+from direct.interval.LerpInterval import LerpFunctionInterval
+from pmachines.items.background import Background
+from pmachines.gui.sidepanel import SidePanel
+from pmachines.items.box import Box, HitStrategy
+from pmachines.items.basketball import Basketball
+from pmachines.items.domino import Domino, DownStrategy
+from pmachines.items.shelf import Shelf
+from pmachines.items.teetertooter import TeeterTooter
+from pmachines.editor.scene import SceneEditor
+from ya2.utils.cursor import MouseCursor
+from ya2.utils.gfx import GfxTools, DirectGuiMixin
+from ya2.utils.gui import GuiTools
+
+
+class Scene(DirectObject):
+
+ json_files = {}
+ scenes_done = []
+
+ def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes, pos_mgr, testing, mouse_coords, persistent, json_name, editor, auto_start_editor):
+ super().__init__()
+ self._world = world
+ self._exit_cb = exit_cb
+ self._testing = testing
+ self._mouse_coords = mouse_coords
+ self._dbg_items = dbg_items
+ self._reload_cb = reload_cb
+ self._pos_mgr = pos_mgr
+ self._pos_mgr = {}
+ self._scenes = scenes
+ self._start_evt_time = None
+ self._enforce_result = ''
+ self.__persistent = persistent
+ self.__json_name = json_name
+ self.__editor = editor
+ self.__scene_editor = None
+ self.json = {}
+ self.accept('enforce_result', self.enforce_result)
+ self._set_camera()
+ self._cursor = MouseCursor(
+ 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+ (.01, .01), testing)
+ self.__set_font()
+ self._set_gui()
+ self._set_lights()
+ self._set_input()
+ self._set_mouse_plane()
+ self.__items = []
+ self._test_items = []
+ self.reset()
+ self._state = 'init'
+ self._paused = False
+ self._item_active = None
+ if auto_close_instr:
+ self.__store_state()
+ self.__restore_state()
+ else:
+ self._set_instructions()
+ self._bg = Background()
+ self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.__items)
+ self._scene_tsk = taskMgr.add(self.on_frame, 'scene_on_frame')
+ if auto_start_editor:
+ self._set_editor()
+ self.accept('editor-inspector-delete', self.__on_inspector_delete)
+
+ @classmethod
+ def filename(cls, scene_name):
+ return f'assets/scenes/{scene_name}.json'
+
+ @classmethod
+ def name(cls, scene_name):
+ if not scene_name in cls.json_files:
+ with open(cls.filename(scene_name)) as f:
+ cls.json_files[scene_name] = loads(f.read())
+ return _(cls.json_files[scene_name]['name'])
+
+ @classmethod
+ def version(cls, scene_name):
+ if not scene_name: return ''
+ if not scene_name in cls.json_files:
+ with open(cls.filename(scene_name)) as f:
+ cls.json_files[scene_name] = loads(f.read())
+ return cls.json_files[scene_name]['version']
+
+ @classmethod
+ def is_done(cls, scene_name):
+ if not cls.scenes_done or len(cls.scenes_done) == 1 and not cls.scenes_done[0]:
+ return False
+ return bool([(name, version) for name, version in cls.scenes_done if scene_name == name and cls.version(scene_name) == version])
+
+ def _instr_txt(self):
+ txt = _('Scene: ') + self.name(self.__json_name) + '\n\n'
+ txt += _(self.__process_json_escape(self.__class__.json_files[self.__json_name]['instructions']))
+ return txt
+
+ def __process_json_escape(self, string):
+ return bytes(string, 'utf-8').decode('unicode-escape')
+
+ @property
+ def items(self):
+ items = self.__items[:]
+ if self.__scene_editor:
+ items += self.__scene_editor.test_items
+ return items
+
+ def _set_items(self):
+ if not self.__json_name: return
+ self.__items = []
+ self._test_items = []
+ if not self.json:
+ with open(f'assets/scenes/{self.__json_name}.json') as f:
+ self.json = loads(f.read())
+ for item in self.json['start_items']:
+ args = {
+ 'world': self._world,
+ 'plane_node': self._mouse_plane_node,
+ 'cb_inst': self.cb_inst,
+ 'curr_bottom': self.current_bottom,
+ 'repos': self.repos,
+ 'count': item['count'],
+ 'json': item}
+ if 'mass' in item:
+ args['mass'] = item['mass']
+ if 'friction' in item:
+ args['friction'] = item['friction']
+ self.__items += [self.__code2class(item['class'])(**args)]
+ for item in self.json['items']:
+ args = {
+ 'world': self._world,
+ 'plane_node': self._mouse_plane_node,
+ 'cb_inst': self.cb_inst,
+ 'curr_bottom': self.current_bottom,
+ 'repos': self.repos,
+ 'json': item}
+ args['pos'] = tuple(item['position'])
+ if 'mass' in item:
+ args['mass'] = item['mass']
+ if 'friction' in item:
+ args['friction'] = item['friction']
+ if 'roll' in item:
+ args['r'] = item['roll']
+ if 'model_scale' in item:
+ args['model_scale'] = item['model_scale']
+ if 'restitution' in item:
+ args['restitution'] = item['restitution']
+ if 'friction' in item:
+ args['friction'] = item['friction']
+ self.__items += [self.__code2class(item['class'])(**args)]
+ if 'strategy' in item:
+ match item['strategy']:
+ case 'DownStrategy':
+ self.__items[-1].set_strategy(self.__code2class(item['strategy'])(self.__items[-1]._np, *item['strategy_args']))
+ case 'HitStrategy':
+ self.__items[-1].set_strategy(self.__code2class(item['strategy'])(self.__item_with_id(item['strategy_args'][0]), self.items[-1].node, self.__items[-1]._world))
+
+ def __code2class(self, code):
+ return {
+ 'Box': Box,
+ 'Basketball': Basketball,
+ 'Domino': Domino,
+ 'Shelf': Shelf,
+ 'TeeterTooter': TeeterTooter,
+ 'DownStrategy': DownStrategy,
+ 'HitStrategy': HitStrategy
+ }[code]
+
+ def __item_with_id(self, id):
+ for item in self.__items:
+ if 'id' in item.json and item.json['id'] == id:
+ return item
+
+ def screenshot(self, task=None):
+ tex = Texture('screenshot')
+ buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
+ cam = base.make_camera(buffer)
+ cam.reparent_to(render)
+ cam.node().get_lens().set_fov(base.camLens.get_fov())
+ cam.set_pos(0, -20, 0)
+ cam.look_at(0, 0, 0)
+ import simplepbr
+ simplepbr.init(
+ window=buffer,
+ camera_node=cam,
+ use_normal_maps=True,
+ use_emission_maps=False,
+ use_occlusion_maps=True,
+ msaa_samples=4,
+ enable_shadows=True)
+ base.graphicsEngine.renderFrame()
+ base.graphicsEngine.renderFrame()
+ fname = self.__json_name
+ if not exists('assets/images/scenes'):
+ makedirs('assets/images/scenes')
+ buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
+ # img = DirectButton(
+ # frameTexture=buffer.get_texture(), relief=FLAT,
+ # frameSize=(-.2, .2, -.2, .2))
+ return buffer.get_texture()
+
+ def current_bottom(self):
+ curr_bottom = 1
+ for item in self.__items:
+ if item.repos_done:
+ curr_bottom = min(curr_bottom, item.get_bottom())
+ return curr_bottom
+
+ def reset(self):
+ [itm.destroy() for itm in self.__items]
+ [itm.remove_node() for itm in self._test_items]
+ self.__items = []
+ self._test_items = []
+ self._set_items()
+ self._set_test_items()
+ self._state = 'init'
+ self._commands = []
+ self._command_idx = 0
+ self._start_evt_time = None
+ if hasattr(self, '_success_txt'):
+ self._success_txt.destroy()
+ del self._success_txt
+ self.__right_btn['state'] = NORMAL
+
+ def enforce_result(self, val):
+ self._enforce_result = val
+ info('enforce result: ' + val)
+
+ def destroy(self):
+ self.__intro_sequence.finish()
+ self.ignore('enforce_result')
+ self._unset_gui()
+ self._unset_lights()
+ self._unset_input()
+ self._unset_mouse_plane()
+ [itm.destroy() for itm in self.__items]
+ [itm.remove_node() for itm in self._test_items]
+ self._bg.destroy()
+ self._side_panel.destroy()
+ self._cursor.destroy()
+ taskMgr.remove(self._scene_tsk)
+ if hasattr(self, '_success_txt'):
+ self._success_txt.destroy()
+ self.ignore('editor-inspector-delete')
+ if self.__scene_editor: self.__scene_editor.destroy()
+
+ def _set_camera(self):
+ base.camera.set_pos(0, -20, 0)
+ base.camera.look_at(0, 0, 0)
+ def camera_ani(t):
+ start_v = (1, -5, 1)
+ end_v = (0, -20, 0)
+ curr_pos = (
+ start_v[0] + (end_v[0] - start_v[0]) * t,
+ start_v[1] + (end_v[1] - start_v[1]) * t,
+ start_v[2] + (end_v[2] - start_v[2]) * t)
+ base.camera.set_pos(*curr_pos)
+ self.repos()
+ camera_interval = LerpFunctionInterval(
+ camera_ani,
+ 1.2,
+ 0,
+ 1,
+ blendType='easeInOut')
+ self.__intro_sequence = Sequence(
+ camera_interval,
+ Func(self.repos))
+ self.__intro_sequence.start()
+
+ def __load_img_btn(self, path, col):
+ img = OnscreenImage('assets/images/buttons/%s.dds' % path)
+ img.set_transparency(True)
+ img.set_color(col)
+ img.detach_node()
+ return img
+
+ def _set_gui(self):
+ def load_images_btn(path, col):
+ colors = {
+ 'gray': [
+ (.6, .6, .6, 1), # ready
+ (1, 1, 1, 1), # press
+ (.8, .8, .8, 1), # rollover
+ (.4, .4, .4, .4)],
+ 'green': [
+ (.1, .68, .1, 1),
+ (.1, 1, .1, 1),
+ (.1, .84, .1, 1),
+ (.4, .1, .1, .4)]}[col]
+ return [self.__load_img_btn(path, col) for col in colors]
+ abl, abr = base.a2dBottomLeft, base.a2dBottomRight
+ btn_info = [
+ ('home', self.on_home, NORMAL, abl, 'gray', _('Exit'), 'right'),
+ ('information', self._set_instructions, NORMAL, abl, 'gray', _('Instructions'), 'right'),
+ ('right', self.on_play, NORMAL, abr, 'green', _('Run'), 'left'),
+ #('next', self.on_next, DISABLED, abr, 'gray'),
+ #('previous', self.on_prev, DISABLED, abr, 'gray'),
+ #('rewind', self.reset, NORMAL, abr, 'gray')
+ ]
+ if self.__editor:
+ btn_info.insert(2, ('wrench', self._set_editor, NORMAL, abl, 'gray', _('Editor'), 'right'))
+ num_l = num_r = 0
+ btns = []
+ tooltip_args = self.__font, .05, (.93, .93, .93, 1)
+ for binfo in btn_info:
+ imgs = load_images_btn(binfo[0], binfo[4])
+ if binfo[3] == base.a2dBottomLeft:
+ sign, num = 1, num_l
+ num_l += 1
+ else:
+ sign, num = -1, num_r
+ num_r += 1
+ fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+ btn = DirectButton(
+ image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
+ parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
+ frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ btn.set_transparency(True)
+ t = tooltip_args + (binfo[6],)
+ btn.set_tooltip(binfo[5], *t)
+ self._pos_mgr[binfo[0]] = btn.pos_pixel()
+ btns += [btn]
+ if self.__editor:
+ self.__home_btn, self.__info_btn, self.__editor_btn, self.__right_btn = btns
+ else:
+ self.__home_btn, self.__info_btn, self.__right_btn = btns
+ # , self.__next_btn, self.__prev_btn, self.__rewind_btn
+ if self._dbg_items:
+ self._info_txt = OnscreenText(
+ '', parent=base.a2dTopRight, scale=0.04,
+ pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
+ if self._mouse_coords:
+ self._coords_txt = OnscreenText(
+ '', parent=base.a2dTopRight, scale=0.04,
+ pos=(-.03, -.12), fg=(.9, .9, .9, 1), align=TextNode.A_right)
+ def update_coords(task):
+ pos = None
+ for hit in self._get_hits():
+ if hit.get_node() == self._mouse_plane_node:
+ pos = hit.get_hit_pos()
+ if pos:
+ txt = '%s %s' % (round(pos.x, 3),
+ round(pos.z, 3))
+ self._coords_txt['text'] = txt
+ return task.cont
+ self._coords_tsk = taskMgr.add(update_coords, 'update_coords')
+
+ def _unset_gui(self):
+ btns = [
+ self.__home_btn, self.__info_btn, self.__right_btn
+ #self.__next_btn, self.__prev_btn, self.__rewind_btn
+ ]
+ if self.__editor: btns += [self.__editor_btn]
+ [btn.destroy() for btn in btns]
+ if self._dbg_items:
+ self._info_txt.destroy()
+ if self._mouse_coords:
+ taskMgr.remove(self._coords_tsk)
+ self._coords_txt.destroy()
+
+ def _set_spotlight(self, name, pos, look_at, color, shadows=False):
+ light = Spotlight(name)
+ if shadows:
+ light.setLens(PerspectiveLens())
+ light_np = render.attach_new_node(light)
+ light_np.set_pos(pos)
+ light_np.look_at(look_at)
+ light.set_color(color)
+ render.set_light(light_np)
+ return light_np
+
+ def _set_lights(self):
+ alight = AmbientLight('alight') # for ao
+ alight.set_color((.15, .15, .15, 1))
+ self._alnp = render.attach_new_node(alight)
+ render.set_light(self._alnp)
+ self._key_light = self._set_spotlight(
+ 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
+ self._shadow_light = self._set_spotlight(
+ 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
+ self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
+ self._shadow_light.node().get_lens().set_film_size(2048, 2048)
+ self._shadow_light.node().get_lens().set_near_far(1, 256)
+ self._shadow_light.node().set_camera_mask(BitMask32(0x01))
+
+ def _unset_lights(self):
+ for light in [self._alnp, self._key_light, self._shadow_light]:
+ render.clear_light(light)
+ light.remove_node()
+
+ def _set_input(self):
+ self.accept('mouse1', self.on_click_l)
+ self.accept('mouse1-up', self.on_release)
+ self.accept('mouse3', self.on_click_r)
+ self.accept('mouse3-up', self.on_release)
+
+ def _unset_input(self):
+ for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
+ self.ignore(evt)
+
+ def _set_mouse_plane(self):
+ shape = BulletPlaneShape((0, -1, 0), 0)
+ #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
+ self._mouse_plane_node = BulletGhostNode('mouse plane')
+ self._mouse_plane_node.addShape(shape)
+ #np = render.attachNewNode(self._mouse_plane_node)
+ #self._world.attachRigidBody(self._mouse_plane_node)
+ self._world.attach_ghost(self._mouse_plane_node)
+
+ def _unset_mouse_plane(self):
+ self._world.remove_ghost(self._mouse_plane_node)
+
+ def _get_hits(self):
+ if not base.mouseWatcherNode.has_mouse(): return []
+ p_from, p_to = GuiTools.get_mouse().from_to_points()
+ return self._world.ray_test_all(p_from, p_to).get_hits()
+
+ def _update_info(self, item):
+ txt = ''
+ if item:
+ txt = '%.3f %.3f\n%.3f°' % (
+ item._np.get_x(), item._np.get_z(), item._np.get_r())
+ self._info_txt['text'] = txt
+
+ def _on_click(self, method):
+ if self._paused:
+ return
+ for hit in self._get_hits():
+ if hit.get_node() == self._mouse_plane_node:
+ pos = hit.get_hit_pos()
+ for hit in self._get_hits():
+ for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
+ if not self._item_active:
+ self._item_active = item
+ if item not in self.__items:
+ method = 'on_click_l'
+ getattr(item, method)(pos)
+ img = 'move' if method == 'on_click_l' else 'rotate'
+ if not (img == 'rotate' and not item._instantiated):
+ self._cursor.set_image('assets/images/buttons/%s.dds' % img)
+
+ def on_click_l(self):
+ self._on_click('on_click_l')
+
+ def on_click_r(self):
+ self._on_click('on_click_r')
+
+ def on_release(self):
+ if self._item_active and not self._item_active._first_command:
+ self._commands = self._commands[:self._command_idx]
+ self._commands += [self._item_active]
+ self._command_idx += 1
+ #self.__prev_btn['state'] = NORMAL
+ #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+ #self.__prev_btn['frameColor'] = fcols[0]
+ #if self._item_active._command_idx == len(self._item_active._commands) - 1:
+ # self.__next_btn['state'] = DISABLED
+ # self.__next_btn['frameColor'] = fcols[1]
+ self._item_active = None
+ [item.on_release() for item in self.__items]
+ self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
+
+ def repos(self):
+ for item in self.__items:
+ item.repos_done = False
+ self.__items = sorted(self.__items, key=lambda itm: itm.__class__.__name__)
+ [item.on_aspect_ratio_changed() for item in self.__items]
+ self._side_panel.update(self.__items)
+ max_x = -float('inf')
+ for item in self.__items:
+ if not item._instantiated:
+ max_x = max(item._np.get_x(), max_x)
+ for item in self.__items:
+ if not item._instantiated:
+ item.repos_x(max_x)
+
+ def on_aspect_ratio_changed(self):
+ self.repos()
+
+ def _win_condition(self):
+ return all(itm.strategy.win_condition() for itm in self.__items) and not self._paused
+
+ def _fail_condition(self):
+ return all(itm.fail_condition() for itm in self.__items) and not self._paused and self._state == 'playing'
+
+ def on_frame(self, task):
+ hits = self._get_hits()
+ pos = None
+ for hit in self._get_hits():
+ if hit.get_node() == self._mouse_plane_node:
+ pos = hit.get_hit_pos()
+ hit_nodes = [hit.get_node() for hit in hits]
+ if self._item_active:
+ items_hit = [self._item_active]
+ else:
+ items_hit = [itm for itm in self.items if itm.node in hit_nodes]
+ items_no_hit = [itm for itm in self.items if itm not in items_hit]
+ [itm.on_mouse_on() for itm in items_hit]
+ [itm.on_mouse_off() for itm in items_no_hit]
+ if pos and self._item_active:
+ self._item_active.on_mouse_move(pos)
+ if self._dbg_items:
+ self._update_info(items_hit[0] if items_hit else None)
+ if not self.__scene_editor and self._win_condition():
+ self._start_evt_time = None
+ self._set_fail() if self._enforce_result == 'fail' else self._set_win()
+ elif self._state == 'playing' and self._fail_condition():
+ self._start_evt_time = None
+ self._set_win() if self._enforce_result == 'win' else self._set_fail()
+ elif self._testing and self._start_evt_time and globalClock.getFrameTime() - self._start_evt_time > 5.0:
+ self._start_evt_time = None
+ self._set_win() if self._enforce_result == 'win' else self._set_fail()
+ if any(itm._overlapping for itm in self.items):
+ self._cursor.set_color((.9, .1, .1, 1))
+ else:
+ self._cursor.set_color((.9, .9, .9, 1))
+ return task.cont
+
+ def cb_inst(self, item):
+ self.__items += [item]
+
+ def on_play(self):
+ self._state = 'playing'
+ #self.__prev_btn['state'] = DISABLED
+ #self.__next_btn['state'] = DISABLED
+ self.__right_btn['state'] = DISABLED
+ [itm.play() for itm in self.__items]
+ self._start_evt_time = globalClock.getFrameTime()
+
+ def on_next(self):
+ self._commands[self._command_idx].redo()
+ self._command_idx += 1
+ #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+ #self.__prev_btn['state'] = NORMAL
+ #self.__prev_btn['frameColor'] = fcols[0]
+ #more_commands = self._command_idx < len(self._commands)
+ #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
+ #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
+
+ def on_prev(self):
+ self._command_idx -= 1
+ self._commands[self._command_idx].undo()
+ #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+ #self.__next_btn['state'] = NORMAL
+ #self.__next_btn['frameColor'] = fcols[0]
+ #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
+ #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
+
+ def on_home(self):
+ self._exit_cb()
+
+ def __set_font(self):
+ self.__font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+ self.__font.clear()
+ self.__font.set_pixels_per_unit(60)
+ self.__font.set_minfilter(Texture.FTLinearMipmapLinear)
+ self.__font.set_outline((0, 0, 0, 1), .8, .2)
+
+
+ def _set_instructions(self):
+ self._paused = True
+ self.__store_state()
+ mgr = TextPropertiesManager.get_global_ptr()
+ for name in ['mouse_l', 'mouse_r']:
+ graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
+ graphic.set_scale(.5)
+ graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
+ graphic.get_texture().set_anisotropic_degree(2)
+ mgr.set_graphic(name, graphic)
+ graphic.set_z(-.2)
+ graphic.set_transparency(True)
+ graphic.detach_node()
+ frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+ frameSize=(-.6, .6, -.3, .3))
+ self._txt = OnscreenText(
+ self._instr_txt(), parent=frm, font=self.__font, scale=0.06,
+ fg=(.9, .9, .9, 1), align=TextNode.A_left)
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+ btn_scale = .05
+ mar = .06 # margin
+ z = h / 2 - self.__font.get_line_height() * self._txt['scale'][1]
+ z += (btn_scale + 2 * mar) / 2
+ self._txt['pos'] = -w / 2, z
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+ fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+ frm['frameSize'] = fsz
+ colors = [
+ (.6, .6, .6, 1), # ready
+ (1, 1, 1, 1), # press
+ (.8, .8, .8, 1), # rollover
+ (.4, .4, .4, .4)]
+ imgs = [self.__load_img_btn('exitRight', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ btn.set_transparency(True)
+ self._pos_mgr['close_instructions'] = btn.pos_pixel()
+
+ def _set_win(self):
+ self.__persistent.save_scene(self.__json_name, self.version(self.__json_name))
+ loader.load_sfx('assets/audio/sfx/success.ogg').play()
+ self._paused = True
+ self.__store_state()
+ frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+ frameSize=(-.6, .6, -.3, .3))
+ font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+ font.clear()
+ font.set_pixels_per_unit(60)
+ font.set_minfilter(Texture.FTLinearMipmapLinear)
+ font.set_outline((0, 0, 0, 1), .8, .2)
+ self._txt = OnscreenText(
+ _('You win!'),
+ parent=frm,
+ font=font, scale=0.2,
+ fg=(.9, .9, .9, 1))
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+ h = u_l[2] - l_r[2]
+ btn_scale = .05
+ mar = .06 # margin
+ z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+ z += (btn_scale + 2 * mar) / 2
+ self._txt['pos'] = 0, z
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+ fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+ frm['frameSize'] = fsz
+ colors = [
+ (.6, .6, .6, 1), # ready
+ (1, 1, 1, 1), # press
+ (.8, .8, .8, 1), # rollover
+ (.4, .4, .4, .4)]
+ imgs = [self.__load_img_btn('home', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_end_home, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ btn.set_transparency(True)
+ self._pos_mgr['home_win'] = btn.pos_pixel()
+ imgs = [self.__load_img_btn('rewind', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(0, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_restart, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._pos_mgr['replay'] = btn.pos_pixel()
+ btn.set_transparency(True)
+ if self.__json_name:
+ enabled = self._scenes.index(self.__json_name) < len(self._scenes) - 1
+ if enabled:
+ next_scene = self._scenes[self._scenes.index(self.__json_name) + 1]
+ else:
+ next_scene = None
+ else:
+ next_scene = None
+ enabled = False
+ imgs = [self.__load_img_btn('right', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_next_scene,
+ extraArgs=[frm, next_scene], relief=FLAT,
+ frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ btn['state'] = NORMAL if enabled else DISABLED
+ self._pos_mgr['next'] = btn.pos_pixel()
+ btn.set_transparency(True)
+
+ def _set_fail(self):
+ loader.load_sfx('assets/audio/sfx/success.ogg').play()
+ self._paused = True
+ self.__store_state()
+ frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+ frameSize=(-.6, .6, -.3, .3))
+ font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+ font.clear()
+ font.set_pixels_per_unit(60)
+ font.set_minfilter(Texture.FTLinearMipmapLinear)
+ font.set_outline((0, 0, 0, 1), .8, .2)
+ self._txt = OnscreenText(
+ _('You have failed!'),
+ parent=frm,
+ font=font, scale=0.2,
+ fg=(.9, .9, .9, 1))
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+ h = u_l[2] - l_r[2]
+ btn_scale = .05
+ mar = .06 # margin
+ z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+ z += (btn_scale + 2 * mar) / 2
+ self._txt['pos'] = 0, z
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+ fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+ frm['frameSize'] = fsz
+ colors = [
+ (.6, .6, .6, 1), # ready
+ (1, 1, 1, 1), # press
+ (.8, .8, .8, 1), # rollover
+ (.4, .4, .4, .4)]
+ imgs = [self.__load_img_btn('home', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_end_home, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._pos_mgr['home_win'] = btn.pos_pixel()
+ btn.set_transparency(True)
+ imgs = [self.__load_img_btn('rewind', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(0, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_restart, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._pos_mgr['replay'] = btn.pos_pixel()
+ btn.set_transparency(True)
+
+ def _on_restart(self, frm):
+ self.__on_close_instructions(frm)
+ self.reset()
+
+ def _on_end_home(self, frm):
+ self.__on_close_instructions(frm)
+ self.on_home()
+
+ def _on_next_scene(self, frm, scene):
+ self.__on_close_instructions(frm)
+ self._reload_cb(scene)
+
+ def __store_state(self):
+ btns = [
+ self.__home_btn, self.__info_btn, self.__right_btn,
+ #self.__next_btn, self.__prev_btn, self.__rewind_btn
+ ]
+ if self.__editor: btns += [self.__editor_btn]
+ self.__btn_state = [btn['state'] for btn in btns]
+ for btn in btns:
+ btn['state'] = DISABLED
+ [itm.store_state() for itm in self.__items]
+
+ def __restore_state(self):
+ btns = [
+ self.__home_btn, self.__info_btn, self.__right_btn,
+ #self.__next_btn, self.__prev_btn, self.__rewind_btn
+ ]
+ if self.__editor: btns += [self.__editor_btn]
+ for btn, state in zip(btns, self.__btn_state):
+ btn['state'] = state
+ [itm.restore_state() for itm in self.__items]
+ self._paused = False
+
+ def __on_close_instructions(self, frm):
+ frm.remove_node()
+ self.__restore_state()
+
+ def _set_test_items(self):
+ def frame_after(task):
+ self._define_test_items()
+ for itm in self._test_items:
+ self._pos_mgr[itm.name] = itm.pos2d_pixel()
+ taskMgr.doMethodLater(1.4, frame_after, 'frame after') # after the intro sequence
+
+ def _define_test_items(self):
+ if not self.__json_name: return
+ if not self.__json_name in self.__class__.json_files:
+ with open(self.__class__.filename(self.__json_name)) as f:
+ self.__class__.json_files[self.__json_name] = loads(f.read())
+ for item in self.__class__.json_files[self.__json_name]['test_items']['pixel_space']:
+ self._pos_mgr[item['id']] = tuple(item['position'])
+ for item in self.__class__.json_files[self.__json_name]['test_items']['world_space']:
+ self._set_test_item(item['id'], tuple(item['position']))
+
+ def _set_test_item(self, name, pos):
+ self._test_items += [GfxTools.build_empty_node(name)]
+ self._test_items[-1].set_pos(pos[0], 0, pos[1])
+
+ def add_item(self, item):
+ self.__items += [item]
+
+ def _set_editor(self):
+ fields = ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
+ SceneContext = namedtuple('SceneContext', fields)
+ context = SceneContext(
+ self._world,
+ self._mouse_plane_node,
+ self.cb_inst,
+ self.current_bottom,
+ self.repos,
+ {})
+ self.__scene_editor = SceneEditor(self.json, self.__json_name, context, self.add_item, self.__items, self._world, self._mouse_plane_node)
+
+ def __on_inspector_delete(self, item):
+ self.__items.remove(item)
+ self.json['items'].remove(item.json)
+ item.destroy()
from ya2.build.screenshots import ScreenshotsBuilder
from ya2.build.images import ImagesBuilder
from ya2.build.models import ModelsBuilder
-from pmachines.app import Pmachines
+from pmachines.application.application import Pmachines
app_name = long_name = 'pmachines'
from ya2.utils.dictfile import DctFile
import importlib
import main
-import pmachines.app
+import pmachines.application.application
class MainTests(TestCase):
def test_update(self):
with (patch.object(main, 'argv', ['python -m unittest']),
- patch.object(pmachines.app, 'argv', ['python -m unittest'])):
+ patch.object(pmachines.application.application, 'argv', ['python -m unittest'])):
_main = Main()
_main._Main__appimage_builder.update = MagicMock()
_main._Main__pmachines._fsm.demand = MagicMock()
_main._Main__pmachines.destroy()
_main._Main__appimage_builder.update.assert_not_called()
with (patch.object(sys, 'argv', ['python -m unittest', '--update']),
- patch.object(pmachines.app, 'argv', ['python -m unittest', '--update'])):
+ patch.object(pmachines.application.application, 'argv', ['python -m unittest', '--update'])):
_main = Main()
_main._Main__appimage_builder.update = MagicMock()
_main.run()
def test_version(self):
with (patch.object(main, 'argv', ['python -m unittest']),
- patch.object(pmachines.app, 'argv', ['python -m unittest'])):
+ patch.object(pmachines.application.application, 'argv', ['python -m unittest'])):
_main = Main()
_main._Main__run_game = MagicMock()
_main.run()
_main._Main__pmachines.destroy()
_main._Main__run_game.assert_called_once()
with (patch.object(main, 'argv', ['python -m unittest', '--version']),
- patch.object(pmachines.app, 'argv', ['python -m unittest', '--version'])):
+ patch.object(pmachines.application.application, 'argv', ['python -m unittest', '--version'])):
_main = Main()
_main._Main__run_game = MagicMock()
_main.run()
_main._Main__run_game.assert_not_called()
@patch.object(main, 'argv', ['python -m unittest'])
- @patch.object(pmachines.app, 'argv', ['python -m unittest'])
+ @patch.object(pmachines.application.application, 'argv', ['python -m unittest'])
def test_run_game(self):
_main = Main()
_main._Main__pmachines.run = MagicMock()
_main._Main__pmachines.destroy()
_main._Main__pmachines.run.assert_called_once()
- @patch.object(pmachines.app, 'argv', ['python -m unittest'])
+ @patch.object(pmachines.application.application, 'argv', ['python -m unittest'])
@patch.object(main, 'argv', ['python -m unittest'])
@patch.object(main, 'print_exc')
def test_run_game_exception(self, print_exc_mock):