ya2 · news · projects · code · about

refactoring
authorFlavio Calva <f.calva@gmail.com>
Wed, 8 Jun 2022 18:25:12 +0000 (19:25 +0100)
committerFlavio Calva <f.calva@gmail.com>
Wed, 8 Jun 2022 18:25:12 +0000 (19:25 +0100)
110 files changed:
audio/__init__.py [new file with mode: 0644]
audio/music.py [new file with mode: 0644]
game/__init__.py [deleted file]
game/app.py [deleted file]
game/items/__init__.py [deleted file]
game/items/background.py [deleted file]
game/items/basketball.py [deleted file]
game/items/box.py [deleted file]
game/items/domino.py [deleted file]
game/items/item.py [deleted file]
game/items/shelf.py [deleted file]
game/items/teetertooter.py [deleted file]
game/menu.py [deleted file]
game/music.py [deleted file]
game/scene.py [deleted file]
game/scenes/__init__.py [deleted file]
game/scenes/scene_basketball.py [deleted file]
game/scenes/scene_box.py [deleted file]
game/scenes/scene_domino.py [deleted file]
game/scenes/scene_domino_box.py [deleted file]
game/scenes/scene_domino_box_basketball.py [deleted file]
game/scenes/scene_teeter_domino_box_basketball.py [deleted file]
game/scenes/scene_teeter_tooter.py [deleted file]
game/sidepanel.py [deleted file]
gui/__init__.py [new file with mode: 0644]
gui/menu.py [new file with mode: 0644]
gui/sidepanel.py [new file with mode: 0644]
logics/__init__.py [new file with mode: 0644]
logics/app.py [new file with mode: 0755]
logics/items/__init__.py [new file with mode: 0644]
logics/items/background.py [new file with mode: 0644]
logics/items/basketball.py [new file with mode: 0644]
logics/items/box.py [new file with mode: 0644]
logics/items/domino.py [new file with mode: 0644]
logics/items/item.py [new file with mode: 0644]
logics/items/shelf.py [new file with mode: 0644]
logics/items/teetertooter.py [new file with mode: 0644]
logics/scene.py [new file with mode: 0644]
logics/scenes/__init__.py [new file with mode: 0644]
logics/scenes/scene_basketball.py [new file with mode: 0644]
logics/scenes/scene_box.py [new file with mode: 0644]
logics/scenes/scene_domino.py [new file with mode: 0644]
logics/scenes/scene_domino_box.py [new file with mode: 0644]
logics/scenes/scene_domino_box_basketball.py [new file with mode: 0644]
logics/scenes/scene_teeter_domino_box_basketball.py [new file with mode: 0644]
logics/scenes/scene_teeter_tooter.py [new file with mode: 0644]
main.py
setup.py
tests/functional_test.py
tests/lib/__init__.py [deleted file]
tests/lib/build/__init__.py [deleted file]
tests/lib/build/test_build.py [deleted file]
tests/lib/build/test_lang.py [deleted file]
tests/lib/build/test_models.py [deleted file]
tests/lib/build/test_mtprocesser.py [deleted file]
tests/lib/engine/test_audio.py [deleted file]
tests/lib/engine/test_cbmux.py [deleted file]
tests/lib/engine/test_clock.py [deleted file]
tests/lib/engine/test_configuration.py [deleted file]
tests/lib/engine/test_engine.py [deleted file]
tests/lib/engine/test_event.py [deleted file]
tests/lib/engine/test_font.py [deleted file]
tests/lib/engine/test_gfx.py [deleted file]
tests/lib/engine/test_joystick.py [deleted file]
tests/lib/test_computer_proxy.py [deleted file]
tests/lib/test_dictfile.py [deleted file]
tests/lib/test_game.py [deleted file]
tests/lib/test_gameobject.py [deleted file]
tests/lib/test_observer.py [deleted file]
tests/ya2/__init__.py [new file with mode: 0644]
tests/ya2/build/__init__.py [new file with mode: 0644]
tests/ya2/build/test_build.py [new file with mode: 0644]
tests/ya2/build/test_lang.py [new file with mode: 0644]
tests/ya2/build/test_models.py [new file with mode: 0644]
tests/ya2/build/test_mtprocesser.py [new file with mode: 0644]
tests/ya2/patterns/__init__.py [new file with mode: 0644]
tests/ya2/utils/__init__.py [new file with mode: 0644]
tests/ya2/utils/test_dictfile.py [new file with mode: 0644]
tests/ya2/utils/test_gameobject.py [new file with mode: 0644]
tests/ya2/utils/test_observer.py [new file with mode: 0644]
ya2/build/screenshots.py
ya2/computer_proxy.py [deleted file]
ya2/decorator.py [deleted file]
ya2/dictfile.py [deleted file]
ya2/engine/__init__.py [deleted file]
ya2/engine/functional.py [deleted file]
ya2/engine/gui/__init__.py [deleted file]
ya2/engine/gui/cursor.py [deleted file]
ya2/engine/lang.py [deleted file]
ya2/engine/log.py [deleted file]
ya2/gameobject.py [deleted file]
ya2/lib/__init__.py [deleted file]
ya2/lib/p3d/__init__.py [deleted file]
ya2/lib/p3d/gfx.py [deleted file]
ya2/lib/p3d/gui.py [deleted file]
ya2/lib/p3d/p3d.py [deleted file]
ya2/observer.py [deleted file]
ya2/p3d/__init__.py [new file with mode: 0644]
ya2/p3d/gfx.py [new file with mode: 0755]
ya2/p3d/gui.py [new file with mode: 0755]
ya2/p3d/p3d.py [new file with mode: 0755]
ya2/patterns/__init__.py [new file with mode: 0644]
ya2/patterns/gameobject.py [new file with mode: 0644]
ya2/patterns/observer.py [new file with mode: 0644]
ya2/utils/__init__.py [new file with mode: 0644]
ya2/utils/cursor.py [new file with mode: 0644]
ya2/utils/dictfile.py [new file with mode: 0644]
ya2/utils/functional.py [new file with mode: 0644]
ya2/utils/lang.py [new file with mode: 0644]
ya2/utils/log.py [new file with mode: 0755]

diff --git a/audio/__init__.py b/audio/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/audio/music.py b/audio/music.py
new file mode 100644 (file)
index 0000000..cf6c0c9
--- /dev/null
@@ -0,0 +1,55 @@
+from os.path import dirname, exists, basename
+from platform import system
+from glob import glob
+from pathlib import Path
+from random import choice
+from logging import info
+from panda3d.core import AudioSound, Filename
+
+
+class MusicMgr:
+
+    def __init__(self, volume):
+        files = self.curr_path + 'assets/audio/music/*.ogg'
+        self._start_music(glob(files))
+        base.musicManager.setVolume(.8 * volume)
+        base.sfxManagerList[0].setVolume(volume)
+        taskMgr.add(self._on_frame, 'on frame music')
+
+    @property
+    def is_appimage(self):
+        par_path = str(Path(__file__).parent.absolute())
+        is_appimage = par_path.startswith('/tmp/.mount_Pmachi')
+        return is_appimage and par_path.endswith('/usr/bin')
+
+    @property
+    def curr_path(self):
+        if system() == 'Windows':
+            return ''
+        if exists('main.py'):
+            return ''
+        else:
+            par_path = str(Path(__file__).parent.absolute())
+        if self.is_appimage:
+            par_path = str(Path(par_path).absolute())
+        par_path += '/'
+        return par_path
+
+    def _start_music(self, files):
+        self._music = loader.load_music(choice(files))
+        info('playing music ' + self._music.get_name())
+        self._music.play()
+
+    def set_volume(self, volume):
+        base.musicManager.setVolume(.8 * volume)
+        base.sfxManagerList[0].setVolume(volume)
+
+    def _on_frame(self, task):
+        if self._music.status() == AudioSound.READY:
+            oggs = Filename(self.curr_path + 'assets/audio/music/*.ogg').to_os_specific()
+            files = glob(oggs)
+            rm_music = Filename(self.curr_path + 'assets/audio/music/' + basename(self._music.get_name())).to_os_specific()
+            # basename is needed in windows
+            files.remove(rm_music)
+            self._start_music(files)
+        return task.cont
diff --git a/game/__init__.py b/game/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/game/app.py b/game/app.py
deleted file mode 100755 (executable)
index ac75b5c..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-import argparse
-import simplepbr
-#import gltf
-from glob import glob
-from importlib import import_module
-from inspect import isclass
-from sys import platform, argv, exit
-from logging import info, debug
-from os.path import exists
-from os import makedirs
-from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
-    Texture, WindowProperties, LVector2i, TextNode
-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 game.music import MusicMgr
-from game.items.background import Background
-from game.menu import Menu
-from game.scene import Scene
-from game.scenes.scene_basketball import SceneBasketBall
-from game.scenes.scene_box import SceneBox
-from game.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
-from game.scenes.scene_domino_box import SceneDominoBox
-from game.scenes.scene_domino import SceneDomino
-from game.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
-from game.scenes.scene_teeter_tooter import SceneTeeterTooter
-from ya2.dictfile import DctFile
-from ya2.lib.p3d.p3d import LibP3d
-from ya2.engine.lang import LangMgr
-from ya2.engine.log import LogMgr
-from ya2.engine.functional import FunctionalTest
-
-
-class MainFsm(FSM):
-
-    def __init__(self, pmachines):
-        super().__init__('Main FSM')
-        self._pmachines = pmachines
-
-    def enterMenu(self):
-        self._pmachines.on_menu_enter()
-
-    def exitMenu(self):
-        self._pmachines.on_menu_exit()
-
-    def enterScene(self, cls):
-        self._pmachines.on_scene_enter(cls)
-
-    def exitScene(self):
-        self._pmachines.on_scene_exit()
-
-
-class PmachinesApp:
-
-    scenes = [
-        SceneDomino,
-        SceneBox,
-        SceneDominoBox,
-        SceneBasketBall,
-        SceneDominoBoxBasketball,
-        SceneTeeterTooter,
-        SceneTeeterDominoBoxBasketball]
-
-    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.updating = args.update
-        self.version = args.version
-        self.log_mgr = LogMgr.init_cls()(self)
-        self._prepare_window(args)
-        if args.update:
-            return
-        if args.functional_test:
-            self._options['settings']['volume'] = 0
-        self._music = MusicMgr(self._options['settings']['volume'])
-        self.lang_mgr = LangMgr(self._options['settings']['language'],
-                                'pmachines',
-                                'assets/locale/')
-        self._fsm = MainFsm(self)
-        if args.screenshot:
-            cls = [cls for cls in self.scenes if cls.__name__ == args.screenshot][0]
-            scene = cls(BulletWorld(), None, True, False, lambda: None, self.scenes)
-            scene.screenshot()
-            scene.destroy()
-            exit()
-        elif self._options['development']['auto_start']:
-            mod_name = 'game.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', cls)
-        else:
-            self._fsm.demand('Menu')
-        if args.functional_test or args.functional_ref:
-            FunctionalTest(args.functional_ref)
-
-    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)
-
-    def on_home(self):
-        self._fsm.demand('Menu')
-
-    def on_menu_exit(self):
-        self._menu_bg.destroy()
-        self._menu.destroy()
-
-    def on_scene_enter(self, cls):
-        self._set_physics()
-        self._scene = cls(
-            self.world, self.on_home,
-            self._options['development']['auto_close_instructions'],
-            self._options['development']['debug_items'],
-            self.reload,
-            self.scenes)
-
-    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' % LibP3d.fixpath(data_path + '/' + optfile))
-        default_opt = {
-            'settings': {
-                'volume': 1,
-                'language': 'en',
-                'fullscreen': 1,
-                'resolution': '',
-                'antialiasing': 1,
-                'shadows': 1},
-            '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}}
-        opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
-        opt_exists = exists(opt_path)
-        self._options = DctFile(
-            LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
-            default_opt)
-        if not opt_exists:
-            self._options.store()
-        res = self._options['settings']['resolution']
-        if res:
-            res = LVector2i(*[int(_res) for _res in res.split('x')])
-        else:
-            resolutions = []
-            if not self.version:
-                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
-        else:
-            props.set_size(res)
-        props.set_fullscreen(fullscreen)
-        props.set_icon_filename('assets/images/icon/pmachines.ico')
-        if not args.screenshot and not self.version:
-            base.win.request_properties(props)
-        #gltf.patch_loader(base.loader)
-        if self._options['development']['simplepbr'] and not self.version:
-            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()
diff --git a/game/items/__init__.py b/game/items/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/game/items/background.py b/game/items/background.py
deleted file mode 100644 (file)
index 2d83fc0..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-from itertools import product
-from panda3d.core import NodePath
-from ya2.lib.p3d.gfx import set_srgb
-
-
-class Background:
-
-    def __init__(self):
-        self._root = NodePath('background_root')
-        self._root.reparent_to(render)
-        ncols, nrows = 16, 8
-        start_size, end_size = 5, 2.5
-        offset = 5
-        for col, row in product(range(ncols), range(nrows)):
-            model = loader.load_model('assets/models/bam/background/background.bam')
-            model.set_scale(end_size / start_size)
-            model.reparent_to(self._root)
-            total_width, total_height = end_size * ncols, end_size * nrows
-            left, bottom = -total_width/2, -total_height/2
-            model.set_pos(left + end_size * col, offset, bottom + end_size * row)
-        self._root.clear_model_nodes()
-        self._root.flatten_strong()
-        set_srgb(self._root)
-
-    def destroy(self):
-        self._root.remove_node()
diff --git a/game/items/basketball.py b/game/items/basketball.py
deleted file mode 100644 (file)
index b27130a..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from panda3d.bullet import BulletSphereShape, BulletRigidBodyNode, BulletGhostNode
-from game.items.item import Item
-
-
-class Basketball(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.92, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/basketball/basketball.bam', .4, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletSphereShape(1))
diff --git a/game/items/box.py b/game/items/box.py
deleted file mode 100644 (file)
index 0687b56..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from game.items.item import Item
-
-
-class Box(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/box/box.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((.5, .5, .5)))
-
-
-class HitStrategy:
-
-    def __init__(self, hit_by, node, world):
-        self._hit_by = hit_by
-        self._node = node
-        self._world = world
-
-    def win_condition(self):
-        for contact in self._world.contact_test(self._node).get_contacts():
-            other = contact.get_node1() if contact.get_node0() == self._node else contact.get_node0()
-            if other == self._hit_by.node:
-                return True
diff --git a/game/items/domino.py b/game/items/domino.py
deleted file mode 100644 (file)
index 7586953..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from game.items.item import Item, StillStrategy
-
-
-class Domino(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/domino/domino.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((.1, .25, .5)))
-
-
-class UpStrategy(StillStrategy):
-
-    def __init__(self, np, tgt_degrees):
-        super().__init__(np)
-        self._tgt_degrees = tgt_degrees
-        self._np = np
-
-    def win_condition(self):
-        return super().win_condition() and abs(self._np.get_r()) <= self._tgt_degrees
-
-
-class DownStrategy(StillStrategy):
-
-    def __init__(self, np, tgt_degrees):
-        super().__init__(np)
-        self._tgt_degrees = tgt_degrees
-        self._np = np
-
-    def win_condition(self):
-        return self._np.get_z() < -6 or super().win_condition() and abs(self._np.get_r()) >= self._tgt_degrees
diff --git a/game/items/item.py b/game/items/item.py
deleted file mode 100644 (file)
index 9eb5afb..0000000
+++ /dev/null
@@ -1,339 +0,0 @@
-from panda3d.core import CullFaceAttrib, Point3, NodePath, Point2, Texture, \
-    Plane, Vec3, BitMask32
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from direct.gui.OnscreenText import OnscreenText
-from ya2.lib.p3d.gfx import P3dGfxMgr, set_srgb
-
-
-class Command:
-
-    def __init__(self, pos, rot):
-        self.pos = pos
-        self.rot = rot
-
-
-class FixedStrategy:
-
-    def win_condition(self):
-        return True
-
-
-class StillStrategy:
-
-    def __init__(self, np):
-        self._np = np
-        self._positions = []
-        self._rotations = []
-
-    def win_condition(self):
-        self._positions += [self._np.get_pos()]
-        self._rotations += [self._np.get_hpr()]
-        if len(self._positions) > 30:
-            self._positions.pop(0)
-        if len(self._rotations) > 30:
-            self._rotations.pop(0)
-        if len(self._positions) < 28:
-            return
-        avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
-        avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
-        avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
-        avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
-        avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
-        avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
-        avg_pos = Point3(avg_x, avg_y, avg_z)
-        avg_rot = Point3(avg_h, avg_p, avg_r)
-        return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
-            all((rot - avg_rot).length() < 1 for rot in self._rotations)
-
-
-class Item:
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, scene_repos, model_path, model_scale=1, exp_num_contacts=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
-        self._world = world
-        self._plane_node = plane_node
-        self._count = count
-        self._cb_inst = cb_inst
-        self._paused = False
-        self._overlapping = False
-        self._first_command = True
-        self.repos_done = False
-        self._mass = mass
-        self._pos = pos
-        self._r = r
-        self.strategy = FixedStrategy()
-        self._exp_num_contacts = exp_num_contacts
-        self._curr_bottom = curr_bottom
-        self._scene_repos = scene_repos
-        self._model_scale = model_scale
-        self._model_path = model_path
-        self._commands = []
-        self._command_idx = -1
-        self._restitution = restitution
-        self._friction = friction
-        self._positions = []
-        self._rotations = []
-        if count:
-            self.node = BulletGhostNode(self.__class__.__name__)
-        else:
-            self.node = BulletRigidBodyNode(self.__class__.__name__)
-        self._set_shape(count)
-        self._np = render.attach_new_node(self.node)
-        if count:
-            world.attach_ghost(self.node)
-        else:
-            world.attach_rigid_body(self.node)
-        self._model = loader.load_model(model_path)
-        set_srgb(self._model)
-        self._model.flatten_light()
-        self._model.reparent_to(self._np)
-        self._np.set_scale(model_scale)
-        self._np.flatten_strong()
-        if count:
-            self._set_outline_model()
-            set_srgb(self._outline_model)
-            self._model.hide(BitMask32(0x01))
-            self._outline_model.hide(BitMask32(0x01))
-        self._start_drag_pos = None
-        self._prev_rot_info = None
-        self._last_nonoverlapping_pos = None
-        self._last_nonoverlapping_rot = None
-        self._instantiated = not count
-        self.interactable = count
-        self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
-        if count:
-            self._repos()
-        else:
-            self._np.set_pos(pos)
-            self._np.set_r(r)
-
-    def _set_shape(self, apply_scale=True):
-        pass
-
-    def set_strategy(self, strategy):
-        self.strategy = strategy
-
-    def _repos(self):
-        p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
-        for hit in self._world.ray_test_all(p_from, p_to).get_hits():
-            if hit.get_node() == self._plane_node:
-                pos = hit.get_hit_pos()
-        corner = P3dGfxMgr.screen_coord(pos)
-        bounds = self._np.get_tight_bounds()
-        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
-        self._np.set_pos(pos)
-        plane = Plane(Vec3(0, 1, 0), bounds[0][1])
-        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
-        margin, ar = .04, base.get_aspect_ratio()
-        margin_x = margin / ar if ar >= 1 else margin
-        margin_z = margin * ar if ar < 1 else margin
-        top = self._curr_bottom()
-        base.camLens.extrude((-1 + margin_x, top - margin_z), near_pt, far_pt)
-        plane.intersects_line(
-            pos3d, render.get_relative_point(base.camera, near_pt),
-            render.get_relative_point(base.camera, far_pt))
-        delta = Vec3(bounds[1][0], bounds[1][1], bounds[0][2])
-        self._np.set_pos(pos3d + delta)
-        if not hasattr(self, '_txt'):
-            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(
-                str(self._count), font=font, scale=0.06, fg=(.9, .9, .9, 1))
-        pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
-        p2d = P3dGfxMgr.screen_coord(pos)
-        self._txt['pos'] = p2d
-        self.repos_done = True
-
-    def repos_x(self, x):
-        self._np.set_x(x)
-        bounds = self._np.get_tight_bounds()
-        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
-        pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
-        p2d = P3dGfxMgr.screen_coord(pos)
-        self._txt['pos'] = p2d
-
-    def get_bottom(self):
-        bounds = self._np.get_tight_bounds()
-        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
-        pos = self._np.get_pos() + (bounds[1][0], bounds[1][1], bounds[0][2])
-        p2d = P3dGfxMgr.screen_coord(pos)
-        ar = base.get_aspect_ratio()
-        return p2d[1] if ar >= 1 else (p2d[1] * ar)
-
-    def get_corner(self):
-        bounds = self._np.get_tight_bounds()
-        return bounds[1][0], bounds[1][1], bounds[0][2]
-
-    def _set_outline_model(self):
-        self._outline_model = loader.load_model(self._model_path)
-        #clockw = CullFaceAttrib.MCullClockwise
-        #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
-        self._outline_model.set_attrib(CullFaceAttrib.make_reverse())
-        self._outline_model.reparent_to(self._np)
-        self._outline_model.set_scale(1.08)
-        self._outline_model.set_light_off()
-        self._outline_model.set_color(.4, .4, .4, 1)
-        self._outline_model.set_color_scale(.4, .4, .4, 1)
-        self._outline_model.hide()
-
-    def on_frame(self, task):
-        self._np.set_y(0)
-        return task.cont
-
-    def undo(self):
-        self._command_idx -= 1
-        self._np.set_pos(self._commands[self._command_idx].pos)
-        self._np.set_hpr(self._commands[self._command_idx].rot)
-
-    def redo(self):
-        self._command_idx += 1
-        self._np.set_pos(self._commands[self._command_idx].pos)
-        self._np.set_hpr(self._commands[self._command_idx].rot)
-
-    def play(self):
-        if not self._instantiated:
-            return
-        self._world.remove_rigid_body(self.node)
-        self.node.set_mass(self._mass)
-        self._world.attach_rigid_body(self.node)
-        self.node.set_restitution(self._restitution)
-        self.node.set_friction(self._friction)
-
-    def on_click_l(self, pos):
-        if self._paused: return
-        self._start_drag_pos = pos, self._np.get_pos()
-        loader.load_sfx('assets/audio/sfx/grab.ogg').play()
-        if not self._instantiated:
-            self._world.remove_ghost(self.node)
-            pos = self._np.get_pos()
-            self._np.remove_node()
-            self.node = BulletRigidBodyNode('box')
-            self._set_shape()
-            self._np = render.attach_new_node(self.node)
-            self._world.attach_rigid_body(self.node)
-            self._model.reparent_to(self._np)
-            self._np.set_pos(pos)
-            self._set_outline_model()
-            self._np.set_scale(self._model_scale)
-            self._model.show(BitMask32(0x01))
-            self._outline_model.hide(BitMask32(0x01))
-            self._instantiated = True
-            self._txt.destroy()
-            self._count -= 1
-            if self._count:
-                item = self.__class__(self._world, self._plane_node, self._cb_inst, self._curr_bottom, self._scene_repos, count=self._count, mass=self._mass, pos=self._pos, r=self._r)  # pylint: disable=no-value-for-parameter
-                self._cb_inst(item)
-            self._scene_repos()
-
-    def on_click_r(self, pos):
-        if self._paused or not self._instantiated: return
-        self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
-        loader.load_sfx('assets/audio/sfx/grab.ogg').play()
-
-    def on_release(self):
-        if self._start_drag_pos or self._prev_rot_info:
-            loader.load_sfx('assets/audio/sfx/release.ogg').play()
-            self._command_idx += 1
-            self._commands = self._commands[:self._command_idx]
-            self._commands += [Command(self._np.get_pos(), self._np.get_hpr())]
-            self._first_command = False
-        self._start_drag_pos = self._prev_rot_info = None
-        if self._overlapping:
-            self._np.set_pos(self._last_nonoverlapping_pos)
-            self._np.set_hpr(self._last_nonoverlapping_rot)
-            self._outline_model.set_color(.4, .4, .4, 1)
-            self._outline_model.set_color_scale(.4, .4, .4, 1)
-            self._overlapping = False
-
-    def on_mouse_on(self):
-        if not self._paused and self.interactable:
-            self._outline_model.show()
-
-    def on_mouse_off(self):
-        if self._start_drag_pos or self._prev_rot_info: return
-        if self.interactable:
-            self._outline_model.hide()
-
-    def on_mouse_move(self, pos):
-        if self._start_drag_pos:
-            d_pos =  pos - self._start_drag_pos[0]
-            self._np.set_pos(self._start_drag_pos[1] + d_pos)
-        if self._prev_rot_info:
-            start_vec = self._prev_rot_info[0] - self._prev_rot_info[1]
-            curr_vec = pos - self._prev_rot_info[1]
-            d_angle = curr_vec.signed_angle_deg(start_vec, (0, -1, 0))
-            self._np.set_r(self._prev_rot_info[2] + d_angle)
-            self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
-        if self._start_drag_pos or self._prev_rot_info:
-            res = self._world.contact_test(self.node)
-            nres = res.get_num_contacts()
-            if nres <= self._exp_num_contacts:
-                self._overlapping = False
-                self._outline_model.set_color(.4, .4, .4, 1)
-                self._outline_model.set_color_scale(.4, .4, .4, 1)
-            if nres > self._exp_num_contacts and not self._overlapping:
-                actual_nres = 0
-                for contact in res.get_contacts():
-                    for node in [contact.get_node0(), contact.get_node1()]:
-                        if isinstance(node, BulletRigidBodyNode) and \
-                                node != self.node:
-                            actual_nres += 1
-                if actual_nres >= 1:
-                    self._overlapping = True
-                    loader.load_sfx('assets/audio/sfx/overlap.ogg').play()
-                    self._outline_model.set_color(.9, .1, .1, 1)
-                    self._outline_model.set_color_scale(.9, .1, .1, 1)
-        if not self._overlapping:
-            self._last_nonoverlapping_pos = self._np.get_pos()
-            self._last_nonoverlapping_rot = self._np.get_hpr()
-
-    def on_aspect_ratio_changed(self):
-        if not self._instantiated:
-            self._repos()
-
-    def store_state(self):
-        self._paused = True
-        self._model.set_transparency(True)
-        self._model.set_alpha_scale(.3)
-        if hasattr(self, '_txt') and not self._txt.is_empty():
-            self._txt.set_alpha_scale(.3)
-
-    def restore_state(self):
-        self._paused = False
-        self._model.set_alpha_scale(1)
-        if hasattr(self, '_txt') and not self._txt.is_empty():
-            self._txt.set_alpha_scale(1)
-
-    def fail_condition(self):
-        if self._np.get_z() < -6:
-            return True
-        self._positions += [self._np.get_pos()]
-        self._rotations += [self._np.get_hpr()]
-        if len(self._positions) > 30:
-            self._positions.pop(0)
-        if len(self._rotations) > 30:
-            self._rotations.pop(0)
-        if len(self._positions) < 28:
-            return
-        avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
-        avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
-        avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
-        avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
-        avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
-        avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
-        avg_pos = Point3(avg_x, avg_y, avg_z)
-        avg_rot = Point3(avg_h, avg_p, avg_r)
-        return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
-            all((rot - avg_rot).length() < 1 for rot in self._rotations)
-
-    def destroy(self):
-        self._np.remove_node()
-        taskMgr.remove(self._box_tsk)
-        if hasattr(self, '_txt'):
-            self._txt.destroy()
-        if not self._instantiated:
-            self._world.remove_ghost(self.node)
-        else:
-            self._world.remove_rigid_body(self.node)
diff --git a/game/items/shelf.py b/game/items/shelf.py
deleted file mode 100644 (file)
index dd39e83..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from game.items.item import Item
-
-
-class Shelf(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/shelf/shelf.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((1, .5, .05)))
diff --git a/game/items/teetertooter.py b/game/items/teetertooter.py
deleted file mode 100644 (file)
index 6fd01f2..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-from panda3d.core import TransformState
-from panda3d.bullet import BulletCylinderShape, BulletRigidBodyNode, BulletGhostNode, YUp, ZUp
-from game.items.item import Item
-
-
-class TeeterTooter(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/teeter_tooter/teeter_tooter.bam', exp_num_contacts=2, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=.5)
-
-    def _set_shape(self, apply_scale=True):
-        scale = self._model_scale if apply_scale else 1
-        self.node.add_shape(
-            BulletCylinderShape(.1, 1.6, YUp),
-            TransformState.makePos((0, 0, scale * .36)))
-        self.node.add_shape(
-            BulletCylinderShape(.1, .7, ZUp),
-            TransformState.makePos((0, scale * .8, scale * -.1)))
-        self.node.add_shape(
-            BulletCylinderShape(.1, .7, ZUp),
-            TransformState.makePos((0, scale * -.8, scale * -.1)))
diff --git a/game/menu.py b/game/menu.py
deleted file mode 100644 (file)
index b20889a..0000000
+++ /dev/null
@@ -1,304 +0,0 @@
-from logging import info, debug
-from sys import platform, exit
-from os import environ, system
-from glob import glob
-from importlib import import_module
-from inspect import isclass
-from webbrowser import open_new_tab
-from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
-    TextProperties, TextPropertiesManager
-from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
-    DirectOptionMenu, DirectSlider, DirectCheckButton
-from direct.gui.DirectGuiGlobals import FLAT
-from direct.gui.OnscreenText import OnscreenText
-from direct.showbase.DirectObject import DirectObject
-from ya2.engine.gui.cursor import MouseCursor
-from game.scene import Scene
-from panda3d.bullet import BulletWorld
-
-
-class Menu(DirectObject):
-
-    def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes, fun_test):
-        super().__init__()
-        self._fsm = fsm
-        self._lang_mgr = lang_mgr
-        self._opt_file = opt_file
-        self._music = music
-        self._pipeline = pipeline
-        self._scenes = scenes
-        self._fun_test = fun_test
-        self._enforced_res = ''
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        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)
-        self._widgets = []
-        self._common = {
-            'scale': .12,
-            'text_font': self._font,
-            'text_fg': (.9, .9, .9, 1),
-            'relief': FLAT,
-            'frameColor': (.4, .4, .4, .14),
-            'rolloverSound': loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            'clickSound': loader.load_sfx('assets/audio/sfx/click.ogg')}
-        self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
-        hlc = self._common_btn['frameColor']
-        hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
-        self._common_opt = {
-            'item_frameColor': self._common_btn['frameColor'],
-            'popupMarker_frameColor': self._common_btn['frameColor'],
-            'item_relief': self._common_btn['relief'],
-            'item_text_font': self._common_btn['text_font'],
-            'item_text_fg': self._common_btn['text_fg'],
-            'textMayChange': 1,
-            'highlightColor': hlc,
-            'text_align': TextNode.A_center,
-        } | self._common_btn
-        f_s = self._common_opt['frameSize']
-        self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
-        self._common_slider = self._common | {
-            'range': (0, 1),
-            'thumb_frameColor': (.4, .4, .4, .4),
-            'thumb_scale': 1.6,
-            'scale': .4}
-        del self._common_slider['rolloverSound']
-        del self._common_slider['clickSound']
-        self._set_main()
-
-    def enforce_res(self, val):
-        self._enforced_res = val
-        info('enforced resolution: ' + val)
-
-    def _set_main(self):
-        self._widgets = []
-        self._widgets += [DirectButton(
-            text=_('Play'), pos=(0, 1, .6), command=self.on_play,
-            **self._common_btn)]
-        self._widgets += [DirectButton(
-            text=_('Options'), pos=(0, 1, .2), command=self.on_options,
-            **self._common_btn)]
-        self._widgets += [DirectButton(
-            text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
-            **self._common_btn)]
-        self._widgets += [DirectButton(
-            text=_('Exit'), pos=(0, 1, -.6), command=lambda: exit(),
-            **self._common_btn)]
-        self._rearrange_width()
-        self.accept('enforce_resolution', self.enforce_res)
-
-    def _set_options(self):
-        self._widgets = []
-        self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
-        items = [fnc() for fnc in self._lang_funcs]
-        inititem = {
-            'en': _('English'),
-            'it': _('Italian')
-        }[self._opt_file['settings']['language']]
-        btn = DirectOptionMenu(
-            text=_('Language'), items=items, initialitem=inititem,
-            pos=(0, 1, .8), command=self.on_language, **self._common_opt)
-        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
-        btn.popupMenu['relief'] = self._common_btn['relief']
-        self._widgets += [btn]
-        self._widgets += [OnscreenText(
-            _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_right)]
-        self._widgets += [DirectSlider(
-            pos=(.5, 1, .57),
-            value=self._opt_file['settings']['volume'],
-            command=self.on_volume,
-            **self._common_slider)]
-        self._slider = self._widgets[-1]
-        self._widgets += [DirectCheckButton(
-            text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['fullscreen'],
-            **self._common_btn)]
-        res = self._opt_file['settings']['resolution']
-        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())]
-        resolutions = list(set(resolutions))
-        resolutions = sorted(resolutions)
-        resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
-        resolutions = ['x'.join(_res) for _res in resolutions]
-        if not res:
-            res = resolutions[-1]
-        btn = DirectOptionMenu(
-            text=_('Resolution'), items=resolutions, initialitem=res,
-            pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
-        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
-        btn.popupMenu['relief'] = self._common_btn['relief']
-        self._widgets += [btn]
-        self._widgets += [DirectCheckButton(
-            text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['antialiasing'],
-            **self._common_btn)]
-        self._widgets += [DirectCheckButton(
-            text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['shadows'],
-            **self._common_btn)]
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-        self.accept('enforce_resolution', self.enforce_res)
-
-    def _set_credits(self):
-        self._widgets = []
-        tp_scale = TextProperties()
-        tp_scale.set_text_scale(.64)
-        TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
-        self._widgets += [OnscreenText(
-            _('Code and gfx\n  \1scale\1Flavio Calva\2\n\n\nMusic\n  \1scale\1Stefan Grossmann\2'),
-            pos=(-.9, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_left)]
-        self._widgets += [DirectButton(
-            text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
-            **self._common_btn | {'scale': .08})]
-        self._widgets += [OnscreenText(
-            _('Special thanks to:\n  \1scale\1rdb\2\n  \1scale\1Luisa Tenuta\2\n  \1scale\1Damiana Ercolani\2'),
-            pos=(.1, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_left)]
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-        self.accept('enforce_resolution', self.enforce_res)
-
-    def on_play(self):
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._widgets = []
-        cmn = self._common_btn.copy() | {
-            'frameSize': (-2.4, 2.4, -2.4, 2.4),
-            'frameColor': (1, 1, 1, .8),
-            'text_scale': .64}
-        left = - (dx := .8) * (min(4, len(self._scenes)) - 1) / 2
-        for i, cls in enumerate(self._scenes):
-            top = .1 if len(self._scenes) < 5 else .6
-            row = 0 if i < 4 else 1
-            self._widgets += [DirectButton(
-                text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
-                command=self.start, extraArgs=[cls], text_wordwrap=6,
-                frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
-                **cmn)]
-            for j in range(4):
-                tnode = self._widgets[-1].component('text%s' % j).textNode
-                height = - tnode.getLineHeight() / 2
-                height += (tnode.get_height() - tnode.get_line_height()) / 2
-                self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-
-    def start(self, cls):
-        self._fsm.demand('Scene', cls)
-
-    def on_options(self):
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_options()
-
-    def on_credits(self):
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_credits()
-
-    def _rearrange_width(self):
-        max_width = 0
-        for wdg in self._widgets:
-            t_n = wdg.component('text0')
-            u_l = t_n.textNode.get_upper_left_3d()
-            l_r = t_n.textNode.get_lower_right_3d()
-            max_width = max(l_r[0] - u_l[0], max_width)
-        for wdg in self._widgets:
-            m_w = max_width / 2 + .8
-            wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
-
-    def on_language(self, arg):
-        lang_code = {
-            _('English'): 'en_EN',
-            _('Italian'): 'it_IT'}[arg]
-        self._lang_mgr.set_lang(lang_code)
-        self._opt_file['settings']['language'] = lang_code[:2]
-        self._opt_file.store()
-        self.on_options()
-
-    def on_volume(self):
-        self._opt_file['settings']['volume'] = self._slider['value']
-        self._music.set_volume(self._slider['value'])
-
-    def on_fullscreen(self, arg):
-        props = WindowProperties()
-        props.set_fullscreen(arg)
-        if not self._fun_test:
-            base.win.request_properties(props)
-            # if we actually switch to fullscreen during the tests then
-            # exwm inside qemu can't restore the correct resolution
-            # i may re-enable this if/when i run the tests onto a
-            # physical machine
-        self._opt_file['settings']['fullscreen'] = int(arg)
-        self._opt_file.store()
-
-    def on_resolution(self, arg):
-        info('on resolution: %s (%s)' % (arg, self._enforced_res))
-        arg = self._enforced_res or arg
-        info('set resolution: %s' % arg)
-        props = WindowProperties()
-        props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
-        base.win.request_properties(props)
-        self._opt_file['settings']['resolution'] = arg
-        self._opt_file.store()
-
-    def on_aa(self, arg):
-        self._pipeline.msaa_samples = 4 if arg else 1
-        debug(f'msaa: {self._pipeline.msaa_samples}')
-        self._opt_file['settings']['antialiasing'] = int(arg)
-        self._opt_file.store()
-
-    def on_shadows(self, arg):
-        self._pipeline.enable_shadows = int(arg)
-        debug(f'shadows: {self._pipeline.enable_shadows}')
-        self._opt_file['settings']['shadows'] = int(arg)
-        self._opt_file.store()
-
-    def on_website(self):
-        if platform.startswith('linux'):
-            environ['LD_LIBRARY_PATH'] = ''
-            system('xdg-open https://www.ya2.it')
-        else:
-            open_new_tab('https://www.ya2.it')
-
-    def on_back(self):
-        self._opt_file.store()
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_main()
-
-    def destroy(self):
-        [wdg.destroy() for wdg in self._widgets]
-        self._cursor.destroy()
-        self.ignore('enforce_resolution')
diff --git a/game/music.py b/game/music.py
deleted file mode 100644 (file)
index cf6c0c9..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-from os.path import dirname, exists, basename
-from platform import system
-from glob import glob
-from pathlib import Path
-from random import choice
-from logging import info
-from panda3d.core import AudioSound, Filename
-
-
-class MusicMgr:
-
-    def __init__(self, volume):
-        files = self.curr_path + 'assets/audio/music/*.ogg'
-        self._start_music(glob(files))
-        base.musicManager.setVolume(.8 * volume)
-        base.sfxManagerList[0].setVolume(volume)
-        taskMgr.add(self._on_frame, 'on frame music')
-
-    @property
-    def is_appimage(self):
-        par_path = str(Path(__file__).parent.absolute())
-        is_appimage = par_path.startswith('/tmp/.mount_Pmachi')
-        return is_appimage and par_path.endswith('/usr/bin')
-
-    @property
-    def curr_path(self):
-        if system() == 'Windows':
-            return ''
-        if exists('main.py'):
-            return ''
-        else:
-            par_path = str(Path(__file__).parent.absolute())
-        if self.is_appimage:
-            par_path = str(Path(par_path).absolute())
-        par_path += '/'
-        return par_path
-
-    def _start_music(self, files):
-        self._music = loader.load_music(choice(files))
-        info('playing music ' + self._music.get_name())
-        self._music.play()
-
-    def set_volume(self, volume):
-        base.musicManager.setVolume(.8 * volume)
-        base.sfxManagerList[0].setVolume(volume)
-
-    def _on_frame(self, task):
-        if self._music.status() == AudioSound.READY:
-            oggs = Filename(self.curr_path + 'assets/audio/music/*.ogg').to_os_specific()
-            files = glob(oggs)
-            rm_music = Filename(self.curr_path + 'assets/audio/music/' + basename(self._music.get_name())).to_os_specific()
-            # basename is needed in windows
-            files.remove(rm_music)
-            self._start_music(files)
-        return task.cont
diff --git a/game/scene.py b/game/scene.py
deleted file mode 100644 (file)
index 6cdf9f9..0000000
+++ /dev/null
@@ -1,587 +0,0 @@
-from os.path import exists
-from os import makedirs
-from glob import glob
-from logging import debug, info
-from importlib import import_module
-from inspect import isclass
-from panda3d.core import AmbientLight, DirectionalLight, Point3, 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 game.items.background import Background
-from game.sidepanel import SidePanel
-from ya2.engine.gui.cursor import MouseCursor
-from ya2.lib.p3d.gfx import P3dGfxMgr
-
-
-class Scene(DirectObject):
-
-    def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes):
-        super().__init__()
-        self._world = world
-        self._exit_cb = exit_cb
-        self._dbg_items = dbg_items
-        self._reload_cb = reload_cb
-        self._scenes = scenes
-        self._enforce_res = ''
-        self.accept('enforce_res', self.enforce_res)
-        self._set_camera()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_gui()
-        self._set_lights()
-        self._set_input()
-        self._set_mouse_plane()
-        self.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, 'on_frame')
-
-    @staticmethod
-    def name():
-        return ''
-
-    def _instr_txt(self):
-        return ''
-
-    def _set_items(self):
-        self.items = []
-
-    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.__class__.__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]
-        self._set_items()
-        self._state = 'init'
-        self._commands = []
-        self._command_idx = 0
-        if hasattr(self, '_success_txt'):
-            self._success_txt.destroy()
-            del self._success_txt
-        self.__right_btn['state'] = NORMAL
-
-    def enforce_res(self, val):
-        self._enforce_res = val
-        info('enforce res: ' + val)
-
-    def destroy(self):
-        self.ignore('enforce_res')
-        self._unset_gui()
-        self._unset_lights()
-        self._unset_input()
-        self._unset_mouse_plane()
-        [itm.destroy() for itm in self.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()
-
-    def _set_camera(self):
-        base.camera.set_pos(0, -20, 0)
-        base.camera.look_at(0, 0, 0)
-
-    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'),
-            ('information', self._set_instructions, NORMAL, abl, 'gray'),
-            ('right', self.on_play, NORMAL, abr, 'green'),
-            #('next', self.on_next, DISABLED, abr, 'gray'),
-            #('previous', self.on_prev, DISABLED, abr, 'gray'),
-            #('rewind', self.reset, NORMAL, abr, 'gray')
-        ]
-        num_l = num_r = 0
-        btns = []
-        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.set_transparency(True)
-            btns += [btn]
-        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)
-
-    def _unset_gui(self):
-        btns = [
-            self.__home_btn, self.__info_btn, self.__right_btn,
-            #self.__next_btn, self.__prev_btn, self.__rewind_btn
-        ]
-        [btn.destroy() for btn in btns]
-        if self._dbg_items:
-            self._info_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 = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
-        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
-                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):
-        pass
-
-    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 self._win_condition():
-            self._set_fail() if self._enforce_res == 'fail' else self._set_win()
-        elif self._state == 'playing' and self._fail_condition():
-            self._set_win() if self._enforce_res == 'win' else self._set_fail()
-        if any(itm._overlapping for itm in self.items):
-            self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
-        else:
-            self._cursor.cursor_img.img.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]
-
-    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_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))
-        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(
-            self._instr_txt(), parent=frm, font=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 - 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.set_transparency(True)
-
-    def _set_win(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 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]
-        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.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.set_transparency(True)
-        enabled = self._scenes.index(self.__class__) < len(self._scenes) - 1
-        if enabled:
-            next_scene = self._scenes[self._scenes.index(self.__class__) + 1]
-        else:
-            next_scene = None
-        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['state'] = NORMAL if enabled else DISABLED
-        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]
-        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.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.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
-        ]
-        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
-        ]
-        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()
diff --git a/game/scenes/__init__.py b/game/scenes/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/game/scenes/scene_basketball.py b/game/scenes/scene_basketball.py
deleted file mode 100644 (file)
index d57adf7..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-from game.scene import Scene
-from game.items.box import Box
-from game.items.shelf import Shelf
-from game.items.domino import Domino, UpStrategy, DownStrategy
-from game.items.basketball import Basketball
-from game.items.teetertooter import TeeterTooter
-
-
-class SceneBasketBall(Scene):
-
-    @staticmethod
-    def name():
-        return _('Basket ball')
-
-    def _set_items(self):
-        self.items = []
-        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-4.45, 0, -3.18), r=27, restitution=1)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-5.45, 0, -3.18), restitution=1)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 30))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
-        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
-        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: you must hit every domino piece\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_box.py b/game/scenes/scene_box.py
deleted file mode 100644 (file)
index c0eee15..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-from game.scene import Scene
-from game.items.box import Box, HitStrategy
-from game.items.shelf import Shelf
-from game.items.domino import Domino
-from game.items.basketball import Basketball
-from game.items.teetertooter import TeeterTooter
-
-
-class SceneBox(Scene):
-
-    @staticmethod
-    def name():
-        return _('Box')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.46, 0, -3.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.43, 0, -3.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.29, 0, .26), r=28.45)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.15, 0, -1.49), r=28.45)]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.55, 0, 1.23), friction=.4)]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.38, 0, -3.35))]
-        self.items[-1].set_strategy(HitStrategy(self.items[-2], self.items[-1].node, self.items[-1]._world))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04), tgt_degrees=88)]
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: the left box must hit the right box\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_domino.py b/game/scenes/scene_domino.py
deleted file mode 100644 (file)
index d3edad2..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-from game.scene import Scene
-from game.items.box import Box
-from game.items.shelf import Shelf
-from game.items.domino import Domino, DownStrategy
-from game.items.basketball import Basketball
-from game.items.teetertooter import TeeterTooter
-
-
-class SceneDomino(Scene):
-
-    @staticmethod
-    def name():
-        return _('Domino')
-
-    def _set_items(self):
-        self.items = []
-        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.2, 0, -.6))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.2, 0, -.6))]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 88))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must fall\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_domino_box.py b/game/scenes/scene_domino_box.py
deleted file mode 100644 (file)
index 629047c..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-from game.scene import Scene
-from game.items.box import Box
-from game.items.shelf import Shelf
-from game.items.domino import Domino, UpStrategy, DownStrategy
-from game.items.basketball import Basketball
-from game.items.teetertooter import TeeterTooter
-
-
-class SceneDominoBox(Scene):
-
-    @staticmethod
-    def name():
-        return _('Domino and box')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.94), r=37)]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: only the last piece of each row must be up\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_domino_box_basketball.py b/game/scenes/scene_domino_box_basketball.py
deleted file mode 100644 (file)
index 84b5129..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-from game.scene import Scene
-from game.items.box import Box
-from game.items.shelf import Shelf
-from game.items.domino import Domino, UpStrategy, DownStrategy
-from game.items.basketball import Basketball
-from game.items.teetertooter import TeeterTooter
-
-
-class SceneDominoBoxBasketball(Scene):
-
-    @staticmethod
-    def name():
-        return _('Domino, box and basket ball')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1, mass=5)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.3, 1, 2.5))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.68, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.35, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.08, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.78, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.53, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must be hit\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_teeter_domino_box_basketball.py b/game/scenes/scene_teeter_domino_box_basketball.py
deleted file mode 100644 (file)
index b433106..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-from game.scene import Scene
-from game.items.box import Box
-from game.items.shelf import Shelf
-from game.items.domino import Domino, UpStrategy, DownStrategy
-from game.items.basketball import Basketball
-from game.items.teetertooter import TeeterTooter
-
-
-class SceneTeeterDominoBoxBasketball(Scene):
-
-    @staticmethod
-    def name():
-        return _('Teeter tooter, domino, box and basket ball')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=2, friction=1)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(.98, 1, 1.02))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.45))]
-        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.20))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=24.60, friction=1, pos=(-6.15, 0, -.93))]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.3, friction=1, model_scale=.5, pos=(-5.38, 0, -.93), r=24.60)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(5.37, 0, -.78))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(7.48, 0, -.78))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.74, 0, -1.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(6.88, 0, -1.95))]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(4.83, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(5.67, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(6.59, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.53, 0, -1.95), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.63, 0, -1.95), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-3.65, 0, 1.05), r=28, friction=0)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.27, 0, 1.72), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.88, 0, 1.72), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.67, 0, .55), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.52, 0, .55), restitution=.95)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-1.73, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.97, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.1, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must be hit\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_teeter_tooter.py b/game/scenes/scene_teeter_tooter.py
deleted file mode 100644 (file)
index 51c5eb3..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-from game.scene import Scene
-from game.items.box import Box
-from game.items.shelf import Shelf
-from game.items.domino import Domino, UpStrategy, DownStrategy
-from game.items.basketball import Basketball
-from game.items.teetertooter import TeeterTooter
-
-
-class SceneTeeterTooter(Scene):
-
-    @staticmethod
-    def name():
-        return _('Teeter tooter')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=1, friction=1)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.76, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.27, 0, -.28))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.38, 0, -.28))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.74, 0, -1.20))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=-25.30, friction=1, pos=(-2.78, 0, -.93))]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.2, friction=1, model_scale=.5, pos=(-3.61, 0, -.99), r=-25.30)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.25, 0, -.57), r=52)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.50, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: you must hit every domino piece\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/sidepanel.py b/game/sidepanel.py
deleted file mode 100644 (file)
index ee59d7d..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-from textwrap import dedent
-from panda3d.core import GeomVertexData, GeomVertexFormat, Geom, \
-    GeomVertexWriter, GeomTriangles, GeomNode, Shader, Point3, Plane, Vec3
-from ya2.lib.p3d.gfx import P3dGfxMgr
-
-
-class SidePanel:
-
-    def __init__(self, world, plane_node, top_l, bottom_r, y, items):
-        self._world = world
-        self._plane_node = plane_node
-        self._set((-1, 1), y)
-        self.update(items)
-
-    def update(self, items):
-        p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
-        for hit in self._world.ray_test_all(p_from, p_to).get_hits():
-            if hit.get_node() == self._plane_node:
-                pos = hit.get_hit_pos()
-        y = 0
-        corner = -20, 20
-        for item in items:
-            if not item._instantiated:
-                bounds = item._np.get_tight_bounds()
-                if bounds[1][1] > y:
-                    y = bounds[1][1]
-                icorner = item.get_corner()
-                icorner = P3dGfxMgr.screen_coord(icorner)
-                if icorner[0] > corner[0]:
-                    corner = icorner[0], corner[1]
-                if icorner[1] < corner[1]:
-                    corner = corner[0], icorner[1]
-        self._set((pos[0], pos[2]), y)
-        bounds = self._np.get_tight_bounds()
-        corner3d = bounds[1][0], bounds[1][1], bounds[0][2]
-        corner2d = P3dGfxMgr.screen_coord(corner3d)
-        plane = Plane(Vec3(0, 1, 0), y)
-        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
-        ar, margin = base.get_aspect_ratio(), .04
-        x = corner[0] / ar if ar >= 1 else corner[0]
-        z = corner[1] * ar if ar < 1 else corner[1]
-        x += margin / ar if ar >= 1 else margin
-        z -= margin * ar if ar < 1 else margin
-        base.camLens.extrude((x, z), near_pt, far_pt)
-        plane.intersects_line(
-            pos3d, render.get_relative_point(base.camera, near_pt),
-            render.get_relative_point(base.camera, far_pt))
-        corner_pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
-        base.camLens.extrude((-1, 1), near_pt, far_pt)
-        plane.intersects_line(
-            corner_pos3d, render.get_relative_point(base.camera, near_pt),
-            render.get_relative_point(base.camera, far_pt))
-        self._np.set_pos((pos3d + corner_pos3d) / 2)
-        scale = Vec3(pos3d[0] - corner_pos3d[0], 1, corner_pos3d[2] - pos3d[2])
-        self._np.set_scale(scale)
-
-    def _set(self, pos, y):
-        if hasattr(self, '_np'):
-            self._np.remove_node()
-        vdata = GeomVertexData('quad', GeomVertexFormat.get_v3(), Geom.UHStatic)
-        vdata.setNumRows(2)
-        vertex = GeomVertexWriter(vdata, 'vertex')
-        vertex.add_data3(.5, 0, -.5)
-        vertex.add_data3(.5, 0, .5)
-        vertex.add_data3(-.5, 0, .5)
-        vertex.add_data3(-.5, 0, -.5)
-        prim = GeomTriangles(Geom.UHStatic)
-        prim.add_vertices(0, 1, 2)
-        prim.add_vertices(0, 2, 3)
-        prim.close_primitive()
-        geom = Geom(vdata)
-        geom.add_primitive(prim)
-        node = GeomNode('gnode')
-        node.add_geom(geom)
-        self._np = render.attach_new_node(node)
-        self._np.setTransparency(True)
-        self._np.set_pos(pos[0], y, pos[1])
-        vert = '''\
-            #version 130
-            uniform mat4 p3d_ModelViewProjectionMatrix;
-            in vec4 p3d_Vertex;
-            void main() {
-                gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; }'''
-        frag = '''\
-            #version 130
-            out vec4 p3d_FragColor;
-            void main() {
-                p3d_FragColor = vec4(.04, .04, .04, .08); }'''
-        self._np.set_shader(Shader.make(Shader.SL_GLSL, dedent(vert), dedent(frag)))
-        # mat = Material()
-        # mat.set_base_color((1, 1, 1, 1))
-        # mat.set_emission((1, 1, 1, 1))
-        # mat.set_metallic(.5)
-        # mat.set_roughness(.5)
-        # np.set_material(mat)
-        # texture_sz = 64
-        # base_color_pnm = PNMImage(texture_sz, texture_sz)
-        # base_color_pnm.fill(.1, .1, .1)
-        # base_color_pnm.add_alpha()
-        # base_color_pnm.alpha_fill(.04)
-        # base_color_tex = Texture('base color')
-        # base_color_tex.load(base_color_pnm)
-        # ts = TextureStage('base color')
-        # ts.set_mode(TextureStage.M_modulate)
-        # np.set_texture(ts, base_color_tex)
-        # emission_pnm = PNMImage(texture_sz, texture_sz)
-        # emission_pnm.fill(0, 0, 0)
-        # emission_tex = Texture('emission')
-        # emission_tex.load(emission_pnm)
-        # ts = TextureStage('emission')
-        # ts.set_mode(TextureStage.M_emission)
-        # np.set_texture(ts, emission_tex)
-        # metal_rough_pnm = PNMImage(texture_sz, texture_sz)
-        # ambient_occlusion = 1
-        # roughness = .5
-        # metallicity = .5
-        # metal_rough_pnm.fill(ambient_occlusion, roughness, metallicity)
-        # metal_rough_tex = Texture('ao metal roughness')
-        # metal_rough_tex.load(metal_rough_pnm)
-        # ts = TextureStage('ao metal roughness')
-        # ts.set_mode(TextureStage.M_selector)
-        # np.set_texture(ts, metal_rough_tex)
-        # normal_pnm = PNMImage(texture_sz, texture_sz)
-        # normal_pnm.fill(.5, .5, .1)
-        # normal_tex = Texture('normals')
-        # normal_tex.load(normal_pnm)
-        # ts = TextureStage('normals')
-        # ts.set_mode(TextureStage.M_normal)
-        # np.set_texture(ts, normal_tex)
-
-    def destroy(self):
-        self._np.remove_node()
diff --git a/gui/__init__.py b/gui/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gui/menu.py b/gui/menu.py
new file mode 100644 (file)
index 0000000..8f21d5b
--- /dev/null
@@ -0,0 +1,304 @@
+from logging import info, debug
+from sys import platform, exit
+from os import environ, system
+from glob import glob
+from importlib import import_module
+from inspect import isclass
+from webbrowser import open_new_tab
+from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
+    TextProperties, TextPropertiesManager
+from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
+    DirectOptionMenu, DirectSlider, DirectCheckButton
+from direct.gui.DirectGuiGlobals import FLAT
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+from ya2.utils.cursor import MouseCursor
+from logics.scene import Scene
+from panda3d.bullet import BulletWorld
+
+
+class Menu(DirectObject):
+
+    def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes, fun_test):
+        super().__init__()
+        self._fsm = fsm
+        self._lang_mgr = lang_mgr
+        self._opt_file = opt_file
+        self._music = music
+        self._pipeline = pipeline
+        self._scenes = scenes
+        self._fun_test = fun_test
+        self._enforced_res = ''
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        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)
+        self._widgets = []
+        self._common = {
+            'scale': .12,
+            'text_font': self._font,
+            'text_fg': (.9, .9, .9, 1),
+            'relief': FLAT,
+            'frameColor': (.4, .4, .4, .14),
+            'rolloverSound': loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            'clickSound': loader.load_sfx('assets/audio/sfx/click.ogg')}
+        self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
+        hlc = self._common_btn['frameColor']
+        hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
+        self._common_opt = {
+            'item_frameColor': self._common_btn['frameColor'],
+            'popupMarker_frameColor': self._common_btn['frameColor'],
+            'item_relief': self._common_btn['relief'],
+            'item_text_font': self._common_btn['text_font'],
+            'item_text_fg': self._common_btn['text_fg'],
+            'textMayChange': 1,
+            'highlightColor': hlc,
+            'text_align': TextNode.A_center,
+        } | self._common_btn
+        f_s = self._common_opt['frameSize']
+        self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
+        self._common_slider = self._common | {
+            'range': (0, 1),
+            'thumb_frameColor': (.4, .4, .4, .4),
+            'thumb_scale': 1.6,
+            'scale': .4}
+        del self._common_slider['rolloverSound']
+        del self._common_slider['clickSound']
+        self._set_main()
+
+    def enforce_res(self, val):
+        self._enforced_res = val
+        info('enforced resolution: ' + val)
+
+    def _set_main(self):
+        self._widgets = []
+        self._widgets += [DirectButton(
+            text=_('Play'), pos=(0, 1, .6), command=self.on_play,
+            **self._common_btn)]
+        self._widgets += [DirectButton(
+            text=_('Options'), pos=(0, 1, .2), command=self.on_options,
+            **self._common_btn)]
+        self._widgets += [DirectButton(
+            text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
+            **self._common_btn)]
+        self._widgets += [DirectButton(
+            text=_('Exit'), pos=(0, 1, -.6), command=lambda: exit(),
+            **self._common_btn)]
+        self._rearrange_width()
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def _set_options(self):
+        self._widgets = []
+        self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
+        items = [fnc() for fnc in self._lang_funcs]
+        inititem = {
+            'en': _('English'),
+            'it': _('Italian')
+        }[self._opt_file['settings']['language']]
+        btn = DirectOptionMenu(
+            text=_('Language'), items=items, initialitem=inititem,
+            pos=(0, 1, .8), command=self.on_language, **self._common_opt)
+        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
+        btn.popupMenu['relief'] = self._common_btn['relief']
+        self._widgets += [btn]
+        self._widgets += [OnscreenText(
+            _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_right)]
+        self._widgets += [DirectSlider(
+            pos=(.5, 1, .57),
+            value=self._opt_file['settings']['volume'],
+            command=self.on_volume,
+            **self._common_slider)]
+        self._slider = self._widgets[-1]
+        self._widgets += [DirectCheckButton(
+            text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['fullscreen'],
+            **self._common_btn)]
+        res = self._opt_file['settings']['resolution']
+        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())]
+        resolutions = list(set(resolutions))
+        resolutions = sorted(resolutions)
+        resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
+        resolutions = ['x'.join(_res) for _res in resolutions]
+        if not res:
+            res = resolutions[-1]
+        btn = DirectOptionMenu(
+            text=_('Resolution'), items=resolutions, initialitem=res,
+            pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
+        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
+        btn.popupMenu['relief'] = self._common_btn['relief']
+        self._widgets += [btn]
+        self._widgets += [DirectCheckButton(
+            text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['antialiasing'],
+            **self._common_btn)]
+        self._widgets += [DirectCheckButton(
+            text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['shadows'],
+            **self._common_btn)]
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def _set_credits(self):
+        self._widgets = []
+        tp_scale = TextProperties()
+        tp_scale.set_text_scale(.64)
+        TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
+        self._widgets += [OnscreenText(
+            _('Code and gfx\n  \1scale\1Flavio Calva\2\n\n\nMusic\n  \1scale\1Stefan Grossmann\2'),
+            pos=(-.9, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_left)]
+        self._widgets += [DirectButton(
+            text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
+            **self._common_btn | {'scale': .08})]
+        self._widgets += [OnscreenText(
+            _('Special thanks to:\n  \1scale\1rdb\2\n  \1scale\1Luisa Tenuta\2\n  \1scale\1Damiana Ercolani\2'),
+            pos=(.1, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_left)]
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def on_play(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._widgets = []
+        cmn = self._common_btn.copy() | {
+            'frameSize': (-2.4, 2.4, -2.4, 2.4),
+            'frameColor': (1, 1, 1, .8),
+            'text_scale': .64}
+        left = - (dx := .8) * (min(4, len(self._scenes)) - 1) / 2
+        for i, cls in enumerate(self._scenes):
+            top = .1 if len(self._scenes) < 5 else .6
+            row = 0 if i < 4 else 1
+            self._widgets += [DirectButton(
+                text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
+                command=self.start, extraArgs=[cls], text_wordwrap=6,
+                frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
+                **cmn)]
+            for j in range(4):
+                tnode = self._widgets[-1].component('text%s' % j).textNode
+                height = - tnode.getLineHeight() / 2
+                height += (tnode.get_height() - tnode.get_line_height()) / 2
+                self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+
+    def start(self, cls):
+        self._fsm.demand('Scene', cls)
+
+    def on_options(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_options()
+
+    def on_credits(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_credits()
+
+    def _rearrange_width(self):
+        max_width = 0
+        for wdg in self._widgets:
+            t_n = wdg.component('text0')
+            u_l = t_n.textNode.get_upper_left_3d()
+            l_r = t_n.textNode.get_lower_right_3d()
+            max_width = max(l_r[0] - u_l[0], max_width)
+        for wdg in self._widgets:
+            m_w = max_width / 2 + .8
+            wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
+
+    def on_language(self, arg):
+        lang_code = {
+            _('English'): 'en_EN',
+            _('Italian'): 'it_IT'}[arg]
+        self._lang_mgr.set_lang(lang_code)
+        self._opt_file['settings']['language'] = lang_code[:2]
+        self._opt_file.store()
+        self.on_options()
+
+    def on_volume(self):
+        self._opt_file['settings']['volume'] = self._slider['value']
+        self._music.set_volume(self._slider['value'])
+
+    def on_fullscreen(self, arg):
+        props = WindowProperties()
+        props.set_fullscreen(arg)
+        if not self._fun_test:
+            base.win.request_properties(props)
+            # if we actually switch to fullscreen during the tests then
+            # exwm inside qemu can't restore the correct resolution
+            # i may re-enable this if/when i run the tests onto a
+            # physical machine
+        self._opt_file['settings']['fullscreen'] = int(arg)
+        self._opt_file.store()
+
+    def on_resolution(self, arg):
+        info('on resolution: %s (%s)' % (arg, self._enforced_res))
+        arg = self._enforced_res or arg
+        info('set resolution: %s' % arg)
+        props = WindowProperties()
+        props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
+        base.win.request_properties(props)
+        self._opt_file['settings']['resolution'] = arg
+        self._opt_file.store()
+
+    def on_aa(self, arg):
+        self._pipeline.msaa_samples = 4 if arg else 1
+        debug(f'msaa: {self._pipeline.msaa_samples}')
+        self._opt_file['settings']['antialiasing'] = int(arg)
+        self._opt_file.store()
+
+    def on_shadows(self, arg):
+        self._pipeline.enable_shadows = int(arg)
+        debug(f'shadows: {self._pipeline.enable_shadows}')
+        self._opt_file['settings']['shadows'] = int(arg)
+        self._opt_file.store()
+
+    def on_website(self):
+        if platform.startswith('linux'):
+            environ['LD_LIBRARY_PATH'] = ''
+            system('xdg-open https://www.ya2.it')
+        else:
+            open_new_tab('https://www.ya2.it')
+
+    def on_back(self):
+        self._opt_file.store()
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_main()
+
+    def destroy(self):
+        [wdg.destroy() for wdg in self._widgets]
+        self._cursor.destroy()
+        self.ignore('enforce_resolution')
diff --git a/gui/sidepanel.py b/gui/sidepanel.py
new file mode 100644 (file)
index 0000000..39afaa4
--- /dev/null
@@ -0,0 +1,132 @@
+from textwrap import dedent
+from panda3d.core import GeomVertexData, GeomVertexFormat, Geom, \
+    GeomVertexWriter, GeomTriangles, GeomNode, Shader, Point3, Plane, Vec3
+from ya2.p3d.gfx import P3dGfxMgr
+
+
+class SidePanel:
+
+    def __init__(self, world, plane_node, top_l, bottom_r, y, items):
+        self._world = world
+        self._plane_node = plane_node
+        self._set((-1, 1), y)
+        self.update(items)
+
+    def update(self, items):
+        p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
+        for hit in self._world.ray_test_all(p_from, p_to).get_hits():
+            if hit.get_node() == self._plane_node:
+                pos = hit.get_hit_pos()
+        y = 0
+        corner = -20, 20
+        for item in items:
+            if not item._instantiated:
+                bounds = item._np.get_tight_bounds()
+                if bounds[1][1] > y:
+                    y = bounds[1][1]
+                icorner = item.get_corner()
+                icorner = P3dGfxMgr.screen_coord(icorner)
+                if icorner[0] > corner[0]:
+                    corner = icorner[0], corner[1]
+                if icorner[1] < corner[1]:
+                    corner = corner[0], icorner[1]
+        self._set((pos[0], pos[2]), y)
+        bounds = self._np.get_tight_bounds()
+        corner3d = bounds[1][0], bounds[1][1], bounds[0][2]
+        corner2d = P3dGfxMgr.screen_coord(corner3d)
+        plane = Plane(Vec3(0, 1, 0), y)
+        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
+        ar, margin = base.get_aspect_ratio(), .04
+        x = corner[0] / ar if ar >= 1 else corner[0]
+        z = corner[1] * ar if ar < 1 else corner[1]
+        x += margin / ar if ar >= 1 else margin
+        z -= margin * ar if ar < 1 else margin
+        base.camLens.extrude((x, z), near_pt, far_pt)
+        plane.intersects_line(
+            pos3d, render.get_relative_point(base.camera, near_pt),
+            render.get_relative_point(base.camera, far_pt))
+        corner_pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
+        base.camLens.extrude((-1, 1), near_pt, far_pt)
+        plane.intersects_line(
+            corner_pos3d, render.get_relative_point(base.camera, near_pt),
+            render.get_relative_point(base.camera, far_pt))
+        self._np.set_pos((pos3d + corner_pos3d) / 2)
+        scale = Vec3(pos3d[0] - corner_pos3d[0], 1, corner_pos3d[2] - pos3d[2])
+        self._np.set_scale(scale)
+
+    def _set(self, pos, y):
+        if hasattr(self, '_np'):
+            self._np.remove_node()
+        vdata = GeomVertexData('quad', GeomVertexFormat.get_v3(), Geom.UHStatic)
+        vdata.setNumRows(2)
+        vertex = GeomVertexWriter(vdata, 'vertex')
+        vertex.add_data3(.5, 0, -.5)
+        vertex.add_data3(.5, 0, .5)
+        vertex.add_data3(-.5, 0, .5)
+        vertex.add_data3(-.5, 0, -.5)
+        prim = GeomTriangles(Geom.UHStatic)
+        prim.add_vertices(0, 1, 2)
+        prim.add_vertices(0, 2, 3)
+        prim.close_primitive()
+        geom = Geom(vdata)
+        geom.add_primitive(prim)
+        node = GeomNode('gnode')
+        node.add_geom(geom)
+        self._np = render.attach_new_node(node)
+        self._np.setTransparency(True)
+        self._np.set_pos(pos[0], y, pos[1])
+        vert = '''\
+            #version 130
+            uniform mat4 p3d_ModelViewProjectionMatrix;
+            in vec4 p3d_Vertex;
+            void main() {
+                gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; }'''
+        frag = '''\
+            #version 130
+            out vec4 p3d_FragColor;
+            void main() {
+                p3d_FragColor = vec4(.04, .04, .04, .08); }'''
+        self._np.set_shader(Shader.make(Shader.SL_GLSL, dedent(vert), dedent(frag)))
+        # mat = Material()
+        # mat.set_base_color((1, 1, 1, 1))
+        # mat.set_emission((1, 1, 1, 1))
+        # mat.set_metallic(.5)
+        # mat.set_roughness(.5)
+        # np.set_material(mat)
+        # texture_sz = 64
+        # base_color_pnm = PNMImage(texture_sz, texture_sz)
+        # base_color_pnm.fill(.1, .1, .1)
+        # base_color_pnm.add_alpha()
+        # base_color_pnm.alpha_fill(.04)
+        # base_color_tex = Texture('base color')
+        # base_color_tex.load(base_color_pnm)
+        # ts = TextureStage('base color')
+        # ts.set_mode(TextureStage.M_modulate)
+        # np.set_texture(ts, base_color_tex)
+        # emission_pnm = PNMImage(texture_sz, texture_sz)
+        # emission_pnm.fill(0, 0, 0)
+        # emission_tex = Texture('emission')
+        # emission_tex.load(emission_pnm)
+        # ts = TextureStage('emission')
+        # ts.set_mode(TextureStage.M_emission)
+        # np.set_texture(ts, emission_tex)
+        # metal_rough_pnm = PNMImage(texture_sz, texture_sz)
+        # ambient_occlusion = 1
+        # roughness = .5
+        # metallicity = .5
+        # metal_rough_pnm.fill(ambient_occlusion, roughness, metallicity)
+        # metal_rough_tex = Texture('ao metal roughness')
+        # metal_rough_tex.load(metal_rough_pnm)
+        # ts = TextureStage('ao metal roughness')
+        # ts.set_mode(TextureStage.M_selector)
+        # np.set_texture(ts, metal_rough_tex)
+        # normal_pnm = PNMImage(texture_sz, texture_sz)
+        # normal_pnm.fill(.5, .5, .1)
+        # normal_tex = Texture('normals')
+        # normal_tex.load(normal_pnm)
+        # ts = TextureStage('normals')
+        # ts.set_mode(TextureStage.M_normal)
+        # np.set_texture(ts, normal_tex)
+
+    def destroy(self):
+        self._np.remove_node()
diff --git a/logics/__init__.py b/logics/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/logics/app.py b/logics/app.py
new file mode 100755 (executable)
index 0000000..904a7a6
--- /dev/null
@@ -0,0 +1,277 @@
+import argparse
+import simplepbr
+#import gltf
+from glob import glob
+from importlib import import_module
+from inspect import isclass
+from sys import platform, argv, exit
+from logging import info, debug
+from os.path import exists
+from os import makedirs
+from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
+    Texture, WindowProperties, LVector2i, TextNode
+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 audio.music import MusicMgr
+from logics.items.background import Background
+from gui.menu import Menu
+from logics.scene import Scene
+from logics.scenes.scene_basketball import SceneBasketBall
+from logics.scenes.scene_box import SceneBox
+from logics.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
+from logics.scenes.scene_domino_box import SceneDominoBox
+from logics.scenes.scene_domino import SceneDomino
+from logics.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
+from logics.scenes.scene_teeter_tooter import SceneTeeterTooter
+from ya2.utils.dictfile import DctFile
+from ya2.p3d.p3d import LibP3d
+from ya2.utils.lang import LangMgr
+from ya2.utils.log import LogMgr
+from ya2.utils.functional import FunctionalTest
+
+
+class MainFsm(FSM):
+
+    def __init__(self, pmachines):
+        super().__init__('Main FSM')
+        self._pmachines = pmachines
+
+    def enterMenu(self):
+        self._pmachines.on_menu_enter()
+
+    def exitMenu(self):
+        self._pmachines.on_menu_exit()
+
+    def enterScene(self, cls):
+        self._pmachines.on_scene_enter(cls)
+
+    def exitScene(self):
+        self._pmachines.on_scene_exit()
+
+
+class PmachinesApp:
+
+    scenes = [
+        SceneDomino,
+        SceneBox,
+        SceneDominoBox,
+        SceneBasketBall,
+        SceneDominoBoxBasketball,
+        SceneTeeterTooter,
+        SceneTeeterDominoBoxBasketball]
+
+    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.updating = args.update
+        self.version = args.version
+        self.log_mgr = LogMgr.init_cls()(self)
+        self._prepare_window(args)
+        if args.update:
+            return
+        if args.functional_test:
+            self._options['settings']['volume'] = 0
+        self._music = MusicMgr(self._options['settings']['volume'])
+        self.lang_mgr = LangMgr(self._options['settings']['language'],
+                                'pmachines',
+                                'assets/locale/')
+        self._fsm = MainFsm(self)
+        if args.screenshot:
+            cls = [cls for cls in self.scenes if cls.__name__ == args.screenshot][0]
+            scene = cls(BulletWorld(), None, True, False, lambda: None, self.scenes)
+            scene.screenshot()
+            scene.destroy()
+            exit()
+        elif self._options['development']['auto_start']:
+            mod_name = 'logics.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', cls)
+        else:
+            self._fsm.demand('Menu')
+        if args.functional_test or args.functional_ref:
+            FunctionalTest(args.functional_ref)
+
+    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)
+
+    def on_home(self):
+        self._fsm.demand('Menu')
+
+    def on_menu_exit(self):
+        self._menu_bg.destroy()
+        self._menu.destroy()
+
+    def on_scene_enter(self, cls):
+        self._set_physics()
+        self._scene = cls(
+            self.world, self.on_home,
+            self._options['development']['auto_close_instructions'],
+            self._options['development']['debug_items'],
+            self.reload,
+            self.scenes)
+
+    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' % LibP3d.fixpath(data_path + '/' + optfile))
+        default_opt = {
+            'settings': {
+                'volume': 1,
+                'language': 'en',
+                'fullscreen': 1,
+                'resolution': '',
+                'antialiasing': 1,
+                'shadows': 1},
+            '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}}
+        opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
+        opt_exists = exists(opt_path)
+        self._options = DctFile(
+            LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
+            default_opt)
+        if not opt_exists:
+            self._options.store()
+        res = self._options['settings']['resolution']
+        if res:
+            res = LVector2i(*[int(_res) for _res in res.split('x')])
+        else:
+            resolutions = []
+            if not self.version:
+                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
+        else:
+            props.set_size(res)
+        props.set_fullscreen(fullscreen)
+        props.set_icon_filename('assets/images/icon/pmachines.ico')
+        if not args.screenshot and not self.version:
+            base.win.request_properties(props)
+        #gltf.patch_loader(base.loader)
+        if self._options['development']['simplepbr'] and not self.version:
+            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()
diff --git a/logics/items/__init__.py b/logics/items/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/logics/items/background.py b/logics/items/background.py
new file mode 100644 (file)
index 0000000..5203202
--- /dev/null
@@ -0,0 +1,26 @@
+from itertools import product
+from panda3d.core import NodePath
+from ya2.p3d.gfx import set_srgb
+
+
+class Background:
+
+    def __init__(self):
+        self._root = NodePath('background_root')
+        self._root.reparent_to(render)
+        ncols, nrows = 16, 8
+        start_size, end_size = 5, 2.5
+        offset = 5
+        for col, row in product(range(ncols), range(nrows)):
+            model = loader.load_model('assets/models/bam/background/background.bam')
+            model.set_scale(end_size / start_size)
+            model.reparent_to(self._root)
+            total_width, total_height = end_size * ncols, end_size * nrows
+            left, bottom = -total_width/2, -total_height/2
+            model.set_pos(left + end_size * col, offset, bottom + end_size * row)
+        self._root.clear_model_nodes()
+        self._root.flatten_strong()
+        set_srgb(self._root)
+
+    def destroy(self):
+        self._root.remove_node()
diff --git a/logics/items/basketball.py b/logics/items/basketball.py
new file mode 100644 (file)
index 0000000..7ab294f
--- /dev/null
@@ -0,0 +1,11 @@
+from panda3d.bullet import BulletSphereShape, BulletRigidBodyNode, BulletGhostNode
+from logics.items.item import Item
+
+
+class Basketball(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.92, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/basketball/basketball.bam', .4, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletSphereShape(1))
diff --git a/logics/items/box.py b/logics/items/box.py
new file mode 100644 (file)
index 0000000..d3ee01d
--- /dev/null
@@ -0,0 +1,25 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from logics.items.item import Item
+
+
+class Box(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/box/box.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((.5, .5, .5)))
+
+
+class HitStrategy:
+
+    def __init__(self, hit_by, node, world):
+        self._hit_by = hit_by
+        self._node = node
+        self._world = world
+
+    def win_condition(self):
+        for contact in self._world.contact_test(self._node).get_contacts():
+            other = contact.get_node1() if contact.get_node0() == self._node else contact.get_node0()
+            if other == self._hit_by.node:
+                return True
diff --git a/logics/items/domino.py b/logics/items/domino.py
new file mode 100644 (file)
index 0000000..8071e69
--- /dev/null
@@ -0,0 +1,33 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from logics.items.item import Item, StillStrategy
+
+
+class Domino(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/domino/domino.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((.1, .25, .5)))
+
+
+class UpStrategy(StillStrategy):
+
+    def __init__(self, np, tgt_degrees):
+        super().__init__(np)
+        self._tgt_degrees = tgt_degrees
+        self._np = np
+
+    def win_condition(self):
+        return super().win_condition() and abs(self._np.get_r()) <= self._tgt_degrees
+
+
+class DownStrategy(StillStrategy):
+
+    def __init__(self, np, tgt_degrees):
+        super().__init__(np)
+        self._tgt_degrees = tgt_degrees
+        self._np = np
+
+    def win_condition(self):
+        return self._np.get_z() < -6 or super().win_condition() and abs(self._np.get_r()) >= self._tgt_degrees
diff --git a/logics/items/item.py b/logics/items/item.py
new file mode 100644 (file)
index 0000000..9f2bf5d
--- /dev/null
@@ -0,0 +1,339 @@
+from panda3d.core import CullFaceAttrib, Point3, NodePath, Point2, Texture, \
+    Plane, Vec3, BitMask32
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from direct.gui.OnscreenText import OnscreenText
+from ya2.p3d.gfx import P3dGfxMgr, set_srgb
+
+
+class Command:
+
+    def __init__(self, pos, rot):
+        self.pos = pos
+        self.rot = rot
+
+
+class FixedStrategy:
+
+    def win_condition(self):
+        return True
+
+
+class StillStrategy:
+
+    def __init__(self, np):
+        self._np = np
+        self._positions = []
+        self._rotations = []
+
+    def win_condition(self):
+        self._positions += [self._np.get_pos()]
+        self._rotations += [self._np.get_hpr()]
+        if len(self._positions) > 30:
+            self._positions.pop(0)
+        if len(self._rotations) > 30:
+            self._rotations.pop(0)
+        if len(self._positions) < 28:
+            return
+        avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
+        avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
+        avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
+        avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
+        avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
+        avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
+        avg_pos = Point3(avg_x, avg_y, avg_z)
+        avg_rot = Point3(avg_h, avg_p, avg_r)
+        return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
+            all((rot - avg_rot).length() < 1 for rot in self._rotations)
+
+
+class Item:
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, scene_repos, model_path, model_scale=1, exp_num_contacts=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
+        self._world = world
+        self._plane_node = plane_node
+        self._count = count
+        self._cb_inst = cb_inst
+        self._paused = False
+        self._overlapping = False
+        self._first_command = True
+        self.repos_done = False
+        self._mass = mass
+        self._pos = pos
+        self._r = r
+        self.strategy = FixedStrategy()
+        self._exp_num_contacts = exp_num_contacts
+        self._curr_bottom = curr_bottom
+        self._scene_repos = scene_repos
+        self._model_scale = model_scale
+        self._model_path = model_path
+        self._commands = []
+        self._command_idx = -1
+        self._restitution = restitution
+        self._friction = friction
+        self._positions = []
+        self._rotations = []
+        if count:
+            self.node = BulletGhostNode(self.__class__.__name__)
+        else:
+            self.node = BulletRigidBodyNode(self.__class__.__name__)
+        self._set_shape(count)
+        self._np = render.attach_new_node(self.node)
+        if count:
+            world.attach_ghost(self.node)
+        else:
+            world.attach_rigid_body(self.node)
+        self._model = loader.load_model(model_path)
+        set_srgb(self._model)
+        self._model.flatten_light()
+        self._model.reparent_to(self._np)
+        self._np.set_scale(model_scale)
+        self._np.flatten_strong()
+        if count:
+            self._set_outline_model()
+            set_srgb(self._outline_model)
+            self._model.hide(BitMask32(0x01))
+            self._outline_model.hide(BitMask32(0x01))
+        self._start_drag_pos = None
+        self._prev_rot_info = None
+        self._last_nonoverlapping_pos = None
+        self._last_nonoverlapping_rot = None
+        self._instantiated = not count
+        self.interactable = count
+        self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
+        if count:
+            self._repos()
+        else:
+            self._np.set_pos(pos)
+            self._np.set_r(r)
+
+    def _set_shape(self, apply_scale=True):
+        pass
+
+    def set_strategy(self, strategy):
+        self.strategy = strategy
+
+    def _repos(self):
+        p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
+        for hit in self._world.ray_test_all(p_from, p_to).get_hits():
+            if hit.get_node() == self._plane_node:
+                pos = hit.get_hit_pos()
+        corner = P3dGfxMgr.screen_coord(pos)
+        bounds = self._np.get_tight_bounds()
+        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
+        self._np.set_pos(pos)
+        plane = Plane(Vec3(0, 1, 0), bounds[0][1])
+        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
+        margin, ar = .04, base.get_aspect_ratio()
+        margin_x = margin / ar if ar >= 1 else margin
+        margin_z = margin * ar if ar < 1 else margin
+        top = self._curr_bottom()
+        base.camLens.extrude((-1 + margin_x, top - margin_z), near_pt, far_pt)
+        plane.intersects_line(
+            pos3d, render.get_relative_point(base.camera, near_pt),
+            render.get_relative_point(base.camera, far_pt))
+        delta = Vec3(bounds[1][0], bounds[1][1], bounds[0][2])
+        self._np.set_pos(pos3d + delta)
+        if not hasattr(self, '_txt'):
+            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(
+                str(self._count), font=font, scale=0.06, fg=(.9, .9, .9, 1))
+        pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
+        p2d = P3dGfxMgr.screen_coord(pos)
+        self._txt['pos'] = p2d
+        self.repos_done = True
+
+    def repos_x(self, x):
+        self._np.set_x(x)
+        bounds = self._np.get_tight_bounds()
+        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
+        pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
+        p2d = P3dGfxMgr.screen_coord(pos)
+        self._txt['pos'] = p2d
+
+    def get_bottom(self):
+        bounds = self._np.get_tight_bounds()
+        bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
+        pos = self._np.get_pos() + (bounds[1][0], bounds[1][1], bounds[0][2])
+        p2d = P3dGfxMgr.screen_coord(pos)
+        ar = base.get_aspect_ratio()
+        return p2d[1] if ar >= 1 else (p2d[1] * ar)
+
+    def get_corner(self):
+        bounds = self._np.get_tight_bounds()
+        return bounds[1][0], bounds[1][1], bounds[0][2]
+
+    def _set_outline_model(self):
+        self._outline_model = loader.load_model(self._model_path)
+        #clockw = CullFaceAttrib.MCullClockwise
+        #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
+        self._outline_model.set_attrib(CullFaceAttrib.make_reverse())
+        self._outline_model.reparent_to(self._np)
+        self._outline_model.set_scale(1.08)
+        self._outline_model.set_light_off()
+        self._outline_model.set_color(.4, .4, .4, 1)
+        self._outline_model.set_color_scale(.4, .4, .4, 1)
+        self._outline_model.hide()
+
+    def on_frame(self, task):
+        self._np.set_y(0)
+        return task.cont
+
+    def undo(self):
+        self._command_idx -= 1
+        self._np.set_pos(self._commands[self._command_idx].pos)
+        self._np.set_hpr(self._commands[self._command_idx].rot)
+
+    def redo(self):
+        self._command_idx += 1
+        self._np.set_pos(self._commands[self._command_idx].pos)
+        self._np.set_hpr(self._commands[self._command_idx].rot)
+
+    def play(self):
+        if not self._instantiated:
+            return
+        self._world.remove_rigid_body(self.node)
+        self.node.set_mass(self._mass)
+        self._world.attach_rigid_body(self.node)
+        self.node.set_restitution(self._restitution)
+        self.node.set_friction(self._friction)
+
+    def on_click_l(self, pos):
+        if self._paused: return
+        self._start_drag_pos = pos, self._np.get_pos()
+        loader.load_sfx('assets/audio/sfx/grab.ogg').play()
+        if not self._instantiated:
+            self._world.remove_ghost(self.node)
+            pos = self._np.get_pos()
+            self._np.remove_node()
+            self.node = BulletRigidBodyNode('box')
+            self._set_shape()
+            self._np = render.attach_new_node(self.node)
+            self._world.attach_rigid_body(self.node)
+            self._model.reparent_to(self._np)
+            self._np.set_pos(pos)
+            self._set_outline_model()
+            self._np.set_scale(self._model_scale)
+            self._model.show(BitMask32(0x01))
+            self._outline_model.hide(BitMask32(0x01))
+            self._instantiated = True
+            self._txt.destroy()
+            self._count -= 1
+            if self._count:
+                item = self.__class__(self._world, self._plane_node, self._cb_inst, self._curr_bottom, self._scene_repos, count=self._count, mass=self._mass, pos=self._pos, r=self._r)  # pylint: disable=no-value-for-parameter
+                self._cb_inst(item)
+            self._scene_repos()
+
+    def on_click_r(self, pos):
+        if self._paused or not self._instantiated: return
+        self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
+        loader.load_sfx('assets/audio/sfx/grab.ogg').play()
+
+    def on_release(self):
+        if self._start_drag_pos or self._prev_rot_info:
+            loader.load_sfx('assets/audio/sfx/release.ogg').play()
+            self._command_idx += 1
+            self._commands = self._commands[:self._command_idx]
+            self._commands += [Command(self._np.get_pos(), self._np.get_hpr())]
+            self._first_command = False
+        self._start_drag_pos = self._prev_rot_info = None
+        if self._overlapping:
+            self._np.set_pos(self._last_nonoverlapping_pos)
+            self._np.set_hpr(self._last_nonoverlapping_rot)
+            self._outline_model.set_color(.4, .4, .4, 1)
+            self._outline_model.set_color_scale(.4, .4, .4, 1)
+            self._overlapping = False
+
+    def on_mouse_on(self):
+        if not self._paused and self.interactable:
+            self._outline_model.show()
+
+    def on_mouse_off(self):
+        if self._start_drag_pos or self._prev_rot_info: return
+        if self.interactable:
+            self._outline_model.hide()
+
+    def on_mouse_move(self, pos):
+        if self._start_drag_pos:
+            d_pos =  pos - self._start_drag_pos[0]
+            self._np.set_pos(self._start_drag_pos[1] + d_pos)
+        if self._prev_rot_info:
+            start_vec = self._prev_rot_info[0] - self._prev_rot_info[1]
+            curr_vec = pos - self._prev_rot_info[1]
+            d_angle = curr_vec.signed_angle_deg(start_vec, (0, -1, 0))
+            self._np.set_r(self._prev_rot_info[2] + d_angle)
+            self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
+        if self._start_drag_pos or self._prev_rot_info:
+            res = self._world.contact_test(self.node)
+            nres = res.get_num_contacts()
+            if nres <= self._exp_num_contacts:
+                self._overlapping = False
+                self._outline_model.set_color(.4, .4, .4, 1)
+                self._outline_model.set_color_scale(.4, .4, .4, 1)
+            if nres > self._exp_num_contacts and not self._overlapping:
+                actual_nres = 0
+                for contact in res.get_contacts():
+                    for node in [contact.get_node0(), contact.get_node1()]:
+                        if isinstance(node, BulletRigidBodyNode) and \
+                                node != self.node:
+                            actual_nres += 1
+                if actual_nres >= 1:
+                    self._overlapping = True
+                    loader.load_sfx('assets/audio/sfx/overlap.ogg').play()
+                    self._outline_model.set_color(.9, .1, .1, 1)
+                    self._outline_model.set_color_scale(.9, .1, .1, 1)
+        if not self._overlapping:
+            self._last_nonoverlapping_pos = self._np.get_pos()
+            self._last_nonoverlapping_rot = self._np.get_hpr()
+
+    def on_aspect_ratio_changed(self):
+        if not self._instantiated:
+            self._repos()
+
+    def store_state(self):
+        self._paused = True
+        self._model.set_transparency(True)
+        self._model.set_alpha_scale(.3)
+        if hasattr(self, '_txt') and not self._txt.is_empty():
+            self._txt.set_alpha_scale(.3)
+
+    def restore_state(self):
+        self._paused = False
+        self._model.set_alpha_scale(1)
+        if hasattr(self, '_txt') and not self._txt.is_empty():
+            self._txt.set_alpha_scale(1)
+
+    def fail_condition(self):
+        if self._np.get_z() < -6:
+            return True
+        self._positions += [self._np.get_pos()]
+        self._rotations += [self._np.get_hpr()]
+        if len(self._positions) > 30:
+            self._positions.pop(0)
+        if len(self._rotations) > 30:
+            self._rotations.pop(0)
+        if len(self._positions) < 28:
+            return
+        avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
+        avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
+        avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
+        avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
+        avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
+        avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
+        avg_pos = Point3(avg_x, avg_y, avg_z)
+        avg_rot = Point3(avg_h, avg_p, avg_r)
+        return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
+            all((rot - avg_rot).length() < 1 for rot in self._rotations)
+
+    def destroy(self):
+        self._np.remove_node()
+        taskMgr.remove(self._box_tsk)
+        if hasattr(self, '_txt'):
+            self._txt.destroy()
+        if not self._instantiated:
+            self._world.remove_ghost(self.node)
+        else:
+            self._world.remove_rigid_body(self.node)
diff --git a/logics/items/shelf.py b/logics/items/shelf.py
new file mode 100644 (file)
index 0000000..b88ecd0
--- /dev/null
@@ -0,0 +1,11 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from logics.items.item import Item
+
+
+class Shelf(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/shelf/shelf.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((1, .5, .05)))
diff --git a/logics/items/teetertooter.py b/logics/items/teetertooter.py
new file mode 100644 (file)
index 0000000..7dc3349
--- /dev/null
@@ -0,0 +1,21 @@
+from panda3d.core import TransformState
+from panda3d.bullet import BulletCylinderShape, BulletRigidBodyNode, BulletGhostNode, YUp, ZUp
+from logics.items.item import Item
+
+
+class TeeterTooter(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/teeter_tooter/teeter_tooter.bam', exp_num_contacts=2, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=.5)
+
+    def _set_shape(self, apply_scale=True):
+        scale = self._model_scale if apply_scale else 1
+        self.node.add_shape(
+            BulletCylinderShape(.1, 1.6, YUp),
+            TransformState.makePos((0, 0, scale * .36)))
+        self.node.add_shape(
+            BulletCylinderShape(.1, .7, ZUp),
+            TransformState.makePos((0, scale * .8, scale * -.1)))
+        self.node.add_shape(
+            BulletCylinderShape(.1, .7, ZUp),
+            TransformState.makePos((0, scale * -.8, scale * -.1)))
diff --git a/logics/scene.py b/logics/scene.py
new file mode 100644 (file)
index 0000000..548ec22
--- /dev/null
@@ -0,0 +1,587 @@
+from os.path import exists
+from os import makedirs
+from glob import glob
+from logging import debug, info
+from importlib import import_module
+from inspect import isclass
+from panda3d.core import AmbientLight, DirectionalLight, Point3, 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 logics.items.background import Background
+from gui.sidepanel import SidePanel
+from ya2.utils.cursor import MouseCursor
+from ya2.p3d.gfx import P3dGfxMgr
+
+
+class Scene(DirectObject):
+
+    def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes):
+        super().__init__()
+        self._world = world
+        self._exit_cb = exit_cb
+        self._dbg_items = dbg_items
+        self._reload_cb = reload_cb
+        self._scenes = scenes
+        self._enforce_res = ''
+        self.accept('enforce_res', self.enforce_res)
+        self._set_camera()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_gui()
+        self._set_lights()
+        self._set_input()
+        self._set_mouse_plane()
+        self.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, 'on_frame')
+
+    @staticmethod
+    def name():
+        return ''
+
+    def _instr_txt(self):
+        return ''
+
+    def _set_items(self):
+        self.items = []
+
+    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.__class__.__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]
+        self._set_items()
+        self._state = 'init'
+        self._commands = []
+        self._command_idx = 0
+        if hasattr(self, '_success_txt'):
+            self._success_txt.destroy()
+            del self._success_txt
+        self.__right_btn['state'] = NORMAL
+
+    def enforce_res(self, val):
+        self._enforce_res = val
+        info('enforce res: ' + val)
+
+    def destroy(self):
+        self.ignore('enforce_res')
+        self._unset_gui()
+        self._unset_lights()
+        self._unset_input()
+        self._unset_mouse_plane()
+        [itm.destroy() for itm in self.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()
+
+    def _set_camera(self):
+        base.camera.set_pos(0, -20, 0)
+        base.camera.look_at(0, 0, 0)
+
+    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'),
+            ('information', self._set_instructions, NORMAL, abl, 'gray'),
+            ('right', self.on_play, NORMAL, abr, 'green'),
+            #('next', self.on_next, DISABLED, abr, 'gray'),
+            #('previous', self.on_prev, DISABLED, abr, 'gray'),
+            #('rewind', self.reset, NORMAL, abr, 'gray')
+        ]
+        num_l = num_r = 0
+        btns = []
+        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.set_transparency(True)
+            btns += [btn]
+        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)
+
+    def _unset_gui(self):
+        btns = [
+            self.__home_btn, self.__info_btn, self.__right_btn,
+            #self.__next_btn, self.__prev_btn, self.__rewind_btn
+        ]
+        [btn.destroy() for btn in btns]
+        if self._dbg_items:
+            self._info_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 = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
+        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
+                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):
+        pass
+
+    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 self._win_condition():
+            self._set_fail() if self._enforce_res == 'fail' else self._set_win()
+        elif self._state == 'playing' and self._fail_condition():
+            self._set_win() if self._enforce_res == 'win' else self._set_fail()
+        if any(itm._overlapping for itm in self.items):
+            self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
+        else:
+            self._cursor.cursor_img.img.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]
+
+    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_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))
+        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(
+            self._instr_txt(), parent=frm, font=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 - 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.set_transparency(True)
+
+    def _set_win(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 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]
+        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.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.set_transparency(True)
+        enabled = self._scenes.index(self.__class__) < len(self._scenes) - 1
+        if enabled:
+            next_scene = self._scenes[self._scenes.index(self.__class__) + 1]
+        else:
+            next_scene = None
+        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['state'] = NORMAL if enabled else DISABLED
+        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]
+        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.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.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
+        ]
+        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
+        ]
+        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()
diff --git a/logics/scenes/__init__.py b/logics/scenes/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/logics/scenes/scene_basketball.py b/logics/scenes/scene_basketball.py
new file mode 100644 (file)
index 0000000..f90e683
--- /dev/null
@@ -0,0 +1,59 @@
+from logics.scene import Scene
+from logics.items.box import Box
+from logics.items.shelf import Shelf
+from logics.items.domino import Domino, UpStrategy, DownStrategy
+from logics.items.basketball import Basketball
+from logics.items.teetertooter import TeeterTooter
+
+
+class SceneBasketBall(Scene):
+
+    @staticmethod
+    def name():
+        return _('Basket ball')
+
+    def _set_items(self):
+        self.items = []
+        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-4.45, 0, -3.18), r=27, restitution=1)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-5.45, 0, -3.18), restitution=1)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 30))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
+        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
+        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: you must hit every domino piece\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_box.py b/logics/scenes/scene_box.py
new file mode 100644 (file)
index 0000000..aed3b73
--- /dev/null
@@ -0,0 +1,43 @@
+from logics.scene import Scene
+from logics.items.box import Box, HitStrategy
+from logics.items.shelf import Shelf
+from logics.items.domino import Domino
+from logics.items.basketball import Basketball
+from logics.items.teetertooter import TeeterTooter
+
+
+class SceneBox(Scene):
+
+    @staticmethod
+    def name():
+        return _('Box')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.46, 0, -3.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.43, 0, -3.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.29, 0, .26), r=28.45)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.15, 0, -1.49), r=28.45)]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.55, 0, 1.23), friction=.4)]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.38, 0, -3.35))]
+        self.items[-1].set_strategy(HitStrategy(self.items[-2], self.items[-1].node, self.items[-1]._world))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04), tgt_degrees=88)]
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: the left box must hit the right box\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_domino.py b/logics/scenes/scene_domino.py
new file mode 100644 (file)
index 0000000..fa14aa5
--- /dev/null
@@ -0,0 +1,43 @@
+from logics.scene import Scene
+from logics.items.box import Box
+from logics.items.shelf import Shelf
+from logics.items.domino import Domino, DownStrategy
+from logics.items.basketball import Basketball
+from logics.items.teetertooter import TeeterTooter
+
+
+class SceneDomino(Scene):
+
+    @staticmethod
+    def name():
+        return _('Domino')
+
+    def _set_items(self):
+        self.items = []
+        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.2, 0, -.6))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.2, 0, -.6))]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 88))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must fall\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_domino_box.py b/logics/scenes/scene_domino_box.py
new file mode 100644 (file)
index 0000000..bbdbb26
--- /dev/null
@@ -0,0 +1,56 @@
+from logics.scene import Scene
+from logics.items.box import Box
+from logics.items.shelf import Shelf
+from logics.items.domino import Domino, UpStrategy, DownStrategy
+from logics.items.basketball import Basketball
+from logics.items.teetertooter import TeeterTooter
+
+
+class SceneDominoBox(Scene):
+
+    @staticmethod
+    def name():
+        return _('Domino and box')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.94), r=37)]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: only the last piece of each row must be up\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_domino_box_basketball.py b/logics/scenes/scene_domino_box_basketball.py
new file mode 100644 (file)
index 0000000..e24c548
--- /dev/null
@@ -0,0 +1,45 @@
+from logics.scene import Scene
+from logics.items.box import Box
+from logics.items.shelf import Shelf
+from logics.items.domino import Domino, UpStrategy, DownStrategy
+from logics.items.basketball import Basketball
+from logics.items.teetertooter import TeeterTooter
+
+
+class SceneDominoBoxBasketball(Scene):
+
+    @staticmethod
+    def name():
+        return _('Domino, box and basket ball')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1, mass=5)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.3, 1, 2.5))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.68, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.35, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.08, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.78, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.53, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must be hit\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_teeter_domino_box_basketball.py b/logics/scenes/scene_teeter_domino_box_basketball.py
new file mode 100644 (file)
index 0000000..75d361c
--- /dev/null
@@ -0,0 +1,55 @@
+from logics.scene import Scene
+from logics.items.box import Box
+from logics.items.shelf import Shelf
+from logics.items.domino import Domino, UpStrategy, DownStrategy
+from logics.items.basketball import Basketball
+from logics.items.teetertooter import TeeterTooter
+
+
+class SceneTeeterDominoBoxBasketball(Scene):
+
+    @staticmethod
+    def name():
+        return _('Teeter tooter, domino, box and basket ball')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=2, friction=1)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(.98, 1, 1.02))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.45))]
+        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.20))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=24.60, friction=1, pos=(-6.15, 0, -.93))]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.3, friction=1, model_scale=.5, pos=(-5.38, 0, -.93), r=24.60)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(5.37, 0, -.78))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(7.48, 0, -.78))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.74, 0, -1.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(6.88, 0, -1.95))]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(4.83, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(5.67, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(6.59, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.53, 0, -1.95), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.63, 0, -1.95), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-3.65, 0, 1.05), r=28, friction=0)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.27, 0, 1.72), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.88, 0, 1.72), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.67, 0, .55), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.52, 0, .55), restitution=.95)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-1.73, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.97, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.1, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must be hit\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/logics/scenes/scene_teeter_tooter.py b/logics/scenes/scene_teeter_tooter.py
new file mode 100644 (file)
index 0000000..3a748ea
--- /dev/null
@@ -0,0 +1,46 @@
+from logics.scene import Scene
+from logics.items.box import Box
+from logics.items.shelf import Shelf
+from logics.items.domino import Domino, UpStrategy, DownStrategy
+from logics.items.basketball import Basketball
+from logics.items.teetertooter import TeeterTooter
+
+
+class SceneTeeterTooter(Scene):
+
+    @staticmethod
+    def name():
+        return _('Teeter tooter')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=1, friction=1)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.76, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.27, 0, -.28))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.38, 0, -.28))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.74, 0, -1.20))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=-25.30, friction=1, pos=(-2.78, 0, -.93))]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.2, friction=1, model_scale=.5, pos=(-3.61, 0, -.99), r=-25.30)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.25, 0, -.57), r=52)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.50, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: you must hit every domino piece\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/main.py b/main.py
index 3d4b45f1e0dd286f7c0f806f1e63af1055019289..00d9b9cc67c1e45ea1a98c8d5eb5d6daba5c2d8a 100644 (file)
--- a/main.py
+++ b/main.py
@@ -1,12 +1,12 @@
 '''This is the main file. This launches the application.'''
-import ya2.engine.log  # so logging's info/debug are logged
+import ya2.utils.log  # so logging's info/debug are logged
 from sys import argv
 from panda3d.core import load_prc_file_data
 if '--version' in argv:
     load_prc_file_data('', 'window-type none')
 from os.path import exists
 from traceback import print_exc
-from game.app import PmachinesApp
+from logics.app import PmachinesApp
 from p3d_appimage import AppImageBuilder
 
 if __name__ == '__main__' or exists('main.pyo'):
index 3e23b3839382227acf86dfe5dc182eeb362d9496..24a302b020ba50e64961959c9a891f6a12498cbd 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -19,8 +19,8 @@ from ya2.build.screenshots import bld_screenshots
 from ya2.build.lang import LanguageBuilder
 from p3d_appimage import AppImageBuilder
 from p3d_flatpak import FlatpakBuilder
-import ya2.engine.log  # so logging's info/debug are logged
-from game.app import PmachinesApp
+import ya2.utils.log  # so logging's info/debug are logged
+from logics.app import PmachinesApp
 
 
 appname = longname = 'pmachines'
index c236c9a2f1a9346acf80cfb2f484acba92a14027..fa2b5b2373b271d373e36aa83dca5faab9e7dadf 100644 (file)
@@ -20,7 +20,7 @@ from sys import exit, argv
 from panda3d.core import Filename
 from direct.showbase.ShowBase import ShowBase
 from direct.gui.OnscreenText import OnscreenText
-from ya2.gameobject import GameObject
+from ya2.patterns.gameobject import GameObject
 from ya2.build.build import _branch
 
 
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tests/lib/build/__init__.py b/tests/lib/build/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tests/lib/build/test_build.py b/tests/lib/build/test_build.py
deleted file mode 100644 (file)
index 6713add..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-'''Unit tests for lib.build.'''
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent.parent))
-from os import getcwd, makedirs, walk
-from os.path import basename
-from shutil import rmtree
-from unittest import TestCase
-from re import compile
-from ya2.build.build import InsideDir, files, exec_cmd, _branch, _version, \
-    to_be_built
-
-
-class BuildTests(TestCase):
-
-    def setUp(self):
-        makedirs('test_get_files/a')
-        makedirs('test_get_files/b')
-        makedirs('test_get_files/c')
-        with open('test_get_files/a/c.ext1', 'w') as ftest:
-            ftest.write('0123456789')
-        with open('test_get_files/a/d.ext2', 'w'): pass
-        with open('test_get_files/b/e.ext2', 'w') as ftest:
-            ftest.write('0123456789')
-        with open('test_get_files/b/f.ext3', 'w'): pass
-        with open('test_get_files/c/g.ext2', 'w'): pass
-
-    def tearDown(self):
-        rmtree('test_get_files')
-
-    def test_exec_cmd(self):
-        self.assertEqual(exec_cmd('echo abc'), 'abc')
-
-    def test_branch(self):
-        self.assertIn(_branch(), ['master', 'rc', 'stable'])
-
-    def test_version(self):
-        patterns = [
-            "^0a[0-9]+$",
-            "^0rc[0-9]+$",
-            "^0\.[0-9]+$"]
-        compiled = [compile(pattern) for pattern in patterns]
-        matches = [pattern.match(_version()) for pattern in compiled]
-        self.assertTrue(any(matches))
-
-    def test_get_files(self):
-        _files = files(['ext2'], 'c')
-        self.assertSetEqual(set(_files),
-                            set(['./test_get_files/a/d.ext2',
-                                 './test_get_files/b/e.ext2']))
-
-    def test_inside_dir(self):
-        dirs = [basename(x[0]) for x in walk('.')]
-        dirs = [dir_ for dir_ in dirs if dir_ not in ['.', '..']]
-        dirname = dirs[0]
-        self.assertNotEqual(basename(getcwd()), dirname)
-        with InsideDir(dirname):
-            self.assertEqual(basename(getcwd()), dirname)
-        self.assertNotEqual(basename(getcwd()), dirname)
-
-    def test_to_be_built(self):
-        tgt = 'test_get_files/tgt.txt'
-        with open('test_get_files/src.txt', 'w') as fsrc:
-            fsrc.write('src')
-        with open(tgt, 'w') as ftgt:
-            ftgt.write('tgt')
-        self.assertTrue(to_be_built(tgt, ['test_get_files/src.txt']))
diff --git a/tests/lib/build/test_lang.py b/tests/lib/build/test_lang.py
deleted file mode 100644 (file)
index 9ccf7cf..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from os import remove, makedirs
-from os.path import exists
-from shutil import rmtree, copy
-from unittest import TestCase
-from ya2.build.lang import LanguageBuilder
-
-
-class LangTests(TestCase):
-
-    def setUp(self):
-        for dirname in ['locale', 'po']:
-            rmtree('./tests/' + dirname, ignore_errors=True)
-            makedirs('./tests/' + dirname, exist_ok=True)
-        copy('assets/locale/po/it_IT.po', './tests/po/')
-
-    def tearDown(self):
-        for dirname in ['locale', 'po']:
-            rmtree('./tests/' + dirname, ignore_errors=True)
-
-    def test_lang(self):
-        LanguageBuilder.pot('test_pmachines', './tests/po/')
-        self.assertTrue(exists('./tests/po/test_pmachines.pot'))
-        LanguageBuilder.merge('it_IT', './tests/po/', './tests/locale/', 'test_pmachines')
-        LanguageBuilder.mo('./tests/locale/it_IT/LC_MESSAGES/test_pmachines.mo',
-                           './tests/locale/', 'test_pmachines')
-        self.assertTrue(exists('./tests/locale/it_IT/LC_MESSAGES/test_pmachines.mo'))
diff --git a/tests/lib/build/test_models.py b/tests/lib/build/test_models.py
deleted file mode 100644 (file)
index e569670..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from os import remove, makedirs, environ
-from os.path import exists
-from shutil import rmtree, copy
-from unittest import TestCase
-from itertools import product
-from time import time
-from ya2.build.models import ModelsBuilder
-
-
-class ModelsBuilderTests(TestCase):
-
-    def setUp(self):
-        self.dirs = ['box', 'domino']
-        for fmt_dir in product(['bam', 'gltf'], self.dirs):
-            rmtree('assets/models/%s/%s' % fmt_dir, ignore_errors=True)
-
-    def test_models(self):
-        if environ.get('FAST') == '1':
-            self.skipTest('skipped slow tests')
-        for fmt_dir in product(['bam', 'gltf'], self.dirs):
-            self.assertFalse(exists('assets/%s/%s' % fmt_dir))
-        start = time()
-        ModelsBuilder().build('assets/models', 1)
-        self.assertTrue(time() - start > 1.5)
-        files = [
-            'assets/models/bam/box/box.bam',
-            'assets/models/bam/box/base.dds',
-            'assets/models/bam/box/ao_metal_roughness.dds',
-            'assets/models/bam/box/normal.dds',
-            'assets/models/bam/domino/domino.bam',
-            'assets/models/bam/domino/base.dds',
-            'assets/models/bam/domino/ao_roughness_metal.dds',
-            'assets/models/bam/domino/normal.dds',
-            'assets/models/gltf/box/box.gltf',
-            'assets/models/gltf/box/base.png',
-            'assets/models/gltf/box/ao_metal_roughness.png',
-            'assets/models/gltf/box/normal.png',
-            'assets/models/gltf/domino/domino.gltf',
-            'assets/models/gltf/domino/base.png',
-            'assets/models/gltf/domino/ao_roughness_metal.png',
-            'assets/models/gltf/domino/normal.png']
-        [self.assertTrue(exists(fname)) for fname in files]
-        #start = time()
-        #ModelsBuilder().build('assets/models', 1)
-        #self.assertTrue(time() - start < 1.5)  # test caching
-        #[self.assertTrue(exists(fname)) for fname in files]
diff --git a/tests/lib/build/test_mtprocesser.py b/tests/lib/build/test_mtprocesser.py
deleted file mode 100644 (file)
index 536cf7c..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-'''lib.build.mtprocesser's unit tests.'''
-from pathlib import Path
-import sys
-sys.path = [path for path in sys.path if path != '']
-
-# we're in yocto/tests/lib/build/
-sys.path.append(str(Path(__file__).parent.parent.parent.parent))
-from pathlib import Path
-from os.path import exists
-from unittest import TestCase
-from ya2.build.mtprocesser import ProcesserMgr
-
-
-class ProcesserMgrTests(TestCase):
-    '''ProcesserMgr's unit tests '''
-
-    def setUp(self):
-        '''unit tests' set up'''
-        for idx in [1, 2]:
-            Path('./tests/%s.txt' % idx).unlink(missing_ok=True)
-
-    def tearDown(self):
-        '''unit tests' tear down'''
-        self.setUp()
-
-    def test_threaded(self):
-        '''test of the threaded case'''
-        pmgr = ProcesserMgr(2)
-        pmgr.add('echo 1 > ./tests/1.txt')
-        pmgr.add('echo 2 > ./tests/2.txt')
-        pmgr.run()
-        self.assertTrue(exists('./tests/1.txt'))
-        self.assertTrue(exists('./tests/2.txt'))
-
-    def test_nothreaded(self):
-        '''test when we don't use threads'''
-        pmgr = ProcesserMgr(1)
-        pmgr.add('echo 1 > ./tests/1.txt')
-        pmgr.run()
-        self.assertTrue(exists('./tests/1.txt'))
diff --git a/tests/lib/engine/test_audio.py b/tests/lib/engine/test_audio.py
deleted file mode 100644 (file)
index 2b1cf76..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from panda3d.core import loadPrcFileData
-from ya2.engine.engine import Engine
-from ya2.engine.audio import EngineAudio
-
-
-class EngineAudioTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        # loadPrcFileData('', 'audio-library-name null')
-        self.eng = Engine()
-        EngineAudio(self.eng, .4)
-
-    def tearDown(self): self.eng.destroy()
-
-    def test_init(self): self.assertIsInstance(self.eng.audio, EngineAudio)
-
-    def test_volume(self):
-        self.assertAlmostEqual(self.eng.lib.volume, .4)
-        self.eng.audio.set_volume(.2)
-        self.assertAlmostEqual(self.eng.lib.volume, .2)
diff --git a/tests/lib/engine/test_cbmux.py b/tests/lib/engine/test_cbmux.py
deleted file mode 100644 (file)
index 8a10040..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from unittest.mock import MagicMock
-from panda3d.core import loadPrcFileData
-from ya2.engine.engine import Engine
-
-
-class EngineCBMuxTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-        self.eng = Engine()
-
-    def tearDown(self):
-        self.eng.destroy()
-
-    def _callback(self, arg): pass
-
-    def test_cbmux(self):
-        self._callback = MagicMock(side_effect=self._callback)
-        self.eng.cb_mux.add_cb(self._callback, [42])
-        taskMgr.step()
-        self._callback.assert_called_with(42)
diff --git a/tests/lib/engine/test_clock.py b/tests/lib/engine/test_clock.py
deleted file mode 100644 (file)
index 9c6e88a..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from unittest.mock import MagicMock
-from panda3d.core import loadPrcFileData
-from ya2.engine.engine import Engine
-from ya2.engine.audio import EngineAudio
-
-
-class EngineClockTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-        self.eng = Engine()
-
-    def tearDown(self):
-        self.eng.destroy()
-
-    def test_clock(self):
-        # this test shows that even if you process frames, the engine's clock
-        # is storing the unpaused time
-        start_time = self.eng.clock.time
-        self.eng.pause.logic.pause()
-        taskMgr.step()
-        self.eng.pause.logic.resume()
-        self.assertEqual(start_time, self.eng.clock.time)
diff --git a/tests/lib/engine/test_configuration.py b/tests/lib/engine/test_configuration.py
deleted file mode 100644 (file)
index 974a502..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from unittest.mock import MagicMock
-from panda3d.core import loadPrcFileData, ConfigVariableInt, ConfigVariableString
-from ya2.engine.engine import Engine
-from ya2.engine.configuration import Cfg
-
-
-class EngineConfigurationTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-        self.eng = Engine()
-
-    def tearDown(self):
-        self.eng.destroy()
-
-    def test_cfg(self):
-        # let's check some fields
-        self.assertEqual(self.eng.cfg.gui_cfg.fps, False)
-        self.assertEqual(self.eng.cfg.profiling_cfg.profiling, False)
-        self.assertEqual(self.eng.cfg.lang_cfg.lang, 'en')
-        self.assertEqual(self.eng.cfg.cursor_cfg.cursor_hidden, False)
-        self.assertEqual(self.eng.cfg.dev_cfg.multithreaded_render, False)
-
-    def test_cfg_configure(self):
-        # let's check that __configure has been executed
-        self.assertEqual(ConfigVariableInt('texture-anosotropic-degree').getValue(), 2)
diff --git a/tests/lib/engine/test_engine.py b/tests/lib/engine/test_engine.py
deleted file mode 100644 (file)
index 4cb3c97..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest.mock import create_autospec
-from unittest import TestCase
-from panda3d.core import loadPrcFileData, NodePath, ConfigVariableBool
-from ya2.engine.engine import Engine
-from ya2.engine.configuration import Cfg, CursorCfg
-
-
-class ConfigurationTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        self.engine.destroy()
-
-    def test_init(self):
-        self.engine = Engine(Cfg(cursor_cfg=CursorCfg(cursor_hidden=True)))
-        self.assertTrue(ConfigVariableBool('cursor-hidden'))
-        self.assertFalse(ConfigVariableBool('fullscreen'))
-
-
-class EngineTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-
-    def tearDown(self):
-        self.engine.destroy()
-
-    def test_init(self):
-        self.engine = Engine()
-        self.engine.camera = create_autospec(NodePath)
-        self.assertIsInstance(self.engine, Engine)
-
-
-# class Accepter(DirectObject):
-#
-#     def __init__(self):
-#         self = evt_dec(self)
-#
-#     def evt_MouseClick(self, arg):
-#         self.button = arg.button, 0
-#
-#     def evt_MouseClickUp(self, arg):
-#         self.button = arg.button, 1
-#
-#
-# class EventsTests(TestCase):
-#
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         self.engine = Engine()
-#         self.engine.mouseWatcherNode = create_autospec(MouseWatcher)
-#         self.engine.camLens = create_autospec(Lens)
-#         self.engine.camera = create_autospec(NodePath)
-#         self.engine.cam = NodePath()
-#
-#     def tearDown(self):
-#         self.engine.destroy()
-#
-#     def test_init(self):
-#         self.assertIsInstance(self.engine, Engine)
-#         mouse_move = MouseMove(0, 0)
-#         self.assertIsInstance(mouse_move, MouseMove)
-#         mouse_enter = MouseEnter(0, 0)
-#         self.assertIsInstance(mouse_enter, MouseEnter)
-#         mouse_exit = MouseExit(0, 0, 0)
-#         self.assertIsInstance(mouse_exit, MouseExit)
-#         mouse_click = MouseClick(0, 0, 0)
-#         self.assertIsInstance(mouse_click, MouseClick)
-#         mouse_clickup = MouseClickUp(0, 0, 0)
-#         self.assertIsInstance(mouse_clickup, MouseClickUp)
-#         mouse_mgr = MouseMgr(self.engine)
-#         self.assertIsInstance(mouse_mgr, MouseMgr)
-#         self.assertEqual(mouse_mgr.pt_from_to, (0, 0))
-#         self.assertEqual(mouse_mgr.pt_from, (0, 0, 0))
-#         self.assertEqual(mouse_mgr.pt_to, (0, 0, 0))
-#         acc = Accepter()
-#         self.engine.messenger.send('mouse1')
-#         self.assertEqual(acc.button, (0, 0))
-#         self.engine.messenger.send('mouse1-up')
-#         self.assertEqual(acc.button, (0, 1))
-#         self.engine.messenger.send('mouse3')
-#         self.assertEqual(acc.button, (1, 0))
-#         self.engine.messenger.send('mouse3-up')
-#         self.assertEqual(acc.button, (1, 1))
diff --git a/tests/lib/engine/test_event.py b/tests/lib/engine/test_event.py
deleted file mode 100644 (file)
index d1e030a..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from unittest.mock import patch, create_autospec
-from panda3d.core import loadPrcFileData, GraphicsWindow
-from ya2.engine.engine import Engine
-from ya2.engine.event import EngineEvent
-
-
-class EngineEventTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-        self.eng = Engine()
-        EngineEvent(self.eng, True)
-
-    def tearDown(self): self.eng.destroy()
-
-    def test_init(self): self.assertIsInstance(self.eng.event, EngineEvent)
-
-    def test_key2desc(self):
-        with patch('builtins.base') as patched_base:
-            # we need to patch it to run without base.win
-            self.assertEqual(str(self.eng.event.key2desc('x')), 'x')
-            base.win.get_keyboard_map().get_mapped_button_label = create_autospec(GraphicsWindow)
-            base.win.get_keyboard_map().get_mapped_button_label.return_value = 'x'
-            self.assertEqual(self.eng.event.key2desc('raw-x'), 'x')
-
-    def test_desc2key(self):
-        with patch('builtins.base') as patched_base:
-            self.assertEqual(self.eng.event.desc2key('x'), 'x')
diff --git a/tests/lib/engine/test_font.py b/tests/lib/engine/test_font.py
deleted file mode 100644 (file)
index b9e98c7..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from panda3d.core import loadPrcFileData, DynamicTextFont
-from ya2.engine.engine import Engine
-
-
-class EngineFontTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-        self.eng = Engine()
-
-    def tearDown(self): self.eng.destroy()
-
-    def test_font(self):
-        font = self.eng.font_mgr.load_font('../assets/fonts/Hanken-Book.ttf')
-        print(font)
-        self.assertIsInstance(font, DynamicTextFont)
diff --git a/tests/lib/engine/test_gfx.py b/tests/lib/engine/test_gfx.py
deleted file mode 100644 (file)
index b80c75f..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from panda3d.core import loadPrcFileData
-from ya2.engine.engine import Engine
-from ya2.lib.p3d.gfx import P3dNode
-
-
-class EngineGfxTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-        self.eng = Engine()
-
-    def tearDown(self): self.eng.destroy()
-
-    def test_init_clean(self):
-        self.eng.gfx.init()
-        self.assertIsInstance(self.eng.gfx.root, P3dNode)
-        self.eng.gfx.clean()
-        self.assertTrue(self.eng.gfx.root.is_empty)
-
-    def test_frame_rate(self):
-        self.eng.gfx.set_frame_rate(60)
diff --git a/tests/lib/engine/test_joystick.py b/tests/lib/engine/test_joystick.py
deleted file mode 100644 (file)
index a4b55a2..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from panda3d.core import loadPrcFileData
-from ya2.engine.engine import Engine
-from ya2.engine.joystick import JoystickMgr, JoystickState
-from ya2.engine.gui.menu import NavInfoPerPlayer
-
-
-class EngineJoystickTests(TestCase):
-
-    def setUp(self):
-        loadPrcFileData('', 'window-type none')
-        loadPrcFileData('', 'audio-library-name null')
-        self.eng = Engine()
-
-    def tearDown(self): self.eng.destroy()
-
-    def test_init(self):
-        self.assertIsInstance(self.eng.joystick_mgr, JoystickMgr)
-
-    def test_get_joystick(self):
-        j_state = self.eng.joystick_mgr.get_joystick(0)
-        self.assertIsInstance(j_state, JoystickState)
-        self.assertEqual(j_state.x, 0)
-        self.assertEqual(j_state.y, 0)
-        self.assertEqual(j_state.b0, 0)
-        self.assertEqual(j_state.b1, 0)
-        self.assertEqual(j_state.b2, 0)
-        self.assertEqual(j_state.b3, 0)
-        self.assertEqual(j_state.dpad_l, 0)
-        self.assertEqual(j_state.dpad_r, 0)
-        self.assertEqual(j_state.dpad_u, 0)
-        self.assertEqual(j_state.dpad_d, 0)
-        self.assertEqual(j_state.trigger_l, 0)
-        self.assertEqual(j_state.trigger_r, 0)
-        self.assertEqual(j_state.shoulder_l, 0)
-        self.assertEqual(j_state.shoulder_r, 0)
-        self.assertEqual(j_state.stick_l, 0)
-        self.assertEqual(j_state.stick_r, 0)
-
-    def test_get_joystick_val(self):
-        jmgr = self.eng.joystick_mgr
-        self.assertEqual(jmgr.get_joystick_val(0, 'face_x'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'face_y'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'face_a'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'face_b'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'dpad_l'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'dpad_r'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'dpad_u'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'dpad_d'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'trigger_l'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'trigger_r'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'shoulder_l'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'shoulder_r'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'stick_l'), 0)
-        self.assertEqual(jmgr.get_joystick_val(0, 'stick_r'), 0)
-
-    def test_bind_keyboard(self):
-        nav_info = [NavInfoPerPlayer(
-            'raw-arrow_left', 'raw-arrow_right', 'raw-arrow_up',
-            'raw-arrow_down', 'raw-rcontrol')]
-        self.eng.joystick_mgr.bind_keyboard(nav_info)
-        self.assertEqual(self.eng.joystick_mgr.nav[0].fire, 'raw-rcontrol')
-        self.eng.joystick_mgr.unbind_keyboard(0)
-        self.assertIsNone(self.eng.joystick_mgr.nav[0], None)
diff --git a/tests/lib/test_computer_proxy.py b/tests/lib/test_computer_proxy.py
deleted file mode 100644 (file)
index 8f14195..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-# from pathlib import Path
-# import sys
-# if '' in sys.path: sys.path.remove('')
-# sys.path.append(str(Path(__file__).parent.parent.parent))
-# from unittest import TestCase
-# from panda3d.core import loadPrcFileData
-# from ya2.gameobject import GameObject
-# from ya2.engine.engine import Engine
-# from ya2.computer_proxy import ComputerProxy, compute_once, once_a_frame
-
-
-# class ExampleProxy(GameObject, ComputerProxy):
-
-#     def __init__(self):
-#         GameObject.__init__(self)
-#         ComputerProxy.__init__(self)
-#         self.reset()
-
-#     def reset(self): self.cnt = 0
-
-#     @compute_once
-#     def inc_cnt(self): self.cnt += 1
-
-#     @once_a_frame
-#     def inc_cnt_frame(self): self.cnt += 1
-
-
-# class ComputerProxyTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-#         self.engine = Engine()
-#         self.example_proxy = ExampleProxy()
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.assertIsInstance(self.example_proxy, ExampleProxy)
-
-#     def test_compute_once(self):
-#         self.example_proxy.reset()
-#         self.example_proxy.inc_cnt()
-#         self.example_proxy.inc_cnt()
-#         self.assertEqual(self.example_proxy.cnt, 1)
-
-#     def test_compute_once_a_frame(self):
-#         self.example_proxy.reset()
-#         self.example_proxy.on_start_frame()
-#         self.example_proxy.inc_cnt_frame()
-#         self.example_proxy.inc_cnt_frame()
-#         self.assertEqual(self.example_proxy.cnt, 1)
-#         self.example_proxy.on_start_frame()
-#         self.example_proxy.inc_cnt_frame()
-#         self.example_proxy.inc_cnt_frame()
-#         self.assertEqual(self.example_proxy.cnt, 2)
diff --git a/tests/lib/test_dictfile.py b/tests/lib/test_dictfile.py
deleted file mode 100644 (file)
index a96006c..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from os import remove
-from os.path import exists
-from unittest import TestCase
-from ya2.dictfile import DctFile
-
-
-class DictFileTests(TestCase):
-
-    def setUp(self):
-        if exists('./tests/test.ini'): remove('./tests/test.ini')
-        self.dctfile = DctFile(
-            './tests/test.ini',
-            {'test': {'a': 0, 'b': 1, 'c': 2}})
-        self.dctfile.store()
-
-    def tearDown(self):
-        remove('./tests/test.ini')
-
-    def test_init(self):
-        self.assertIsNotNone(self.dctfile)
-
-    def test_deepupdate(self):
-        self.dctfile['a'] = {'b': {'c': 4}}
-        self.assertEqual(self.dctfile['a']['b']['c'], 4)
-        self.dctfile['a'] = \
-            DctFile.deepupdate(self.dctfile['a'], {'b': {'c': 5}})
-        self.assertEqual(self.dctfile['a']['b']['c'], 5)
-
-    def test_store(self):
-        self.assertEqual(self.dctfile['test']['c'], 2)
-        other = DctFile('./tests/test.ini')
-        self.dctfile['test']['c'] = 3
-        self.assertEqual(self.dctfile['test']['c'], 3)
-        self.assertEqual(other['test']['c'], 2)
-        self.dctfile.store()
-        other = DctFile('./tests/test.ini')
-        self.assertEqual(other['test']['c'], 3)
-
-    def test_operations(self):
-        self.assertEqual(self.dctfile['test']['c'], 2)
-        self.dctfile['d'] = 3
-        self.assertEqual(self.dctfile['d'], 3)
-        self.assertIn('d', self.dctfile.dct)
-        del self.dctfile['d']
-        self.assertNotIn('d', self.dctfile.dct)
diff --git a/tests/lib/test_game.py b/tests/lib/test_game.py
deleted file mode 100644 (file)
index 8f50a50..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-# from pathlib import Path
-# import sys
-# if '' in sys.path: sys.path.remove('')
-# sys.path.append(str(Path(__file__).parent.parent.parent))
-# from unittest import TestCase
-# from panda3d.core import loadPrcFileData
-# from ya2.engine.engine import Engine
-# from ya2.engine.configuration import Cfg
-# from ya2.game import GameLogic, Game
-# from ya2.gameobject import GameObject, FsmColleague, AudioColleague, \
-#     EventColleague, LogicColleague
-
-
-# class LogicTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.eng.destroy()
-
-#     def test_init(self):
-#         self.eng = Engine()
-#         game_obj = GameObject()
-#         logic = GameLogic(game_obj)
-#         self.assertIsInstance(logic, GameLogic)
-
-
-# class GameInstance(Game):
-
-#     def __init__(self):
-#         conf = Cfg()
-#         Game.__init__(self, conf)
-#         self.fsm = FsmColleague(self)
-#         self.logic = LogicColleague(self)
-#         self.audio = AudioColleague(self)
-#         self.event = EventColleague(self)
-
-#     def destroy(self):
-#         self.fsm.destroy()
-#         self.logic.destroy()
-#         self.audio.destroy()
-#         self.event.destroy()
-
-
-# class GameTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def test_init(self):
-#         self.game = GameInstance()
-#         self.assertIsInstance(self.game, Game)
-#         self.game.destroy()
-
-#     def tearDown(self):
-#         self.game.eng.destroy()
diff --git a/tests/lib/test_gameobject.py b/tests/lib/test_gameobject.py
deleted file mode 100644 (file)
index 2e5a2a0..0000000
+++ /dev/null
@@ -1,215 +0,0 @@
-# from pathlib import Path
-# import sys
-# if '' in sys.path: sys.path.remove('')
-# sys.path.append(str(Path(__file__).parent.parent.parent))
-# from unittest.mock import patch
-# from unittest import TestCase
-# from panda3d.core import loadPrcFileData
-# from ya2.engine.engine import Engine
-# from ya2.gameobject import AiColleague, AudioColleague, EventColleague, \
-#     FsmColleague, GameObject, GfxColleague, GuiColleague, LogicColleague, \
-#     PhysColleague, Colleague
-
-
-# class AiTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         ai = AiColleague(game_obj)
-#         self.assertIsInstance(ai, AiColleague)
-
-
-# class AudioTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         audio = AudioColleague(game_obj)
-#         self.assertIsInstance(audio, AudioColleague)
-
-
-# class ColleagueTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         colleague = Colleague(game_obj)
-#         self.assertIsInstance(colleague, Colleague)
-
-
-# class EventTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         event = EventColleague(game_obj)
-#         self.assertIsInstance(event, EventColleague)
-
-
-# class FsmTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         fsm = FsmColleague(game_obj)
-#         self.assertIsInstance(fsm, FsmColleague)
-
-
-# class GfxTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         gfx = GfxColleague(game_obj)
-#         self.assertIsInstance(gfx, GfxColleague)
-
-
-# class GuiTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         gui = GuiColleague(game_obj)
-#         self.assertIsInstance(gui, GuiColleague)
-
-
-# class LogicTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         logic = LogicColleague(game_obj)
-#         self.assertIsInstance(logic, LogicColleague)
-
-
-# class PhysicsTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     def test_init(self):
-#         self.engine = Engine()
-#         game_obj = GameObject()
-#         phys = PhysColleague(game_obj)
-#         self.assertIsInstance(phys, PhysColleague)
-
-
-# class GameObjectInstance(GameObject):
-
-#     def __init__(self):
-#         GameObject.__init__(self)
-#         self.fsm = FsmColleague(self)
-#         self.event = EventColleague(self)
-#         self.ai = AiColleague(self)
-#         self.phys = PhysColleague(self)
-#         self.audio = AudioColleague(self)
-#         self.logic = LogicColleague(self)
-#         self.gui = GuiColleague(self)
-#         self.gfx = GfxColleague(self)
-
-#     def destroy(self):
-#         self.fsm.destroy()
-#         self.event.destroy()
-#         self.ai.destroy()
-#         self.phys.destroy()
-#         self.audio.destroy()
-#         self.logic.destroy()
-#         self.gui.destroy()
-#         self.gfx.destroy()
-
-
-# class GameObjectTests(TestCase):
-
-#     def setUp(self):
-#         loadPrcFileData('', 'window-type none')
-#         loadPrcFileData('', 'audio-library-name null')
-
-#     def tearDown(self):
-#         self.engine.destroy()
-
-#     @patch.object(GfxColleague, 'destroy')
-#     @patch.object(GuiColleague, 'destroy')
-#     @patch.object(LogicColleague, 'destroy')
-#     @patch.object(AudioColleague, 'destroy')
-#     @patch.object(PhysColleague, 'destroy')
-#     @patch.object(AiColleague, 'destroy')
-#     @patch.object(EventColleague, 'destroy')
-#     @patch.object(FsmColleague, 'destroy')
-#     def test_init(
-#             self, mock_fsm_destroy, mock_event_destroy, mock_ai_destroy,
-#             mock_phys_destroy, mock_audio_destroy, mock_logic_destroy,
-#             mock_gui_destroy, mock_gfx_destroy):
-#         self.engine = Engine()
-#         mock_event_destroy.__name__ = 'destroy'
-#         game_obj = GameObjectInstance()
-#         self.assertIsInstance(game_obj, GameObject)
-#         game_obj.destroy()
-#         assert mock_fsm_destroy.called
-#         assert mock_event_destroy.called
-#         assert mock_ai_destroy.called
-#         assert mock_phys_destroy.called
-#         assert mock_audio_destroy.called
-#         assert mock_logic_destroy.called
-#         assert mock_gui_destroy.called
-#         assert mock_gfx_destroy.called
diff --git a/tests/lib/test_observer.py b/tests/lib/test_observer.py
deleted file mode 100644 (file)
index ee07e5f..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from unittest.mock import MagicMock
-from ya2.observer import Subject
-
-
-class Observed(Subject): pass
-
-
-class Observer:
-
-    def __init__(self, observed): self.__observed = observed
-
-    def callback(self): pass
-
-
-class ObserverTests(TestCase):
-
-    def test_all(self):
-        observed = Observed()
-        observer = Observer(observed)
-        observer.callback = MagicMock(side_effect=observer.callback)
-        observer.callback.__name__ = 'callback'
-        self.assertFalse(observed.observing(observer.callback))
-        observed.attach(observer.callback)
-        self.assertTrue(observed.observing(observer.callback))
-        observer.callback.assert_not_called()
-        observed.notify('callback')
-        observer.callback.assert_called()
-        observed.detach(observer.callback)
-        self.assertFalse(observed.observing(observer.callback))
diff --git a/tests/ya2/__init__.py b/tests/ya2/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/ya2/build/__init__.py b/tests/ya2/build/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/ya2/build/test_build.py b/tests/ya2/build/test_build.py
new file mode 100644 (file)
index 0000000..6713add
--- /dev/null
@@ -0,0 +1,68 @@
+'''Unit tests for lib.build.'''
+from pathlib import Path
+import sys
+if '' in sys.path: sys.path.remove('')
+sys.path.append(str(Path(__file__).parent.parent.parent.parent))
+from os import getcwd, makedirs, walk
+from os.path import basename
+from shutil import rmtree
+from unittest import TestCase
+from re import compile
+from ya2.build.build import InsideDir, files, exec_cmd, _branch, _version, \
+    to_be_built
+
+
+class BuildTests(TestCase):
+
+    def setUp(self):
+        makedirs('test_get_files/a')
+        makedirs('test_get_files/b')
+        makedirs('test_get_files/c')
+        with open('test_get_files/a/c.ext1', 'w') as ftest:
+            ftest.write('0123456789')
+        with open('test_get_files/a/d.ext2', 'w'): pass
+        with open('test_get_files/b/e.ext2', 'w') as ftest:
+            ftest.write('0123456789')
+        with open('test_get_files/b/f.ext3', 'w'): pass
+        with open('test_get_files/c/g.ext2', 'w'): pass
+
+    def tearDown(self):
+        rmtree('test_get_files')
+
+    def test_exec_cmd(self):
+        self.assertEqual(exec_cmd('echo abc'), 'abc')
+
+    def test_branch(self):
+        self.assertIn(_branch(), ['master', 'rc', 'stable'])
+
+    def test_version(self):
+        patterns = [
+            "^0a[0-9]+$",
+            "^0rc[0-9]+$",
+            "^0\.[0-9]+$"]
+        compiled = [compile(pattern) for pattern in patterns]
+        matches = [pattern.match(_version()) for pattern in compiled]
+        self.assertTrue(any(matches))
+
+    def test_get_files(self):
+        _files = files(['ext2'], 'c')
+        self.assertSetEqual(set(_files),
+                            set(['./test_get_files/a/d.ext2',
+                                 './test_get_files/b/e.ext2']))
+
+    def test_inside_dir(self):
+        dirs = [basename(x[0]) for x in walk('.')]
+        dirs = [dir_ for dir_ in dirs if dir_ not in ['.', '..']]
+        dirname = dirs[0]
+        self.assertNotEqual(basename(getcwd()), dirname)
+        with InsideDir(dirname):
+            self.assertEqual(basename(getcwd()), dirname)
+        self.assertNotEqual(basename(getcwd()), dirname)
+
+    def test_to_be_built(self):
+        tgt = 'test_get_files/tgt.txt'
+        with open('test_get_files/src.txt', 'w') as fsrc:
+            fsrc.write('src')
+        with open(tgt, 'w') as ftgt:
+            ftgt.write('tgt')
+        self.assertTrue(to_be_built(tgt, ['test_get_files/src.txt']))
diff --git a/tests/ya2/build/test_lang.py b/tests/ya2/build/test_lang.py
new file mode 100644 (file)
index 0000000..9ccf7cf
--- /dev/null
@@ -0,0 +1,30 @@
+from pathlib import Path
+import sys
+if '' in sys.path: sys.path.remove('')
+sys.path.append(str(Path(__file__).parent.parent.parent))
+from os import remove, makedirs
+from os.path import exists
+from shutil import rmtree, copy
+from unittest import TestCase
+from ya2.build.lang import LanguageBuilder
+
+
+class LangTests(TestCase):
+
+    def setUp(self):
+        for dirname in ['locale', 'po']:
+            rmtree('./tests/' + dirname, ignore_errors=True)
+            makedirs('./tests/' + dirname, exist_ok=True)
+        copy('assets/locale/po/it_IT.po', './tests/po/')
+
+    def tearDown(self):
+        for dirname in ['locale', 'po']:
+            rmtree('./tests/' + dirname, ignore_errors=True)
+
+    def test_lang(self):
+        LanguageBuilder.pot('test_pmachines', './tests/po/')
+        self.assertTrue(exists('./tests/po/test_pmachines.pot'))
+        LanguageBuilder.merge('it_IT', './tests/po/', './tests/locale/', 'test_pmachines')
+        LanguageBuilder.mo('./tests/locale/it_IT/LC_MESSAGES/test_pmachines.mo',
+                           './tests/locale/', 'test_pmachines')
+        self.assertTrue(exists('./tests/locale/it_IT/LC_MESSAGES/test_pmachines.mo'))
diff --git a/tests/ya2/build/test_models.py b/tests/ya2/build/test_models.py
new file mode 100644 (file)
index 0000000..e569670
--- /dev/null
@@ -0,0 +1,50 @@
+from pathlib import Path
+import sys
+if '' in sys.path: sys.path.remove('')
+sys.path.append(str(Path(__file__).parent.parent.parent))
+from os import remove, makedirs, environ
+from os.path import exists
+from shutil import rmtree, copy
+from unittest import TestCase
+from itertools import product
+from time import time
+from ya2.build.models import ModelsBuilder
+
+
+class ModelsBuilderTests(TestCase):
+
+    def setUp(self):
+        self.dirs = ['box', 'domino']
+        for fmt_dir in product(['bam', 'gltf'], self.dirs):
+            rmtree('assets/models/%s/%s' % fmt_dir, ignore_errors=True)
+
+    def test_models(self):
+        if environ.get('FAST') == '1':
+            self.skipTest('skipped slow tests')
+        for fmt_dir in product(['bam', 'gltf'], self.dirs):
+            self.assertFalse(exists('assets/%s/%s' % fmt_dir))
+        start = time()
+        ModelsBuilder().build('assets/models', 1)
+        self.assertTrue(time() - start > 1.5)
+        files = [
+            'assets/models/bam/box/box.bam',
+            'assets/models/bam/box/base.dds',
+            'assets/models/bam/box/ao_metal_roughness.dds',
+            'assets/models/bam/box/normal.dds',
+            'assets/models/bam/domino/domino.bam',
+            'assets/models/bam/domino/base.dds',
+            'assets/models/bam/domino/ao_roughness_metal.dds',
+            'assets/models/bam/domino/normal.dds',
+            'assets/models/gltf/box/box.gltf',
+            'assets/models/gltf/box/base.png',
+            'assets/models/gltf/box/ao_metal_roughness.png',
+            'assets/models/gltf/box/normal.png',
+            'assets/models/gltf/domino/domino.gltf',
+            'assets/models/gltf/domino/base.png',
+            'assets/models/gltf/domino/ao_roughness_metal.png',
+            'assets/models/gltf/domino/normal.png']
+        [self.assertTrue(exists(fname)) for fname in files]
+        #start = time()
+        #ModelsBuilder().build('assets/models', 1)
+        #self.assertTrue(time() - start < 1.5)  # test caching
+        #[self.assertTrue(exists(fname)) for fname in files]
diff --git a/tests/ya2/build/test_mtprocesser.py b/tests/ya2/build/test_mtprocesser.py
new file mode 100644 (file)
index 0000000..536cf7c
--- /dev/null
@@ -0,0 +1,40 @@
+'''lib.build.mtprocesser's unit tests.'''
+from pathlib import Path
+import sys
+sys.path = [path for path in sys.path if path != '']
+
+# we're in yocto/tests/lib/build/
+sys.path.append(str(Path(__file__).parent.parent.parent.parent))
+from pathlib import Path
+from os.path import exists
+from unittest import TestCase
+from ya2.build.mtprocesser import ProcesserMgr
+
+
+class ProcesserMgrTests(TestCase):
+    '''ProcesserMgr's unit tests '''
+
+    def setUp(self):
+        '''unit tests' set up'''
+        for idx in [1, 2]:
+            Path('./tests/%s.txt' % idx).unlink(missing_ok=True)
+
+    def tearDown(self):
+        '''unit tests' tear down'''
+        self.setUp()
+
+    def test_threaded(self):
+        '''test of the threaded case'''
+        pmgr = ProcesserMgr(2)
+        pmgr.add('echo 1 > ./tests/1.txt')
+        pmgr.add('echo 2 > ./tests/2.txt')
+        pmgr.run()
+        self.assertTrue(exists('./tests/1.txt'))
+        self.assertTrue(exists('./tests/2.txt'))
+
+    def test_nothreaded(self):
+        '''test when we don't use threads'''
+        pmgr = ProcesserMgr(1)
+        pmgr.add('echo 1 > ./tests/1.txt')
+        pmgr.run()
+        self.assertTrue(exists('./tests/1.txt'))
diff --git a/tests/ya2/patterns/__init__.py b/tests/ya2/patterns/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/ya2/utils/__init__.py b/tests/ya2/utils/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/ya2/utils/test_dictfile.py b/tests/ya2/utils/test_dictfile.py
new file mode 100644 (file)
index 0000000..6a9d528
--- /dev/null
@@ -0,0 +1,49 @@
+from pathlib import Path
+import sys
+if '' in sys.path: sys.path.remove('')
+sys.path.append(str(Path(__file__).parent.parent.parent))
+from os import remove
+from os.path import exists
+from unittest import TestCase
+from ya2.utils.dictfile import DctFile
+
+
+class DictFileTests(TestCase):
+
+    def setUp(self):
+        if exists('./tests/test.ini'): remove('./tests/test.ini')
+        self.dctfile = DctFile(
+            './tests/test.ini',
+            {'test': {'a': 0, 'b': 1, 'c': 2}})
+        self.dctfile.store()
+
+    def tearDown(self):
+        remove('./tests/test.ini')
+
+    def test_init(self):
+        self.assertIsNotNone(self.dctfile)
+
+    def test_deepupdate(self):
+        self.dctfile['a'] = {'b': {'c': 4}}
+        self.assertEqual(self.dctfile['a']['b']['c'], 4)
+        self.dctfile['a'] = \
+            DctFile.deepupdate(self.dctfile['a'], {'b': {'c': 5}})
+        self.assertEqual(self.dctfile['a']['b']['c'], 5)
+
+    def test_store(self):
+        self.assertEqual(self.dctfile['test']['c'], 2)
+        other = DctFile('./tests/test.ini')
+        self.dctfile['test']['c'] = 3
+        self.assertEqual(self.dctfile['test']['c'], 3)
+        self.assertEqual(other['test']['c'], 2)
+        self.dctfile.store()
+        other = DctFile('./tests/test.ini')
+        self.assertEqual(other['test']['c'], 3)
+
+    def test_operations(self):
+        self.assertEqual(self.dctfile['test']['c'], 2)
+        self.dctfile['d'] = 3
+        self.assertEqual(self.dctfile['d'], 3)
+        self.assertIn('d', self.dctfile.dct)
+        del self.dctfile['d']
+        self.assertNotIn('d', self.dctfile.dct)
diff --git a/tests/ya2/utils/test_gameobject.py b/tests/ya2/utils/test_gameobject.py
new file mode 100644 (file)
index 0000000..58cae86
--- /dev/null
@@ -0,0 +1,228 @@
+from pathlib import Path
+import sys
+if '' in sys.path: sys.path.remove('')
+sys.path.append(str(Path(__file__).parent.parent.parent))
+from unittest.mock import patch
+from unittest import TestCase
+from panda3d.core import loadPrcFileData
+#from ya2.engine.engine import Engine
+from ya2.patterns.gameobject import AiColleague, AudioColleague, EventColleague, \
+    FsmColleague, GameObject, GfxColleague, GuiColleague, LogicColleague, \
+    PhysColleague, Colleague
+
+
+class AiTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        #self.engine = Engine()
+        game_obj = GameObject()
+        ai = AiColleague(game_obj)
+        self.assertIsInstance(ai, AiColleague)
+
+
+class AudioTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        #self.engine = Engine()
+        game_obj = GameObject()
+        audio = AudioColleague(game_obj)
+        self.assertIsInstance(audio, AudioColleague)
+
+
+class ColleagueTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        #self.engine = Engine()
+        game_obj = GameObject()
+        colleague = Colleague(game_obj)
+        self.assertIsInstance(colleague, Colleague)
+
+
+class EventTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        #self.engine = Engine()
+        game_obj = GameObject()
+        event = EventColleague(game_obj)
+        self.assertIsInstance(event, EventColleague)
+
+
+class FsmTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        #self.engine = Engine()
+        game_obj = GameObject()
+        fsm = FsmColleague(game_obj)
+        self.assertIsInstance(fsm, FsmColleague)
+
+
+class GfxTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        #self.engine = Engine()
+        game_obj = GameObject()
+        gfx = GfxColleague(game_obj)
+        self.assertIsInstance(gfx, GfxColleague)
+
+
+class GuiTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        pass
+        #self.engine = Engine()
+        game_obj = GameObject()
+        gui = GuiColleague(game_obj)
+        self.assertIsInstance(gui, GuiColleague)
+
+
+class LogicTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        pass
+        #self.engine = Engine()
+        game_obj = GameObject()
+        logic = LogicColleague(game_obj)
+        self.assertIsInstance(logic, LogicColleague)
+
+
+class PhysicsTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    def test_init(self):
+        pass
+        #self.engine = Engine()
+        game_obj = GameObject()
+        phys = PhysColleague(game_obj)
+        self.assertIsInstance(phys, PhysColleague)
+
+
+class GameObjectInstance(GameObject):
+
+    def __init__(self):
+        GameObject.__init__(self)
+        self.fsm = FsmColleague(self)
+        self.event = EventColleague(self)
+        self.ai = AiColleague(self)
+        self.phys = PhysColleague(self)
+        self.audio = AudioColleague(self)
+        self.logic = LogicColleague(self)
+        self.gui = GuiColleague(self)
+        self.gfx = GfxColleague(self)
+
+    def destroy(self):
+        self.fsm.destroy()
+        self.event.destroy()
+        self.ai.destroy()
+        self.phys.destroy()
+        self.audio.destroy()
+        self.logic.destroy()
+        self.gui.destroy()
+        self.gfx.destroy()
+
+
+class GameObjectTests(TestCase):
+
+    def setUp(self):
+        loadPrcFileData('', 'window-type none')
+        loadPrcFileData('', 'audio-library-name null')
+
+    def tearDown(self):
+        pass
+        #self.engine.destroy()
+
+    @patch.object(GfxColleague, 'destroy')
+    @patch.object(GuiColleague, 'destroy')
+    @patch.object(LogicColleague, 'destroy')
+    @patch.object(AudioColleague, 'destroy')
+    @patch.object(PhysColleague, 'destroy')
+    @patch.object(AiColleague, 'destroy')
+    @patch.object(EventColleague, 'destroy')
+    @patch.object(FsmColleague, 'destroy')
+    def test_init(
+            self, mock_fsm_destroy, mock_event_destroy, mock_ai_destroy,
+            mock_phys_destroy, mock_audio_destroy, mock_logic_destroy,
+            mock_gui_destroy, mock_gfx_destroy):
+        #self.engine = Engine()
+        mock_event_destroy.__name__ = 'destroy'
+        game_obj = GameObjectInstance()
+        self.assertIsInstance(game_obj, GameObject)
+        game_obj.destroy()
+        assert mock_fsm_destroy.called
+        assert mock_event_destroy.called
+        assert mock_ai_destroy.called
+        assert mock_phys_destroy.called
+        assert mock_audio_destroy.called
+        assert mock_logic_destroy.called
+        assert mock_gui_destroy.called
+        assert mock_gfx_destroy.called
diff --git a/tests/ya2/utils/test_observer.py b/tests/ya2/utils/test_observer.py
new file mode 100644 (file)
index 0000000..4a7c98e
--- /dev/null
@@ -0,0 +1,34 @@
+from pathlib import Path
+import sys
+if '' in sys.path: sys.path.remove('')
+sys.path.append(str(Path(__file__).parent.parent.parent))
+from unittest import TestCase
+from unittest.mock import MagicMock
+from ya2.patterns.observer import Subject
+
+
+class Observed(Subject): pass
+
+
+class Observer:
+
+    def __init__(self, observed): self.__observed = observed
+
+    def callback(self): pass
+
+
+class ObserverTests(TestCase):
+
+    def test_all(self):
+        observed = Observed()
+        observer = Observer(observed)
+        observer.callback = MagicMock(side_effect=observer.callback)
+        observer.callback.__name__ = 'callback'
+        self.assertFalse(observed.observing(observer.callback))
+        observed.attach(observer.callback)
+        self.assertTrue(observed.observing(observer.callback))
+        observer.callback.assert_not_called()
+        observed.notify('callback')
+        observer.callback.assert_called()
+        observed.detach(observer.callback)
+        self.assertFalse(observed.observing(observer.callback))
index 0eb1261d6f364a7f2b82071f55916b47b6c16b5b..964cc595e0e726bcbdcd3402d54e4b88e0d87a51 100644 (file)
@@ -3,7 +3,7 @@ from glob import glob
 from importlib import import_module
 from inspect import isclass
 from multiprocessing import Pool
-from game.scene import Scene
+from logics.scene import Scene
 
 
 def do_screenshot(cls):
diff --git a/ya2/computer_proxy.py b/ya2/computer_proxy.py
deleted file mode 100644 (file)
index 505a273..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-from functools import wraps
-
-
-def compute_once(fun):
-    @wraps(fun)
-    def wrapper(*args, **kwargs):
-        self = args[0]
-        key = fun.__name__, args  # add support for kwargs
-        if key not in self.buffered_vals:
-            self.buffered_vals[key] = fun(*args, **kwargs)
-        return self.buffered_vals[key]
-    return wrapper
-
-
-def once_a_frame(fun):
-    @wraps(fun)
-    def wrapper(*args, **kwargs):
-        self = args[0]
-        key = fun.__name__, args  # add support for kwargs
-        if key not in self.buffered_vals_frm:
-            self.buffered_vals_frm[key] = fun(*args, **kwargs)
-        return self.buffered_vals_frm[key]
-    return wrapper
-
-
-class ComputerProxy:
-
-    def __init__(self):
-        self.eng.attach_obs(self.on_start_frame)
-        # there are issues if the object has another on_start_frame
-        self.buffered_vals, self.buffered_vals_frm = {}, {}
-
-    def on_start_frame(self):
-        self.buffered_vals_frm = {}
-
-    def destroy(self):
-        self.eng.detach_obs(self.on_start_frame)
-        self.buffered_vals = self.buffered_vals_frm = None
diff --git a/ya2/decorator.py b/ya2/decorator.py
deleted file mode 100644 (file)
index 18f8911..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# class Decorator:\r
-\r
-#     def __init__(self, decorated):\r
-#         self.__dict__['_decorated'] = decorated\r
-\r
-#     def __getattr__(self, attr): return getattr(self._decorated, attr)\r
-\r
-#     def __setattr__(self, attr, value):\r
-#         return setattr(self._decorated, attr, value)\r
diff --git a/ya2/dictfile.py b/ya2/dictfile.py
deleted file mode 100644 (file)
index a466655..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-import sys
-from logging import info
-from os import makedirs
-from os.path import dirname
-from collections.abc import Mapping
-from configparser import ConfigParser
-from json import load, dumps
-from ya2.gameobject import GameObject
-from ya2.lib.p3d.p3d import LibP3d
-
-
-class DctFile(GameObject):
-
-    def __init__(self, fpath, default_dct=None, persistent=True):
-        GameObject.__init__(self)
-        default_dct = default_dct or {}
-        if sys.platform == 'darwin' and LibP3d.runtime():
-            fpath = dirname(__file__) + '/' + fpath
-        self.fpath = fpath
-        self.persistent = persistent
-        try:
-            #with open(fpath) as json: fdct = load(json)
-            config = ConfigParser()
-            config.read(fpath)
-            fdct = {section: dict(config.items(section)) for section in config.sections()}
-            fdct = self.__typed_dct(fdct)
-            self.dct = self.__add_default(default_dct, fdct)
-        except IOError: self.dct = default_dct
-
-    @staticmethod
-    def __typed_dct(dct):
-        def convert_single_val(val):
-            try: return int(val)
-            except ValueError:
-                try: return float(val)
-                except ValueError:
-                    if not val or val[0] != '[':
-                        return val
-                    else:
-                        raise ValueError
-        def converted(val):
-            try: return convert_single_val(val)
-            except ValueError:
-                return [elm.strip() for elm in val[1:-1].split(',')]
-        new_dct = {}
-        for section, sec_dct in dct.items():
-            for key, val in sec_dct.items():
-                if section not in new_dct:
-                    new_dct[section] = {}
-                new_dct[section][key] = converted(val)
-        return new_dct
-
-    @staticmethod
-    def __add_default(dct, upd):
-        for key, val in upd.items():
-            if isinstance(val, Mapping):
-                dct[key] = DctFile.__add_default(dct.get(key, {}), val)
-            else: dct[key] = upd[key]
-        return dct
-
-    @staticmethod
-    def deepupdate(dct, new_dct):
-        for key, val in new_dct.items():
-            if isinstance(val, Mapping):
-                dct[key] = DctFile.deepupdate(dct.get(key, {}), val)
-            else: dct[key] = val
-        return dct
-
-    def store(self):
-        info('storing %s' % self.fpath)
-        if not self.persistent: return
-        #json_str = dumps(self.dct, sort_keys=True, indent=4,
-        #                 separators=(',', ': '))
-        #with open(self.fpath, 'w') as json: json.write(json_str)
-        fdct = {}
-        for section, sec_dct in self.dct.items():
-            if section not in fdct:
-                fdct[section] = {}
-            for key, val in sec_dct.items():
-                if type(val) == list:
-                    fdct[section][key] = '[%s]' % ', '.join(val)
-                else:
-                    fdct[section][key] = val
-        config = ConfigParser()
-        for key in self.dct:
-            config[key] = fdct[key]
-        if dirname(self.fpath):
-            makedirs(dirname(self.fpath), exist_ok=True)
-        with open(self.fpath, 'w') as ini_file:
-            config.write(ini_file)
-
-    def __getitem__(self, arg): return self.dct[arg]
-
-    def __setitem__(self, arg, val): self.dct[arg] = val
-
-    def __delitem__(self, arg): del self.dct[arg]
diff --git a/ya2/engine/__init__.py b/ya2/engine/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/ya2/engine/functional.py b/ya2/engine/functional.py
deleted file mode 100644 (file)
index 05dfa78..0000000
+++ /dev/null
@@ -1,652 +0,0 @@
-import datetime
-from os import getcwd, system
-from logging import debug, info
-from pathlib import Path
-from shutil import rmtree
-from os import makedirs
-from os.path import join, exists
-from glob import glob
-from sys import exit
-from multiprocessing.connection import Listener
-from threading import Thread
-from panda3d.core import Filename
-from direct.gui.OnscreenText import OnscreenText
-from ya2.gameobject import GameObject
-from ya2.build.build import _branch
-
-
-class ListenerThread(Thread):
-
-    def __init__(self, callbacks):
-        Thread.__init__(self)
-        address = ('localhost', 6000)
-        self._listener = Listener(address)
-        self._listener._listener._socket.settimeout(15)
-        try:
-            self._conn = self._listener.accept()
-        except TimeoutError:
-            info('listener timeout')
-        self._callbacks = callbacks
-
-    def run(self):
-        running = hasattr(self, '_conn')
-        while running:
-            try:
-                msg = self._conn.recv()
-                if msg[0] == 'screenshot':
-                    taskMgr.doMethodLater(.01, self._callbacks[0], 'cb0', [msg[1]])
-                elif msg[0] == 'enforce_res':
-                    taskMgr.doMethodLater(.01, self._callbacks[1], 'cb1', [msg[1]])
-                elif msg[0] == 'verify':
-                    taskMgr.doMethodLater(.01, self._callbacks[2], 'cb2')
-                elif msg[0] == 'set_idx':
-                    taskMgr.doMethodLater(.01, self._callbacks[3], 'cb3', [msg[1]])
-                elif msg[0] == 'enforce_resolution':
-                    taskMgr.doMethodLater(.01, self._callbacks[4], 'cb4', [msg[1]])
-            except EOFError:
-                running = False
-
-
-class FunctionalTest(GameObject):
-
-    def __init__(self, ref):
-        super().__init__()
-        self._listener = ListenerThread([self._do_screenshot, self._do_enforce_res, self.__verify, self._set_idx, self._do_enforce_resolution])
-        self._listener.start()
-        self.txt = OnscreenText('', fg=(1, 0, 0, 1), scale=.16)
-        #self._path = ''
-        #if self.eng.is_appimage:
-        self._path = str(Filename().get_user_appdata_directory())
-        self._path += '/pmachines/'
-        self._path += 'tests/functional%s/' % ('_ref' if ref else '')
-        home = '/home/flavio'  # we must force this for wine
-        # if self._path.startswith('/c/users/') and exists(str(Path.home()) + '/.local/share/flatpak-wine601/default/'):
-        #     self._path = str(Path.home()) + '/.local/share/flatpak-wine601/default/drive_' + self._path[1:]
-        if self._path.startswith('/c/users/') and exists(home + '/.wine/'):
-            self._path = home + '/.wine/drive_' + self._path[1:]
-        if ref:
-            self._path = join(
-                Filename().get_user_appdata_directory(),
-                'pmachines/tests/functional_ref_%s/' % _branch())
-        self._fnames = []
-        #taskMgr.add(self.on_frame_unpausable, 'on-frame-unpausable')
-        #self._do_screenshots(idx)
-
-    def _set_idx(self, idx):
-        if int(idx) == 1:
-            rmtree(self._path, ignore_errors=True)
-        info('creating dir: %s' % self._path)
-        makedirs(self._path, exist_ok=True)
-
-    def _do_screenshot(self, name):
-        self._fnames += [self._path + name]
-        #time = datetime.datetime.now().strftime('%y%m%d%H%M%S')
-        #res = base.win.save_screenshot(Filename(path or ("yocto%s.png" % time)))
-        #debug('screenshot %s (%s)' % (path or ("yocto%s.png" % time), res))
-        res = base.screenshot(self._path + name, False)
-        info('screenshot %s (%s; %s)' % (self._path + name, res, getcwd()))
-
-    def _do_enforce_res(self, res):
-        info('enforce_res %s' % res)
-        messenger.send('enforce_res', [res])
-
-    def _do_enforce_resolution(self, res):
-        info('enforce resolution %s (callback)' % res)
-        messenger.send('enforce_resolution', [res])
-
-    #def _screenshot(self, time, name):
-        #self._fnames += [self._path + name + '.png']
-        #self._tasks += [(
-        #    self._curr_time + time,
-        #    lambda: self._do_screenshot(self._path + name + '.png'),
-        #    'screenshot: %s' % name)]
-        #def txt(show_hide):
-        #    self.txt['text'] = name
-        #    (self.txt.show if show_hide else self.txt.hide)()
-        #self._tasks += [(
-        #    self._curr_time + time + .1,
-        #    lambda: txt(True),
-        #    'screenshot: %s (show)' % name)]
-        #self._tasks += [(
-        #    self._curr_time + time + FunctionalTest.evt_time - .1,
-        #    lambda: txt(False),
-        #    'screenshot: %s (hide)' % name)]
-        #self._curr_time += time
-
-    #def __keypress(self, key):
-        #'''Emulates a keypress'''
-        #dev = base.win.getInputDevice(0)
-        #dev.buttonDown(key)
-        #dev.buttonUp(key)
-
-    #def __char_entered(self, char):
-        #'''Emulates a character being entered.'''
-        #dev = base.win.getInputDevice(0)
-        #dev.keystroke(ord(char))
-
-    # def _event(self, time, evt, messenger_evt=False, append_up=True, mouse_args=None):
-    #     def _append_up(evt_name):
-    #         return evt + ('' if evt.endswith('-up') or not append_up else '-up')
-    #     def cback_char(_evt):
-    #         self.__char_entered(_evt)
-    #     def cback_keyp(_evt):
-    #         self.__keypress(_evt)
-    #         self.__keypress('raw-' + _evt)
-    #     cback = lambda: (cback_char(evt) if len(evt) == 1 else cback_keyp(evt))
-    #     if evt in ['mousemove', 'mouseclick', 'mousedrag']:
-    #         if evt == 'mousemove':
-    #             cback = lambda: self.__mouse_move(*mouse_args)
-    #         elif evt == 'mouseclick':
-    #             cback = lambda: self.__mouse_click(*mouse_args)
-    #         elif evt == 'mousedrag':
-    #             cback = lambda: self.__mouse_drag(*mouse_args)
-    #     if messenger_evt:
-    #         cback = lambda: messenger.send(_append_up(evt))
-    #     self._tasks += [(
-    #         self._curr_time + time,
-    #         cback,
-    #         'event: %s' % evt)]
-    #     def txt(show_hide):
-    #         self.txt['text'] = evt
-    #         (self.txt.show if show_hide else self.txt.hide)()
-    #     self._tasks += [(
-    #         self._curr_time + time + .2,
-    #         lambda: txt(True),
-    #         'event: %s (show)' % evt)]
-    #     self._tasks += [(
-    #         self._curr_time + time + .8,
-    #         lambda: txt(False),
-    #         'event: %s (hide)' % evt)]
-    #     self._curr_time += time
-
-    # def _enforce_res(self, time, res):
-    #     cback = lambda: messenger.send('enforce_res', [res])
-    #     self._tasks += [(
-    #         self._curr_time + time,
-    #         cback,
-    #         'enforce res: %s' % res)]
-    #     self._curr_time += time
-
-    def __verify(self, task):
-        files = glob(self._path + '*')
-        for fname in self._fnames:
-            info('verifying %s' % fname)
-            assert exists(fname)
-
-    #def on_frame_unpausable(self, task):
-        #self._process_conn()
-        #for tsk in self._tasks:
-        #    #if self._prev_time <= tsk[0] < self.eng.event.unpaused_time:
-        #    if self._prev_time <= tsk[0] < globalClock.getFrameTime():
-        #        debug('%s %s' % (tsk[0], tsk[2]))
-        #        tsk[1]()
-        #self._prev_time = globalClock.getFrameTime()  # self.eng.event.unpaused_time
-        #return task.cont
-
-    # def _do_screenshots_1(self):
-    #     info('_do_screenshots_1')
-    #     self._screenshot(FunctionalTest.start_time, 'main_menu')
-    #     self._do_screenshots_credits()
-    #     self._do_screenshots_options()
-    #     self._do_screenshots_exit()
-
-    # def _do_screenshots_credits(self):
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'credits_menu')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 680), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'main_menu_back_from_credits')
-    #     # # go to credits
-    #     # self._event(FunctionalTest.evt_time, 'joypad0-dpad_down', True)
-    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
-    #     # self._event(FunctionalTest.evt_time, 'joypad0-dpad_down', True)
-    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'main_menu_highlight')
-    #     # self._event(FunctionalTest.evt_time, 'rcontrol')
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'credits_menu')
-    #     # # go to supporters
-    #     # self._event(FunctionalTest.evt_time, 'joypad0-face_a', True)
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'supporters_menu')
-    #     # # back to main
-    #     # self._event(FunctionalTest.evt_time, 'rcontrol')
-    #     # self._event(FunctionalTest.evt_time, 'joypad0-face_b', True)
-    #     # self._event(FunctionalTest.evt_time, 'arrow_up')
-    #     # self._event(FunctionalTest.evt_time, 'arrow_up')
-    #     # self._event(FunctionalTest.evt_time, 'arrow_up')
-    #     # self._event(FunctionalTest.evt_time, 'arrow_up')
-
-    # def _do_screenshots_options(self):
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 300), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu')
-    #     # languages
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 60), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'open_languages')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(980, 120), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_italian')
-    #     # volume
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(740, 163), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_drag_1')
-    #     # antialiasing
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 440), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'antialiasing_no')
-    #     # shadows
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 540), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'shadows_no')
-    #     # test aa and shadows
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 680), 'left'])  # back
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(230, 160), 'left'])  # domino
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(900, 490), 'left'])  # close instructions
-    #     self._screenshot(FunctionalTest.screenshot_time, 'aa_no_shadows_no')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(25, 740), 'left'])  # home
-
-    # def _do_screenshots_restore_options(self):
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 300), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_restored')
-    #     # languages
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 60), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'open_languages_restored')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(980, 20), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_english')
-    #     # volume
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(719, 163), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_drag_2')
-    #     # fullscreen
-    #     # the first one is because of the windowed mode in test
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 250), 'left'])
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'fullscreen')
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 250), 'left'])
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'fullscreen')
-    #     # self._event(8 + FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 250), 'left'])
-    #     # self._screenshot(8 + FunctionalTest.screenshot_time, 'back_from_fullscreen')
-    #     # resolution
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 340), 'left'])
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'resolutions')
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1020, 160), 'left'])
-    #     # self._screenshot(FunctionalTest.screenshot_time, '1440x900')
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(740, 400), 'left'])
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'resolutions_2')
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1110, 80), 'left'])
-    #     # self._screenshot(FunctionalTest.screenshot_time, '1360x768')
-    #     # antialiasing
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 440), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'antialiasing_yes')
-    #     # shadows
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 540), 'left'])
-    #     self._screenshot(FunctionalTest.screenshot_time, 'shadows_yes')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 680), 'left'])  # back
-
-    # #     # go to options
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu')
-    # #     # language
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'language_open')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'language_highlight')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'language_it')
-    # #     # volume
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_right')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_right')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'volume')
-    # #     # car's number
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'cars_open')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'cars_changed')
-    # #     # back
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
-
-    # def _do_screenshots_play(self):
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
-    #     self._screenshot(FunctionalTest.screenshot_time, 'play_menu')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 680), 'left'])  # back
-    #     self._screenshot(FunctionalTest.screenshot_time, 'back_from_play')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(230, 160), 'left'])  # domino scene
-    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_domino_instructions')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(850, 490), 'left'])  # close instructions
-    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_domino')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(25, 740), 'left'])  # home
-    #     self._screenshot(FunctionalTest.screenshot_time, 'home_back_from_scene')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(230, 160), 'left'])  # domino
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(850, 490), 'left'])  # close instructions
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(70, 740), 'left'])  # info
-    #     self._screenshot(FunctionalTest.screenshot_time, 'info')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(850, 490), 'left'])  # close instructions
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (430, 280), 'left'])  # drag a piece
-    #     self._screenshot(FunctionalTest.screenshot_time, 'domino_dragged')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1220, 740), 'left'])  # rewind
-    #     self._screenshot(FunctionalTest.screenshot_time, 'rewind')
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (550, 380), 'left'])  # drag a piece
-    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (715, 380), 'left'])  # drag a piece
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(630, 450), 'left'])  # home
-    #     self._screenshot(FunctionalTest.screenshot_time, 'home_back_from_fail')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(230, 160), 'left'])  # domino
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(850, 490), 'left'])  # close instructions
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (550, 380), 'left'])  # drag a piece
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (715, 380), 'left'])  # drag a piece
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino_2')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (570, 380), 'left'])  # drag a piece
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(570, 355), (605, 355), 'right'])  # rotate the piece
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (715, 380), 'left'])  # drag a piece
-    #     self._enforce_res(FunctionalTest.evt_time, 'win')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_domino')
-    #     self._enforce_res(FunctionalTest.evt_time, '')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
-    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_box')
-    #     # scene 2
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(880, 490), 'left'])  # close instructions
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (710, 620), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (710, 540), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_box')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (710, 620), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (710, 540), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (705, 460), 'left'])  # drag a box
-    #     self._enforce_res(FunctionalTest.evt_time, 'win')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_box')
-    #     self._enforce_res(FunctionalTest.evt_time, '')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
-    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_box_domino')
-    #     # scene 3
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(930, 485), 'left'])  # close instructions
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (910, 440), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (910, 360), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_box_domino')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (910, 440), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (835, 250), 'left'])  # drag a box
-    #     self._enforce_res(FunctionalTest.evt_time, 'win')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_box_domino')
-    #     self._enforce_res(FunctionalTest.evt_time, '')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
-    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_basketball')
-    #     # scene 4
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(870, 490), 'left'])  # close instructions
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(55, 50), (650, 310), 'left'])  # drag a ball
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_basketball')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(55, 50), (380, 50), 'left'])  # drag a ball
-    #     self._enforce_res(FunctionalTest.evt_time, 'win')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_basketball')
-    #     self._enforce_res(FunctionalTest.evt_time, '')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
-    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_domino_box_basketball')
-    #     # scene 5
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(865, 490), 'left'])  # close instructions
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (580, 440), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(30, 60), (590, 370), 'left'])  # drag a piece
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino_box_basketball')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (580, 440), 'left'])  # drag a box
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(30, 60), (660, 440), 'left'])  # drag a piece
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(660, 425), (625, 425), 'right'])  # rotate a piece
-    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(660, 435), (650, 445), 'left'])  # drag a piece
-    #     self._enforce_res(FunctionalTest.evt_time, 'win')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
-    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_domino_box_basketball')
-    #     self._enforce_res(FunctionalTest.evt_time, '')
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'scene_teeter_tooter')
-    #     # # scene 6
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(820, 455), 'left'])  # close instructions
-    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(60, 60), (490, 300), 'left'])  # drag a box
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1260, 695), 'left'])  # play
-    #     # self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_teeter_tooter')
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(640, 420), 'left'])  # replay
-    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(60, 60), (490, 150), 'left'])  # drag a box
-    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(515, 115), (515, 122), 'right'])  # rotate a box
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1260, 695), 'left'])  # play
-    #     # self._screenshot(16 + FunctionalTest.screenshot_time, 'win_teeter_tooter')
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(690, 420), 'left'])  # next
-    #     # self._screenshot(FunctionalTest.screenshot_time, 'scene_teeter_domino_box_basketball')
-    #     # # scene 7
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(880, 455), 'left'])  # close instructions
-    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(60, 60), (155, 180), 'left'])  # drag a box
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1260, 695), 'left'])  # play
-    #     # self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_teeter_domino_box_basketball')
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(640, 420), 'left'])  # replay
-    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(60, 60), (170, 80), 'left'])  # drag a box
-    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(195, 50), (195, 80), 'right'])  # rotate a box
-    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1260, 695), 'left'])  # play
-    #     # self._screenshot(16 + FunctionalTest.screenshot_time, 'win_teeter_domino_box_basketball')
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(630, 450), 'left'])  # home
-    #     self._screenshot(FunctionalTest.screenshot_time, 'home_from_play')
-
-    # def _do_screenshots_exit(self):
-    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
-    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
-    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
-    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
-    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
-    #     # self._event(FunctionalTest.evt_time, 'rcontrol')
-    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
-    #     self._verify()
-    #     # self._event(FunctionalTest.evt_time, 'rcontrol')
-    #     # self._exit()
-    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 600), 'left'])
-
-
-    # def _do_screenshots_2(self):
-    #     info('_do_screenshots_2')
-    #     self._screenshot(FunctionalTest.start_time, 'main_menu_2')
-    #     self._do_screenshots_restore_options()
-    #     self._do_screenshots_play()
-    #     self._do_screenshots_exit()
-    # #     self._do_screenshots_game()
-    # #     self._do_screenshots_end()
-
-    # # def _do_screenshots_restore_options(self):
-    # #     # go to options
-    # #     self._event(FunctionalTest.evt_time, 'joypad0-dpad_down', True)
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_restored')
-    # #     # # language
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'language_en_restored')
-    # #     # # volume
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'volume_restored')
-    # #     # car's number
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'cars_restored')
-    # #     # graphics settings
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'graphics_settings')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'antialiasing')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'shadows')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'fog')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'normal_mapping')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'occlusion')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     # input
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'input')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_rec')
-    # #     self._event(FunctionalTest.evt_time, '8', True, False)
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_changed')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_up', True, False)
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_restored')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'w', True, False)
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_already')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_already_closed')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p2')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p3')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p4')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
-
-    # # def _do_screenshots_game(self):
-    # #     # single player
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'single_player_menu')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'track_page')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'car_page_start')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'car_page_sel')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_start')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_entry')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._event(FunctionalTest.evt_time, 'backspace')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_entry_empty')
-    # #     self._event(FunctionalTest.evt_time, 'f')
-    # #     self._event(FunctionalTest.evt_time, 'l')
-    # #     self._event(FunctionalTest.evt_time, 'a')
-    # #     self._event(FunctionalTest.evt_time, 'v')
-    # #     self._event(FunctionalTest.evt_time, 'i')
-    # #     self._event(FunctionalTest.evt_time, 'o')
-    # #     self._event(FunctionalTest.evt_time, 'enter')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_entry_full')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_right')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_sel')
-    # #     # some ai tests
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._event(40, 'escape-up')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'ingame_menu')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'race_back')
-    # #     self._event(FunctionalTest.evt_time, 'escape-up')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'ingame_sel')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'main_page_back_race')
-
-    # # def _do_screenshots_end(self):
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'exit_page')
-    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
-    # #     self._screenshot(FunctionalTest.screenshot_time, 'exit_page_sel')
-    # #     self._verify()
-    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
-    # #     self._exit()
-
-    # def _do_screenshots(self, idx):
-    #     [self._do_screenshots_1, self._do_screenshots_2][int(idx) - 1]()
diff --git a/ya2/engine/gui/__init__.py b/ya2/engine/gui/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/ya2/engine/gui/cursor.py b/ya2/engine/gui/cursor.py
deleted file mode 100644 (file)
index 9a8680a..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-from panda3d.core import GraphicsWindow, WindowProperties
-from ya2.lib.p3d.gui import P3dImg as Img
-from ya2.gameobject import GameObject
-
-
-class MouseCursorFacade:
-
-    def show(self):
-        if not self.eng.cfg.dev_cfg.functional_test:
-            return self.cursor_img.show()
-    def hide(self): return self.cursor_img.hide()
-
-
-class MouseCursor(GameObject, MouseCursorFacade):
-
-    def __init__(self, filepath, scale, color, hotspot):
-        GameObject.__init__(self)
-        MouseCursorFacade.__init__(self)
-        if not filepath: return
-        self.__set_std_cursor(False)
-        self.cursor_img = Img(filepath, scale=scale, foreground=True)
-        self.cursor_img.img.set_color(color)
-        #if self.eng.cfg.dev_cfg.functional_test:
-        #    self.cursor_img.hide()
-        self.hotspot_dx = scale[0] * (1 - 2 * hotspot[0])
-        self.hotspot_dy = scale[2] * (1 - 2 * hotspot[1])
-        #self.eng.attach_obs(self.on_frame)
-        #self.eng.attach_obs(self.on_frame_unpausable)
-        self._tsk = taskMgr.add(self.__on_frame, 'on frame cursor')
-
-    @staticmethod
-    def __set_std_cursor(show):
-        props = WindowProperties()
-        props.set_cursor_hidden(not show)
-        if isinstance(base.win, GraphicsWindow):
-            base.win.requestProperties(props)
-
-    #def show_standard(self): self.eng.lib.show_std_cursor()
-
-    #def hide_standard(self): self.eng.lib.hide_std_cursor()
-
-    #def cursor_top(self):
-    #    self.cursor_img.reparent_to(self.cursor_img.parent)
-
-    def __on_frame(self, task):
-        mwn = base.mouseWatcherNode
-        if not mwn or not mwn.hasMouse():
-            return task.again
-        mouse = mwn.get_mouse_x(), mwn.get_mouse_y()
-        h_x = mouse[0] * base.getAspectRatio() + self.hotspot_dx
-        self.cursor_img.set_pos((h_x, mouse[1] - self.hotspot_dy))
-        return task.again
-
-    #def on_frame(self):
-    #    if not self.eng.pause.paused: self.__on_frame()
-
-    #def on_frame_unpausable(self):
-    #    if self.eng.pause.paused: self.__on_frame()
-
-    def set_image(self, img):
-        self.cursor_img.img.set_texture(loader.load_texture(img), 1)
-
-    def destroy(self):
-        taskMgr.remove(self._tsk)
-        self.cursor_img.destroy()
diff --git a/ya2/engine/lang.py b/ya2/engine/lang.py
deleted file mode 100644 (file)
index 4c2074d..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-from logging import info
-from os.path import join, exists, dirname
-from gettext import translation
-from pathlib import Path
-from ya2.gameobject import GameObject
-import sys
-
-
-def is_runtime(): return not exists('main.py')
-
-
-def is_appimage():
-    par_path = str(Path(__file__).parent.absolute())
-    is_appimage = par_path.startswith('/tmp/.mount_Pmachines')
-    return is_appimage and par_path.endswith('/usr/bin')
-
-
-def curr_path():
-    if not is_runtime(): return ''
-    if sys.platform == 'darwin':
-        return dirname(__file__) + '/../Resources/'
-    # return dirname(__file__)
-    par_path = str(Path(__file__).parent.absolute())
-    if is_appimage():
-        return str(Path(par_path).absolute())
-    is_snap = par_path.startswith('/snap/')
-    is_snap = is_snap and par_path.endswith('/x1')
-    if is_snap:
-        return str(Path(par_path).absolute())
-    #return getcwd()
-    curr_path = dirname(__file__)
-    info('current path: %s' % curr_path)
-    return curr_path + '/'
-
-
-class LangMgr(GameObject):
-
-    def __init__(self, lang, domain, dpath):
-        GameObject.__init__(self)
-        self.lang = lang
-        self.domain = domain
-        self.dpath = join(curr_path(), dpath)
-        info('language: %s, %s' % (self.domain, self.dpath))
-        self.set_lang(lang)
-
-    @property
-    def lang_codes(self):
-        return [lang[1] for lang in self.eng.cfg.lang_cfg.languages]
-
-    def set_lang(self, lang):
-        self.lang = lang
-        args = lang, self.domain, self.dpath
-        info('setting language %s, %s, %s' % args)
-        tra = translation(self.domain, self.dpath, [lang], fallback=True)
-        tra.install()
diff --git a/ya2/engine/log.py b/ya2/engine/log.py
deleted file mode 100755 (executable)
index e2c80fd..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-from logging import basicConfig, info, INFO, DEBUG, getLogger
-from configparser import ConfigParser
-from sys import platform, argv
-from platform import system
-from pathlib import Path
-from glob import glob
-from json import load, dumps
-#from datetime import datetime
-from pprint import pprint
-from os import getcwd, environ
-from os.path import exists, dirname
-from traceback import print_stack
-from sys import version_info
-# from platform import system, release, architecture, platform, processor, \
-#     version, machine
-# from multiprocessing import cpu_count
-from panda3d.core import Filename, GraphicsWindow, PandaSystem
-from panda3d.bullet import get_bullet_version
-from ya2.gameobject import Colleague
-from ya2.lib.p3d.p3d import LibP3d
-import sys
-
-
-lev = INFO
-opt_path = ''
-if platform in ['win32', 'linux'] and not exists('main.py'):
-    # it is the deployed version for windows
-    opt_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
-opath = LibP3d.fixpath(opt_path + '/options.ini') if opt_path else \
-        'options.ini'
-if exists(opath):
-    with open(opath) as json_file:
-        #optfile = load(json_file)
-        optfile = ConfigParser()
-        optfile.read(opath)
-        # optfile['development']['verbose'] and int(optfile['development']['verbose']) or \
-        if optfile['development']['verbose_log'] and int(optfile['development']['verbose_log']):
-            lev = DEBUG
-
-basicConfig(level=lev, format='%(asctime)s %(message)s', datefmt='%H:%M:%S')
-getLogger().setLevel(lev)  # it doesn't work otherwise
-
-
-class LogMgrBase(Colleague):  # headless log manager
-
-    @staticmethod
-    def init_cls():
-        return LogMgr if base.win else LogMgrBase
-
-    def __init__(self, mediator):
-        Colleague.__init__(self, mediator)
-        self.log_cfg()
-
-    def log(self, msg, verbose=False):
-        if verbose and not self.eng.cfg.dev_cfg.verbose_log: return
-        info(msg)
-
-    @property
-    def is_appimage(self):
-        par_path = str(Path(__file__).parent.absolute())
-        is_appimage = par_path.startswith('/tmp/.mount_Pmachi')
-        return is_appimage and par_path.endswith('/usr/bin')
-
-    # @property
-    # def curr_path(self):
-    #     # this is different from the music's one since it does not work
-    #     # with the version in windows
-    #     if sys.platform == 'darwin':
-    #         return dirname(__file__) + '/../Resources/'
-    #     # return dirname(__file__)
-    #     par_path = str(Path(__file__).parent.absolute())
-    #     if self.is_appimage:
-    #         return str(Path(par_path).absolute())
-    #     is_snap = par_path.startswith('/snap/')
-    #     is_snap = is_snap and par_path.endswith('/x1')
-    #     if is_snap:
-    #         return str(Path(par_path).absolute())
-    #     #return getcwd()
-    #     #curr_path = dirname(__file__)
-    #     curr_path = str(Path(__file__).parent.parent.parent.absolute())
-    #     info('current path: %s' % curr_path)
-    #     return curr_path
-
-    @property
-    def curr_path(self):
-        if system() == 'Windows':
-            return ''
-        if exists('main.py'):
-            return ''
-        else:
-            par_path = str(Path(__file__).parent.absolute())
-        if self.is_appimage:
-            par_path = str(Path(par_path).absolute())
-        par_path += '/'
-        return par_path
-
-    @property
-    def build_version(self):
-        appimg_mnt = glob('/tmp/.mount_Pmachi*')
-        if appimg_mnt:
-            #with open(appimg_mnt[0] + '/usr/bin/appimage_version.txt') as fver:
-            with open(self.curr_path + 'assets/bld_version.txt') as fver:
-                return fver.read().strip()
-        try:
-            with open(self.curr_path + 'assets/bld_version.txt') as fver:
-                return fver.read().strip()
-        except FileNotFoundError:
-            info('not found ' + self.curr_path + 'assets/bld_version.txt')
-            return 'notfound'
-
-    def log_cfg(self):
-        if '--version' in argv:
-            path = str(Filename.get_user_appdata_directory())
-            home = '/home/flavio'  # we must force this for wine
-            if path.startswith('/c/users/') and exists(home + '/.wine/'):
-                path = home + '/.wine/drive_' + path[1:]
-            info('writing %s' % path + '/pmachines/obs_version.txt')
-            with open(path + '/pmachines/obs_version.txt', 'w') as f:
-                #f.write(self.eng.logic.version)
-                f.write(self.build_version)
-            if not platform.startswith('win'):
-                from os import ttyname  # here because it doesn't work on windows
-                import sys
-                try:
-                    with open(ttyname(0), 'w') as fout:
-                        sys.stdout = fout
-                        print('version: ' + self.build_version)  # self.eng.logic.version)
-                except OSError:  # it doesn't work with crontab
-                    print('version: ' + self.build_version)
-        messages = ['version: ' + self.build_version]  # self.eng.logic.version]
-        messages += ['argv[0]: %s' % argv[0]]
-        messages += ['getcwd: %s' % getcwd()]
-        messages += ['__file__: %s' % __file__]
-        for elm in environ.items():
-            messages += ['env::%s: %s' % elm]
-        # os_info = (system(), release(), version())
-        # messages += ['operative system: %s %s %s' % os_info]
-        # messages += ['architecture: ' + str(architecture())]
-        # messages += ['machine: ' + machine()]
-        # messages += ['platform: ' + platform()]
-        # messages += ['processor: ' + processor()]
-        # try:
-        #     messages += ['cores: ' + str(cpu_count())]
-        # except NotImplementedError:  # on Windows
-        #     messages += ['cores: not implemented']
-        lib_ver = PandaSystem.get_version_string()
-        try:
-            import psutil
-            mem = psutil.virtual_memory().total / 1000000000.0
-            messages += ['memory: %s GB' % round(mem, 2)]
-        except ImportError: info("can't import psutil")  # windows
-        lib_commit = PandaSystem.get_git_commit()
-        py_ver = [str(elm) for elm in version_info[:3]]
-        messages += ['python version: %s' % '.'.join(py_ver)]
-        messages += ['panda version: %s %s' % (lib_ver, lib_commit)]
-        messages += ['bullet version: ' + str(get_bullet_version())]
-        messages += ['appdata: ' + str(Filename.get_user_appdata_directory())]
-        if base.win and isinstance(base.win, GraphicsWindow):  # not headless
-            print(base.win.get_keyboard_map())
-        list(map(self.log, messages))
-
-    @staticmethod
-    def log_tasks():
-        info('tasks: %s' % taskMgr.getAllTasks())
-        info('do-laters: %s' % taskMgr.getDoLaters())
-
-    @staticmethod
-    def plog(obj):
-        print('\n\n')
-        print_stack()
-        pprint(obj)
-        print('\n\n')
-
-
-class LogMgr(LogMgrBase):
-
-    def log_cfg(self):
-        LogMgrBase.log_cfg(self)
-        messages = [base.win.get_gsg().get_driver_vendor()]
-        messages += [base.win.get_gsg().get_driver_renderer()]
-        shad_maj = base.win.get_gsg().get_driver_shader_version_major()
-        shad_min = base.win.get_gsg().get_driver_shader_version_minor()
-        messages += ['shader: {maj}.{min}'.format(maj=shad_maj, min=shad_min)]
-        messages += [base.win.get_gsg().get_driver_version()]
-        drv_maj = base.win.get_gsg().get_driver_version_major()
-        drv_min = base.win.get_gsg().get_driver_version_minor()
-        drv = 'driver version: {maj}.{min}'
-        messages += [drv.format(maj=drv_maj, min=drv_min)]
-        fullscreen = None
-        if isinstance(base.win, GraphicsWindow):
-            fullscreen = base.win.get_properties().get_fullscreen()
-        messages += ['fullscreen: ' + str(fullscreen)]
-        def resolution():
-            if not isinstance(base.win, GraphicsWindow):
-                return 800, 600
-            win_prop = base.win.get_properties()
-            return win_prop.get_x_size(), win_prop.get_y_size()
-        res_x, res_y = resolution()
-        res_tmpl = 'resolution: {res_x}x{res_y}'
-        messages += [res_tmpl.format(res_x=res_x, res_y=res_y)]
-        list(map(self.log, messages))
diff --git a/ya2/gameobject.py b/ya2/gameobject.py
deleted file mode 100644 (file)
index 82be1ca..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-from direct.fsm.FSM import FSM
-from direct.showbase.DirectObject import DirectObject
-from ya2.observer import Subject
-
-
-class Colleague(Subject):
-
-    eng = None
-
-    def __init__(self, mediator):
-        Subject.__init__(self)
-        self.mediator = mediator  # refactor: remove it
-
-    def destroy(self):
-        self.mediator = None
-        Subject.destroy(self)
-
-
-class FsmColleague(FSM, Colleague):
-
-    def __init__(self, mediator):
-        FSM.__init__(self, self.__class__.__name__)
-        Colleague.__init__(self, mediator)
-
-    def destroy(self):
-        if self.state: self.cleanup()
-        Colleague.destroy(self)
-
-
-class EventColleague(Colleague, DirectObject):
-
-    def destroy(self):
-        self.ignoreAll()
-        Colleague.destroy(self)
-
-
-class AudioColleague(Colleague): pass
-
-
-class AiColleague(Colleague): pass
-
-
-class GfxColleague(Colleague): pass
-
-
-class GuiColleague(Colleague): pass
-
-
-class LogicColleague(Colleague):
-
-    def on_start(self): pass
-
-
-class PhysColleague(Colleague): pass
-
-
-class GODirector:
-
-    def __init__(self, tgt_obj, init_lst, end_cb):
-        self.__obj = tgt_obj
-        tgt_obj.attach(self.on_comp_blt)
-        self.end_cb = end_cb
-        self.completed = [False for _ in init_lst]
-        self.pending = {}
-        self.__init_lst = init_lst
-        for idx, _ in enumerate(init_lst): self.__process_lst(tgt_obj, idx)
-
-    def __process_lst(self, obj, idx):
-        if not self.__init_lst[idx]:
-            self.end_lst(idx)
-            return
-        comp_info = self.__init_lst[idx].pop(0)
-        attr_name, cls, arguments = comp_info
-        self.pending[cls.__name__] = idx
-        setattr(obj, attr_name, cls(*arguments))
-
-    def on_comp_blt(self, obj):
-        self.__process_lst(obj.mediator, self.pending[obj.__class__.__name__])
-
-    def end_lst(self, idx):
-        self.completed[idx] = True
-        if all(self.completed):
-            if self.end_cb: self.end_cb()
-            self.destroy()
-
-    def destroy(self):
-        self.__obj.detach(self.on_comp_blt)
-        self.__obj = self.end_cb = self.__init_lst = None
-
-
-class GameObject(Subject): pass
diff --git a/ya2/lib/__init__.py b/ya2/lib/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/ya2/lib/p3d/__init__.py b/ya2/lib/p3d/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/ya2/lib/p3d/gfx.py b/ya2/lib/p3d/gfx.py
deleted file mode 100755 (executable)
index 56f6a87..0000000
+++ /dev/null
@@ -1,369 +0,0 @@
-import datetime
-from logging import debug, info
-from os import getcwd
-from os.path import exists, dirname
-from panda3d.core import get_model_path, AntialiasAttrib, PandaNode, \
-    LightRampAttrib, Camera, OrthographicLens, NodePath, OmniBoundingVolume, \
-    AmbientLight as P3DAmbientLight, Spotlight as P3DSpotlight, Point2, \
-    Point3, Texture
-from direct.filter.CommonFilters import CommonFilters
-from direct.actor.Actor import Actor
-# from ya2.lib.p3d.p3d import LibP3d
-
-
-def set_srgb(model):
-    for texture in model.find_all_textures():
-        if texture.get_format() in [Texture.F_rgba, Texture.F_rgbm]:
-            texture.set_format(Texture.F_srgb_alpha)
-        elif texture.get_format() in [Texture.F_rgb]:
-            texture.set_format(Texture.F_srgb)
-
-
-# class RenderToTexture:
-
-#     def __init__(self, size=(256, 256)):
-#         self.__set_buffer(size)
-#         self.__set_display_region()
-#         self.__set_camera()
-#         self.__set_root()
-#         self.display_region.set_camera(self.camera)
-
-#     def __set_buffer(self, size):
-#         self.buffer = base.win.make_texture_buffer('result buffer', size[0],
-#                                                    size[1])
-#         self.buffer.set_sort(-100)
-
-#     def __set_display_region(self):
-#         self.display_region = self.buffer.make_display_region()
-#         self.display_region.set_sort(20)
-
-#     def __set_camera(self):
-#         self.camera = NodePath(Camera('camera 2d'))
-#         lens = OrthographicLens()
-#         lens.set_film_size(1, 1)
-#         lens.set_near_far(-1000, 1000)
-#         self.camera.node().set_lens(lens)
-
-#     def __set_root(self):
-#         self.root = NodePath('root')
-#         self.root.set_depth_test(False)
-#         self.root.set_depth_write(False)
-#         self.camera.reparent_to(self.root)
-
-#     @property
-#     def texture(self): return self.buffer.get_texture()
-
-#     def destroy(self):
-#         base.graphicsEngine.remove_window(self.buffer)
-#         if base.win:  # if you close the window during a race
-#             base.win.remove_display_region(self.display_region)
-#         list(map(lambda node: node.remove_node(), [self.camera, self.root]))
-
-
-class P3dGfxMgr:
-
-    def __init__(self, model_path, antialiasing, shaders, srgb):
-        self.root = P3dNode(render)
-        self.__srgb = srgb
-        self.callbacks = {}
-        self.filters = None
-        get_model_path().append_directory(model_path)
-        if LibP3d.runtime():
-            root_dir = LibP3d.p3dpath(dirname(__file__))
-            paths = [root_dir + '/' + model_path, root_dir]
-            list(map(get_model_path().append_directory, paths))
-        render.set_shader_auto()
-        # render.set_two_sided(True)  # it breaks shadows
-        if antialiasing: render.set_antialias(AntialiasAttrib.MAuto)
-        if shaders and base.win:
-            self.filters = CommonFilters(base.win, base.cam)
-
-    def load_model(self, filename, callback=None, anim=None):
-        ext = '.bam' if exists(filename + '.bam') else ''
-        if anim:
-            anim_dct = {'anim': filename + '-Anim' + ext}
-            node = P3dNode(self.set_srgb(Actor(filename + ext, anim_dct)))
-        elif callback:
-            callb = lambda model: callback(P3dNode(self.set_srgb(model)))
-            node = loader.loadModel(filename + ext, callback=callb)
-        else:
-            node = P3dNode(self.set_srgb(
-                loader.loadModel(LibP3d.p3dpath(filename + ext))))
-        return node
-
-    def set_srgb(self, model):
-        if self.__srgb:
-            for texture in model.find_all_textures():
-                if texture.get_format() in [Texture.F_rgba, Texture.F_rgbm]:
-                    texture.set_format(Texture.F_srgb_alpha)
-                elif texture.get_format() in [Texture.F_rgb]:
-                    texture.set_format(Texture.F_srgb)
-        return model
-
-    @staticmethod
-    def toggle_aa():
-        aa_not_none = render.get_antialias() != AntialiasAttrib.MNone
-        if render.has_antialias() and aa_not_none:
-            render.clear_antialias()
-        else: render.set_antialias(AntialiasAttrib.MAuto, 1)
-
-    def set_toon(self):
-        tmp_node = NodePath(PandaNode('temp node'))
-        tmp_node.set_attrib(LightRampAttrib.make_single_threshold(.5, .4))
-        tmp_node.set_shader_auto()
-        base.cam.node().set_initial_state(tmp_node.get_state())
-        self.filters.set_cartoon_ink(separation=1)
-
-    def set_bloom(self):
-        if not base.win: return
-        self.filters.setBloom(
-            blend=(.3, .4, .3, 0), mintrigger=.6, maxtrigger=1.0, desat=.6,
-            intensity=1.0, size='medium')
-        # default: (.3, .4, .3, 0), .6, 1, .6, 1, 'medium'
-
-    @staticmethod
-    def pos2d(node):
-        p3d = base.cam.get_relative_point(node.node, Point3(0, 0, 0))
-        p2d = Point2()
-        return p2d if base.camLens.project(p3d, p2d) else None
-
-    @staticmethod
-    def screen_coord(pos):
-        new_node = NodePath('temp')
-        new_node.set_pos(pos)
-        coord3d = new_node.get_pos(base.cam)
-        new_node.remove_node()
-        coord2d = Point2()
-        base.camLens.project(coord3d, coord2d)
-        coord_r2d = Point3(coord2d[0], 0, coord2d[1])
-        coord_a2d = base.aspect2d.get_relative_point(render2d, coord_r2d)
-        return coord_a2d[0], coord_a2d[2]
-
-    @staticmethod
-    def world_from_to(pos):
-        p_from, p_to = Point3(), Point3()    # in camera coordinates
-        base.camLens.extrude(pos, p_from, p_to)
-        p_from = render.get_relative_point(base.cam, p_from)  # global coords
-        p_to = render.get_relative_point(base.cam, p_to)  # global coords
-        return p_from, p_to
-
-    @property
-    def shader_support(self):
-        return base.win.get_gsg().get_supports_basic_shaders()
-
-    def screenshot(self, path=None):
-        time = datetime.datetime.now().strftime('%y%m%d%H%M%S')
-        #res = base.win.save_screenshot(Filename(path or ("yocto%s.png" % time)))
-        #debug('screenshot %s (%s)' % (path or ("yocto%s.png" % time), res))
-        res = base.screenshot(path or ("pmachines%s.png" % time), False)
-        info('screenshot %s (%s; %s)' % (path or ("pmachines%s.png" % time), res, getcwd()))
-
-    @staticmethod
-    def enable_shader(): render.set_shader_auto()
-
-    @staticmethod
-    def disable_shader(): render.set_shader_off()
-
-    @staticmethod
-    def print_stats(two_d=True, three_d=True, analyze=True, ls=True):
-        '''Print graphics stats. They use standard output (from p3d).'''
-        info = []
-        if two_d and analyze:
-            info +=[('render2d.analyze', base.render2d.analyze)]
-        if three_d and analyze:
-            info +=[('render.analyze', base.render.analyze)]
-        if two_d and ls:
-            info +=[('render2d.ls', base.render2d.ls)]
-        if three_d and ls:
-            info +=[('render.ls', base.render.ls)]
-        for elm in info:
-            print('\n\n#####\n%s()' % elm[0])
-            elm[1]()
-
-
-# class P3dNode:
-
-#     def __init__(self, nodepath):
-#         self.nodepath = nodepath
-#         self.node.set_python_tag('libnode', self)
-
-#     def set_collide_mask(self, mask): return self.node.set_collide_mask(mask)
-#     def set_x(self, val): return self.node.set_x(val)
-#     def set_y(self, val): return self.node.set_y(val)
-#     def set_z(self, val): return self.node.set_z(val)
-#     def set_hpr(self, val): return self.node.set_hpr(val)
-#     def set_h(self, val): return self.node.set_h(val)
-#     def set_p(self, val): return self.node.set_p(val)
-#     def set_r(self, val): return self.node.set_r(val)
-#     def set_scale(self, val): return self.node.set_scale(val)
-#     def set_transparency(self, val): return self.node.set_transparency(val)
-#     def set_alpha_scale(self, val): return self.node.set_alpha_scale(val)
-#     def set_texture(self, texturestage, texture):
-#         return self.node.set_texture(texturestage, texture)
-#     def has_tag(self, name): return self.node.has_tag(name)
-#     def get_tag(self, name): return self.node.get_tag(name)
-#     def get_python_tag(self, name): return self.node.get_python_tag(name)
-#     def remove_node(self): return self.node.remove_node()
-#     def flatten_strong(self): return self.node.flatten_strong()
-#     def clear_model_nodes(self): return self.node.clear_model_nodes()
-#     def show(self): return self.node.show()
-#     def set_depth_offset(self, val): return self.node.set_depth_offset(val)
-#     def loop(self, val): return self.node.loop(val)
-#     def cleanup(self): return self.node.cleanup()
-#     def write_bam_file(self, fname): return self.node.write_bam_file(fname)
-
-#     def attach_node(self, name):
-#         return P3dNode(self.node.attach_new_node(name))
-
-#     def add_shape(self, shape):
-#         return self.node.node().add_shape(shape._mesh_shape)
-#         #TODO: don't access a protected member
-
-#     @property
-#     def name(self): return self.node.get_name()
-
-#     @property
-#     def node(self): return self.nodepath
-
-#     @property
-#     def p3dnode(self): return self.node.node()
-
-#     def set_pos(self, pos): return self.node.set_pos(pos._vec)
-#         #TODO: don't access a protected member
-
-#     def get_pos(self, other=None):
-#         return self.node.get_pos(* [] if other is None else [other.node])
-
-#     @property
-#     def x(self): return self.node.get_x()
-
-#     @property
-#     def y(self): return self.node.get_y()
-
-#     @property
-#     def z(self): return self.node.get_z()
-
-#     @property
-#     def hpr(self): return self.node.get_hpr()
-
-#     @property
-#     def h(self): return self.node.get_h()
-
-#     @property
-#     def p(self): return self.node.get_p()
-
-#     @property
-#     def r(self): return self.node.get_r()
-
-#     @property
-#     def scale(self): return self.node.get_scale()
-
-#     @property
-#     def is_empty(self): return self.node.is_empty()
-
-#     def get_relative_vector(self, node, vec):
-#         return self.node.get_relative_vector(node.node, vec)
-
-#     def set_material(self, mat): return self.node.set_material(mat, 1)
-
-#     def set_python_tag(self, name, val):
-#         return self.node.set_python_tag(name, val)
-
-#     def get_distance(self, other_node):
-#         return self.node.get_distance(other_node.node)
-
-#     def reparent_to(self, parent): return self.node.reparent_to(parent.node)
-
-#     def wrt_reparent_to(self, parent):
-#         return self.node.wrt_reparent_to(parent.node)
-
-#     @staticmethod
-#     def __get_pandanode(nodepath):
-#         if nodepath.has_python_tag('libnode'):
-#             return nodepath.get_python_tag('libnode')
-#         return P3dNode(nodepath)
-
-#     def find_all_matches(self, name):
-#         nodes = self.node.find_all_matches(name)
-#         return [self.__get_pandanode(node) for node in nodes]
-
-#     def find(self, name):
-#         model = self.node.find(name)
-#         if model: return self.__get_pandanode(model)
-
-#     def optimize(self):
-#         self.node.prepare_scene(base.win.get_gsg())  # crash with texture.set_format
-#         self.node.premunge_scene(base.win.get_gsg())
-
-#     def hide(self, mask=None):
-#         return self.node.hide(*[] if mask is None else [mask])
-
-#     @property
-#     def tight_bounds(self): return self.node.get_tight_bounds()
-
-#     @property
-#     def parent(self): return self.node.get_parent()
-
-#     @property
-#     def children(self): return self.node.get_children()
-
-#     def destroy(self): return self.node.remove_node()
-
-
-# class P3dAnimNode:
-
-#     def __init__(self, filepath, anim_dct):
-#         self.node = Actor(filepath, anim_dct)
-
-#     def loop(self, val): return self.node.loop(val)
-
-#     def reparent_to(self, node): self.node.reparent_to(node)
-
-#     @property
-#     def name(self): return self.node.get_name()
-
-#     def optimize(self):
-#         self.node.prepare_scene(base.win.get_gsg())
-#         self.node.premunge_scene(base.win.get_gsg())
-
-#     def set_omni(self):
-#         self.node.node().set_bounds(OmniBoundingVolume())
-#         self.node.node().set_final(True)
-
-#     def destroy(self): self.node.cleanup()
-
-
-# class P3dAmbientLight:
-
-#     def __init__(self, color):
-#         ambient_lgt = P3DAmbientLight('ambient light')
-#         ambient_lgt.set_color(color)
-#         self.ambient_np = render.attach_new_node(ambient_lgt)
-#         render.set_light(self.ambient_np)
-
-#     def destroy(self):
-#         render.clear_light(self.ambient_np)
-#         self.ambient_np.remove_node()
-
-
-# class P3dSpotlight:
-
-#     def __init__(self, mask=None):
-#         self.spot_lgt = render.attach_new_node(P3DSpotlight('spot'))
-#         snode = self.spot_lgt.node()
-#         snode.set_scene(render)
-#         snode.set_shadow_caster(True, 1024, 1024)
-#         snode.get_lens().set_fov(40)
-#         snode.get_lens().set_near_far(20, 200)
-#         if mask: snode.set_camera_mask(mask)
-#         render.set_light(self.spot_lgt)
-
-#     def set_pos(self, pos): return self.spot_lgt.set_pos(*pos)
-
-#     def look_at(self, pos): return self.spot_lgt.look_at(*pos)
-
-#     def set_color(self, color): return self.spot_lgt.set_color(*color)
-
-#     def destroy(self):
-#         render.clear_light(self.spot_lgt)
-#         self.spot_lgt.remove_node()
diff --git a/ya2/lib/p3d/gui.py b/ya2/lib/p3d/gui.py
deleted file mode 100755 (executable)
index 5e6a214..0000000
+++ /dev/null
@@ -1,405 +0,0 @@
-from inspect import getmro
-from panda3d.core import TextNode, Texture
-from direct.gui.DirectGuiGlobals import FLAT, ENTER, EXIT, DISABLED, NORMAL, \
-    B1PRESS
-from direct.showbase.DirectObject import DirectObject
-from direct.gui.DirectButton import DirectButton
-from direct.gui.DirectCheckButton import DirectCheckButton
-from direct.gui.DirectOptionMenu import DirectOptionMenu
-from direct.gui.OnscreenImage import OnscreenImage
-from direct.gui.DirectSlider import DirectSlider
-from direct.gui.DirectEntry import DirectEntry, ENTRY_FOCUS_STATE
-from direct.gui.DirectLabel import DirectLabel
-from direct.gui.DirectFrame import DirectFrame
-from direct.gui.OnscreenText import OnscreenText
-from direct.gui.DirectScrolledFrame import DirectScrolledFrame
-from ya2.observer import Subject
-# from ya2.lib.ivals import Seq, Wait, PosIval, Func
-
-
-class CommonBase:
-
-    def set_widget(self):
-        from ya2.lib.gui import Frame, Slider, Btn, Label, OptionMenu, \
-            CheckBtn, Entry, Img, Text, ScrolledFrame
-        from ya2.lib.p3d.widget import FrameMixin, SliderMixin, BtnMixin, \
-            OptionMenuMixin, CheckBtnMixin, EntryMixin, ImgMixin, \
-            ScrolledFrameMixin
-        self.__class__ = self.__class__  # for pylint
-        libwdg2wdg = {
-            FrameMixin: [Frame],
-            ScrolledFrameMixin: [ScrolledFrame],
-            SliderMixin: [Slider],
-            BtnMixin: [Btn, Label],
-            OptionMenuMixin: [OptionMenu],
-            CheckBtnMixin: [CheckBtn],
-            EntryMixin: [Entry],
-            ImgMixin: [Img, Text]}
-        for libwdg, wdgcls in libwdg2wdg.items():
-            if any(cls in getmro(self.__class__) for cls in wdgcls):
-                par_cls = libwdg
-        clsname = self.__class__.__name__ + 'Widget'
-        self.__class__ = type(clsname, (self.__class__, par_cls), {})
-        self.init(self)
-        if not hasattr(self, 'bind'): return
-        bind_args = [(ENTER, self.on_wdg_enter), (EXIT, self.on_wdg_exit)]
-        list(map(lambda args: self.bind(*args), bind_args))
-
-    def set_enter_transition(self):
-        start_pos = self.get_pos()
-        pos = self.pos - (3.6, 0)
-        self.set_pos((pos.x, 1, pos.y))
-        Seq(
-            Wait(abs(pos.y - 1) / 4),
-            PosIval(self.get_np(), .5, start_pos)
-        ).start()
-
-    def set_exit_transition(self, destroy):
-        start_pos = self.get_pos()
-        end_pos = (self.pos.x + 3.6, 1, self.pos.y)
-        seq = Seq(
-            Wait(abs(self.pos.y - 1) / 4),
-            PosIval(self.get_np(), .5, end_pos),
-            Func(self.destroy if destroy else self.hide))
-        if not destroy: seq += Func(self.set_pos, start_pos)
-        seq.start()
-
-    def translate(self):
-        if hasattr(self, 'bind_transl'): self.wdg['text'] = self.bind_transl
-
-
-class P3dImg(CommonBase):
-
-    def __init__(self, filepath, pos=(0, 0), scale=1.0, background=False,
-                 foreground=False, parent=None):
-        self.img = OnscreenImage(
-            filepath, pos=(pos[0], 1, pos[1]), scale=scale, parent=parent)
-        if background: self.img.set_bin('background', 10)
-        alpha_formats = [12]  # panda3d.core.texture.Frgba
-        if self.img.get_texture().get_format() in alpha_formats:
-            self.img.set_transparency(True)
-        if foreground: self.img.set_bin('gui-popup', 50)
-
-    def reparent_to(self, node): return self.img.reparent_to(node)
-    def show(self): return self.img.show()
-    def hide(self): return self.img.hide()
-    def set_shader(self, shader): return self.img.set_shader(shader)
-    def set_shader_input(self, name, val):
-        return self.img.set_shader_input(name, val)
-    def set_texture(self, texturestage, texture):
-        return self.img.set_texture(texturestage, texture)
-
-    def set_exit_transition(self, destroy):
-        start_pos = self.get_pos()
-        end_pos = (self.pos.x + 3.6, 1, self.pos.y)
-        seq = Seq(
-            Wait(abs(self.pos.y - 1) / 4),
-            PosIval(self.get_np(), .5, end_pos),
-            Func(self.destroy if destroy else self.hide))
-        if not destroy: seq += Func(self.set_pos, (start_pos[0], start_pos[2]))
-        seq.start()
-
-    def set_pos(self, pos): return self.img.set_pos(pos[0], 1, pos[1])
-
-    def get_pos(self, pos=None): return self.img.get_pos(*[pos] if pos else [])
-
-    @property
-    def parent(self): return self.img.get_parent()
-
-    @property
-    def hidden(self): return self.img.is_hidden()
-
-    def set_transparent(self): return self.img.set_transparency(True)
-
-    def destroy(self): self.img = self.img.destroy()
-
-
-# class P3dBase(CommonBase):
-
-#     def __init__(self, tra_src=None, tra_tra=None):
-#         # self.text_src_tra = None  # it breaks the gui
-#         if tra_src and tra_tra: self.bind_tra(tra_src, tra_tra)
-
-#     def set_pos(self, pos): return self.wdg.set_pos(pos)
-#     def show(self): return self.wdg.show()
-#     def hide(self): return self.wdg.hide()
-
-#     def bind_tra(self, text_src, text_transl):
-#         # text_transl is not used, anyway we need it since we have this kind of
-#         # use: self.bind_transl('example str', _('example str'))
-#         # this allows to change translations on the fly keeping the source
-#         # text for remapping it later
-#         # TODO: try reverse mapping? i.e. retrieve the src string from the
-#         # translated one
-#         self.text_src_tra = text_src
-#         self.text_tra_tra = text_transl
-#         tra = lambda self: _(self.text_tra_tra)
-#         self.__class__.bind_transl = property(tra)
-#         self['text'] = self.bind_transl
-
-#     def get_pos(self, pos=None):
-#         return self.wdg.get_pos(*[pos] if pos else [])
-
-#     def __setitem__(self, key, value): self.wdg[key] = value
-
-#     def __getitem__(self, key): return self.wdg[key]
-
-#     def get_np(self): return self.wdg
-
-#     @property
-#     def hidden(self): return self.wdg.is_hidden()
-
-#     def destroy(self): self.wdg.destroy()
-
-
-# class P3dAbs(P3dBase):
-
-#     def get_value(self): return self.wdg.getValue()
-#     def initialiseoptions(self): return self.wdg.initialiseoptions()
-#     def set_z(self, val): return self.wdg.set_z(val)
-#     def set_shader(self, shader): return self.wdg.set_shader(shader)
-#     def set_shader_input(self, name, val):
-#         return self.wdg.set_shader_input(name, val)
-#     def set_transparency(self, val): return self.wdg.set_transparency(val)
-#     def bind(self, evt, mth): return self.wdg.bind(evt, mth)
-
-#     def attachNewNode(self, gui_itm, sort_order):
-#         # it won't work if we name it attach_node. hopefully this will be
-#         # possible when we'll use decorators in place of mixins
-#         return self.wdg.attachNewNode(gui_itm, sort_order)
-
-#     @property
-#     def is_enabled(self): return self.wdg['state'] != DISABLED
-
-
-# class P3dBtn(P3dAbs):
-
-#     def __init__(
-#             self, text='', parent=None, pos=(0, 0), scale=(1, 1),
-#             cmd=None, frame_size=(-1, 1, -1, 1), click_snd=None,
-#             text_fg=(1, 1, 1, 1), frame_col=(1, 1, 1, 1), text_font=None,
-#             over_snd=None, extra_args=None, frame_texture=None, img=None,
-#             tra_src=None, tra_tra=None, text_scale=1.0):
-#         str2par = {'bottomcenter': base.a2dBottomCenter}
-#         parent = str2par.get(parent, parent)
-#         extra_args = extra_args or []
-#         self.wdg = DirectButton(
-#             text=text, parent=parent, pos=(pos[0], 1, pos[1]),
-#             scale=(scale[0], 1, scale[1]), command=cmd,
-#             frameSize=frame_size, clickSound=click_snd, text_fg=text_fg,
-#             frameColor=frame_col, text_font=text_font, rolloverSound=over_snd,
-#             extraArgs=extra_args, frameTexture=frame_texture, image=img,
-#             text_scale=text_scale)
-#         P3dAbs.__init__(self, tra_src, tra_tra)
-#         self['relief'] = FLAT
-#         args = [(ENTER, self._on_enter), (EXIT, self._on_exit)]
-#         list(map(lambda args: self.bind(*args), args))
-
-#     def _on_enter(self, pos): pass  # pos comes from mouse
-
-#     def _on_exit(self, pos): pass  # pos comes from mouse
-
-#     # we add these with the mixins
-#     # def enable(self): self['state'] = NORMAL
-
-#     # def disable(self): self['state'] = DISABLED
-
-
-# class P3dSlider(P3dAbs):
-
-#     def __init__(
-#             self, parent=None, pos=(0, 0), scale=1, val=0,
-#             frame_col=(1, 1, 1, 1), thumb_frame_col=(1, 1, 1, 1),
-#             cmd=None, range_=(0, 1), tra_src=None, tra_tra=None):
-#         self.wdg = DirectSlider(
-#             parent=parent, pos=(pos[0], 1, pos[1]), scale=scale, value=val,
-#             frameColor=frame_col, thumb_frameColor=thumb_frame_col,
-#             command=cmd, range=range_)
-#         P3dAbs.__init__(self, tra_src, tra_tra)
-
-
-# class P3dCheckBtn(P3dAbs):
-
-#     def __init__(
-#             self, pos=(0, 0), text='', indicator_val=False,
-#             indicator_frame_col=(1, 1, 1, 1), frame_col=(1, 1, 1, 1),
-#             scale=(1, 1, 1), click_snd=None, over_snd=None,
-#             text_fg=(1, 1, 1, 1), text_font=None, cmd=None, tra_src=None,
-#             tra_tra=None):
-#         self.wdg = DirectCheckButton(
-#             pos=(pos[0], 1, pos[1]), text=text, indicatorValue=indicator_val,
-#             indicator_frameColor=indicator_frame_col,
-#             frameColor=frame_col, scale=scale, clickSound=click_snd,
-#             rolloverSound=over_snd, text_fg=text_fg, text_font=text_font,
-#             command=cmd)
-#         P3dAbs.__init__(self, tra_src, tra_tra)
-
-
-# class P3dOptionMenu(P3dAbs):
-
-#     def __init__(
-#             self, text='', items=None, pos=(0, 0), scale=(1, 1, 1),
-#             initialitem='', cmd=None, frame_size=(-1, 1, -1, 1),
-#             click_snd=None, over_snd=None, text_may_change=False,
-#             text_fg=(1, 1, 1, 1), item_frame_col=(1, 1, 1, 1),
-#             frame_col=(1, 1, 1, 1), highlight_col=(1, 1, 1, 1),
-#             text_scale=.05, popup_marker_col=(1, 1, 1, 1),
-#             item_relief=None, item_text_font=None, text_font=None,
-#             tra_src=None, tra_tra=None):
-#         items = items or []
-#         self.wdg = DirectOptionMenu(
-#             text=text, items=items, pos=(pos[0], 1, pos[1]), scale=scale,
-#             initialitem=initialitem, command=cmd, frameSize=frame_size,
-#             clickSound=click_snd, rolloverSound=over_snd,
-#             textMayChange=text_may_change, text_fg=text_fg,
-#             item_frameColor=item_frame_col, frameColor=frame_col,
-#             highlightColor=highlight_col, text_scale=text_scale,
-#             popupMarker_frameColor=popup_marker_col,
-#             item_relief=item_relief, item_text_font=item_text_font,
-#             text_font=text_font)
-#         P3dAbs.__init__(self, tra_src, tra_tra)
-
-#     def set(self, idx, f_cmd=1): return self.wdg.set(idx, f_cmd)
-
-#     @property
-#     def curr_val(self): return self.wdg.get()
-
-#     @property
-#     def curr_idx(self): return self.wdg.selectedIndex
-
-
-# class P3dEntry(P3dAbs, DirectObject, Subject):
-
-#     def __init__(
-#             self, scale=.05, pos=(0, 0), entry_font=None, width=12,
-#             frame_col=(1, 1, 1, 1), initial_text='', obscured=False,
-#             cmd=None, focus_in_cmd=None, focus_in_args=None,
-#             focus_out_cmd=None, focus_out_args=None, parent=None,
-#             tra_src=None, tra_tra=None, text_fg=(1, 1, 1, 1), on_tab=None,
-#             on_click=None):
-#         self.__focused = False
-#         self.__focus_in_cmd = focus_in_cmd
-#         self.__focus_out_cmd = focus_out_cmd
-#         DirectObject.__init__(self)
-#         Subject.__init__(self)
-#         focus_in_args = focus_in_args or []
-#         focus_out_args = focus_out_args or []
-#         self.wdg = DirectEntry(
-#             scale=scale, pos=(pos[0], 1, pos[1]), entryFont=entry_font,
-#             width=width, frameColor=frame_col, initialText=initial_text,
-#             obscured=obscured, command=cmd, focusInCommand=self._focus_in_cmd,
-#             focusInExtraArgs=focus_in_args,
-#             focusOutCommand=self._focus_out_cmd,
-#             focusOutExtraArgs=focus_out_args, parent=parent,
-#             text_fg=text_fg)
-#         P3dAbs.__init__(self, tra_src, tra_tra)
-#         if on_tab:
-#             self.on_tab_cb = on_tab
-#             self.accept('tab-up', self.on_tab)
-#         if on_click: self.wdg.bind(B1PRESS, on_click)
-
-#     def set(self, txt): return self.wdg.set(txt)
-
-#     def _focus_in_cmd(self, *args):
-#         self.__focused = True
-#         if self.__focus_in_cmd: self.__focus_in_cmd(*args)
-#         self.notify('on_entry_enter')
-
-#     def _focus_out_cmd(self, *args):
-#         self.__focused = False
-#         if self.__focus_out_cmd: self.__focus_out_cmd(*args)
-#         self.notify('on_entry_exit')
-
-#     def on_tab(self):
-#         if self.wdg['focus'] == ENTRY_FOCUS_STATE: self.on_tab_cb()
-
-#     @property
-#     def focused(self): return self.__focused
-
-#     @property
-#     def text(self): return self.wdg.get()
-
-#     def enter_text(self, txt):
-#         return self.wdg.enterText(txt)
-
-#     def enable(self): self['state'] = NORMAL
-
-#     def disable(self): self['state'] = DISABLED
-
-#     def destroy(self):
-#         self.ignore('tab-up')
-#         self.on_tab_cb = None
-#         Subject.destroy(self)
-#         P3dAbs.destroy(self)
-
-
-# class P3dLabel(P3dAbs):
-
-#     def __init__(
-#             self, text='', pos=(0, 0), parent=None, text_wordwrap=12,
-#             text_align=None, text_fg=(1, 1, 1, 1), text_font=None, scale=.05,
-#             frame_col=(1, 1, 1, 1), tra_src=None, tra_tra=None, hpr=(0, 0, 0)):
-#         self.wdg = DirectLabel(
-#             text=text, pos=(pos[0], 1, pos[1]), parent=parent,
-#             text_wordwrap=text_wordwrap, text_align=text_align,
-#             text_fg=text_fg, text_font=text_font, scale=scale,
-#             frameColor=frame_col, hpr=hpr)
-#         P3dAbs.__init__(self, tra_src, tra_tra)
-
-#     def set_bin(self, bin_name, priority): return self.wdg.set_bin(bin_name, priority)
-
-#     def set_x(self, x): return self.wdg.set_x(x)
-
-#     def set_alpha_scale(self, alpha): return self.wdg.set_alpha_scale(alpha)
-
-
-# class P3dTxt(P3dBase):
-
-#     def __init__(
-#             self, txt='', pos=(0, 0), scale=.05, wordwrap=12, parent=None,
-#             fg=(1, 1, 1, 1), font=None, align=None, tra_src=None,
-#             tra_tra=None):
-#         str2par = {'bottomleft': base.a2dBottomLeft,
-#                    'bottomright': base.a2dBottomRight,
-#                    'leftcenter': base.a2dLeftCenter}
-#         str2al = {'left': TextNode.A_left, 'right': TextNode.A_right,
-#                   'center': TextNode.A_center}
-#         if parent and parent in str2par: parent = str2par[parent]
-#         if align: align = str2al[align]
-#         self.wdg = OnscreenText(
-#             text=txt, pos=pos, scale=scale, wordwrap=wordwrap,
-#             parent=parent, fg=fg, font=font, align=align)
-#         P3dBase.__init__(self, tra_src, tra_tra)
-
-#     def set_r(self, r): return self.wdg.set_r(r)
-
-
-# class P3dFrame(P3dAbs):
-
-#     def __init__(self, frame_size=(-1, 1, -1, 1), frame_col=(1, 1, 1, 1),
-#                  pos=(0, 0), parent=None, texture_coord=False):
-#         P3dAbs.__init__(self)
-#         self.wdg = DirectFrame(frameSize=frame_size, frameColor=frame_col,
-#                                pos=(pos[0], 1, pos[1]), parent=parent)
-#         if texture_coord: self.wdg['frameTexture'] = Texture()
-
-
-# class P3dScrolledFrame(P3dAbs):
-
-#     def __init__(
-#             self, frame_sz=(-1, 1, -1, 1), canvas_sz=(0, 1, 0, 1),
-#             scrollbar_width=.05, frame_col=(1, 1, 1, 1),
-#             pos=(0, 0), parent='topleft'):
-#         P3dAbs.__init__(self)
-#         par2p3d = {'topleft': base.a2dTopLeft}
-#         if parent and parent in par2p3d: parent = par2p3d[parent]
-#         self.wdg = DirectScrolledFrame(
-#             frameSize=frame_sz,
-#             canvasSize=canvas_sz,
-#             scrollBarWidth=scrollbar_width,
-#             frameColor=frame_col,
-#             pos=(pos[0], 1, pos[1]),
-#             parent=parent)
-
-#     @property
-#     def canvas(self): return self.wdg.getCanvas()
diff --git a/ya2/lib/p3d/p3d.py b/ya2/lib/p3d/p3d.py
deleted file mode 100755 (executable)
index f4115e9..0000000
+++ /dev/null
@@ -1,352 +0,0 @@
-import sys
-from logging import info
-from os.path import exists, dirname
-from os import getcwd, _exit
-from glob import glob
-from pathlib import Path
-from panda3d.core import loadPrcFileData, Texture, TextPropertiesManager, \
-    TextProperties, PandaSystem, Filename, WindowProperties, GraphicsWindow
-from panda3d.bullet import get_bullet_version
-from direct.showbase.ShowBase import ShowBase
-from direct.showbase.DirectObject import DirectObject
-from direct.task.Task import Task
-#from gltf import patch_loader
-
-
-# class LibShowBase(ShowBase): pass
-
-
-class LibP3d(DirectObject):
-
-    task_cont = Task.cont
-
-    def __init__(self):
-        DirectObject.__init__(self)
-        self.__end_cb = self.__notify = None
-        self.__logged_keys = {}
-
-    @staticmethod
-    def runtime(): return not exists('main.py')
-
-    @staticmethod
-    def configure():
-        loadPrcFileData('', 'notify-level-ya2 info')
-        # loadPrcFileData('', 'gl-version 3 2')
-
-    @staticmethod
-    def fixpath(path):
-        home = '/home/flavio'
-        if sys.platform == 'win32' and not exists(exists(home + '/.wine/')):
-            if path.startswith('/'): path = path[1] + ':\\' + path[3:]
-            path = path.replace('/', '\\')
-        return path
-
-    @staticmethod
-    def p3dpath(path): return Filename.fromOsSpecific(path)
-
-    @property
-    def last_frame_dt(self): return globalClock.get_dt()
-
-    @property
-    def build_version(self):
-        appimg_mnt = glob('/tmp/.mount_Yocto*')
-        if appimg_mnt:
-            #with open(appimg_mnt[0] + '/usr/bin/appimage_version.txt') as fver:
-            with open(self.curr_path + '/assets/bld_version.txt') as fver:
-                return fver.read().strip()
-        try:
-            with open(self.curr_path + '/assets/bld_version.txt') as fver:
-                return fver.read().strip()
-        except FileNotFoundError:
-            info(self.curr_path + '/assets/bld_version.txt')
-            return 'notfound'
-
-    @property
-    def is_appimage(self):
-        par_path = str(Path(__file__).parent.absolute())
-        is_appimage = par_path.startswith('/tmp/.mount_Yocto')
-        return is_appimage and par_path.endswith('/usr/bin')
-
-    @property
-    def curr_path(self):
-        if sys.platform == 'darwin':
-            return dirname(__file__) + '/../Resources/'
-        # return dirname(__file__)
-        par_path = str(Path(__file__).parent.absolute())
-        if self.is_appimage:
-            return str(Path(par_path).absolute())
-        is_snap = par_path.startswith('/snap/')
-        is_snap = is_snap and par_path.endswith('/x1')
-        if is_snap:
-            return str(Path(par_path).absolute())
-        #return getcwd()
-        curr_path = dirname(__file__)
-        info('current path: %s' % curr_path)
-        return curr_path
-
-    @staticmethod
-    def send(msg): return messenger.send(msg)
-
-    @staticmethod
-    def do_later(time, meth, args=None):
-        args = args or []
-        return taskMgr.doMethodLater(
-            time, lambda meth, args: meth(*args), meth.__name__, [meth, args])
-
-    @staticmethod
-    def add_task(mth, priority=0):
-        return taskMgr.add(mth, mth.__name__, priority)
-
-    @staticmethod
-    def remove_task(tsk): taskMgr.remove(tsk)
-
-    def init(self, green=(.2, .8, .2, 1), red=(.8, .2, .2, 1), end_cb=None):
-        LibShowBase()
-        base.disableMouse()
-        #patch_loader(base.loader)
-        self.__end_cb = end_cb
-        self.__init_win()
-        self.__init_fonts(green, red)
-        self.__set_roots()
-        self.accept('aspectRatioChanged', self.on_aspect_ratio_changed)
-
-    @staticmethod
-    def __set_roots():
-        base.a2dTopQuarter = base.aspect2d.attachNewNode('a2dTopQuarter')
-        base.a2dTopQuarter.set_pos(base.a2dLeft / 2, 0, base.a2dTop)
-        base.a2dTopThirdQuarter = \
-            base.aspect2d.attachNewNode('a2dTopThirdQuarter')
-        base.a2dTopThirdQuarter.set_pos(base.a2dRight / 2, 0, base.a2dTop)
-        base.a2dCenterQuarter = base.aspect2d.attachNewNode('a2dCenterQuarter')
-        base.a2dCenterQuarter.set_pos(base.a2dLeft / 2, 0, 0)
-        base.a2dCenterThirdQuarter = \
-            base.aspect2d.attachNewNode('a2dCenterThirdQuarter')
-        base.a2dCenterThirdQuarter.set_pos(base.a2dRight / 2, 0, 0)
-        base.a2dBottomQuarter = base.aspect2d.attachNewNode('a2dBottomQuarter')
-        base.a2dBottomQuarter.set_pos(base.a2dLeft / 2, 0, base.a2dBottom)
-        base.a2dBottomThirdQuarter = \
-            base.aspect2d.attachNewNode('a2dBottomThirdQuarter')
-        base.a2dBottomThirdQuarter.set_pos(
-            base.a2dRight / 2, 0, base.a2dBottom)
-
-    @staticmethod
-    def on_aspect_ratio_changed():
-        base.a2dTopQuarter.set_pos(base.a2dLeft / 2, 0, base.a2dTop)
-        base.a2dTopThirdQuarter.set_pos(base.a2dRight / 2, 0, base.a2dTop)
-        base.a2dBottomQuarter.set_pos(base.a2dLeft / 2, 0, base.a2dBottom)
-        base.a2dBottomThirdQuarter.set_pos(
-            base.a2dRight / 2, 0, base.a2dBottom)
-
-    @property
-    def has_window(self): return bool(base.win)
-
-    @property
-    def resolution(self):
-        if not isinstance(base.win, GraphicsWindow):
-            return 800, 600
-        win_prop = base.win.get_properties()
-        return win_prop.get_x_size(), win_prop.get_y_size()
-
-    @property
-    def resolutions(self):
-        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)
-        ret = [res(idx) for idx in range(d_i.get_total_display_modes())]
-        return ret if ret else [self.resolution]
-
-    @staticmethod
-    def toggle_fullscreen():
-        props = WindowProperties()
-        props.set_fullscreen(not base.win.is_fullscreen())
-        base.win.request_properties(props)
-
-    @staticmethod
-    def set_resolution(res, fullscreen=None):
-        props = WindowProperties()
-        props.set_size(res)
-        if fullscreen: props.set_fullscreen(True)
-        if isinstance(base.win, GraphicsWindow):
-            base.win.request_properties(props)
-
-    def __init_win(self):
-        if base.win and isinstance(base.win, GraphicsWindow):
-            base.win.set_close_request_event('window-closed')
-        # not headless
-        self.accept('window-closed', self.__on_end)
-
-    @staticmethod
-    def __init_fonts(green=(.2, .8, .2, 1), red=(.8, .2, .2, 1)):
-        tp_mgr = TextPropertiesManager.get_global_ptr()
-        for namecol, col in zip(['green', 'red'], [green, red]):
-            props = TextProperties()
-            props.set_text_color(col)
-            tp_mgr.set_properties(namecol, props)
-        for namesize, col in zip(['small', 'smaller'], [.46, .72]):
-            props = TextProperties()
-            props.set_text_scale(.46)
-            tp_mgr.set_properties(namesize, props)
-        tp_italic = TextProperties()
-        tp_italic.set_slant(.2)
-        tp_mgr.set_properties('italic', tp_italic)
-
-    def __on_end(self):
-        base.closeWindow(base.win)
-        if self.__end_cb: self.__end_cb()
-        _exit(0)
-
-    @staticmethod
-    def load_font(filepath, outline=True):
-        font = base.loader.loadFont(filepath)
-        font.set_pixels_per_unit(60)
-        font.set_minfilter(Texture.FTLinearMipmapLinear)
-        if outline: font.set_outline((0, 0, 0, 1), .8, .2)
-        return font
-
-    @staticmethod
-    def log(msg): print(msg)
-
-    @property
-    def version(self): return PandaSystem.get_version_string()
-
-    @property
-    def lib_commit(self): return PandaSystem.get_git_commit()
-
-    @property
-    def phys_version(self): return get_bullet_version()
-
-    @property
-    def user_appdata_dir(self): return Filename.get_user_appdata_directory()
-
-    @property
-    def driver_vendor(self): return base.win.get_gsg().get_driver_vendor()
-
-    @property
-    def driver_renderer(self): return base.win.get_gsg().get_driver_renderer()
-
-    @property
-    def driver_shader_version_major(self):
-        return base.win.get_gsg().get_driver_shader_version_major()
-
-    @property
-    def driver_shader_version_minor(self):
-        return base.win.get_gsg().get_driver_shader_version_minor()
-
-    @property
-    def driver_version(self): return base.win.get_gsg().get_driver_version()
-
-    @property
-    def driver_version_major(self):
-        return base.win.get_gsg().get_driver_version_major()
-
-    @property
-    def driver_version_minor(self):
-        return base.win.get_gsg().get_driver_version_minor()
-
-    @property
-    def fullscreen(self):
-        if isinstance(base.win, GraphicsWindow):
-            return base.win.get_properties().get_fullscreen()
-
-    @property
-    def volume(self): return base.sfxManagerList[0].get_volume()
-
-    @volume.setter
-    def volume(self, vol): base.sfxManagerList[0].set_volume(vol)
-
-    @property
-    def mousepos(self):
-        mwn = base.mouseWatcherNode
-        if not mwn: return 0, 0
-        if not mwn.hasMouse(): return 0, 0
-        return mwn.get_mouse_x(), mwn.get_mouse_y()
-
-    @property
-    def aspect_ratio(self): return base.getAspectRatio()
-
-    @staticmethod
-    def set_icon(filename):
-        props = WindowProperties()
-        props.set_icon_filename(filename)
-        if isinstance(base.win, GraphicsWindow):
-            base.win.requestProperties(props)
-
-    @staticmethod
-    def __set_std_cursor(show):
-        props = WindowProperties()
-        props.set_cursor_hidden(not show)
-        if isinstance(base.win, GraphicsWindow):
-            base.win.requestProperties(props)
-
-    @staticmethod
-    def show_std_cursor(): LibP3d.__set_std_cursor(True)
-
-    @staticmethod
-    def hide_std_cursor(): LibP3d.__set_std_cursor(False)
-
-    @staticmethod
-    def find_geoms(model, name):  # no need to be cached
-        geoms = model.node.find_all_matches('**/+GeomNode')
-        is_nm = lambda geom: geom.get_name().startswith(name)
-        named_geoms = [geom for geom in geoms if is_nm(geom)]
-        return [ng for ng in named_geoms if name in ng.get_name()]
-
-    @staticmethod
-    def load_sfx(filepath, loop=False):
-        sfx = loader.loadSfx(filepath)
-        sfx.set_loop(loop)
-        return sfx
-
-    def remap_code(self, key):
-        kmap = base.win.get_keyboard_map()
-        for i in range(kmap.get_num_buttons()):
-            if key.lower() == kmap.get_mapped_button_label(i).lower():
-                self.__log_key(
-                    'code mapping %s to key %s' %
-                    (key, kmap.get_mapped_button(i)), key,
-                    kmap.get_mapped_button(i))
-                return kmap.get_mapped_button(i)
-        for i in range(kmap.get_num_buttons()):
-            if key.lower() == kmap.get_mapped_button(i).get_name().lower():
-                self.__log_key(
-                    'code mapping %s to key %s' %
-                    (key, kmap.get_mapped_button(i)), key,
-                    kmap.get_mapped_button(i))
-                return kmap.get_mapped_button(i)
-        self.__log_key('not found a code mapping for %s' %
-                       key, key, 'not_found')
-        return key
-
-    def remap_str(self, key):
-        if not base.win:  # when launched with --version
-            return key
-        #if isinstance(base.win, GraphicsBuffer):
-        #    return key
-        kmap = base.win.get_keyboard_map()
-        for i in range(kmap.get_num_buttons()):
-            if str(key).lower() == kmap.get_mapped_button_label(i).lower():
-                self.__log_key(
-                    'string mapping %s to key %s' %
-                    (key, kmap.get_mapped_button(i).get_name()), key,
-                    kmap.get_mapped_button(i).get_name())
-                return kmap.get_mapped_button(i).get_name()
-        for i in range(kmap.get_num_buttons()):
-            if key.lower() == kmap.get_mapped_button(i).get_name().lower():
-                self.__log_key(
-                    'string mapping %s to key %s' %
-                    (key, kmap.get_mapped_button(i).get_name()), key,
-                    kmap.get_mapped_button(i).get_name())
-                return kmap.get_mapped_button(i).get_name()
-        self.__log_key('not found a string mapping for %s' %
-                       key, key, kmap.get_mapped_button(i).get_name())
-        return key
-
-    def __log_key(self, msg, key1, key2):
-        if key1 in self.__logged_keys and self.__logged_keys[key1] == key2:
-            return
-        self.__logged_keys[key1] = key2
-        print(msg)
-
-    def destroy(self): pass
diff --git a/ya2/observer.py b/ya2/observer.py
deleted file mode 100644 (file)
index 7c49116..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-class ObsInfo:
-
-    def __init__(self, mth, sort, args):
-        self.mth = mth
-        self.sort = sort
-        self.args = args
-
-    def __repr__(self): return str(self.mth)
-
-
-class Subject:
-
-    def __init__(self):
-        self.observers = {}
-
-    def attach(self, obs_meth, sort=10, rename='', args=None):
-        args = args or []
-        onm = rename or obs_meth.__name__
-        if onm not in self.observers: self.observers[onm] = []
-        self.observers[onm] += [ObsInfo(obs_meth, sort, args)]
-        sorted_obs = sorted(self.observers[onm], key=lambda obs: obs.sort)
-        self.observers[onm] = sorted_obs
-
-    def detach(self, obs_meth, lambda_call=None):
-        if isinstance(obs_meth, str):
-            onm = obs_meth
-            observers = [obs for obs in self.observers[onm]
-                         if obs.mth == lambda_call]
-        else:
-            onm = obs_meth.__name__
-            observers = [obs for obs in self.observers[onm]
-                         if obs.mth == obs_meth]
-        if not observers: raise Exception
-        list(map(self.observers[onm].remove, observers))
-
-    def notify(self, meth, *args, **kwargs):
-        if meth not in self.observers: return  # no obs for this notification
-        for obs in self.observers[meth][:]:
-            if obs in self.observers[meth]:  # if an obs removes another one
-                try:
-                    act_args = obs.args + list(args)
-                    obs.mth(*act_args, **kwargs)
-                except SystemError:
-                    print('Quit')
-                    import sys; sys.exit()
-
-    def observing(self, obs_meth):
-        if callable(obs_meth): obs_meth = obs_meth.__name__
-        return obs_meth in self.observers and self.observers[obs_meth]
-
-    def destroy(self): self.observers = None
-
-
-class Observer: pass
diff --git a/ya2/p3d/__init__.py b/ya2/p3d/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ya2/p3d/gfx.py b/ya2/p3d/gfx.py
new file mode 100755 (executable)
index 0000000..56f6a87
--- /dev/null
@@ -0,0 +1,369 @@
+import datetime
+from logging import debug, info
+from os import getcwd
+from os.path import exists, dirname
+from panda3d.core import get_model_path, AntialiasAttrib, PandaNode, \
+    LightRampAttrib, Camera, OrthographicLens, NodePath, OmniBoundingVolume, \
+    AmbientLight as P3DAmbientLight, Spotlight as P3DSpotlight, Point2, \
+    Point3, Texture
+from direct.filter.CommonFilters import CommonFilters
+from direct.actor.Actor import Actor
+# from ya2.lib.p3d.p3d import LibP3d
+
+
+def set_srgb(model):
+    for texture in model.find_all_textures():
+        if texture.get_format() in [Texture.F_rgba, Texture.F_rgbm]:
+            texture.set_format(Texture.F_srgb_alpha)
+        elif texture.get_format() in [Texture.F_rgb]:
+            texture.set_format(Texture.F_srgb)
+
+
+# class RenderToTexture:
+
+#     def __init__(self, size=(256, 256)):
+#         self.__set_buffer(size)
+#         self.__set_display_region()
+#         self.__set_camera()
+#         self.__set_root()
+#         self.display_region.set_camera(self.camera)
+
+#     def __set_buffer(self, size):
+#         self.buffer = base.win.make_texture_buffer('result buffer', size[0],
+#                                                    size[1])
+#         self.buffer.set_sort(-100)
+
+#     def __set_display_region(self):
+#         self.display_region = self.buffer.make_display_region()
+#         self.display_region.set_sort(20)
+
+#     def __set_camera(self):
+#         self.camera = NodePath(Camera('camera 2d'))
+#         lens = OrthographicLens()
+#         lens.set_film_size(1, 1)
+#         lens.set_near_far(-1000, 1000)
+#         self.camera.node().set_lens(lens)
+
+#     def __set_root(self):
+#         self.root = NodePath('root')
+#         self.root.set_depth_test(False)
+#         self.root.set_depth_write(False)
+#         self.camera.reparent_to(self.root)
+
+#     @property
+#     def texture(self): return self.buffer.get_texture()
+
+#     def destroy(self):
+#         base.graphicsEngine.remove_window(self.buffer)
+#         if base.win:  # if you close the window during a race
+#             base.win.remove_display_region(self.display_region)
+#         list(map(lambda node: node.remove_node(), [self.camera, self.root]))
+
+
+class P3dGfxMgr:
+
+    def __init__(self, model_path, antialiasing, shaders, srgb):
+        self.root = P3dNode(render)
+        self.__srgb = srgb
+        self.callbacks = {}
+        self.filters = None
+        get_model_path().append_directory(model_path)
+        if LibP3d.runtime():
+            root_dir = LibP3d.p3dpath(dirname(__file__))
+            paths = [root_dir + '/' + model_path, root_dir]
+            list(map(get_model_path().append_directory, paths))
+        render.set_shader_auto()
+        # render.set_two_sided(True)  # it breaks shadows
+        if antialiasing: render.set_antialias(AntialiasAttrib.MAuto)
+        if shaders and base.win:
+            self.filters = CommonFilters(base.win, base.cam)
+
+    def load_model(self, filename, callback=None, anim=None):
+        ext = '.bam' if exists(filename + '.bam') else ''
+        if anim:
+            anim_dct = {'anim': filename + '-Anim' + ext}
+            node = P3dNode(self.set_srgb(Actor(filename + ext, anim_dct)))
+        elif callback:
+            callb = lambda model: callback(P3dNode(self.set_srgb(model)))
+            node = loader.loadModel(filename + ext, callback=callb)
+        else:
+            node = P3dNode(self.set_srgb(
+                loader.loadModel(LibP3d.p3dpath(filename + ext))))
+        return node
+
+    def set_srgb(self, model):
+        if self.__srgb:
+            for texture in model.find_all_textures():
+                if texture.get_format() in [Texture.F_rgba, Texture.F_rgbm]:
+                    texture.set_format(Texture.F_srgb_alpha)
+                elif texture.get_format() in [Texture.F_rgb]:
+                    texture.set_format(Texture.F_srgb)
+        return model
+
+    @staticmethod
+    def toggle_aa():
+        aa_not_none = render.get_antialias() != AntialiasAttrib.MNone
+        if render.has_antialias() and aa_not_none:
+            render.clear_antialias()
+        else: render.set_antialias(AntialiasAttrib.MAuto, 1)
+
+    def set_toon(self):
+        tmp_node = NodePath(PandaNode('temp node'))
+        tmp_node.set_attrib(LightRampAttrib.make_single_threshold(.5, .4))
+        tmp_node.set_shader_auto()
+        base.cam.node().set_initial_state(tmp_node.get_state())
+        self.filters.set_cartoon_ink(separation=1)
+
+    def set_bloom(self):
+        if not base.win: return
+        self.filters.setBloom(
+            blend=(.3, .4, .3, 0), mintrigger=.6, maxtrigger=1.0, desat=.6,
+            intensity=1.0, size='medium')
+        # default: (.3, .4, .3, 0), .6, 1, .6, 1, 'medium'
+
+    @staticmethod
+    def pos2d(node):
+        p3d = base.cam.get_relative_point(node.node, Point3(0, 0, 0))
+        p2d = Point2()
+        return p2d if base.camLens.project(p3d, p2d) else None
+
+    @staticmethod
+    def screen_coord(pos):
+        new_node = NodePath('temp')
+        new_node.set_pos(pos)
+        coord3d = new_node.get_pos(base.cam)
+        new_node.remove_node()
+        coord2d = Point2()
+        base.camLens.project(coord3d, coord2d)
+        coord_r2d = Point3(coord2d[0], 0, coord2d[1])
+        coord_a2d = base.aspect2d.get_relative_point(render2d, coord_r2d)
+        return coord_a2d[0], coord_a2d[2]
+
+    @staticmethod
+    def world_from_to(pos):
+        p_from, p_to = Point3(), Point3()    # in camera coordinates
+        base.camLens.extrude(pos, p_from, p_to)
+        p_from = render.get_relative_point(base.cam, p_from)  # global coords
+        p_to = render.get_relative_point(base.cam, p_to)  # global coords
+        return p_from, p_to
+
+    @property
+    def shader_support(self):
+        return base.win.get_gsg().get_supports_basic_shaders()
+
+    def screenshot(self, path=None):
+        time = datetime.datetime.now().strftime('%y%m%d%H%M%S')
+        #res = base.win.save_screenshot(Filename(path or ("yocto%s.png" % time)))
+        #debug('screenshot %s (%s)' % (path or ("yocto%s.png" % time), res))
+        res = base.screenshot(path or ("pmachines%s.png" % time), False)
+        info('screenshot %s (%s; %s)' % (path or ("pmachines%s.png" % time), res, getcwd()))
+
+    @staticmethod
+    def enable_shader(): render.set_shader_auto()
+
+    @staticmethod
+    def disable_shader(): render.set_shader_off()
+
+    @staticmethod
+    def print_stats(two_d=True, three_d=True, analyze=True, ls=True):
+        '''Print graphics stats. They use standard output (from p3d).'''
+        info = []
+        if two_d and analyze:
+            info +=[('render2d.analyze', base.render2d.analyze)]
+        if three_d and analyze:
+            info +=[('render.analyze', base.render.analyze)]
+        if two_d and ls:
+            info +=[('render2d.ls', base.render2d.ls)]
+        if three_d and ls:
+            info +=[('render.ls', base.render.ls)]
+        for elm in info:
+            print('\n\n#####\n%s()' % elm[0])
+            elm[1]()
+
+
+# class P3dNode:
+
+#     def __init__(self, nodepath):
+#         self.nodepath = nodepath
+#         self.node.set_python_tag('libnode', self)
+
+#     def set_collide_mask(self, mask): return self.node.set_collide_mask(mask)
+#     def set_x(self, val): return self.node.set_x(val)
+#     def set_y(self, val): return self.node.set_y(val)
+#     def set_z(self, val): return self.node.set_z(val)
+#     def set_hpr(self, val): return self.node.set_hpr(val)
+#     def set_h(self, val): return self.node.set_h(val)
+#     def set_p(self, val): return self.node.set_p(val)
+#     def set_r(self, val): return self.node.set_r(val)
+#     def set_scale(self, val): return self.node.set_scale(val)
+#     def set_transparency(self, val): return self.node.set_transparency(val)
+#     def set_alpha_scale(self, val): return self.node.set_alpha_scale(val)
+#     def set_texture(self, texturestage, texture):
+#         return self.node.set_texture(texturestage, texture)
+#     def has_tag(self, name): return self.node.has_tag(name)
+#     def get_tag(self, name): return self.node.get_tag(name)
+#     def get_python_tag(self, name): return self.node.get_python_tag(name)
+#     def remove_node(self): return self.node.remove_node()
+#     def flatten_strong(self): return self.node.flatten_strong()
+#     def clear_model_nodes(self): return self.node.clear_model_nodes()
+#     def show(self): return self.node.show()
+#     def set_depth_offset(self, val): return self.node.set_depth_offset(val)
+#     def loop(self, val): return self.node.loop(val)
+#     def cleanup(self): return self.node.cleanup()
+#     def write_bam_file(self, fname): return self.node.write_bam_file(fname)
+
+#     def attach_node(self, name):
+#         return P3dNode(self.node.attach_new_node(name))
+
+#     def add_shape(self, shape):
+#         return self.node.node().add_shape(shape._mesh_shape)
+#         #TODO: don't access a protected member
+
+#     @property
+#     def name(self): return self.node.get_name()
+
+#     @property
+#     def node(self): return self.nodepath
+
+#     @property
+#     def p3dnode(self): return self.node.node()
+
+#     def set_pos(self, pos): return self.node.set_pos(pos._vec)
+#         #TODO: don't access a protected member
+
+#     def get_pos(self, other=None):
+#         return self.node.get_pos(* [] if other is None else [other.node])
+
+#     @property
+#     def x(self): return self.node.get_x()
+
+#     @property
+#     def y(self): return self.node.get_y()
+
+#     @property
+#     def z(self): return self.node.get_z()
+
+#     @property
+#     def hpr(self): return self.node.get_hpr()
+
+#     @property
+#     def h(self): return self.node.get_h()
+
+#     @property
+#     def p(self): return self.node.get_p()
+
+#     @property
+#     def r(self): return self.node.get_r()
+
+#     @property
+#     def scale(self): return self.node.get_scale()
+
+#     @property
+#     def is_empty(self): return self.node.is_empty()
+
+#     def get_relative_vector(self, node, vec):
+#         return self.node.get_relative_vector(node.node, vec)
+
+#     def set_material(self, mat): return self.node.set_material(mat, 1)
+
+#     def set_python_tag(self, name, val):
+#         return self.node.set_python_tag(name, val)
+
+#     def get_distance(self, other_node):
+#         return self.node.get_distance(other_node.node)
+
+#     def reparent_to(self, parent): return self.node.reparent_to(parent.node)
+
+#     def wrt_reparent_to(self, parent):
+#         return self.node.wrt_reparent_to(parent.node)
+
+#     @staticmethod
+#     def __get_pandanode(nodepath):
+#         if nodepath.has_python_tag('libnode'):
+#             return nodepath.get_python_tag('libnode')
+#         return P3dNode(nodepath)
+
+#     def find_all_matches(self, name):
+#         nodes = self.node.find_all_matches(name)
+#         return [self.__get_pandanode(node) for node in nodes]
+
+#     def find(self, name):
+#         model = self.node.find(name)
+#         if model: return self.__get_pandanode(model)
+
+#     def optimize(self):
+#         self.node.prepare_scene(base.win.get_gsg())  # crash with texture.set_format
+#         self.node.premunge_scene(base.win.get_gsg())
+
+#     def hide(self, mask=None):
+#         return self.node.hide(*[] if mask is None else [mask])
+
+#     @property
+#     def tight_bounds(self): return self.node.get_tight_bounds()
+
+#     @property
+#     def parent(self): return self.node.get_parent()
+
+#     @property
+#     def children(self): return self.node.get_children()
+
+#     def destroy(self): return self.node.remove_node()
+
+
+# class P3dAnimNode:
+
+#     def __init__(self, filepath, anim_dct):
+#         self.node = Actor(filepath, anim_dct)
+
+#     def loop(self, val): return self.node.loop(val)
+
+#     def reparent_to(self, node): self.node.reparent_to(node)
+
+#     @property
+#     def name(self): return self.node.get_name()
+
+#     def optimize(self):
+#         self.node.prepare_scene(base.win.get_gsg())
+#         self.node.premunge_scene(base.win.get_gsg())
+
+#     def set_omni(self):
+#         self.node.node().set_bounds(OmniBoundingVolume())
+#         self.node.node().set_final(True)
+
+#     def destroy(self): self.node.cleanup()
+
+
+# class P3dAmbientLight:
+
+#     def __init__(self, color):
+#         ambient_lgt = P3DAmbientLight('ambient light')
+#         ambient_lgt.set_color(color)
+#         self.ambient_np = render.attach_new_node(ambient_lgt)
+#         render.set_light(self.ambient_np)
+
+#     def destroy(self):
+#         render.clear_light(self.ambient_np)
+#         self.ambient_np.remove_node()
+
+
+# class P3dSpotlight:
+
+#     def __init__(self, mask=None):
+#         self.spot_lgt = render.attach_new_node(P3DSpotlight('spot'))
+#         snode = self.spot_lgt.node()
+#         snode.set_scene(render)
+#         snode.set_shadow_caster(True, 1024, 1024)
+#         snode.get_lens().set_fov(40)
+#         snode.get_lens().set_near_far(20, 200)
+#         if mask: snode.set_camera_mask(mask)
+#         render.set_light(self.spot_lgt)
+
+#     def set_pos(self, pos): return self.spot_lgt.set_pos(*pos)
+
+#     def look_at(self, pos): return self.spot_lgt.look_at(*pos)
+
+#     def set_color(self, color): return self.spot_lgt.set_color(*color)
+
+#     def destroy(self):
+#         render.clear_light(self.spot_lgt)
+#         self.spot_lgt.remove_node()
diff --git a/ya2/p3d/gui.py b/ya2/p3d/gui.py
new file mode 100755 (executable)
index 0000000..46d2a21
--- /dev/null
@@ -0,0 +1,405 @@
+from inspect import getmro
+from panda3d.core import TextNode, Texture
+from direct.gui.DirectGuiGlobals import FLAT, ENTER, EXIT, DISABLED, NORMAL, \
+    B1PRESS
+from direct.showbase.DirectObject import DirectObject
+from direct.gui.DirectButton import DirectButton
+from direct.gui.DirectCheckButton import DirectCheckButton
+from direct.gui.DirectOptionMenu import DirectOptionMenu
+from direct.gui.OnscreenImage import OnscreenImage
+from direct.gui.DirectSlider import DirectSlider
+from direct.gui.DirectEntry import DirectEntry, ENTRY_FOCUS_STATE
+from direct.gui.DirectLabel import DirectLabel
+from direct.gui.DirectFrame import DirectFrame
+from direct.gui.OnscreenText import OnscreenText
+from direct.gui.DirectScrolledFrame import DirectScrolledFrame
+from ya2.patterns.observer import Subject
+# from ya2.lib.ivals import Seq, Wait, PosIval, Func
+
+
+class CommonBase:
+
+    def set_widget(self):
+        from ya2.lib.gui import Frame, Slider, Btn, Label, OptionMenu, \
+            CheckBtn, Entry, Img, Text, ScrolledFrame
+        from ya2.p3d.widget import FrameMixin, SliderMixin, BtnMixin, \
+            OptionMenuMixin, CheckBtnMixin, EntryMixin, ImgMixin, \
+            ScrolledFrameMixin
+        self.__class__ = self.__class__  # for pylint
+        libwdg2wdg = {
+            FrameMixin: [Frame],
+            ScrolledFrameMixin: [ScrolledFrame],
+            SliderMixin: [Slider],
+            BtnMixin: [Btn, Label],
+            OptionMenuMixin: [OptionMenu],
+            CheckBtnMixin: [CheckBtn],
+            EntryMixin: [Entry],
+            ImgMixin: [Img, Text]}
+        for libwdg, wdgcls in libwdg2wdg.items():
+            if any(cls in getmro(self.__class__) for cls in wdgcls):
+                par_cls = libwdg
+        clsname = self.__class__.__name__ + 'Widget'
+        self.__class__ = type(clsname, (self.__class__, par_cls), {})
+        self.init(self)
+        if not hasattr(self, 'bind'): return
+        bind_args = [(ENTER, self.on_wdg_enter), (EXIT, self.on_wdg_exit)]
+        list(map(lambda args: self.bind(*args), bind_args))
+
+    def set_enter_transition(self):
+        start_pos = self.get_pos()
+        pos = self.pos - (3.6, 0)
+        self.set_pos((pos.x, 1, pos.y))
+        Seq(
+            Wait(abs(pos.y - 1) / 4),
+            PosIval(self.get_np(), .5, start_pos)
+        ).start()
+
+    def set_exit_transition(self, destroy):
+        start_pos = self.get_pos()
+        end_pos = (self.pos.x + 3.6, 1, self.pos.y)
+        seq = Seq(
+            Wait(abs(self.pos.y - 1) / 4),
+            PosIval(self.get_np(), .5, end_pos),
+            Func(self.destroy if destroy else self.hide))
+        if not destroy: seq += Func(self.set_pos, start_pos)
+        seq.start()
+
+    def translate(self):
+        if hasattr(self, 'bind_transl'): self.wdg['text'] = self.bind_transl
+
+
+class P3dImg(CommonBase):
+
+    def __init__(self, filepath, pos=(0, 0), scale=1.0, background=False,
+                 foreground=False, parent=None):
+        self.img = OnscreenImage(
+            filepath, pos=(pos[0], 1, pos[1]), scale=scale, parent=parent)
+        if background: self.img.set_bin('background', 10)
+        alpha_formats = [12]  # panda3d.core.texture.Frgba
+        if self.img.get_texture().get_format() in alpha_formats:
+            self.img.set_transparency(True)
+        if foreground: self.img.set_bin('gui-popup', 50)
+
+    def reparent_to(self, node): return self.img.reparent_to(node)
+    def show(self): return self.img.show()
+    def hide(self): return self.img.hide()
+    def set_shader(self, shader): return self.img.set_shader(shader)
+    def set_shader_input(self, name, val):
+        return self.img.set_shader_input(name, val)
+    def set_texture(self, texturestage, texture):
+        return self.img.set_texture(texturestage, texture)
+
+    def set_exit_transition(self, destroy):
+        start_pos = self.get_pos()
+        end_pos = (self.pos.x + 3.6, 1, self.pos.y)
+        seq = Seq(
+            Wait(abs(self.pos.y - 1) / 4),
+            PosIval(self.get_np(), .5, end_pos),
+            Func(self.destroy if destroy else self.hide))
+        if not destroy: seq += Func(self.set_pos, (start_pos[0], start_pos[2]))
+        seq.start()
+
+    def set_pos(self, pos): return self.img.set_pos(pos[0], 1, pos[1])
+
+    def get_pos(self, pos=None): return self.img.get_pos(*[pos] if pos else [])
+
+    @property
+    def parent(self): return self.img.get_parent()
+
+    @property
+    def hidden(self): return self.img.is_hidden()
+
+    def set_transparent(self): return self.img.set_transparency(True)
+
+    def destroy(self): self.img = self.img.destroy()
+
+
+# class P3dBase(CommonBase):
+
+#     def __init__(self, tra_src=None, tra_tra=None):
+#         # self.text_src_tra = None  # it breaks the gui
+#         if tra_src and tra_tra: self.bind_tra(tra_src, tra_tra)
+
+#     def set_pos(self, pos): return self.wdg.set_pos(pos)
+#     def show(self): return self.wdg.show()
+#     def hide(self): return self.wdg.hide()
+
+#     def bind_tra(self, text_src, text_transl):
+#         # text_transl is not used, anyway we need it since we have this kind of
+#         # use: self.bind_transl('example str', _('example str'))
+#         # this allows to change translations on the fly keeping the source
+#         # text for remapping it later
+#         # TODO: try reverse mapping? i.e. retrieve the src string from the
+#         # translated one
+#         self.text_src_tra = text_src
+#         self.text_tra_tra = text_transl
+#         tra = lambda self: _(self.text_tra_tra)
+#         self.__class__.bind_transl = property(tra)
+#         self['text'] = self.bind_transl
+
+#     def get_pos(self, pos=None):
+#         return self.wdg.get_pos(*[pos] if pos else [])
+
+#     def __setitem__(self, key, value): self.wdg[key] = value
+
+#     def __getitem__(self, key): return self.wdg[key]
+
+#     def get_np(self): return self.wdg
+
+#     @property
+#     def hidden(self): return self.wdg.is_hidden()
+
+#     def destroy(self): self.wdg.destroy()
+
+
+# class P3dAbs(P3dBase):
+
+#     def get_value(self): return self.wdg.getValue()
+#     def initialiseoptions(self): return self.wdg.initialiseoptions()
+#     def set_z(self, val): return self.wdg.set_z(val)
+#     def set_shader(self, shader): return self.wdg.set_shader(shader)
+#     def set_shader_input(self, name, val):
+#         return self.wdg.set_shader_input(name, val)
+#     def set_transparency(self, val): return self.wdg.set_transparency(val)
+#     def bind(self, evt, mth): return self.wdg.bind(evt, mth)
+
+#     def attachNewNode(self, gui_itm, sort_order):
+#         # it won't work if we name it attach_node. hopefully this will be
+#         # possible when we'll use decorators in place of mixins
+#         return self.wdg.attachNewNode(gui_itm, sort_order)
+
+#     @property
+#     def is_enabled(self): return self.wdg['state'] != DISABLED
+
+
+# class P3dBtn(P3dAbs):
+
+#     def __init__(
+#             self, text='', parent=None, pos=(0, 0), scale=(1, 1),
+#             cmd=None, frame_size=(-1, 1, -1, 1), click_snd=None,
+#             text_fg=(1, 1, 1, 1), frame_col=(1, 1, 1, 1), text_font=None,
+#             over_snd=None, extra_args=None, frame_texture=None, img=None,
+#             tra_src=None, tra_tra=None, text_scale=1.0):
+#         str2par = {'bottomcenter': base.a2dBottomCenter}
+#         parent = str2par.get(parent, parent)
+#         extra_args = extra_args or []
+#         self.wdg = DirectButton(
+#             text=text, parent=parent, pos=(pos[0], 1, pos[1]),
+#             scale=(scale[0], 1, scale[1]), command=cmd,
+#             frameSize=frame_size, clickSound=click_snd, text_fg=text_fg,
+#             frameColor=frame_col, text_font=text_font, rolloverSound=over_snd,
+#             extraArgs=extra_args, frameTexture=frame_texture, image=img,
+#             text_scale=text_scale)
+#         P3dAbs.__init__(self, tra_src, tra_tra)
+#         self['relief'] = FLAT
+#         args = [(ENTER, self._on_enter), (EXIT, self._on_exit)]
+#         list(map(lambda args: self.bind(*args), args))
+
+#     def _on_enter(self, pos): pass  # pos comes from mouse
+
+#     def _on_exit(self, pos): pass  # pos comes from mouse
+
+#     # we add these with the mixins
+#     # def enable(self): self['state'] = NORMAL
+
+#     # def disable(self): self['state'] = DISABLED
+
+
+# class P3dSlider(P3dAbs):
+
+#     def __init__(
+#             self, parent=None, pos=(0, 0), scale=1, val=0,
+#             frame_col=(1, 1, 1, 1), thumb_frame_col=(1, 1, 1, 1),
+#             cmd=None, range_=(0, 1), tra_src=None, tra_tra=None):
+#         self.wdg = DirectSlider(
+#             parent=parent, pos=(pos[0], 1, pos[1]), scale=scale, value=val,
+#             frameColor=frame_col, thumb_frameColor=thumb_frame_col,
+#             command=cmd, range=range_)
+#         P3dAbs.__init__(self, tra_src, tra_tra)
+
+
+# class P3dCheckBtn(P3dAbs):
+
+#     def __init__(
+#             self, pos=(0, 0), text='', indicator_val=False,
+#             indicator_frame_col=(1, 1, 1, 1), frame_col=(1, 1, 1, 1),
+#             scale=(1, 1, 1), click_snd=None, over_snd=None,
+#             text_fg=(1, 1, 1, 1), text_font=None, cmd=None, tra_src=None,
+#             tra_tra=None):
+#         self.wdg = DirectCheckButton(
+#             pos=(pos[0], 1, pos[1]), text=text, indicatorValue=indicator_val,
+#             indicator_frameColor=indicator_frame_col,
+#             frameColor=frame_col, scale=scale, clickSound=click_snd,
+#             rolloverSound=over_snd, text_fg=text_fg, text_font=text_font,
+#             command=cmd)
+#         P3dAbs.__init__(self, tra_src, tra_tra)
+
+
+# class P3dOptionMenu(P3dAbs):
+
+#     def __init__(
+#             self, text='', items=None, pos=(0, 0), scale=(1, 1, 1),
+#             initialitem='', cmd=None, frame_size=(-1, 1, -1, 1),
+#             click_snd=None, over_snd=None, text_may_change=False,
+#             text_fg=(1, 1, 1, 1), item_frame_col=(1, 1, 1, 1),
+#             frame_col=(1, 1, 1, 1), highlight_col=(1, 1, 1, 1),
+#             text_scale=.05, popup_marker_col=(1, 1, 1, 1),
+#             item_relief=None, item_text_font=None, text_font=None,
+#             tra_src=None, tra_tra=None):
+#         items = items or []
+#         self.wdg = DirectOptionMenu(
+#             text=text, items=items, pos=(pos[0], 1, pos[1]), scale=scale,
+#             initialitem=initialitem, command=cmd, frameSize=frame_size,
+#             clickSound=click_snd, rolloverSound=over_snd,
+#             textMayChange=text_may_change, text_fg=text_fg,
+#             item_frameColor=item_frame_col, frameColor=frame_col,
+#             highlightColor=highlight_col, text_scale=text_scale,
+#             popupMarker_frameColor=popup_marker_col,
+#             item_relief=item_relief, item_text_font=item_text_font,
+#             text_font=text_font)
+#         P3dAbs.__init__(self, tra_src, tra_tra)
+
+#     def set(self, idx, f_cmd=1): return self.wdg.set(idx, f_cmd)
+
+#     @property
+#     def curr_val(self): return self.wdg.get()
+
+#     @property
+#     def curr_idx(self): return self.wdg.selectedIndex
+
+
+# class P3dEntry(P3dAbs, DirectObject, Subject):
+
+#     def __init__(
+#             self, scale=.05, pos=(0, 0), entry_font=None, width=12,
+#             frame_col=(1, 1, 1, 1), initial_text='', obscured=False,
+#             cmd=None, focus_in_cmd=None, focus_in_args=None,
+#             focus_out_cmd=None, focus_out_args=None, parent=None,
+#             tra_src=None, tra_tra=None, text_fg=(1, 1, 1, 1), on_tab=None,
+#             on_click=None):
+#         self.__focused = False
+#         self.__focus_in_cmd = focus_in_cmd
+#         self.__focus_out_cmd = focus_out_cmd
+#         DirectObject.__init__(self)
+#         Subject.__init__(self)
+#         focus_in_args = focus_in_args or []
+#         focus_out_args = focus_out_args or []
+#         self.wdg = DirectEntry(
+#             scale=scale, pos=(pos[0], 1, pos[1]), entryFont=entry_font,
+#             width=width, frameColor=frame_col, initialText=initial_text,
+#             obscured=obscured, command=cmd, focusInCommand=self._focus_in_cmd,
+#             focusInExtraArgs=focus_in_args,
+#             focusOutCommand=self._focus_out_cmd,
+#             focusOutExtraArgs=focus_out_args, parent=parent,
+#             text_fg=text_fg)
+#         P3dAbs.__init__(self, tra_src, tra_tra)
+#         if on_tab:
+#             self.on_tab_cb = on_tab
+#             self.accept('tab-up', self.on_tab)
+#         if on_click: self.wdg.bind(B1PRESS, on_click)
+
+#     def set(self, txt): return self.wdg.set(txt)
+
+#     def _focus_in_cmd(self, *args):
+#         self.__focused = True
+#         if self.__focus_in_cmd: self.__focus_in_cmd(*args)
+#         self.notify('on_entry_enter')
+
+#     def _focus_out_cmd(self, *args):
+#         self.__focused = False
+#         if self.__focus_out_cmd: self.__focus_out_cmd(*args)
+#         self.notify('on_entry_exit')
+
+#     def on_tab(self):
+#         if self.wdg['focus'] == ENTRY_FOCUS_STATE: self.on_tab_cb()
+
+#     @property
+#     def focused(self): return self.__focused
+
+#     @property
+#     def text(self): return self.wdg.get()
+
+#     def enter_text(self, txt):
+#         return self.wdg.enterText(txt)
+
+#     def enable(self): self['state'] = NORMAL
+
+#     def disable(self): self['state'] = DISABLED
+
+#     def destroy(self):
+#         self.ignore('tab-up')
+#         self.on_tab_cb = None
+#         Subject.destroy(self)
+#         P3dAbs.destroy(self)
+
+
+# class P3dLabel(P3dAbs):
+
+#     def __init__(
+#             self, text='', pos=(0, 0), parent=None, text_wordwrap=12,
+#             text_align=None, text_fg=(1, 1, 1, 1), text_font=None, scale=.05,
+#             frame_col=(1, 1, 1, 1), tra_src=None, tra_tra=None, hpr=(0, 0, 0)):
+#         self.wdg = DirectLabel(
+#             text=text, pos=(pos[0], 1, pos[1]), parent=parent,
+#             text_wordwrap=text_wordwrap, text_align=text_align,
+#             text_fg=text_fg, text_font=text_font, scale=scale,
+#             frameColor=frame_col, hpr=hpr)
+#         P3dAbs.__init__(self, tra_src, tra_tra)
+
+#     def set_bin(self, bin_name, priority): return self.wdg.set_bin(bin_name, priority)
+
+#     def set_x(self, x): return self.wdg.set_x(x)
+
+#     def set_alpha_scale(self, alpha): return self.wdg.set_alpha_scale(alpha)
+
+
+# class P3dTxt(P3dBase):
+
+#     def __init__(
+#             self, txt='', pos=(0, 0), scale=.05, wordwrap=12, parent=None,
+#             fg=(1, 1, 1, 1), font=None, align=None, tra_src=None,
+#             tra_tra=None):
+#         str2par = {'bottomleft': base.a2dBottomLeft,
+#                    'bottomright': base.a2dBottomRight,
+#                    'leftcenter': base.a2dLeftCenter}
+#         str2al = {'left': TextNode.A_left, 'right': TextNode.A_right,
+#                   'center': TextNode.A_center}
+#         if parent and parent in str2par: parent = str2par[parent]
+#         if align: align = str2al[align]
+#         self.wdg = OnscreenText(
+#             text=txt, pos=pos, scale=scale, wordwrap=wordwrap,
+#             parent=parent, fg=fg, font=font, align=align)
+#         P3dBase.__init__(self, tra_src, tra_tra)
+
+#     def set_r(self, r): return self.wdg.set_r(r)
+
+
+# class P3dFrame(P3dAbs):
+
+#     def __init__(self, frame_size=(-1, 1, -1, 1), frame_col=(1, 1, 1, 1),
+#                  pos=(0, 0), parent=None, texture_coord=False):
+#         P3dAbs.__init__(self)
+#         self.wdg = DirectFrame(frameSize=frame_size, frameColor=frame_col,
+#                                pos=(pos[0], 1, pos[1]), parent=parent)
+#         if texture_coord: self.wdg['frameTexture'] = Texture()
+
+
+# class P3dScrolledFrame(P3dAbs):
+
+#     def __init__(
+#             self, frame_sz=(-1, 1, -1, 1), canvas_sz=(0, 1, 0, 1),
+#             scrollbar_width=.05, frame_col=(1, 1, 1, 1),
+#             pos=(0, 0), parent='topleft'):
+#         P3dAbs.__init__(self)
+#         par2p3d = {'topleft': base.a2dTopLeft}
+#         if parent and parent in par2p3d: parent = par2p3d[parent]
+#         self.wdg = DirectScrolledFrame(
+#             frameSize=frame_sz,
+#             canvasSize=canvas_sz,
+#             scrollBarWidth=scrollbar_width,
+#             frameColor=frame_col,
+#             pos=(pos[0], 1, pos[1]),
+#             parent=parent)
+
+#     @property
+#     def canvas(self): return self.wdg.getCanvas()
diff --git a/ya2/p3d/p3d.py b/ya2/p3d/p3d.py
new file mode 100755 (executable)
index 0000000..f4115e9
--- /dev/null
@@ -0,0 +1,352 @@
+import sys
+from logging import info
+from os.path import exists, dirname
+from os import getcwd, _exit
+from glob import glob
+from pathlib import Path
+from panda3d.core import loadPrcFileData, Texture, TextPropertiesManager, \
+    TextProperties, PandaSystem, Filename, WindowProperties, GraphicsWindow
+from panda3d.bullet import get_bullet_version
+from direct.showbase.ShowBase import ShowBase
+from direct.showbase.DirectObject import DirectObject
+from direct.task.Task import Task
+#from gltf import patch_loader
+
+
+# class LibShowBase(ShowBase): pass
+
+
+class LibP3d(DirectObject):
+
+    task_cont = Task.cont
+
+    def __init__(self):
+        DirectObject.__init__(self)
+        self.__end_cb = self.__notify = None
+        self.__logged_keys = {}
+
+    @staticmethod
+    def runtime(): return not exists('main.py')
+
+    @staticmethod
+    def configure():
+        loadPrcFileData('', 'notify-level-ya2 info')
+        # loadPrcFileData('', 'gl-version 3 2')
+
+    @staticmethod
+    def fixpath(path):
+        home = '/home/flavio'
+        if sys.platform == 'win32' and not exists(exists(home + '/.wine/')):
+            if path.startswith('/'): path = path[1] + ':\\' + path[3:]
+            path = path.replace('/', '\\')
+        return path
+
+    @staticmethod
+    def p3dpath(path): return Filename.fromOsSpecific(path)
+
+    @property
+    def last_frame_dt(self): return globalClock.get_dt()
+
+    @property
+    def build_version(self):
+        appimg_mnt = glob('/tmp/.mount_Yocto*')
+        if appimg_mnt:
+            #with open(appimg_mnt[0] + '/usr/bin/appimage_version.txt') as fver:
+            with open(self.curr_path + '/assets/bld_version.txt') as fver:
+                return fver.read().strip()
+        try:
+            with open(self.curr_path + '/assets/bld_version.txt') as fver:
+                return fver.read().strip()
+        except FileNotFoundError:
+            info(self.curr_path + '/assets/bld_version.txt')
+            return 'notfound'
+
+    @property
+    def is_appimage(self):
+        par_path = str(Path(__file__).parent.absolute())
+        is_appimage = par_path.startswith('/tmp/.mount_Yocto')
+        return is_appimage and par_path.endswith('/usr/bin')
+
+    @property
+    def curr_path(self):
+        if sys.platform == 'darwin':
+            return dirname(__file__) + '/../Resources/'
+        # return dirname(__file__)
+        par_path = str(Path(__file__).parent.absolute())
+        if self.is_appimage:
+            return str(Path(par_path).absolute())
+        is_snap = par_path.startswith('/snap/')
+        is_snap = is_snap and par_path.endswith('/x1')
+        if is_snap:
+            return str(Path(par_path).absolute())
+        #return getcwd()
+        curr_path = dirname(__file__)
+        info('current path: %s' % curr_path)
+        return curr_path
+
+    @staticmethod
+    def send(msg): return messenger.send(msg)
+
+    @staticmethod
+    def do_later(time, meth, args=None):
+        args = args or []
+        return taskMgr.doMethodLater(
+            time, lambda meth, args: meth(*args), meth.__name__, [meth, args])
+
+    @staticmethod
+    def add_task(mth, priority=0):
+        return taskMgr.add(mth, mth.__name__, priority)
+
+    @staticmethod
+    def remove_task(tsk): taskMgr.remove(tsk)
+
+    def init(self, green=(.2, .8, .2, 1), red=(.8, .2, .2, 1), end_cb=None):
+        LibShowBase()
+        base.disableMouse()
+        #patch_loader(base.loader)
+        self.__end_cb = end_cb
+        self.__init_win()
+        self.__init_fonts(green, red)
+        self.__set_roots()
+        self.accept('aspectRatioChanged', self.on_aspect_ratio_changed)
+
+    @staticmethod
+    def __set_roots():
+        base.a2dTopQuarter = base.aspect2d.attachNewNode('a2dTopQuarter')
+        base.a2dTopQuarter.set_pos(base.a2dLeft / 2, 0, base.a2dTop)
+        base.a2dTopThirdQuarter = \
+            base.aspect2d.attachNewNode('a2dTopThirdQuarter')
+        base.a2dTopThirdQuarter.set_pos(base.a2dRight / 2, 0, base.a2dTop)
+        base.a2dCenterQuarter = base.aspect2d.attachNewNode('a2dCenterQuarter')
+        base.a2dCenterQuarter.set_pos(base.a2dLeft / 2, 0, 0)
+        base.a2dCenterThirdQuarter = \
+            base.aspect2d.attachNewNode('a2dCenterThirdQuarter')
+        base.a2dCenterThirdQuarter.set_pos(base.a2dRight / 2, 0, 0)
+        base.a2dBottomQuarter = base.aspect2d.attachNewNode('a2dBottomQuarter')
+        base.a2dBottomQuarter.set_pos(base.a2dLeft / 2, 0, base.a2dBottom)
+        base.a2dBottomThirdQuarter = \
+            base.aspect2d.attachNewNode('a2dBottomThirdQuarter')
+        base.a2dBottomThirdQuarter.set_pos(
+            base.a2dRight / 2, 0, base.a2dBottom)
+
+    @staticmethod
+    def on_aspect_ratio_changed():
+        base.a2dTopQuarter.set_pos(base.a2dLeft / 2, 0, base.a2dTop)
+        base.a2dTopThirdQuarter.set_pos(base.a2dRight / 2, 0, base.a2dTop)
+        base.a2dBottomQuarter.set_pos(base.a2dLeft / 2, 0, base.a2dBottom)
+        base.a2dBottomThirdQuarter.set_pos(
+            base.a2dRight / 2, 0, base.a2dBottom)
+
+    @property
+    def has_window(self): return bool(base.win)
+
+    @property
+    def resolution(self):
+        if not isinstance(base.win, GraphicsWindow):
+            return 800, 600
+        win_prop = base.win.get_properties()
+        return win_prop.get_x_size(), win_prop.get_y_size()
+
+    @property
+    def resolutions(self):
+        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)
+        ret = [res(idx) for idx in range(d_i.get_total_display_modes())]
+        return ret if ret else [self.resolution]
+
+    @staticmethod
+    def toggle_fullscreen():
+        props = WindowProperties()
+        props.set_fullscreen(not base.win.is_fullscreen())
+        base.win.request_properties(props)
+
+    @staticmethod
+    def set_resolution(res, fullscreen=None):
+        props = WindowProperties()
+        props.set_size(res)
+        if fullscreen: props.set_fullscreen(True)
+        if isinstance(base.win, GraphicsWindow):
+            base.win.request_properties(props)
+
+    def __init_win(self):
+        if base.win and isinstance(base.win, GraphicsWindow):
+            base.win.set_close_request_event('window-closed')
+        # not headless
+        self.accept('window-closed', self.__on_end)
+
+    @staticmethod
+    def __init_fonts(green=(.2, .8, .2, 1), red=(.8, .2, .2, 1)):
+        tp_mgr = TextPropertiesManager.get_global_ptr()
+        for namecol, col in zip(['green', 'red'], [green, red]):
+            props = TextProperties()
+            props.set_text_color(col)
+            tp_mgr.set_properties(namecol, props)
+        for namesize, col in zip(['small', 'smaller'], [.46, .72]):
+            props = TextProperties()
+            props.set_text_scale(.46)
+            tp_mgr.set_properties(namesize, props)
+        tp_italic = TextProperties()
+        tp_italic.set_slant(.2)
+        tp_mgr.set_properties('italic', tp_italic)
+
+    def __on_end(self):
+        base.closeWindow(base.win)
+        if self.__end_cb: self.__end_cb()
+        _exit(0)
+
+    @staticmethod
+    def load_font(filepath, outline=True):
+        font = base.loader.loadFont(filepath)
+        font.set_pixels_per_unit(60)
+        font.set_minfilter(Texture.FTLinearMipmapLinear)
+        if outline: font.set_outline((0, 0, 0, 1), .8, .2)
+        return font
+
+    @staticmethod
+    def log(msg): print(msg)
+
+    @property
+    def version(self): return PandaSystem.get_version_string()
+
+    @property
+    def lib_commit(self): return PandaSystem.get_git_commit()
+
+    @property
+    def phys_version(self): return get_bullet_version()
+
+    @property
+    def user_appdata_dir(self): return Filename.get_user_appdata_directory()
+
+    @property
+    def driver_vendor(self): return base.win.get_gsg().get_driver_vendor()
+
+    @property
+    def driver_renderer(self): return base.win.get_gsg().get_driver_renderer()
+
+    @property
+    def driver_shader_version_major(self):
+        return base.win.get_gsg().get_driver_shader_version_major()
+
+    @property
+    def driver_shader_version_minor(self):
+        return base.win.get_gsg().get_driver_shader_version_minor()
+
+    @property
+    def driver_version(self): return base.win.get_gsg().get_driver_version()
+
+    @property
+    def driver_version_major(self):
+        return base.win.get_gsg().get_driver_version_major()
+
+    @property
+    def driver_version_minor(self):
+        return base.win.get_gsg().get_driver_version_minor()
+
+    @property
+    def fullscreen(self):
+        if isinstance(base.win, GraphicsWindow):
+            return base.win.get_properties().get_fullscreen()
+
+    @property
+    def volume(self): return base.sfxManagerList[0].get_volume()
+
+    @volume.setter
+    def volume(self, vol): base.sfxManagerList[0].set_volume(vol)
+
+    @property
+    def mousepos(self):
+        mwn = base.mouseWatcherNode
+        if not mwn: return 0, 0
+        if not mwn.hasMouse(): return 0, 0
+        return mwn.get_mouse_x(), mwn.get_mouse_y()
+
+    @property
+    def aspect_ratio(self): return base.getAspectRatio()
+
+    @staticmethod
+    def set_icon(filename):
+        props = WindowProperties()
+        props.set_icon_filename(filename)
+        if isinstance(base.win, GraphicsWindow):
+            base.win.requestProperties(props)
+
+    @staticmethod
+    def __set_std_cursor(show):
+        props = WindowProperties()
+        props.set_cursor_hidden(not show)
+        if isinstance(base.win, GraphicsWindow):
+            base.win.requestProperties(props)
+
+    @staticmethod
+    def show_std_cursor(): LibP3d.__set_std_cursor(True)
+
+    @staticmethod
+    def hide_std_cursor(): LibP3d.__set_std_cursor(False)
+
+    @staticmethod
+    def find_geoms(model, name):  # no need to be cached
+        geoms = model.node.find_all_matches('**/+GeomNode')
+        is_nm = lambda geom: geom.get_name().startswith(name)
+        named_geoms = [geom for geom in geoms if is_nm(geom)]
+        return [ng for ng in named_geoms if name in ng.get_name()]
+
+    @staticmethod
+    def load_sfx(filepath, loop=False):
+        sfx = loader.loadSfx(filepath)
+        sfx.set_loop(loop)
+        return sfx
+
+    def remap_code(self, key):
+        kmap = base.win.get_keyboard_map()
+        for i in range(kmap.get_num_buttons()):
+            if key.lower() == kmap.get_mapped_button_label(i).lower():
+                self.__log_key(
+                    'code mapping %s to key %s' %
+                    (key, kmap.get_mapped_button(i)), key,
+                    kmap.get_mapped_button(i))
+                return kmap.get_mapped_button(i)
+        for i in range(kmap.get_num_buttons()):
+            if key.lower() == kmap.get_mapped_button(i).get_name().lower():
+                self.__log_key(
+                    'code mapping %s to key %s' %
+                    (key, kmap.get_mapped_button(i)), key,
+                    kmap.get_mapped_button(i))
+                return kmap.get_mapped_button(i)
+        self.__log_key('not found a code mapping for %s' %
+                       key, key, 'not_found')
+        return key
+
+    def remap_str(self, key):
+        if not base.win:  # when launched with --version
+            return key
+        #if isinstance(base.win, GraphicsBuffer):
+        #    return key
+        kmap = base.win.get_keyboard_map()
+        for i in range(kmap.get_num_buttons()):
+            if str(key).lower() == kmap.get_mapped_button_label(i).lower():
+                self.__log_key(
+                    'string mapping %s to key %s' %
+                    (key, kmap.get_mapped_button(i).get_name()), key,
+                    kmap.get_mapped_button(i).get_name())
+                return kmap.get_mapped_button(i).get_name()
+        for i in range(kmap.get_num_buttons()):
+            if key.lower() == kmap.get_mapped_button(i).get_name().lower():
+                self.__log_key(
+                    'string mapping %s to key %s' %
+                    (key, kmap.get_mapped_button(i).get_name()), key,
+                    kmap.get_mapped_button(i).get_name())
+                return kmap.get_mapped_button(i).get_name()
+        self.__log_key('not found a string mapping for %s' %
+                       key, key, kmap.get_mapped_button(i).get_name())
+        return key
+
+    def __log_key(self, msg, key1, key2):
+        if key1 in self.__logged_keys and self.__logged_keys[key1] == key2:
+            return
+        self.__logged_keys[key1] = key2
+        print(msg)
+
+    def destroy(self): pass
diff --git a/ya2/patterns/__init__.py b/ya2/patterns/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ya2/patterns/gameobject.py b/ya2/patterns/gameobject.py
new file mode 100644 (file)
index 0000000..6f9b292
--- /dev/null
@@ -0,0 +1,91 @@
+from direct.fsm.FSM import FSM
+from direct.showbase.DirectObject import DirectObject
+from ya2.patterns.observer import Subject
+
+
+class Colleague(Subject):
+
+    eng = None
+
+    def __init__(self, mediator):
+        Subject.__init__(self)
+        self.mediator = mediator  # refactor: remove it
+
+    def destroy(self):
+        self.mediator = None
+        Subject.destroy(self)
+
+
+class FsmColleague(FSM, Colleague):
+
+    def __init__(self, mediator):
+        FSM.__init__(self, self.__class__.__name__)
+        Colleague.__init__(self, mediator)
+
+    def destroy(self):
+        if self.state: self.cleanup()
+        Colleague.destroy(self)
+
+
+class EventColleague(Colleague, DirectObject):
+
+    def destroy(self):
+        self.ignoreAll()
+        Colleague.destroy(self)
+
+
+class AudioColleague(Colleague): pass
+
+
+class AiColleague(Colleague): pass
+
+
+class GfxColleague(Colleague): pass
+
+
+class GuiColleague(Colleague): pass
+
+
+class LogicColleague(Colleague):
+
+    def on_start(self): pass
+
+
+class PhysColleague(Colleague): pass
+
+
+class GODirector:
+
+    def __init__(self, tgt_obj, init_lst, end_cb):
+        self.__obj = tgt_obj
+        tgt_obj.attach(self.on_comp_blt)
+        self.end_cb = end_cb
+        self.completed = [False for _ in init_lst]
+        self.pending = {}
+        self.__init_lst = init_lst
+        for idx, _ in enumerate(init_lst): self.__process_lst(tgt_obj, idx)
+
+    def __process_lst(self, obj, idx):
+        if not self.__init_lst[idx]:
+            self.end_lst(idx)
+            return
+        comp_info = self.__init_lst[idx].pop(0)
+        attr_name, cls, arguments = comp_info
+        self.pending[cls.__name__] = idx
+        setattr(obj, attr_name, cls(*arguments))
+
+    def on_comp_blt(self, obj):
+        self.__process_lst(obj.mediator, self.pending[obj.__class__.__name__])
+
+    def end_lst(self, idx):
+        self.completed[idx] = True
+        if all(self.completed):
+            if self.end_cb: self.end_cb()
+            self.destroy()
+
+    def destroy(self):
+        self.__obj.detach(self.on_comp_blt)
+        self.__obj = self.end_cb = self.__init_lst = None
+
+
+class GameObject(Subject): pass
diff --git a/ya2/patterns/observer.py b/ya2/patterns/observer.py
new file mode 100644 (file)
index 0000000..7c49116
--- /dev/null
@@ -0,0 +1,54 @@
+class ObsInfo:
+
+    def __init__(self, mth, sort, args):
+        self.mth = mth
+        self.sort = sort
+        self.args = args
+
+    def __repr__(self): return str(self.mth)
+
+
+class Subject:
+
+    def __init__(self):
+        self.observers = {}
+
+    def attach(self, obs_meth, sort=10, rename='', args=None):
+        args = args or []
+        onm = rename or obs_meth.__name__
+        if onm not in self.observers: self.observers[onm] = []
+        self.observers[onm] += [ObsInfo(obs_meth, sort, args)]
+        sorted_obs = sorted(self.observers[onm], key=lambda obs: obs.sort)
+        self.observers[onm] = sorted_obs
+
+    def detach(self, obs_meth, lambda_call=None):
+        if isinstance(obs_meth, str):
+            onm = obs_meth
+            observers = [obs for obs in self.observers[onm]
+                         if obs.mth == lambda_call]
+        else:
+            onm = obs_meth.__name__
+            observers = [obs for obs in self.observers[onm]
+                         if obs.mth == obs_meth]
+        if not observers: raise Exception
+        list(map(self.observers[onm].remove, observers))
+
+    def notify(self, meth, *args, **kwargs):
+        if meth not in self.observers: return  # no obs for this notification
+        for obs in self.observers[meth][:]:
+            if obs in self.observers[meth]:  # if an obs removes another one
+                try:
+                    act_args = obs.args + list(args)
+                    obs.mth(*act_args, **kwargs)
+                except SystemError:
+                    print('Quit')
+                    import sys; sys.exit()
+
+    def observing(self, obs_meth):
+        if callable(obs_meth): obs_meth = obs_meth.__name__
+        return obs_meth in self.observers and self.observers[obs_meth]
+
+    def destroy(self): self.observers = None
+
+
+class Observer: pass
diff --git a/ya2/utils/__init__.py b/ya2/utils/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ya2/utils/cursor.py b/ya2/utils/cursor.py
new file mode 100644 (file)
index 0000000..36ff05b
--- /dev/null
@@ -0,0 +1,65 @@
+from panda3d.core import GraphicsWindow, WindowProperties
+from ya2.p3d.gui import P3dImg as Img
+from ya2.patterns.gameobject import GameObject
+
+
+class MouseCursorFacade:
+
+    def show(self):
+        if not self.eng.cfg.dev_cfg.functional_test:
+            return self.cursor_img.show()
+    def hide(self): return self.cursor_img.hide()
+
+
+class MouseCursor(GameObject, MouseCursorFacade):
+
+    def __init__(self, filepath, scale, color, hotspot):
+        GameObject.__init__(self)
+        MouseCursorFacade.__init__(self)
+        if not filepath: return
+        self.__set_std_cursor(False)
+        self.cursor_img = Img(filepath, scale=scale, foreground=True)
+        self.cursor_img.img.set_color(color)
+        #if self.eng.cfg.dev_cfg.functional_test:
+        #    self.cursor_img.hide()
+        self.hotspot_dx = scale[0] * (1 - 2 * hotspot[0])
+        self.hotspot_dy = scale[2] * (1 - 2 * hotspot[1])
+        #self.eng.attach_obs(self.on_frame)
+        #self.eng.attach_obs(self.on_frame_unpausable)
+        self._tsk = taskMgr.add(self.__on_frame, 'on frame cursor')
+
+    @staticmethod
+    def __set_std_cursor(show):
+        props = WindowProperties()
+        props.set_cursor_hidden(not show)
+        if isinstance(base.win, GraphicsWindow):
+            base.win.requestProperties(props)
+
+    #def show_standard(self): self.eng.lib.show_std_cursor()
+
+    #def hide_standard(self): self.eng.lib.hide_std_cursor()
+
+    #def cursor_top(self):
+    #    self.cursor_img.reparent_to(self.cursor_img.parent)
+
+    def __on_frame(self, task):
+        mwn = base.mouseWatcherNode
+        if not mwn or not mwn.hasMouse():
+            return task.again
+        mouse = mwn.get_mouse_x(), mwn.get_mouse_y()
+        h_x = mouse[0] * base.getAspectRatio() + self.hotspot_dx
+        self.cursor_img.set_pos((h_x, mouse[1] - self.hotspot_dy))
+        return task.again
+
+    #def on_frame(self):
+    #    if not self.eng.pause.paused: self.__on_frame()
+
+    #def on_frame_unpausable(self):
+    #    if self.eng.pause.paused: self.__on_frame()
+
+    def set_image(self, img):
+        self.cursor_img.img.set_texture(loader.load_texture(img), 1)
+
+    def destroy(self):
+        taskMgr.remove(self._tsk)
+        self.cursor_img.destroy()
diff --git a/ya2/utils/dictfile.py b/ya2/utils/dictfile.py
new file mode 100644 (file)
index 0000000..b30aefe
--- /dev/null
@@ -0,0 +1,96 @@
+import sys
+from logging import info
+from os import makedirs
+from os.path import dirname
+from collections.abc import Mapping
+from configparser import ConfigParser
+from json import load, dumps
+from ya2.patterns.gameobject import GameObject
+from ya2.p3d.p3d import LibP3d
+
+
+class DctFile(GameObject):
+
+    def __init__(self, fpath, default_dct=None, persistent=True):
+        GameObject.__init__(self)
+        default_dct = default_dct or {}
+        if sys.platform == 'darwin' and LibP3d.runtime():
+            fpath = dirname(__file__) + '/' + fpath
+        self.fpath = fpath
+        self.persistent = persistent
+        try:
+            #with open(fpath) as json: fdct = load(json)
+            config = ConfigParser()
+            config.read(fpath)
+            fdct = {section: dict(config.items(section)) for section in config.sections()}
+            fdct = self.__typed_dct(fdct)
+            self.dct = self.__add_default(default_dct, fdct)
+        except IOError: self.dct = default_dct
+
+    @staticmethod
+    def __typed_dct(dct):
+        def convert_single_val(val):
+            try: return int(val)
+            except ValueError:
+                try: return float(val)
+                except ValueError:
+                    if not val or val[0] != '[':
+                        return val
+                    else:
+                        raise ValueError
+        def converted(val):
+            try: return convert_single_val(val)
+            except ValueError:
+                return [elm.strip() for elm in val[1:-1].split(',')]
+        new_dct = {}
+        for section, sec_dct in dct.items():
+            for key, val in sec_dct.items():
+                if section not in new_dct:
+                    new_dct[section] = {}
+                new_dct[section][key] = converted(val)
+        return new_dct
+
+    @staticmethod
+    def __add_default(dct, upd):
+        for key, val in upd.items():
+            if isinstance(val, Mapping):
+                dct[key] = DctFile.__add_default(dct.get(key, {}), val)
+            else: dct[key] = upd[key]
+        return dct
+
+    @staticmethod
+    def deepupdate(dct, new_dct):
+        for key, val in new_dct.items():
+            if isinstance(val, Mapping):
+                dct[key] = DctFile.deepupdate(dct.get(key, {}), val)
+            else: dct[key] = val
+        return dct
+
+    def store(self):
+        info('storing %s' % self.fpath)
+        if not self.persistent: return
+        #json_str = dumps(self.dct, sort_keys=True, indent=4,
+        #                 separators=(',', ': '))
+        #with open(self.fpath, 'w') as json: json.write(json_str)
+        fdct = {}
+        for section, sec_dct in self.dct.items():
+            if section not in fdct:
+                fdct[section] = {}
+            for key, val in sec_dct.items():
+                if type(val) == list:
+                    fdct[section][key] = '[%s]' % ', '.join(val)
+                else:
+                    fdct[section][key] = val
+        config = ConfigParser()
+        for key in self.dct:
+            config[key] = fdct[key]
+        if dirname(self.fpath):
+            makedirs(dirname(self.fpath), exist_ok=True)
+        with open(self.fpath, 'w') as ini_file:
+            config.write(ini_file)
+
+    def __getitem__(self, arg): return self.dct[arg]
+
+    def __setitem__(self, arg, val): self.dct[arg] = val
+
+    def __delitem__(self, arg): del self.dct[arg]
diff --git a/ya2/utils/functional.py b/ya2/utils/functional.py
new file mode 100644 (file)
index 0000000..3ac3995
--- /dev/null
@@ -0,0 +1,652 @@
+import datetime
+from os import getcwd, system
+from logging import debug, info
+from pathlib import Path
+from shutil import rmtree
+from os import makedirs
+from os.path import join, exists
+from glob import glob
+from sys import exit
+from multiprocessing.connection import Listener
+from threading import Thread
+from panda3d.core import Filename
+from direct.gui.OnscreenText import OnscreenText
+from ya2.patterns.gameobject import GameObject
+from ya2.build.build import _branch
+
+
+class ListenerThread(Thread):
+
+    def __init__(self, callbacks):
+        Thread.__init__(self)
+        address = ('localhost', 6000)
+        self._listener = Listener(address)
+        self._listener._listener._socket.settimeout(15)
+        try:
+            self._conn = self._listener.accept()
+        except TimeoutError:
+            info('listener timeout')
+        self._callbacks = callbacks
+
+    def run(self):
+        running = hasattr(self, '_conn')
+        while running:
+            try:
+                msg = self._conn.recv()
+                if msg[0] == 'screenshot':
+                    taskMgr.doMethodLater(.01, self._callbacks[0], 'cb0', [msg[1]])
+                elif msg[0] == 'enforce_res':
+                    taskMgr.doMethodLater(.01, self._callbacks[1], 'cb1', [msg[1]])
+                elif msg[0] == 'verify':
+                    taskMgr.doMethodLater(.01, self._callbacks[2], 'cb2')
+                elif msg[0] == 'set_idx':
+                    taskMgr.doMethodLater(.01, self._callbacks[3], 'cb3', [msg[1]])
+                elif msg[0] == 'enforce_resolution':
+                    taskMgr.doMethodLater(.01, self._callbacks[4], 'cb4', [msg[1]])
+            except EOFError:
+                running = False
+
+
+class FunctionalTest(GameObject):
+
+    def __init__(self, ref):
+        super().__init__()
+        self._listener = ListenerThread([self._do_screenshot, self._do_enforce_res, self.__verify, self._set_idx, self._do_enforce_resolution])
+        self._listener.start()
+        self.txt = OnscreenText('', fg=(1, 0, 0, 1), scale=.16)
+        #self._path = ''
+        #if self.eng.is_appimage:
+        self._path = str(Filename().get_user_appdata_directory())
+        self._path += '/pmachines/'
+        self._path += 'tests/functional%s/' % ('_ref' if ref else '')
+        home = '/home/flavio'  # we must force this for wine
+        # if self._path.startswith('/c/users/') and exists(str(Path.home()) + '/.local/share/flatpak-wine601/default/'):
+        #     self._path = str(Path.home()) + '/.local/share/flatpak-wine601/default/drive_' + self._path[1:]
+        if self._path.startswith('/c/users/') and exists(home + '/.wine/'):
+            self._path = home + '/.wine/drive_' + self._path[1:]
+        if ref:
+            self._path = join(
+                Filename().get_user_appdata_directory(),
+                'pmachines/tests/functional_ref_%s/' % _branch())
+        self._fnames = []
+        #taskMgr.add(self.on_frame_unpausable, 'on-frame-unpausable')
+        #self._do_screenshots(idx)
+
+    def _set_idx(self, idx):
+        if int(idx) == 1:
+            rmtree(self._path, ignore_errors=True)
+        info('creating dir: %s' % self._path)
+        makedirs(self._path, exist_ok=True)
+
+    def _do_screenshot(self, name):
+        self._fnames += [self._path + name]
+        #time = datetime.datetime.now().strftime('%y%m%d%H%M%S')
+        #res = base.win.save_screenshot(Filename(path or ("yocto%s.png" % time)))
+        #debug('screenshot %s (%s)' % (path or ("yocto%s.png" % time), res))
+        res = base.screenshot(self._path + name, False)
+        info('screenshot %s (%s; %s)' % (self._path + name, res, getcwd()))
+
+    def _do_enforce_res(self, res):
+        info('enforce_res %s' % res)
+        messenger.send('enforce_res', [res])
+
+    def _do_enforce_resolution(self, res):
+        info('enforce resolution %s (callback)' % res)
+        messenger.send('enforce_resolution', [res])
+
+    #def _screenshot(self, time, name):
+        #self._fnames += [self._path + name + '.png']
+        #self._tasks += [(
+        #    self._curr_time + time,
+        #    lambda: self._do_screenshot(self._path + name + '.png'),
+        #    'screenshot: %s' % name)]
+        #def txt(show_hide):
+        #    self.txt['text'] = name
+        #    (self.txt.show if show_hide else self.txt.hide)()
+        #self._tasks += [(
+        #    self._curr_time + time + .1,
+        #    lambda: txt(True),
+        #    'screenshot: %s (show)' % name)]
+        #self._tasks += [(
+        #    self._curr_time + time + FunctionalTest.evt_time - .1,
+        #    lambda: txt(False),
+        #    'screenshot: %s (hide)' % name)]
+        #self._curr_time += time
+
+    #def __keypress(self, key):
+        #'''Emulates a keypress'''
+        #dev = base.win.getInputDevice(0)
+        #dev.buttonDown(key)
+        #dev.buttonUp(key)
+
+    #def __char_entered(self, char):
+        #'''Emulates a character being entered.'''
+        #dev = base.win.getInputDevice(0)
+        #dev.keystroke(ord(char))
+
+    # def _event(self, time, evt, messenger_evt=False, append_up=True, mouse_args=None):
+    #     def _append_up(evt_name):
+    #         return evt + ('' if evt.endswith('-up') or not append_up else '-up')
+    #     def cback_char(_evt):
+    #         self.__char_entered(_evt)
+    #     def cback_keyp(_evt):
+    #         self.__keypress(_evt)
+    #         self.__keypress('raw-' + _evt)
+    #     cback = lambda: (cback_char(evt) if len(evt) == 1 else cback_keyp(evt))
+    #     if evt in ['mousemove', 'mouseclick', 'mousedrag']:
+    #         if evt == 'mousemove':
+    #             cback = lambda: self.__mouse_move(*mouse_args)
+    #         elif evt == 'mouseclick':
+    #             cback = lambda: self.__mouse_click(*mouse_args)
+    #         elif evt == 'mousedrag':
+    #             cback = lambda: self.__mouse_drag(*mouse_args)
+    #     if messenger_evt:
+    #         cback = lambda: messenger.send(_append_up(evt))
+    #     self._tasks += [(
+    #         self._curr_time + time,
+    #         cback,
+    #         'event: %s' % evt)]
+    #     def txt(show_hide):
+    #         self.txt['text'] = evt
+    #         (self.txt.show if show_hide else self.txt.hide)()
+    #     self._tasks += [(
+    #         self._curr_time + time + .2,
+    #         lambda: txt(True),
+    #         'event: %s (show)' % evt)]
+    #     self._tasks += [(
+    #         self._curr_time + time + .8,
+    #         lambda: txt(False),
+    #         'event: %s (hide)' % evt)]
+    #     self._curr_time += time
+
+    # def _enforce_res(self, time, res):
+    #     cback = lambda: messenger.send('enforce_res', [res])
+    #     self._tasks += [(
+    #         self._curr_time + time,
+    #         cback,
+    #         'enforce res: %s' % res)]
+    #     self._curr_time += time
+
+    def __verify(self, task):
+        files = glob(self._path + '*')
+        for fname in self._fnames:
+            info('verifying %s' % fname)
+            assert exists(fname)
+
+    #def on_frame_unpausable(self, task):
+        #self._process_conn()
+        #for tsk in self._tasks:
+        #    #if self._prev_time <= tsk[0] < self.eng.event.unpaused_time:
+        #    if self._prev_time <= tsk[0] < globalClock.getFrameTime():
+        #        debug('%s %s' % (tsk[0], tsk[2]))
+        #        tsk[1]()
+        #self._prev_time = globalClock.getFrameTime()  # self.eng.event.unpaused_time
+        #return task.cont
+
+    # def _do_screenshots_1(self):
+    #     info('_do_screenshots_1')
+    #     self._screenshot(FunctionalTest.start_time, 'main_menu')
+    #     self._do_screenshots_credits()
+    #     self._do_screenshots_options()
+    #     self._do_screenshots_exit()
+
+    # def _do_screenshots_credits(self):
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'credits_menu')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 680), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'main_menu_back_from_credits')
+    #     # # go to credits
+    #     # self._event(FunctionalTest.evt_time, 'joypad0-dpad_down', True)
+    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
+    #     # self._event(FunctionalTest.evt_time, 'joypad0-dpad_down', True)
+    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'main_menu_highlight')
+    #     # self._event(FunctionalTest.evt_time, 'rcontrol')
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'credits_menu')
+    #     # # go to supporters
+    #     # self._event(FunctionalTest.evt_time, 'joypad0-face_a', True)
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'supporters_menu')
+    #     # # back to main
+    #     # self._event(FunctionalTest.evt_time, 'rcontrol')
+    #     # self._event(FunctionalTest.evt_time, 'joypad0-face_b', True)
+    #     # self._event(FunctionalTest.evt_time, 'arrow_up')
+    #     # self._event(FunctionalTest.evt_time, 'arrow_up')
+    #     # self._event(FunctionalTest.evt_time, 'arrow_up')
+    #     # self._event(FunctionalTest.evt_time, 'arrow_up')
+
+    # def _do_screenshots_options(self):
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 300), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu')
+    #     # languages
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 60), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'open_languages')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(980, 120), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_italian')
+    #     # volume
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(740, 163), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_drag_1')
+    #     # antialiasing
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 440), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'antialiasing_no')
+    #     # shadows
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 540), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'shadows_no')
+    #     # test aa and shadows
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 680), 'left'])  # back
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(230, 160), 'left'])  # domino
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(900, 490), 'left'])  # close instructions
+    #     self._screenshot(FunctionalTest.screenshot_time, 'aa_no_shadows_no')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(25, 740), 'left'])  # home
+
+    # def _do_screenshots_restore_options(self):
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 300), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_restored')
+    #     # languages
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 60), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'open_languages_restored')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(980, 20), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_english')
+    #     # volume
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(719, 163), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_drag_2')
+    #     # fullscreen
+    #     # the first one is because of the windowed mode in test
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 250), 'left'])
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'fullscreen')
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 250), 'left'])
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'fullscreen')
+    #     # self._event(8 + FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 250), 'left'])
+    #     # self._screenshot(8 + FunctionalTest.screenshot_time, 'back_from_fullscreen')
+    #     # resolution
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 340), 'left'])
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'resolutions')
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1020, 160), 'left'])
+    #     # self._screenshot(FunctionalTest.screenshot_time, '1440x900')
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(740, 400), 'left'])
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'resolutions_2')
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1110, 80), 'left'])
+    #     # self._screenshot(FunctionalTest.screenshot_time, '1360x768')
+    #     # antialiasing
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 440), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'antialiasing_yes')
+    #     # shadows
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 540), 'left'])
+    #     self._screenshot(FunctionalTest.screenshot_time, 'shadows_yes')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 680), 'left'])  # back
+
+    # #     # go to options
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu')
+    # #     # language
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'language_open')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'language_highlight')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'language_it')
+    # #     # volume
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_right')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_right')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'volume')
+    # #     # car's number
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'cars_open')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'cars_changed')
+    # #     # back
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
+
+    # def _do_screenshots_play(self):
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
+    #     self._screenshot(FunctionalTest.screenshot_time, 'play_menu')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 680), 'left'])  # back
+    #     self._screenshot(FunctionalTest.screenshot_time, 'back_from_play')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(230, 160), 'left'])  # domino scene
+    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_domino_instructions')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(850, 490), 'left'])  # close instructions
+    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_domino')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(25, 740), 'left'])  # home
+    #     self._screenshot(FunctionalTest.screenshot_time, 'home_back_from_scene')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(230, 160), 'left'])  # domino
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(850, 490), 'left'])  # close instructions
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(70, 740), 'left'])  # info
+    #     self._screenshot(FunctionalTest.screenshot_time, 'info')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(850, 490), 'left'])  # close instructions
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (430, 280), 'left'])  # drag a piece
+    #     self._screenshot(FunctionalTest.screenshot_time, 'domino_dragged')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1220, 740), 'left'])  # rewind
+    #     self._screenshot(FunctionalTest.screenshot_time, 'rewind')
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (550, 380), 'left'])  # drag a piece
+    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (715, 380), 'left'])  # drag a piece
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(630, 450), 'left'])  # home
+    #     self._screenshot(FunctionalTest.screenshot_time, 'home_back_from_fail')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 140), 'left'])  # play
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(230, 160), 'left'])  # domino
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(850, 490), 'left'])  # close instructions
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (550, 380), 'left'])  # drag a piece
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (715, 380), 'left'])  # drag a piece
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino_2')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (570, 380), 'left'])  # drag a piece
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(570, 355), (605, 355), 'right'])  # rotate the piece
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(35, 60), (715, 380), 'left'])  # drag a piece
+    #     self._enforce_res(FunctionalTest.evt_time, 'win')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_domino')
+    #     self._enforce_res(FunctionalTest.evt_time, '')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
+    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_box')
+    #     # scene 2
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(880, 490), 'left'])  # close instructions
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (710, 620), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (710, 540), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_box')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (710, 620), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (710, 540), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (705, 460), 'left'])  # drag a box
+    #     self._enforce_res(FunctionalTest.evt_time, 'win')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_box')
+    #     self._enforce_res(FunctionalTest.evt_time, '')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
+    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_box_domino')
+    #     # scene 3
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(930, 485), 'left'])  # close instructions
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (910, 440), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (910, 360), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_box_domino')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (910, 440), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (835, 250), 'left'])  # drag a box
+    #     self._enforce_res(FunctionalTest.evt_time, 'win')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_box_domino')
+    #     self._enforce_res(FunctionalTest.evt_time, '')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
+    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_basketball')
+    #     # scene 4
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(870, 490), 'left'])  # close instructions
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(55, 50), (650, 310), 'left'])  # drag a ball
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_basketball')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(55, 50), (380, 50), 'left'])  # drag a ball
+    #     self._enforce_res(FunctionalTest.evt_time, 'win')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_basketball')
+    #     self._enforce_res(FunctionalTest.evt_time, '')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
+    #     self._screenshot(FunctionalTest.screenshot_time, 'scene_domino_box_basketball')
+    #     # scene 5
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(865, 490), 'left'])  # close instructions
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (580, 440), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(30, 60), (590, 370), 'left'])  # drag a piece
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_domino_box_basketball')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 450), 'left'])  # replay
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(65, 60), (580, 440), 'left'])  # drag a box
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(30, 60), (660, 440), 'left'])  # drag a piece
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(660, 425), (625, 425), 'right'])  # rotate a piece
+    #     self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(660, 435), (650, 445), 'left'])  # drag a piece
+    #     self._enforce_res(FunctionalTest.evt_time, 'win')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1340, 740), 'left'])  # play
+    #     self._screenshot(16 + FunctionalTest.screenshot_time, 'win_domino_box_basketball')
+    #     self._enforce_res(FunctionalTest.evt_time, '')
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(735, 450), 'left'])  # next
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'scene_teeter_tooter')
+    #     # # scene 6
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(820, 455), 'left'])  # close instructions
+    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(60, 60), (490, 300), 'left'])  # drag a box
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1260, 695), 'left'])  # play
+    #     # self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_teeter_tooter')
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(640, 420), 'left'])  # replay
+    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(60, 60), (490, 150), 'left'])  # drag a box
+    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(515, 115), (515, 122), 'right'])  # rotate a box
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1260, 695), 'left'])  # play
+    #     # self._screenshot(16 + FunctionalTest.screenshot_time, 'win_teeter_tooter')
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(690, 420), 'left'])  # next
+    #     # self._screenshot(FunctionalTest.screenshot_time, 'scene_teeter_domino_box_basketball')
+    #     # # scene 7
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(880, 455), 'left'])  # close instructions
+    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(60, 60), (155, 180), 'left'])  # drag a box
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1260, 695), 'left'])  # play
+    #     # self._screenshot(16 + FunctionalTest.screenshot_time, 'fail_teeter_domino_box_basketball')
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(640, 420), 'left'])  # replay
+    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(60, 60), (170, 80), 'left'])  # drag a box
+    #     # self._event(FunctionalTest.evt_time, 'mousedrag', False, False, [(195, 50), (195, 80), 'right'])  # rotate a box
+    #     # self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(1260, 695), 'left'])  # play
+    #     # self._screenshot(16 + FunctionalTest.screenshot_time, 'win_teeter_domino_box_basketball')
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(630, 450), 'left'])  # home
+    #     self._screenshot(FunctionalTest.screenshot_time, 'home_from_play')
+
+    # def _do_screenshots_exit(self):
+    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
+    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
+    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
+    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
+    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
+    #     # self._event(FunctionalTest.evt_time, 'rcontrol')
+    #     # self._event(FunctionalTest.evt_time, 'arrow_down')
+    #     self._verify()
+    #     # self._event(FunctionalTest.evt_time, 'rcontrol')
+    #     # self._exit()
+    #     self._event(FunctionalTest.evt_time, 'mouseclick', False, False, [(680, 600), 'left'])
+
+
+    # def _do_screenshots_2(self):
+    #     info('_do_screenshots_2')
+    #     self._screenshot(FunctionalTest.start_time, 'main_menu_2')
+    #     self._do_screenshots_restore_options()
+    #     self._do_screenshots_play()
+    #     self._do_screenshots_exit()
+    # #     self._do_screenshots_game()
+    # #     self._do_screenshots_end()
+
+    # # def _do_screenshots_restore_options(self):
+    # #     # go to options
+    # #     self._event(FunctionalTest.evt_time, 'joypad0-dpad_down', True)
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'options_menu_restored')
+    # #     # # language
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'language_en_restored')
+    # #     # # volume
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'volume_restored')
+    # #     # car's number
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'cars_restored')
+    # #     # graphics settings
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'graphics_settings')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'antialiasing')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'shadows')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'fog')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'normal_mapping')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'occlusion')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     # input
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'input')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_rec')
+    # #     self._event(FunctionalTest.evt_time, '8', True, False)
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_changed')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_up', True, False)
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_restored')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'w', True, False)
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_already')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p1_already_closed')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p2')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p3')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'keyboard_p4')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
+
+    # # def _do_screenshots_game(self):
+    # #     # single player
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'single_player_menu')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'track_page')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'car_page_start')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'car_page_sel')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_start')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_left')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_up')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_entry')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._event(FunctionalTest.evt_time, 'backspace')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_entry_empty')
+    # #     self._event(FunctionalTest.evt_time, 'f')
+    # #     self._event(FunctionalTest.evt_time, 'l')
+    # #     self._event(FunctionalTest.evt_time, 'a')
+    # #     self._event(FunctionalTest.evt_time, 'v')
+    # #     self._event(FunctionalTest.evt_time, 'i')
+    # #     self._event(FunctionalTest.evt_time, 'o')
+    # #     self._event(FunctionalTest.evt_time, 'enter')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_entry_full')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_right')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'driver_page_sel')
+    # #     # some ai tests
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._event(40, 'escape-up')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'ingame_menu')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'race_back')
+    # #     self._event(FunctionalTest.evt_time, 'escape-up')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'ingame_sel')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'main_page_back_race')
+
+    # # def _do_screenshots_end(self):
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'exit_page')
+    # #     self._event(FunctionalTest.evt_time, 'arrow_down')
+    # #     self._screenshot(FunctionalTest.screenshot_time, 'exit_page_sel')
+    # #     self._verify()
+    # #     self._event(FunctionalTest.evt_time, 'rcontrol')
+    # #     self._exit()
+
+    # def _do_screenshots(self, idx):
+    #     [self._do_screenshots_1, self._do_screenshots_2][int(idx) - 1]()
diff --git a/ya2/utils/lang.py b/ya2/utils/lang.py
new file mode 100644 (file)
index 0000000..bbfa49b
--- /dev/null
@@ -0,0 +1,55 @@
+from logging import info
+from os.path import join, exists, dirname
+from gettext import translation
+from pathlib import Path
+from ya2.patterns.gameobject import GameObject
+import sys
+
+
+def is_runtime(): return not exists('main.py')
+
+
+def is_appimage():
+    par_path = str(Path(__file__).parent.absolute())
+    is_appimage = par_path.startswith('/tmp/.mount_Pmachines')
+    return is_appimage and par_path.endswith('/usr/bin')
+
+
+def curr_path():
+    if not is_runtime(): return ''
+    if sys.platform == 'darwin':
+        return dirname(__file__) + '/../Resources/'
+    # return dirname(__file__)
+    par_path = str(Path(__file__).parent.absolute())
+    if is_appimage():
+        return str(Path(par_path).absolute())
+    is_snap = par_path.startswith('/snap/')
+    is_snap = is_snap and par_path.endswith('/x1')
+    if is_snap:
+        return str(Path(par_path).absolute())
+    #return getcwd()
+    curr_path = dirname(__file__)
+    info('current path: %s' % curr_path)
+    return curr_path + '/'
+
+
+class LangMgr(GameObject):
+
+    def __init__(self, lang, domain, dpath):
+        GameObject.__init__(self)
+        self.lang = lang
+        self.domain = domain
+        self.dpath = join(curr_path(), dpath)
+        info('language: %s, %s' % (self.domain, self.dpath))
+        self.set_lang(lang)
+
+    @property
+    def lang_codes(self):
+        return [lang[1] for lang in self.eng.cfg.lang_cfg.languages]
+
+    def set_lang(self, lang):
+        self.lang = lang
+        args = lang, self.domain, self.dpath
+        info('setting language %s, %s, %s' % args)
+        tra = translation(self.domain, self.dpath, [lang], fallback=True)
+        tra.install()
diff --git a/ya2/utils/log.py b/ya2/utils/log.py
new file mode 100755 (executable)
index 0000000..68fa628
--- /dev/null
@@ -0,0 +1,201 @@
+from logging import basicConfig, info, INFO, DEBUG, getLogger
+from configparser import ConfigParser
+from sys import platform, argv
+from platform import system
+from pathlib import Path
+from glob import glob
+from json import load, dumps
+#from datetime import datetime
+from pprint import pprint
+from os import getcwd, environ
+from os.path import exists, dirname
+from traceback import print_stack
+from sys import version_info
+# from platform import system, release, architecture, platform, processor, \
+#     version, machine
+# from multiprocessing import cpu_count
+from panda3d.core import Filename, GraphicsWindow, PandaSystem
+from panda3d.bullet import get_bullet_version
+from ya2.patterns.gameobject import Colleague
+from ya2.p3d.p3d import LibP3d
+import sys
+
+
+lev = INFO
+opt_path = ''
+if platform in ['win32', 'linux'] and not exists('main.py'):
+    # it is the deployed version for windows
+    opt_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
+opath = LibP3d.fixpath(opt_path + '/options.ini') if opt_path else \
+        'options.ini'
+if exists(opath):
+    with open(opath) as json_file:
+        #optfile = load(json_file)
+        optfile = ConfigParser()
+        optfile.read(opath)
+        # optfile['development']['verbose'] and int(optfile['development']['verbose']) or \
+        if optfile['development']['verbose_log'] and int(optfile['development']['verbose_log']):
+            lev = DEBUG
+
+basicConfig(level=lev, format='%(asctime)s %(message)s', datefmt='%H:%M:%S')
+getLogger().setLevel(lev)  # it doesn't work otherwise
+
+
+class LogMgrBase(Colleague):  # headless log manager
+
+    @staticmethod
+    def init_cls():
+        return LogMgr if base.win else LogMgrBase
+
+    def __init__(self, mediator):
+        Colleague.__init__(self, mediator)
+        self.log_cfg()
+
+    def log(self, msg, verbose=False):
+        if verbose and not self.eng.cfg.dev_cfg.verbose_log: return
+        info(msg)
+
+    @property
+    def is_appimage(self):
+        par_path = str(Path(__file__).parent.absolute())
+        is_appimage = par_path.startswith('/tmp/.mount_Pmachi')
+        return is_appimage and par_path.endswith('/usr/bin')
+
+    # @property
+    # def curr_path(self):
+    #     # this is different from the music's one since it does not work
+    #     # with the version in windows
+    #     if sys.platform == 'darwin':
+    #         return dirname(__file__) + '/../Resources/'
+    #     # return dirname(__file__)
+    #     par_path = str(Path(__file__).parent.absolute())
+    #     if self.is_appimage:
+    #         return str(Path(par_path).absolute())
+    #     is_snap = par_path.startswith('/snap/')
+    #     is_snap = is_snap and par_path.endswith('/x1')
+    #     if is_snap:
+    #         return str(Path(par_path).absolute())
+    #     #return getcwd()
+    #     #curr_path = dirname(__file__)
+    #     curr_path = str(Path(__file__).parent.parent.parent.absolute())
+    #     info('current path: %s' % curr_path)
+    #     return curr_path
+
+    @property
+    def curr_path(self):
+        if system() == 'Windows':
+            return ''
+        if exists('main.py'):
+            return ''
+        else:
+            par_path = str(Path(__file__).parent.absolute())
+        if self.is_appimage:
+            par_path = str(Path(par_path).absolute())
+        par_path += '/'
+        return par_path
+
+    @property
+    def build_version(self):
+        appimg_mnt = glob('/tmp/.mount_Pmachi*')
+        if appimg_mnt:
+            #with open(appimg_mnt[0] + '/usr/bin/appimage_version.txt') as fver:
+            with open(self.curr_path + 'assets/bld_version.txt') as fver:
+                return fver.read().strip()
+        try:
+            with open(self.curr_path + 'assets/bld_version.txt') as fver:
+                return fver.read().strip()
+        except FileNotFoundError:
+            info('not found ' + self.curr_path + 'assets/bld_version.txt')
+            return 'notfound'
+
+    def log_cfg(self):
+        if '--version' in argv:
+            path = str(Filename.get_user_appdata_directory())
+            home = '/home/flavio'  # we must force this for wine
+            if path.startswith('/c/users/') and exists(home + '/.wine/'):
+                path = home + '/.wine/drive_' + path[1:]
+            info('writing %s' % path + '/pmachines/obs_version.txt')
+            with open(path + '/pmachines/obs_version.txt', 'w') as f:
+                #f.write(self.eng.logic.version)
+                f.write(self.build_version)
+            if not platform.startswith('win'):
+                from os import ttyname  # here because it doesn't work on windows
+                import sys
+                try:
+                    with open(ttyname(0), 'w') as fout:
+                        sys.stdout = fout
+                        print('version: ' + self.build_version)  # self.eng.logic.version)
+                except OSError:  # it doesn't work with crontab
+                    print('version: ' + self.build_version)
+        messages = ['version: ' + self.build_version]  # self.eng.logic.version]
+        messages += ['argv[0]: %s' % argv[0]]
+        messages += ['getcwd: %s' % getcwd()]
+        messages += ['__file__: %s' % __file__]
+        for elm in environ.items():
+            messages += ['env::%s: %s' % elm]
+        # os_info = (system(), release(), version())
+        # messages += ['operative system: %s %s %s' % os_info]
+        # messages += ['architecture: ' + str(architecture())]
+        # messages += ['machine: ' + machine()]
+        # messages += ['platform: ' + platform()]
+        # messages += ['processor: ' + processor()]
+        # try:
+        #     messages += ['cores: ' + str(cpu_count())]
+        # except NotImplementedError:  # on Windows
+        #     messages += ['cores: not implemented']
+        lib_ver = PandaSystem.get_version_string()
+        try:
+            import psutil
+            mem = psutil.virtual_memory().total / 1000000000.0
+            messages += ['memory: %s GB' % round(mem, 2)]
+        except ImportError: info("can't import psutil")  # windows
+        lib_commit = PandaSystem.get_git_commit()
+        py_ver = [str(elm) for elm in version_info[:3]]
+        messages += ['python version: %s' % '.'.join(py_ver)]
+        messages += ['panda version: %s %s' % (lib_ver, lib_commit)]
+        messages += ['bullet version: ' + str(get_bullet_version())]
+        messages += ['appdata: ' + str(Filename.get_user_appdata_directory())]
+        if base.win and isinstance(base.win, GraphicsWindow):  # not headless
+            print(base.win.get_keyboard_map())
+        list(map(self.log, messages))
+
+    @staticmethod
+    def log_tasks():
+        info('tasks: %s' % taskMgr.getAllTasks())
+        info('do-laters: %s' % taskMgr.getDoLaters())
+
+    @staticmethod
+    def plog(obj):
+        print('\n\n')
+        print_stack()
+        pprint(obj)
+        print('\n\n')
+
+
+class LogMgr(LogMgrBase):
+
+    def log_cfg(self):
+        LogMgrBase.log_cfg(self)
+        messages = [base.win.get_gsg().get_driver_vendor()]
+        messages += [base.win.get_gsg().get_driver_renderer()]
+        shad_maj = base.win.get_gsg().get_driver_shader_version_major()
+        shad_min = base.win.get_gsg().get_driver_shader_version_minor()
+        messages += ['shader: {maj}.{min}'.format(maj=shad_maj, min=shad_min)]
+        messages += [base.win.get_gsg().get_driver_version()]
+        drv_maj = base.win.get_gsg().get_driver_version_major()
+        drv_min = base.win.get_gsg().get_driver_version_minor()
+        drv = 'driver version: {maj}.{min}'
+        messages += [drv.format(maj=drv_maj, min=drv_min)]
+        fullscreen = None
+        if isinstance(base.win, GraphicsWindow):
+            fullscreen = base.win.get_properties().get_fullscreen()
+        messages += ['fullscreen: ' + str(fullscreen)]
+        def resolution():
+            if not isinstance(base.win, GraphicsWindow):
+                return 800, 600
+            win_prop = base.win.get_properties()
+            return win_prop.get_x_size(), win_prop.get_y_size()
+        res_x, res_y = resolution()
+        res_tmpl = 'resolution: {res_x}x{res_y}'
+        messages += [res_tmpl.format(res_x=res_x, res_y=res_y)]
+        list(map(self.log, messages))