-from os.path import exists
-from os import makedirs
-from logging import info
-from json import loads
-from collections import namedtuple
-from panda3d.core import AmbientLight, Texture, TextPropertiesManager, \
- TextNode, Spotlight, PerspectiveLens, BitMask32
-from panda3d.bullet import BulletPlaneShape, BulletGhostNode
-from direct.gui.OnscreenImage import OnscreenImage
-from direct.gui.OnscreenText import OnscreenText
-from direct.gui.DirectGui import DirectButton, DirectFrame
-from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
-from direct.showbase.DirectObject import DirectObject
-from direct.interval.IntervalGlobal import Sequence, Func
-from direct.interval.LerpInterval import LerpFunctionInterval
-from pmachines.items.background import Background
-from pmachines.gui.sidepanel import SidePanel
-from pmachines.items.box import Box, HitStrategy
-from pmachines.items.basketball import Basketball
-from pmachines.items.domino import Domino, DownStrategy
-from pmachines.items.shelf import Shelf
-from pmachines.items.teetertooter import TeeterTooter
-from pmachines.editor.scene import SceneEditor
-from ya2.utils.cursor import MouseCursor
-from ya2.utils.gfx import GfxTools, DirectGuiMixin
-from ya2.utils.gui import GuiTools
-
-
-class Scene(DirectObject):
-
- json_files = {}
- scenes_done = []
-
- def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes, pos_mgr, testing, mouse_coords, persistent, json_name, editor, auto_start_editor):
- super().__init__()
- self._world = world
- self._exit_cb = exit_cb
- self._testing = testing
- self._mouse_coords = mouse_coords
- self._dbg_items = dbg_items
- self._reload_cb = reload_cb
- self._pos_mgr = pos_mgr
- self._pos_mgr = {}
- self._scenes = scenes
- self._start_evt_time = None
- self._enforce_result = ''
- self.__persistent = persistent
- self.__json_name = json_name
- self.__editor = editor
- self.__scene_editor = None
- self.json = {}
- self.accept('enforce_result', self.enforce_result)
- self._set_camera()
- self._cursor = MouseCursor(
- 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
- (.01, .01), testing)
- self.__set_font()
- self._set_gui()
- self._set_lights()
- self._set_input()
- self._set_mouse_plane()
- self.__items = []
- self._test_items = []
- self.reset()
- self._state = 'init'
- self._paused = False
- self._item_active = None
- if auto_close_instr:
- self.__store_state()
- self.__restore_state()
- else:
- self._set_instructions()
- self._bg = Background()
- self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.__items)
- self._scene_tsk = taskMgr.add(self.on_frame, 'scene_on_frame')
- if auto_start_editor:
- self._set_editor()
- self.accept('editor-inspector-delete', self.__on_inspector_delete)
-
- @classmethod
- def filename(cls, scene_name):
- return f'assets/scenes/{scene_name}.json'
-
- @classmethod
- def name(cls, scene_name):
- if not scene_name in cls.json_files:
- with open(cls.filename(scene_name)) as f:
- cls.json_files[scene_name] = loads(f.read())
- return _(cls.json_files[scene_name]['name'])
-
- @classmethod
- def version(cls, scene_name):
- if not scene_name: return ''
- if not scene_name in cls.json_files:
- with open(cls.filename(scene_name)) as f:
- cls.json_files[scene_name] = loads(f.read())
- return cls.json_files[scene_name]['version']
-
- @classmethod
- def is_done(cls, scene_name):
- if not cls.scenes_done or len(cls.scenes_done) == 1 and not cls.scenes_done[0]:
- return False
- return bool([(name, version) for name, version in cls.scenes_done if scene_name == name and cls.version(scene_name) == version])
-
- def _instr_txt(self):
- txt = _('Scene: ') + self.name(self.__json_name) + '\n\n'
- txt += _(self.__process_json_escape(self.__class__.json_files[self.__json_name]['instructions']))
- return txt
-
- def __process_json_escape(self, string):
- return bytes(string, 'utf-8').decode('unicode-escape')
-
- @property
- def items(self):
- items = self.__items[:]
- if self.__scene_editor:
- items += self.__scene_editor.test_items
- return items
-
- def _set_items(self):
- if not self.__json_name: return
- self.__items = []
- self._test_items = []
- if not self.json:
- with open(f'assets/scenes/{self.__json_name}.json') as f:
- self.json = loads(f.read())
- for item in self.json['start_items']:
- args = {
- 'world': self._world,
- 'plane_node': self._mouse_plane_node,
- 'cb_inst': self.cb_inst,
- 'curr_bottom': self.current_bottom,
- 'repos': self.repos,
- 'count': item['count'],
- 'json': item}
- if 'mass' in item:
- args['mass'] = item['mass']
- if 'friction' in item:
- args['friction'] = item['friction']
- self.__items += [self.__code2class(item['class'])(**args)]
- for item in self.json['items']:
- args = {
- 'world': self._world,
- 'plane_node': self._mouse_plane_node,
- 'cb_inst': self.cb_inst,
- 'curr_bottom': self.current_bottom,
- 'repos': self.repos,
- 'json': item}
- args['pos'] = tuple(item['position'])
- if 'mass' in item:
- args['mass'] = item['mass']
- if 'friction' in item:
- args['friction'] = item['friction']
- if 'roll' in item:
- args['r'] = item['roll']
- if 'model_scale' in item:
- args['model_scale'] = item['model_scale']
- if 'restitution' in item:
- args['restitution'] = item['restitution']
- if 'friction' in item:
- args['friction'] = item['friction']
- self.__items += [self.__code2class(item['class'])(**args)]
- if 'strategy' in item:
- match item['strategy']:
- case 'DownStrategy':
- self.__items[-1].set_strategy(self.__code2class(item['strategy'])(self.__items[-1]._np, *item['strategy_args']))
- case 'HitStrategy':
- self.__items[-1].set_strategy(self.__code2class(item['strategy'])(self.__item_with_id(item['strategy_args'][0]), self.items[-1].node, self.__items[-1]._world))
-
- def __code2class(self, code):
- return {
- 'Box': Box,
- 'Basketball': Basketball,
- 'Domino': Domino,
- 'Shelf': Shelf,
- 'TeeterTooter': TeeterTooter,
- 'DownStrategy': DownStrategy,
- 'HitStrategy': HitStrategy
- }[code]
-
- def __item_with_id(self, id):
- for item in self.__items:
- if 'id' in item.json and item.json['id'] == id:
- return item
-
- def screenshot(self, task=None):
- tex = Texture('screenshot')
- buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
- cam = base.make_camera(buffer)
- cam.reparent_to(render)
- cam.node().get_lens().set_fov(base.camLens.get_fov())
- cam.set_pos(0, -20, 0)
- cam.look_at(0, 0, 0)
- import simplepbr
- simplepbr.init(
- window=buffer,
- camera_node=cam,
- use_normal_maps=True,
- use_emission_maps=False,
- use_occlusion_maps=True,
- msaa_samples=4,
- enable_shadows=True)
- base.graphicsEngine.renderFrame()
- base.graphicsEngine.renderFrame()
- fname = self.__json_name
- if not exists('assets/images/scenes'):
- makedirs('assets/images/scenes')
- buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
- # img = DirectButton(
- # frameTexture=buffer.get_texture(), relief=FLAT,
- # frameSize=(-.2, .2, -.2, .2))
- return buffer.get_texture()
-
- def current_bottom(self):
- curr_bottom = 1
- for item in self.__items:
- if item.repos_done:
- curr_bottom = min(curr_bottom, item.get_bottom())
- return curr_bottom
-
- def reset(self):
- [itm.destroy() for itm in self.__items]
- [itm.remove_node() for itm in self._test_items]
- self.__items = []
- self._test_items = []
- self._set_items()
- self._set_test_items()
- self._state = 'init'
- self._commands = []
- self._command_idx = 0
- self._start_evt_time = None
- if hasattr(self, '_success_txt'):
- self._success_txt.destroy()
- del self._success_txt
- self.__right_btn['state'] = NORMAL
-
- def enforce_result(self, val):
- self._enforce_result = val
- info('enforce result: ' + val)
-
- def destroy(self):
- self.__intro_sequence.finish()
- self.ignore('enforce_result')
- self._unset_gui()
- self._unset_lights()
- self._unset_input()
- self._unset_mouse_plane()
- [itm.destroy() for itm in self.__items]
- [itm.remove_node() for itm in self._test_items]
- self._bg.destroy()
- self._side_panel.destroy()
- self._cursor.destroy()
- taskMgr.remove(self._scene_tsk)
- if hasattr(self, '_success_txt'):
- self._success_txt.destroy()
- self.ignore('editor-inspector-delete')
- if self.__scene_editor: self.__scene_editor.destroy()
-
- def _set_camera(self):
- base.camera.set_pos(0, -20, 0)
- base.camera.look_at(0, 0, 0)
- def camera_ani(t):
- start_v = (1, -5, 1)
- end_v = (0, -20, 0)
- curr_pos = (
- start_v[0] + (end_v[0] - start_v[0]) * t,
- start_v[1] + (end_v[1] - start_v[1]) * t,
- start_v[2] + (end_v[2] - start_v[2]) * t)
- base.camera.set_pos(*curr_pos)
- self.repos()
- camera_interval = LerpFunctionInterval(
- camera_ani,
- 1.2,
- 0,
- 1,
- blendType='easeInOut')
- self.__intro_sequence = Sequence(
- camera_interval,
- Func(self.repos))
- self.__intro_sequence.start()
-
- def __load_img_btn(self, path, col):
- img = OnscreenImage('assets/images/buttons/%s.dds' % path)
- img.set_transparency(True)
- img.set_color(col)
- img.detach_node()
- return img
-
- def _set_gui(self):
- def load_images_btn(path, col):
- colors = {
- 'gray': [
- (.6, .6, .6, 1), # ready
- (1, 1, 1, 1), # press
- (.8, .8, .8, 1), # rollover
- (.4, .4, .4, .4)],
- 'green': [
- (.1, .68, .1, 1),
- (.1, 1, .1, 1),
- (.1, .84, .1, 1),
- (.4, .1, .1, .4)]}[col]
- return [self.__load_img_btn(path, col) for col in colors]
- abl, abr = base.a2dBottomLeft, base.a2dBottomRight
- btn_info = [
- ('home', self.on_home, NORMAL, abl, 'gray', _('Exit'), 'right'),
- ('information', self._set_instructions, NORMAL, abl, 'gray', _('Instructions'), 'right'),
- ('right', self.on_play, NORMAL, abr, 'green', _('Run'), 'left'),
- #('next', self.on_next, DISABLED, abr, 'gray'),
- #('previous', self.on_prev, DISABLED, abr, 'gray'),
- #('rewind', self.reset, NORMAL, abr, 'gray')
- ]
- if self.__editor:
- btn_info.insert(2, ('wrench', self._set_editor, NORMAL, abl, 'gray', _('Editor'), 'right'))
- num_l = num_r = 0
- btns = []
- tooltip_args = self.__font, .05, (.93, .93, .93, 1)
- for binfo in btn_info:
- imgs = load_images_btn(binfo[0], binfo[4])
- if binfo[3] == base.a2dBottomLeft:
- sign, num = 1, num_l
- num_l += 1
- else:
- sign, num = -1, num_r
- num_r += 1
- fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
- btn = DirectButton(
- image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
- parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
- frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- btn.set_transparency(True)
- t = tooltip_args + (binfo[6],)
- btn.set_tooltip(binfo[5], *t)
- self._pos_mgr[binfo[0]] = btn.pos_pixel()
- btns += [btn]
- if self.__editor:
- self.__home_btn, self.__info_btn, self.__editor_btn, self.__right_btn = btns
- else:
- self.__home_btn, self.__info_btn, self.__right_btn = btns
- # , self.__next_btn, self.__prev_btn, self.__rewind_btn
- if self._dbg_items:
- self._info_txt = OnscreenText(
- '', parent=base.a2dTopRight, scale=0.04,
- pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
- if self._mouse_coords:
- self._coords_txt = OnscreenText(
- '', parent=base.a2dTopRight, scale=0.04,
- pos=(-.03, -.12), fg=(.9, .9, .9, 1), align=TextNode.A_right)
- def update_coords(task):
- pos = None
- for hit in self._get_hits():
- if hit.get_node() == self._mouse_plane_node:
- pos = hit.get_hit_pos()
- if pos:
- txt = '%s %s' % (round(pos.x, 3),
- round(pos.z, 3))
- self._coords_txt['text'] = txt
- return task.cont
- self._coords_tsk = taskMgr.add(update_coords, 'update_coords')
-
- def _unset_gui(self):
- btns = [
- self.__home_btn, self.__info_btn, self.__right_btn
- #self.__next_btn, self.__prev_btn, self.__rewind_btn
- ]
- if self.__editor: btns += [self.__editor_btn]
- [btn.destroy() for btn in btns]
- if self._dbg_items:
- self._info_txt.destroy()
- if self._mouse_coords:
- taskMgr.remove(self._coords_tsk)
- self._coords_txt.destroy()
-
- def _set_spotlight(self, name, pos, look_at, color, shadows=False):
- light = Spotlight(name)
- if shadows:
- light.setLens(PerspectiveLens())
- light_np = render.attach_new_node(light)
- light_np.set_pos(pos)
- light_np.look_at(look_at)
- light.set_color(color)
- render.set_light(light_np)
- return light_np
-
- def _set_lights(self):
- alight = AmbientLight('alight') # for ao
- alight.set_color((.15, .15, .15, 1))
- self._alnp = render.attach_new_node(alight)
- render.set_light(self._alnp)
- self._key_light = self._set_spotlight(
- 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
- self._shadow_light = self._set_spotlight(
- 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
- self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
- self._shadow_light.node().get_lens().set_film_size(2048, 2048)
- self._shadow_light.node().get_lens().set_near_far(1, 256)
- self._shadow_light.node().set_camera_mask(BitMask32(0x01))
-
- def _unset_lights(self):
- for light in [self._alnp, self._key_light, self._shadow_light]:
- render.clear_light(light)
- light.remove_node()
-
- def _set_input(self):
- self.accept('mouse1', self.on_click_l)
- self.accept('mouse1-up', self.on_release)
- self.accept('mouse3', self.on_click_r)
- self.accept('mouse3-up', self.on_release)
-
- def _unset_input(self):
- for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
- self.ignore(evt)
-
- def _set_mouse_plane(self):
- shape = BulletPlaneShape((0, -1, 0), 0)
- #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
- self._mouse_plane_node = BulletGhostNode('mouse plane')
- self._mouse_plane_node.addShape(shape)
- #np = render.attachNewNode(self._mouse_plane_node)
- #self._world.attachRigidBody(self._mouse_plane_node)
- self._world.attach_ghost(self._mouse_plane_node)
-
- def _unset_mouse_plane(self):
- self._world.remove_ghost(self._mouse_plane_node)
-
- def _get_hits(self):
- if not base.mouseWatcherNode.has_mouse(): return []
- p_from, p_to = GuiTools.get_mouse().from_to_points()
- return self._world.ray_test_all(p_from, p_to).get_hits()
-
- def _update_info(self, item):
- txt = ''
- if item:
- txt = '%.3f %.3f\n%.3f°' % (
- item._np.get_x(), item._np.get_z(), item._np.get_r())
- self._info_txt['text'] = txt
-
- def _on_click(self, method):
- if self._paused:
- return
- for hit in self._get_hits():
- if hit.get_node() == self._mouse_plane_node:
- pos = hit.get_hit_pos()
- for hit in self._get_hits():
- for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
- if not self._item_active:
- self._item_active = item
- if item not in self.__items:
- method = 'on_click_l'
- getattr(item, method)(pos)
- img = 'move' if method == 'on_click_l' else 'rotate'
- if not (img == 'rotate' and not item._instantiated):
- self._cursor.set_image('assets/images/buttons/%s.dds' % img)
-
- def on_click_l(self):
- self._on_click('on_click_l')
-
- def on_click_r(self):
- self._on_click('on_click_r')
-
- def on_release(self):
- if self._item_active and not self._item_active._first_command:
- self._commands = self._commands[:self._command_idx]
- self._commands += [self._item_active]
- self._command_idx += 1
- #self.__prev_btn['state'] = NORMAL
- #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
- #self.__prev_btn['frameColor'] = fcols[0]
- #if self._item_active._command_idx == len(self._item_active._commands) - 1:
- # self.__next_btn['state'] = DISABLED
- # self.__next_btn['frameColor'] = fcols[1]
- self._item_active = None
- [item.on_release() for item in self.__items]
- self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
-
- def repos(self):
- for item in self.__items:
- item.repos_done = False
- self.__items = sorted(self.__items, key=lambda itm: itm.__class__.__name__)
- [item.on_aspect_ratio_changed() for item in self.__items]
- self._side_panel.update(self.__items)
- max_x = -float('inf')
- for item in self.__items:
- if not item._instantiated:
- max_x = max(item._np.get_x(), max_x)
- for item in self.__items:
- if not item._instantiated:
- item.repos_x(max_x)
-
- def on_aspect_ratio_changed(self):
- self.repos()
-
- def _win_condition(self):
- return all(itm.strategy.win_condition() for itm in self.__items) and not self._paused
-
- def _fail_condition(self):
- return all(itm.fail_condition() for itm in self.__items) and not self._paused and self._state == 'playing'
-
- def on_frame(self, task):
- hits = self._get_hits()
- pos = None
- for hit in self._get_hits():
- if hit.get_node() == self._mouse_plane_node:
- pos = hit.get_hit_pos()
- hit_nodes = [hit.get_node() for hit in hits]
- if self._item_active:
- items_hit = [self._item_active]
- else:
- items_hit = [itm for itm in self.items if itm.node in hit_nodes]
- items_no_hit = [itm for itm in self.items if itm not in items_hit]
- [itm.on_mouse_on() for itm in items_hit]
- [itm.on_mouse_off() for itm in items_no_hit]
- if pos and self._item_active:
- self._item_active.on_mouse_move(pos)
- if self._dbg_items:
- self._update_info(items_hit[0] if items_hit else None)
- if not self.__scene_editor and self._win_condition():
- self._start_evt_time = None
- self._set_fail() if self._enforce_result == 'fail' else self._set_win()
- elif self._state == 'playing' and self._fail_condition():
- self._start_evt_time = None
- self._set_win() if self._enforce_result == 'win' else self._set_fail()
- elif self._testing and self._start_evt_time and globalClock.getFrameTime() - self._start_evt_time > 5.0:
- self._start_evt_time = None
- self._set_win() if self._enforce_result == 'win' else self._set_fail()
- if any(itm._overlapping for itm in self.items):
- self._cursor.set_color((.9, .1, .1, 1))
- else:
- self._cursor.set_color((.9, .9, .9, 1))
- return task.cont
-
- def cb_inst(self, item):
- self.__items += [item]
-
- def on_play(self):
- self._state = 'playing'
- #self.__prev_btn['state'] = DISABLED
- #self.__next_btn['state'] = DISABLED
- self.__right_btn['state'] = DISABLED
- [itm.play() for itm in self.__items]
- self._start_evt_time = globalClock.getFrameTime()
-
- def on_next(self):
- self._commands[self._command_idx].redo()
- self._command_idx += 1
- #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
- #self.__prev_btn['state'] = NORMAL
- #self.__prev_btn['frameColor'] = fcols[0]
- #more_commands = self._command_idx < len(self._commands)
- #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
- #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
-
- def on_prev(self):
- self._command_idx -= 1
- self._commands[self._command_idx].undo()
- #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
- #self.__next_btn['state'] = NORMAL
- #self.__next_btn['frameColor'] = fcols[0]
- #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
- #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
-
- def on_home(self):
- self._exit_cb()
-
- def __set_font(self):
- self.__font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
- self.__font.clear()
- self.__font.set_pixels_per_unit(60)
- self.__font.set_minfilter(Texture.FTLinearMipmapLinear)
- self.__font.set_outline((0, 0, 0, 1), .8, .2)
-
-
- def _set_instructions(self):
- self._paused = True
- self.__store_state()
- mgr = TextPropertiesManager.get_global_ptr()
- for name in ['mouse_l', 'mouse_r']:
- graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
- graphic.set_scale(.5)
- graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
- graphic.get_texture().set_anisotropic_degree(2)
- mgr.set_graphic(name, graphic)
- graphic.set_z(-.2)
- graphic.set_transparency(True)
- graphic.detach_node()
- frm = DirectFrame(frameColor=(.4, .4, .4, .06),
- frameSize=(-.6, .6, -.3, .3))
- self._txt = OnscreenText(
- self._instr_txt(), parent=frm, font=self.__font, scale=0.06,
- fg=(.9, .9, .9, 1), align=TextNode.A_left)
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
- btn_scale = .05
- mar = .06 # margin
- z = h / 2 - self.__font.get_line_height() * self._txt['scale'][1]
- z += (btn_scale + 2 * mar) / 2
- self._txt['pos'] = -w / 2, z
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
- fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
- frm['frameSize'] = fsz
- colors = [
- (.6, .6, .6, 1), # ready
- (1, 1, 1, 1), # press
- (.8, .8, .8, 1), # rollover
- (.4, .4, .4, .4)]
- imgs = [self.__load_img_btn('exitRight', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- btn.set_transparency(True)
- self._pos_mgr['close_instructions'] = btn.pos_pixel()
-
- def _set_win(self):
- self.__persistent.save_scene(self.__json_name, self.version(self.__json_name))
- loader.load_sfx('assets/audio/sfx/success.ogg').play()
- self._paused = True
- self.__store_state()
- frm = DirectFrame(frameColor=(.4, .4, .4, .06),
- frameSize=(-.6, .6, -.3, .3))
- font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
- font.clear()
- font.set_pixels_per_unit(60)
- font.set_minfilter(Texture.FTLinearMipmapLinear)
- font.set_outline((0, 0, 0, 1), .8, .2)
- self._txt = OnscreenText(
- _('You win!'),
- parent=frm,
- font=font, scale=0.2,
- fg=(.9, .9, .9, 1))
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
- h = u_l[2] - l_r[2]
- btn_scale = .05
- mar = .06 # margin
- z = h / 2 - font.get_line_height() * self._txt['scale'][1]
- z += (btn_scale + 2 * mar) / 2
- self._txt['pos'] = 0, z
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
- fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
- frm['frameSize'] = fsz
- colors = [
- (.6, .6, .6, 1), # ready
- (1, 1, 1, 1), # press
- (.8, .8, .8, 1), # rollover
- (.4, .4, .4, .4)]
- imgs = [self.__load_img_btn('home', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_end_home, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- btn.set_transparency(True)
- self._pos_mgr['home_win'] = btn.pos_pixel()
- imgs = [self.__load_img_btn('rewind', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(0, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_restart, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['replay'] = btn.pos_pixel()
- btn.set_transparency(True)
- if self.__json_name:
- enabled = self._scenes.index(self.__json_name) < len(self._scenes) - 1
- if enabled:
- next_scene = self._scenes[self._scenes.index(self.__json_name) + 1]
- else:
- next_scene = None
- else:
- next_scene = None
- enabled = False
- imgs = [self.__load_img_btn('right', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_next_scene,
- extraArgs=[frm, next_scene], relief=FLAT,
- frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- btn['state'] = NORMAL if enabled else DISABLED
- self._pos_mgr['next'] = btn.pos_pixel()
- btn.set_transparency(True)
-
- def _set_fail(self):
- loader.load_sfx('assets/audio/sfx/success.ogg').play()
- self._paused = True
- self.__store_state()
- frm = DirectFrame(frameColor=(.4, .4, .4, .06),
- frameSize=(-.6, .6, -.3, .3))
- font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
- font.clear()
- font.set_pixels_per_unit(60)
- font.set_minfilter(Texture.FTLinearMipmapLinear)
- font.set_outline((0, 0, 0, 1), .8, .2)
- self._txt = OnscreenText(
- _('You have failed!'),
- parent=frm,
- font=font, scale=0.2,
- fg=(.9, .9, .9, 1))
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
- h = u_l[2] - l_r[2]
- btn_scale = .05
- mar = .06 # margin
- z = h / 2 - font.get_line_height() * self._txt['scale'][1]
- z += (btn_scale + 2 * mar) / 2
- self._txt['pos'] = 0, z
- u_l = self._txt.textNode.get_upper_left_3d()
- l_r = self._txt.textNode.get_lower_right_3d()
- c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
- fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
- frm['frameSize'] = fsz
- colors = [
- (.6, .6, .6, 1), # ready
- (1, 1, 1, 1), # press
- (.8, .8, .8, 1), # rollover
- (.4, .4, .4, .4)]
- imgs = [self.__load_img_btn('home', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_end_home, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['home_win'] = btn.pos_pixel()
- btn.set_transparency(True)
- imgs = [self.__load_img_btn('rewind', col) for col in colors]
- btn = DirectButton(
- image=imgs, scale=btn_scale,
- pos=(0, 1, l_r[2] - mar - btn_scale),
- parent=frm, command=self._on_restart, extraArgs=[frm],
- relief=FLAT, frameColor=(.6, .6, .6, .08),
- rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
- clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
- btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['replay'] = btn.pos_pixel()
- btn.set_transparency(True)
-
- def _on_restart(self, frm):
- self.__on_close_instructions(frm)
- self.reset()
-
- def _on_end_home(self, frm):
- self.__on_close_instructions(frm)
- self.on_home()
-
- def _on_next_scene(self, frm, scene):
- self.__on_close_instructions(frm)
- self._reload_cb(scene)
-
- def __store_state(self):
- btns = [
- self.__home_btn, self.__info_btn, self.__right_btn,
- #self.__next_btn, self.__prev_btn, self.__rewind_btn
- ]
- if self.__editor: btns += [self.__editor_btn]
- self.__btn_state = [btn['state'] for btn in btns]
- for btn in btns:
- btn['state'] = DISABLED
- [itm.store_state() for itm in self.__items]
-
- def __restore_state(self):
- btns = [
- self.__home_btn, self.__info_btn, self.__right_btn,
- #self.__next_btn, self.__prev_btn, self.__rewind_btn
- ]
- if self.__editor: btns += [self.__editor_btn]
- for btn, state in zip(btns, self.__btn_state):
- btn['state'] = state
- [itm.restore_state() for itm in self.__items]
- self._paused = False
-
- def __on_close_instructions(self, frm):
- frm.remove_node()
- self.__restore_state()
-
- def _set_test_items(self):
- def frame_after(task):
- self._define_test_items()
- for itm in self._test_items:
- self._pos_mgr[itm.name] = itm.pos2d_pixel()
- taskMgr.doMethodLater(1.4, frame_after, 'frame after') # after the intro sequence
-
- def _define_test_items(self):
- if not self.__json_name: return
- if not self.__json_name in self.__class__.json_files:
- with open(self.__class__.filename(self.__json_name)) as f:
- self.__class__.json_files[self.__json_name] = loads(f.read())
- for item in self.__class__.json_files[self.__json_name]['test_items']['pixel_space']:
- self._pos_mgr[item['id']] = tuple(item['position'])
- for item in self.__class__.json_files[self.__json_name]['test_items']['world_space']:
- self._set_test_item(item['id'], tuple(item['position']))
-
- def _set_test_item(self, name, pos):
- self._test_items += [GfxTools.build_empty_node(name)]
- self._test_items[-1].set_pos(pos[0], 0, pos[1])
-
- def add_item(self, item):
- self.__items += [item]
-
- def _set_editor(self):
- fields = ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
- SceneContext = namedtuple('SceneContext', fields)
- context = SceneContext(
- self._world,
- self._mouse_plane_node,
- self.cb_inst,
- self.current_bottom,
- self.repos,
- {})
- self.__scene_editor = SceneEditor(self.json, self.__json_name, context, self.add_item, self.__items, self._world, self._mouse_plane_node)
-
- def __on_inspector_delete(self, item):
- self.__items.remove(item)
- self.json['items'].remove(item.json)
- item.destroy()