ya2 · news · projects · code · about

version in json files
[pmachines.git] / pmachines / scene.py
CommitLineData
31237524 1from os.path import exists
a0462193 2from os import makedirs
c991401b 3from logging import info
3fe18d73 4from json import loads
c991401b
FC
5from panda3d.core import AmbientLight, Texture, TextPropertiesManager, \
6 TextNode, Spotlight, PerspectiveLens, BitMask32, NodePath
36099535
FC
7from panda3d.bullet import BulletPlaneShape, BulletGhostNode
8from direct.gui.OnscreenImage import OnscreenImage
2aaa10d3
FC
9from direct.gui.OnscreenText import OnscreenText
10from direct.gui.DirectGui import DirectButton, DirectFrame
36099535 11from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
1be87278 12from direct.showbase.DirectObject import DirectObject
fe0a68a0
FC
13from direct.interval.IntervalGlobal import Sequence, Func
14from direct.interval.LerpInterval import LerpFunctionInterval
4586cbf6
FC
15from pmachines.items.background import Background
16from pmachines.gui.sidepanel import SidePanel
b35b1f62
FC
17from ya2.utils.cursor import MouseCursor
18from ya2.p3d.gfx import P3dGfxMgr
2d1773b1 19from ya2.p3d.p3d import LibP3d
1be87278
FC
20
21
22class Scene(DirectObject):
23
92c29685 24 scenes_done = []
3fe18d73 25 json = {}
92c29685
FC
26
27 def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes, pos_mgr, testing, mouse_coords, persistent):
1be87278
FC
28 super().__init__()
29 self._world = world
5964572b 30 self._exit_cb = exit_cb
ce302b41 31 self._testing = testing
7fa58640 32 self._mouse_coords = mouse_coords
31237524 33 self._dbg_items = dbg_items
9914cfc9 34 self._reload_cb = reload_cb
2d1773b1
FC
35 self._pos_mgr = pos_mgr
36 self._pos_mgr.reset()
6168d0c2 37 self._scenes = scenes
ce302b41 38 self._start_evt_time = None
fa3662a6 39 self._enforce_res = ''
92c29685 40 self.__persistent = persistent
fa3662a6 41 self.accept('enforce_res', self.enforce_res)
1be87278 42 self._set_camera()
79f81d48 43 self._cursor = MouseCursor(
7e487769 44 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
79f81d48 45 (.01, .01))
36099535 46 self._set_gui()
1be87278
FC
47 self._set_lights()
48 self._set_input()
c8d8653f 49 self._set_mouse_plane()
d5932612 50 self.items = []
067a36db 51 self._test_items = []
d5932612 52 self.reset()
0a0994e4 53 self._state = 'init'
9830561d 54 self._paused = False
7c0a81ae 55 self._item_active = None
e669403e 56 if auto_close_instr:
32aa4dae 57 self.__store_state()
e669403e
FC
58 self.__restore_state()
59 else:
60 self._set_instructions()
5964572b
FC
61 self._bg = Background()
62 self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
63 self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
64
8c9bf90e
FC
65 @staticmethod
66 def name():
67 return ''
68
3fe18d73
FC
69 @classmethod
70 def version(cls):
71 if not cls.json:
72 with open(cls.filename) as f:
73 cls.json = loads(f.read())
74 return cls.json['version']
75
92c29685
FC
76 @classmethod
77 def is_done(cls):
3fe18d73 78 return bool([(name, version) for name, version in cls.scenes_done if cls.__name__ == name and cls.version() == version])
92c29685 79
0eff64a3
FC
80 def _instr_txt(self):
81 return ''
82
83 def _set_items(self):
84 self.items = []
067a36db 85 self._test_items = []
0eff64a3 86
0d5a5427 87 def screenshot(self, task=None):
ecdf8933
FC
88 tex = Texture('screenshot')
89 buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
90 cam = base.make_camera(buffer)
91 cam.reparent_to(render)
92 cam.node().get_lens().set_fov(base.camLens.get_fov())
93 cam.set_pos(0, -20, 0)
94 cam.look_at(0, 0, 0)
95 import simplepbr
96 simplepbr.init(
97 window=buffer,
98 camera_node=cam,
99 use_normal_maps=True,
100 use_emission_maps=False,
101 use_occlusion_maps=True,
102 msaa_samples=4,
103 enable_shadows=True)
104 base.graphicsEngine.renderFrame()
105 base.graphicsEngine.renderFrame()
63e7aeb2 106 fname = self.__class__.__name__
40cc6e36
FC
107 if not exists('assets/images/scenes'):
108 makedirs('assets/images/scenes')
63e7aeb2 109 buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
ecdf8933
FC
110 # img = DirectButton(
111 # frameTexture=buffer.get_texture(), relief=FLAT,
112 # frameSize=(-.2, .2, -.2, .2))
0d5a5427 113 return buffer.get_texture()
ecdf8933 114
d5932612
FC
115 def current_bottom(self):
116 curr_bottom = 1
117 for item in self.items:
118 if item.repos_done:
119 curr_bottom = min(curr_bottom, item.get_bottom())
120 return curr_bottom
121
05fd23ec 122 def reset(self):
d5932612 123 [itm.destroy() for itm in self.items]
067a36db
FC
124 [itm.remove_node() for itm in self._test_items]
125 self.items = []
126 self._test_items = []
0eff64a3 127 self._set_items()
067a36db 128 self._set_test_items()
0a0994e4 129 self._state = 'init'
32aa4dae
FC
130 self._commands = []
131 self._command_idx = 0
ce302b41 132 self._start_evt_time = None
b41381b2
FC
133 if hasattr(self, '_success_txt'):
134 self._success_txt.destroy()
135 del self._success_txt
136 self.__right_btn['state'] = NORMAL
05fd23ec 137
fa3662a6
FC
138 def enforce_res(self, val):
139 self._enforce_res = val
74ed9732 140 info('enforce res: ' + val)
fa3662a6 141
5964572b 142 def destroy(self):
fe0a68a0 143 self.__intro_sequence.finish()
fa3662a6 144 self.ignore('enforce_res')
5964572b
FC
145 self._unset_gui()
146 self._unset_lights()
147 self._unset_input()
148 self._unset_mouse_plane()
149 [itm.destroy() for itm in self.items]
067a36db 150 [itm.remove_node() for itm in self._test_items]
5964572b
FC
151 self._bg.destroy()
152 self._side_panel.destroy()
407412a5 153 self._cursor.destroy()
5964572b 154 taskMgr.remove(self._scene_tsk)
9fc7f6fb
FC
155 if hasattr(self, '_success_txt'):
156 self._success_txt.destroy()
1be87278
FC
157
158 def _set_camera(self):
159 base.camera.set_pos(0, -20, 0)
160 base.camera.look_at(0, 0, 0)
fe0a68a0
FC
161 def camera_ani(t):
162 start_v = (1, -5, 1)
163 end_v = (0, -20, 0)
164 curr_pos = (
165 start_v[0] + (end_v[0] - start_v[0]) * t,
166 start_v[1] + (end_v[1] - start_v[1]) * t,
167 start_v[2] + (end_v[2] - start_v[2]) * t)
168 base.camera.set_pos(*curr_pos)
169 self.repos()
170 camera_interval = LerpFunctionInterval(
171 camera_ani,
172 1.2,
173 0,
174 1,
175 blendType='easeInOut')
176 self.__intro_sequence = Sequence(
177 camera_interval,
178 Func(self.repos))
179 self.__intro_sequence.start()
1be87278 180
a0acba9a 181 def __load_img_btn(self, path, col):
7e487769 182 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
a0acba9a
FC
183 img.set_transparency(True)
184 img.set_color(col)
185 img.detach_node()
186 return img
187
36099535 188 def _set_gui(self):
01b221a6
FC
189 def load_images_btn(path, col):
190 colors = {
191 'gray': [
192 (.6, .6, .6, 1), # ready
193 (1, 1, 1, 1), # press
194 (.8, .8, .8, 1), # rollover
195 (.4, .4, .4, .4)],
196 'green': [
197 (.1, .68, .1, 1),
198 (.1, 1, .1, 1),
199 (.1, .84, .1, 1),
200 (.4, .1, .1, .4)]}[col]
a0acba9a 201 return [self.__load_img_btn(path, col) for col in colors]
36099535
FC
202 abl, abr = base.a2dBottomLeft, base.a2dBottomRight
203 btn_info = [
5964572b 204 ('home', self.on_home, NORMAL, abl, 'gray'),
9830561d 205 ('information', self._set_instructions, NORMAL, abl, 'gray'),
01b221a6 206 ('right', self.on_play, NORMAL, abr, 'green'),
f26497a5
FC
207 #('next', self.on_next, DISABLED, abr, 'gray'),
208 #('previous', self.on_prev, DISABLED, abr, 'gray'),
209 #('rewind', self.reset, NORMAL, abr, 'gray')
210 ]
36099535 211 num_l = num_r = 0
a0acba9a 212 btns = []
36099535 213 for binfo in btn_info:
01b221a6 214 imgs = load_images_btn(binfo[0], binfo[4])
36099535
FC
215 if binfo[3] == base.a2dBottomLeft:
216 sign, num = 1, num_l
217 num_l += 1
218 else:
219 sign, num = -1, num_r
220 num_r += 1
221 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
222 btn = DirectButton(
223 image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
224 parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
54a1397e
FC
225 frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
226 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
227 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
36099535 228 btn.set_transparency(True)
2d1773b1 229 self._pos_mgr.register(binfo[0], LibP3d.wdg_pos(btn))
a0acba9a 230 btns += [btn]
f26497a5
FC
231 self.__home_btn, self.__info_btn, self.__right_btn = btns
232 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
31237524
FC
233 if self._dbg_items:
234 self._info_txt = OnscreenText(
235 '', parent=base.a2dTopRight, scale=0.04,
236 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
7fa58640
FC
237 if self._mouse_coords:
238 self._coords_txt = OnscreenText(
239 '', parent=base.a2dTopRight, scale=0.04,
240 pos=(-.03, -.12), fg=(.9, .9, .9, 1), align=TextNode.A_right)
241 def update_coords(task):
242 pos = None
243 for hit in self._get_hits():
244 if hit.get_node() == self._mouse_plane_node:
245 pos = hit.get_hit_pos()
246 if pos:
247 txt = '%s %s' % (round(pos.x, 3),
248 round(pos.z, 3))
249 self._coords_txt['text'] = txt
250 return task.cont
251 self._coords_tsk = taskMgr.add(update_coords, 'update_coords')
36099535 252
5964572b
FC
253 def _unset_gui(self):
254 btns = [
255 self.__home_btn, self.__info_btn, self.__right_btn,
f26497a5
FC
256 #self.__next_btn, self.__prev_btn, self.__rewind_btn
257 ]
5964572b 258 [btn.destroy() for btn in btns]
31237524
FC
259 if self._dbg_items:
260 self._info_txt.destroy()
7fa58640
FC
261 if self._mouse_coords:
262 taskMgr.remove(self._coords_tsk)
263 self._coords_txt.destroy()
5964572b 264
64eae9c7
FC
265 def _set_spotlight(self, name, pos, look_at, color, shadows=False):
266 light = Spotlight(name)
267 if shadows:
268 light.setLens(PerspectiveLens())
1be87278 269 light_np = render.attach_new_node(light)
64eae9c7
FC
270 light_np.set_pos(pos)
271 light_np.look_at(look_at)
1be87278
FC
272 light.set_color(color)
273 render.set_light(light_np)
5964572b 274 return light_np
1be87278
FC
275
276 def _set_lights(self):
277 alight = AmbientLight('alight') # for ao
64eae9c7
FC
278 alight.set_color((.15, .15, .15, 1))
279 self._alnp = render.attach_new_node(alight)
280 render.set_light(self._alnp)
281 self._key_light = self._set_spotlight(
282 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
283 self._shadow_light = self._set_spotlight(
284 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
285 self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
286 self._shadow_light.node().get_lens().set_film_size(2048, 2048)
287 self._shadow_light.node().get_lens().set_near_far(1, 256)
288 self._shadow_light.node().set_camera_mask(BitMask32(0x01))
5964572b
FC
289
290 def _unset_lights(self):
64eae9c7 291 for light in [self._alnp, self._key_light, self._shadow_light]:
5964572b
FC
292 render.clear_light(light)
293 light.remove_node()
1be87278
FC
294
295 def _set_input(self):
49c79300 296 self.accept('mouse1', self.on_click_l)
c8d8653f 297 self.accept('mouse1-up', self.on_release)
49c79300
FC
298 self.accept('mouse3', self.on_click_r)
299 self.accept('mouse3-up', self.on_release)
c8d8653f 300
5964572b
FC
301 def _unset_input(self):
302 for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
303 self.ignore(evt)
304
c8d8653f 305 def _set_mouse_plane(self):
651713a9 306 shape = BulletPlaneShape((0, -1, 0), 0)
c8d8653f
FC
307 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
308 self._mouse_plane_node = BulletGhostNode('mouse plane')
309 self._mouse_plane_node.addShape(shape)
310 #np = render.attachNewNode(self._mouse_plane_node)
311 #self._world.attachRigidBody(self._mouse_plane_node)
06af3aa9 312 self._world.attach_ghost(self._mouse_plane_node)
1be87278 313
5964572b
FC
314 def _unset_mouse_plane(self):
315 self._world.remove_ghost(self._mouse_plane_node)
316
37ba71d8
FC
317 def _get_hits(self):
318 if not base.mouseWatcherNode.has_mouse(): return []
13263131 319 p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
37ba71d8
FC
320 return self._world.ray_test_all(p_from, p_to).get_hits()
321
31237524
FC
322 def _update_info(self, item):
323 txt = ''
324 if item:
325 txt = '%.3f %.3f\n%.3f°' % (
326 item._np.get_x(), item._np.get_z(), item._np.get_r())
327 self._info_txt['text'] = txt
328
49c79300 329 def _on_click(self, method):
a0acba9a
FC
330 if self._paused:
331 return
c8d8653f
FC
332 for hit in self._get_hits():
333 if hit.get_node() == self._mouse_plane_node:
334 pos = hit.get_hit_pos()
37ba71d8 335 for hit in self._get_hits():
ef3c36bf 336 for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
7c0a81ae
FC
337 if not self._item_active:
338 self._item_active = item
49c79300 339 getattr(item, method)(pos)
79f81d48 340 img = 'move' if method == 'on_click_l' else 'rotate'
a6843832 341 if not (img == 'rotate' and not item._instantiated):
7e487769 342 self._cursor.set_image('assets/images/buttons/%s.dds' % img)
49c79300
FC
343
344 def on_click_l(self):
345 self._on_click('on_click_l')
346
347 def on_click_r(self):
348 self._on_click('on_click_r')
c8d8653f
FC
349
350 def on_release(self):
32aa4dae
FC
351 if self._item_active and not self._item_active._first_command:
352 self._commands = self._commands[:self._command_idx]
353 self._commands += [self._item_active]
354 self._command_idx += 1
f26497a5
FC
355 #self.__prev_btn['state'] = NORMAL
356 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
357 #self.__prev_btn['frameColor'] = fcols[0]
358 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
359 # self.__next_btn['state'] = DISABLED
360 # self.__next_btn['frameColor'] = fcols[1]
7c0a81ae 361 self._item_active = None
49c79300 362 [item.on_release() for item in self.items]
7e487769 363 self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
37ba71d8 364
e1438449 365 def repos(self):
d5932612
FC
366 for item in self.items:
367 item.repos_done = False
e1438449 368 self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
651713a9 369 [item.on_aspect_ratio_changed() for item in self.items]
a5dc83f4 370 self._side_panel.update(self.items)
7790323d 371 max_x = -float('inf')
e1438449
FC
372 for item in self.items:
373 if not item._instantiated:
374 max_x = max(item._np.get_x(), max_x)
375 for item in self.items:
376 if not item._instantiated:
377 item.repos_x(max_x)
378
379 def on_aspect_ratio_changed(self):
380 self.repos()
651713a9 381
0a0994e4 382 def _win_condition(self):
0e86689f
FC
383 pass
384
0a0994e4
FC
385 def _fail_condition(self):
386 return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
387
37ba71d8 388 def on_frame(self, task):
c8d8653f
FC
389 hits = self._get_hits()
390 pos = None
391 for hit in self._get_hits():
392 if hit.get_node() == self._mouse_plane_node:
393 pos = hit.get_hit_pos()
394 hit_nodes = [hit.get_node() for hit in hits]
7c0a81ae
FC
395 if self._item_active:
396 items_hit = [self._item_active]
397 else:
398 items_hit = [itm for itm in self.items if itm.node in hit_nodes]
37ba71d8
FC
399 items_no_hit = [itm for itm in self.items if itm not in items_hit]
400 [itm.on_mouse_on() for itm in items_hit]
401 [itm.on_mouse_off() for itm in items_no_hit]
7c0a81ae
FC
402 if pos and self._item_active:
403 self._item_active.on_mouse_move(pos)
31237524
FC
404 if self._dbg_items:
405 self._update_info(items_hit[0] if items_hit else None)
0a0994e4 406 if self._win_condition():
ce302b41 407 self._start_evt_time = None
fa3662a6 408 self._set_fail() if self._enforce_res == 'fail' else self._set_win()
0a0994e4 409 elif self._state == 'playing' and self._fail_condition():
ce302b41
FC
410 self._start_evt_time = None
411 self._set_win() if self._enforce_res == 'win' else self._set_fail()
412 elif self._testing and self._start_evt_time and globalClock.getFrameTime() - self._start_evt_time > 5.0:
413 self._start_evt_time = None
fa3662a6 414 self._set_win() if self._enforce_res == 'win' else self._set_fail()
93cf86b2
FC
415 if any(itm._overlapping for itm in self.items):
416 self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
417 else:
418 self._cursor.cursor_img.img.set_color(.9, .9, .9, 1)
37ba71d8 419 return task.cont
c8d8653f 420
7850ccc4
FC
421 def cb_inst(self, item):
422 self.items += [item]
423
c8d8653f 424 def on_play(self):
0a0994e4 425 self._state = 'playing'
f26497a5
FC
426 #self.__prev_btn['state'] = DISABLED
427 #self.__next_btn['state'] = DISABLED
b41381b2 428 self.__right_btn['state'] = DISABLED
c8d8653f 429 [itm.play() for itm in self.items]
ce302b41 430 self._start_evt_time = globalClock.getFrameTime()
36099535
FC
431
432 def on_next(self):
32aa4dae
FC
433 self._commands[self._command_idx].redo()
434 self._command_idx += 1
c991401b 435 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
f26497a5
FC
436 #self.__prev_btn['state'] = NORMAL
437 #self.__prev_btn['frameColor'] = fcols[0]
c991401b 438 #more_commands = self._command_idx < len(self._commands)
f26497a5
FC
439 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
440 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
36099535
FC
441
442 def on_prev(self):
32aa4dae
FC
443 self._command_idx -= 1
444 self._commands[self._command_idx].undo()
c991401b 445 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
f26497a5
FC
446 #self.__next_btn['state'] = NORMAL
447 #self.__next_btn['frameColor'] = fcols[0]
448 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
449 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
36099535 450
36099535 451 def on_home(self):
5964572b 452 self._exit_cb()
36099535 453
2aaa10d3 454 def _set_instructions(self):
9830561d
FC
455 self._paused = True
456 self.__store_state()
2aaa10d3
FC
457 mgr = TextPropertiesManager.get_global_ptr()
458 for name in ['mouse_l', 'mouse_r']:
7e487769 459 graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
2aaa10d3
FC
460 graphic.set_scale(.5)
461 graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
462 graphic.get_texture().set_anisotropic_degree(2)
463 mgr.set_graphic(name, graphic)
464 graphic.set_z(-.2)
465 graphic.set_transparency(True)
466 graphic.detach_node()
467 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
468 frameSize=(-.6, .6, -.3, .3))
469 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
470 font.clear()
471 font.set_pixels_per_unit(60)
472 font.set_minfilter(Texture.FTLinearMipmapLinear)
473 font.set_outline((0, 0, 0, 1), .8, .2)
2aaa10d3 474 self._txt = OnscreenText(
0eff64a3
FC
475 self._instr_txt(), parent=frm, font=font, scale=0.06,
476 fg=(.9, .9, .9, 1), align=TextNode.A_left)
2aaa10d3
FC
477 u_l = self._txt.textNode.get_upper_left_3d()
478 l_r = self._txt.textNode.get_lower_right_3d()
479 w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
a0acba9a
FC
480 btn_scale = .05
481 mar = .06 # margin
2aaa10d3 482 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
a0acba9a 483 z += (btn_scale + 2 * mar) / 2
2aaa10d3
FC
484 self._txt['pos'] = -w / 2, z
485 u_l = self._txt.textNode.get_upper_left_3d()
486 l_r = self._txt.textNode.get_lower_right_3d()
a0acba9a
FC
487 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
488 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
2aaa10d3 489 frm['frameSize'] = fsz
a0acba9a
FC
490 colors = [
491 (.6, .6, .6, 1), # ready
492 (1, 1, 1, 1), # press
493 (.8, .8, .8, 1), # rollover
494 (.4, .4, .4, .4)]
495 imgs = [self.__load_img_btn('exitRight', col) for col in colors]
496 btn = DirectButton(
497 image=imgs, scale=btn_scale,
498 pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
499 parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
500 relief=FLAT, frameColor=(.6, .6, .6, .08),
501 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
502 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
503 btn.set_transparency(True)
2d1773b1 504 self._pos_mgr.register('close_instructions', LibP3d.wdg_pos(btn))
a0acba9a 505
0a0994e4 506 def _set_win(self):
3fe18d73 507 self.__persistent.save_scene(self.__class__.__name__, self.version())
9914cfc9
FC
508 loader.load_sfx('assets/audio/sfx/success.ogg').play()
509 self._paused = True
510 self.__store_state()
511 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
512 frameSize=(-.6, .6, -.3, .3))
513 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
514 font.clear()
515 font.set_pixels_per_unit(60)
516 font.set_minfilter(Texture.FTLinearMipmapLinear)
517 font.set_outline((0, 0, 0, 1), .8, .2)
518 self._txt = OnscreenText(
519 _('You win!'),
520 parent=frm,
521 font=font, scale=0.2,
522 fg=(.9, .9, .9, 1))
523 u_l = self._txt.textNode.get_upper_left_3d()
524 l_r = self._txt.textNode.get_lower_right_3d()
c991401b
FC
525 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
526 h = u_l[2] - l_r[2]
9914cfc9
FC
527 btn_scale = .05
528 mar = .06 # margin
529 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
530 z += (btn_scale + 2 * mar) / 2
531 self._txt['pos'] = 0, z
532 u_l = self._txt.textNode.get_upper_left_3d()
533 l_r = self._txt.textNode.get_lower_right_3d()
534 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
535 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
536 frm['frameSize'] = fsz
537 colors = [
538 (.6, .6, .6, 1), # ready
539 (1, 1, 1, 1), # press
540 (.8, .8, .8, 1), # rollover
541 (.4, .4, .4, .4)]
542 imgs = [self.__load_img_btn('home', col) for col in colors]
543 btn = DirectButton(
544 image=imgs, scale=btn_scale,
545 pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
546 parent=frm, command=self._on_end_home, extraArgs=[frm],
547 relief=FLAT, frameColor=(.6, .6, .6, .08),
548 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
549 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
550 btn.set_transparency(True)
2d1773b1 551 self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
9914cfc9
FC
552 imgs = [self.__load_img_btn('rewind', col) for col in colors]
553 btn = DirectButton(
554 image=imgs, scale=btn_scale,
555 pos=(0, 1, l_r[2] - mar - btn_scale),
556 parent=frm, command=self._on_restart, extraArgs=[frm],
557 relief=FLAT, frameColor=(.6, .6, .6, .08),
558 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
559 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
2d1773b1 560 self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
9914cfc9 561 btn.set_transparency(True)
6168d0c2 562 enabled = self._scenes.index(self.__class__) < len(self._scenes) - 1
9914cfc9 563 if enabled:
6168d0c2 564 next_scene = self._scenes[self._scenes.index(self.__class__) + 1]
9914cfc9
FC
565 else:
566 next_scene = None
567 imgs = [self.__load_img_btn('right', col) for col in colors]
568 btn = DirectButton(
569 image=imgs, scale=btn_scale,
570 pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
571 parent=frm, command=self._on_next_scene,
572 extraArgs=[frm, next_scene], relief=FLAT,
573 frameColor=(.6, .6, .6, .08),
574 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
575 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
576 btn['state'] = NORMAL if enabled else DISABLED
2d1773b1 577 self._pos_mgr.register('next', LibP3d.wdg_pos(btn))
9914cfc9
FC
578 btn.set_transparency(True)
579
0a0994e4
FC
580 def _set_fail(self):
581 loader.load_sfx('assets/audio/sfx/success.ogg').play()
582 self._paused = True
583 self.__store_state()
584 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
585 frameSize=(-.6, .6, -.3, .3))
586 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
587 font.clear()
588 font.set_pixels_per_unit(60)
589 font.set_minfilter(Texture.FTLinearMipmapLinear)
590 font.set_outline((0, 0, 0, 1), .8, .2)
591 self._txt = OnscreenText(
592 _('You have failed!'),
593 parent=frm,
594 font=font, scale=0.2,
595 fg=(.9, .9, .9, 1))
596 u_l = self._txt.textNode.get_upper_left_3d()
597 l_r = self._txt.textNode.get_lower_right_3d()
c991401b
FC
598 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
599 h = u_l[2] - l_r[2]
0a0994e4
FC
600 btn_scale = .05
601 mar = .06 # margin
602 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
603 z += (btn_scale + 2 * mar) / 2
604 self._txt['pos'] = 0, z
605 u_l = self._txt.textNode.get_upper_left_3d()
606 l_r = self._txt.textNode.get_lower_right_3d()
607 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
608 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
609 frm['frameSize'] = fsz
610 colors = [
611 (.6, .6, .6, 1), # ready
612 (1, 1, 1, 1), # press
613 (.8, .8, .8, 1), # rollover
614 (.4, .4, .4, .4)]
615 imgs = [self.__load_img_btn('home', col) for col in colors]
616 btn = DirectButton(
617 image=imgs, scale=btn_scale,
618 pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
619 parent=frm, command=self._on_end_home, extraArgs=[frm],
620 relief=FLAT, frameColor=(.6, .6, .6, .08),
621 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
622 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
2d1773b1 623 self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
0a0994e4
FC
624 btn.set_transparency(True)
625 imgs = [self.__load_img_btn('rewind', col) for col in colors]
626 btn = DirectButton(
627 image=imgs, scale=btn_scale,
628 pos=(0, 1, l_r[2] - mar - btn_scale),
629 parent=frm, command=self._on_restart, extraArgs=[frm],
630 relief=FLAT, frameColor=(.6, .6, .6, .08),
631 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
632 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
2d1773b1 633 self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
0a0994e4
FC
634 btn.set_transparency(True)
635
9914cfc9
FC
636 def _on_restart(self, frm):
637 self.__on_close_instructions(frm)
638 self.reset()
639
640 def _on_end_home(self, frm):
641 self.__on_close_instructions(frm)
642 self.on_home()
643
644 def _on_next_scene(self, frm, scene):
645 self.__on_close_instructions(frm)
646 self._reload_cb(scene)
647
a0acba9a
FC
648 def __store_state(self):
649 btns = [
650 self.__home_btn, self.__info_btn, self.__right_btn,
f26497a5
FC
651 #self.__next_btn, self.__prev_btn, self.__rewind_btn
652 ]
a0acba9a
FC
653 self.__btn_state = [btn['state'] for btn in btns]
654 for btn in btns:
655 btn['state'] = DISABLED
656 [itm.store_state() for itm in self.items]
657
658 def __restore_state(self):
659 btns = [
660 self.__home_btn, self.__info_btn, self.__right_btn,
f26497a5
FC
661 #self.__next_btn, self.__prev_btn, self.__rewind_btn
662 ]
a0acba9a
FC
663 for btn, state in zip(btns, self.__btn_state):
664 btn['state'] = state
665 [itm.restore_state() for itm in self.items]
666 self._paused = False
667
668 def __on_close_instructions(self, frm):
669 frm.remove_node()
670 self.__restore_state()
067a36db
FC
671
672 def _set_test_items(self):
673 def frame_after(task):
674 self._define_test_items()
675 for itm in self._test_items:
676 self._pos_mgr.register(itm.name, P3dGfxMgr.pos2d_p2d(itm))
677 taskMgr.doMethodLater(.01, frame_after, 'frame after')
678
679 def _set_test_item(self, name, pos):
680 self._test_items += [NodePath(name)]
681 self._test_items[-1].set_pos(pos[0], 0, pos[1])