source: nagslang/game_object.py@ 609:9ea26b835271

Last change on this file since 609:9ea26b835271 was 609:9ea26b835271, checked in by Jeremy Thurgood <firxen@…>, 8 years ago

KeyedHatch

File size: 27.1 KB
Line 
1import pymunk
2import pymunk.pygame_util
3
4import math
5
6from nagslang import environment
7from nagslang import puzzle
8from nagslang import render
9from nagslang.mutators import FLIP_H, ImageOverlay, rotator, scaler
10from nagslang.constants import (
11 COLLISION_TYPE_DOOR, COLLISION_TYPE_FURNITURE, COLLISION_TYPE_PROJECTILE,
12 COLLISION_TYPE_SWITCH, COLLISION_TYPE_SHEEP, COLLISION_TYPE_SHEEP_PEN,
13 COLLISION_TYPE_WEREWOLF_ATTACK, SWITCH_PUSHERS, ZORDER_FLOOR, ZORDER_LOW,
14 ZORDER_HIGH)
15from nagslang.resources import resources
16from nagslang.events import DoorEvent
17from nagslang.sound import sound
18
19
20class Result(object):
21 '''
22 Return from an update() function, to add new objects to the world, and/or
23 remove old objects.
24 '''
25 def __init__(self, add=(), remove=()):
26 self.add = add
27 self.remove = remove
28
29 def merge(self, result):
30 if result is not None:
31 self.add += result.add
32 self.remove += result.remove
33 return self
34
35
36def get_editable_game_objects():
37 classes = []
38 for cls_name, cls in globals().iteritems():
39 if isinstance(cls, type) and hasattr(cls, 'requires'):
40 classes.append((cls_name, cls))
41 return classes
42
43
44class Physicser(object):
45 def __init__(self, space):
46 self._space = space
47
48 def get_space(self):
49 return self._space
50
51 def set_space(self, new_space):
52 self._space = new_space
53
54 def set_game_object(self, game_object):
55 self.game_object = game_object
56
57 def get_shape(self):
58 raise NotImplementedError()
59
60 def add_to_space(self):
61 shape = self.get_shape()
62 self.get_space().add(shape)
63 if not shape.body.is_static:
64 self.get_space().add(shape.body)
65
66 def remove_from_space(self):
67 shape = self.get_shape()
68 self.get_space().remove(shape)
69 if not shape.body.is_static:
70 self.get_space().remove(shape.body)
71
72 def get_render_position(self, surface):
73 pos = self.get_shape().body.position
74 return pymunk.pygame_util.to_pygame(pos, surface)
75
76 def get_angle(self):
77 return self.get_shape().body.angle
78
79 def get_velocity(self):
80 return self.get_shape().body.velocity
81
82 def _get_position(self):
83 return self.get_shape().body.position
84
85 def _set_position(self, position):
86 self.get_shape().body.position = position
87
88 position = property(_get_position, _set_position)
89
90 def apply_impulse(self, j, r=(0, 0)):
91 return self.get_shape().body.apply_impulse(j, r)
92
93
94class SingleShapePhysicser(Physicser):
95 def __init__(self, space, shape):
96 super(SingleShapePhysicser, self).__init__(space)
97 self._shape = shape
98 shape.physicser = self
99
100 def get_shape(self):
101 return self._shape
102
103
104class MultiShapePhysicser(Physicser):
105 def __init__(self, space, shape, *extra_shapes):
106 super(MultiShapePhysicser, self).__init__(space)
107 self._shape = shape
108 self._extra_shapes = extra_shapes
109 shape.physicser = self
110
111 def get_shape(self):
112 return self._shape
113
114 def add_to_space(self):
115 shape = self.get_shape()
116 self.get_space().add(shape)
117 if not shape.body.is_static:
118 self.get_space().add(shape.body)
119 for s in self._extra_shapes:
120 self.get_space().add(s)
121
122 def remove_from_space(self):
123 shape = self.get_shape()
124 self.get_space().remove(shape)
125 if not shape.body.is_static:
126 self.get_space().remove(shape.body)
127 for s in self._extra_shapes:
128 self.get_space().remove(s)
129
130
131def damping_velocity_func(body, gravity, damping, dt):
132 """Apply custom damping to this body's velocity.
133 """
134 damping = getattr(body, 'damping', damping)
135 return pymunk.Body.update_velocity(body, gravity, damping, dt)
136
137
138def make_body(mass, moment, position, damping=None):
139 body = pymunk.Body(mass, moment)
140 body.position = tuple(position)
141 if damping is not None:
142 body.damping = damping
143 body.velocity_func = damping_velocity_func
144 return body
145
146
147class GameObject(object):
148 """A representation of a thing in the game world.
149
150 This has a rendery thing, physicsy things and maybe some other things.
151 """
152
153 zorder = ZORDER_LOW
154 is_moving = False # `True` if a movement animation should play.
155
156 def __init__(self, physicser, renderer, puzzler=None, overlay=None,
157 interactible=None):
158 self.lifetime = 0
159 self.physicser = physicser
160 if physicser is not None:
161 physicser.set_game_object(self)
162 self.physicser.add_to_space()
163 self.renderer = renderer
164 renderer.set_game_object(self)
165 self.puzzler = puzzler
166 if puzzler is not None:
167 puzzler.set_game_object(self)
168 self.overlay = overlay
169 if overlay is not None:
170 self.overlay.set_game_object(self)
171 self.interactible = interactible
172 if interactible is not None:
173 self.interactible.set_game_object(self)
174 self._timers = {}
175 self._active_timers = {}
176
177 def add_timer(self, name, secs):
178 self._timers[name] = secs
179
180 def start_timer(self, name, secs=None):
181 if secs is None:
182 secs = self._timers[name]
183 self._active_timers[name] = secs
184
185 def check_timer(self, name):
186 return name in self._active_timers
187
188 def set_stored_state_dict(self, stored_state):
189 """Override this to set up whatever state storage you want.
190
191 The `stored_state` dict passed in contains whatever saved state we
192 might have for this object. If the return value of this method
193 evaluates to `True`, the contents of the `stored_state` dict will be
194 saved, otherwise it will be discarded.
195 """
196 pass
197
198 def get_space(self):
199 return self.physicser.get_space()
200
201 def get_shape(self):
202 return self.physicser.get_shape()
203
204 def get_render_position(self, surface):
205 return self.physicser.get_render_position(surface)
206
207 def get_render_angle(self):
208 return self.physicser.get_angle()
209
210 def get_facing_direction(self):
211 """Used by rendererd that care what direction an object is facing.
212 """
213 return None
214
215 def render(self, surface):
216 return self.renderer.render(surface)
217
218 def update(self, dt):
219 self.lifetime += dt
220 for timer in self._active_timers.keys():
221 self._active_timers[timer] -= dt
222 if self._active_timers[timer] <= 0:
223 self._active_timers.pop(timer)
224 self.renderer.update(dt)
225
226 def hit(self, weapon):
227 '''Was hit with a weapon (such as a bullet)'''
228 pass
229
230 def collide_with_protagonist(self, protagonist):
231 """Called as a `pre_solve` collision callback with the protagonist.
232
233 You can return `False` to ignore the collision, anything else
234 (including `None`) to process the collision as normal.
235 """
236 return True
237
238 def collide_with_furniture(self, furniture):
239 return True
240
241 def collide_with_claw_attack(self, claw_attack):
242 return True
243
244 def environmental_movement(self, vec):
245 self.physicser.apply_impulse(vec)
246
247 @classmethod
248 def requires(cls):
249 """Hints for the level editor"""
250 return [("name", "string")]
251
252 @classmethod
253 def movable(cls):
254 # Are we movable
255 hints = cls.requires()
256 for x in hints:
257 if 'position' in x:
258 return True
259 return False
260
261
262class FloorSwitch(GameObject):
263 zorder = ZORDER_FLOOR
264
265 def __init__(self, space, position):
266 body = make_body(None, None, position)
267 self.shape = pymunk.Circle(body, 30)
268 self.shape.collision_type = COLLISION_TYPE_SWITCH
269 self.shape.sensor = True
270 super(FloorSwitch, self).__init__(
271 SingleShapePhysicser(space, self.shape),
272 render.ImageStateRenderer({
273 True: resources.get_image('objects', 'sensor_on.png'),
274 False: resources.get_image('objects', 'sensor_off.png'),
275 }),
276 puzzle.CollidePuzzler(*SWITCH_PUSHERS),
277 )
278
279 @classmethod
280 def requires(cls):
281 return [("name", "string"), ("position", "coordinates")]
282
283
284class Note(GameObject):
285 zorder = ZORDER_FLOOR
286
287 def __init__(self, space, position, message):
288 body = make_body(None, None, position)
289 self.shape = pymunk.Circle(body, 30)
290 self.shape.sensor = True
291 super(Note, self).__init__(
292 SingleShapePhysicser(space, self.shape),
293 render.ImageRenderer(resources.get_image('objects', 'note.png')),
294 puzzle.CollidePuzzler(),
295 render.TextOverlay(message),
296 )
297
298 @classmethod
299 def requires(cls):
300 return [("name", "string"), ("position", "coordinates"),
301 ("message", "text")]
302
303
304class EphemeralNote(GameObject):
305 def __init__(self, message, timeout, **kwargs):
306 kwargs.setdefault('bg_colour', (255, 180, 180, 192))
307 super(EphemeralNote, self).__init__(
308 None,
309 render.NullRenderer(),
310 puzzle.YesPuzzler(),
311 render.TextOverlay(message, **kwargs),
312 )
313 self.add_timer('timeout', timeout)
314 self.start_timer('timeout')
315
316 def update(self, dt):
317 super(EphemeralNote, self).update(dt)
318 if not self.check_timer('timeout'):
319 return Result(remove=[self])
320
321
322class FloorLight(GameObject):
323 zorder = ZORDER_FLOOR
324
325 def __init__(self, space, position, state_source):
326 body = make_body(None, None, position)
327 self.shape = pymunk.Circle(body, 10)
328 self.shape.collision_type = COLLISION_TYPE_SWITCH
329 self.shape.sensor = True
330 super(FloorLight, self).__init__(
331 SingleShapePhysicser(space, self.shape),
332 render.ImageStateRenderer({
333 True: resources.get_image('objects', 'light_on.png'),
334 False: resources.get_image('objects', 'light_off.png'),
335 }),
336 puzzle.StateProxyPuzzler(state_source),
337 )
338
339 @classmethod
340 def requires(cls):
341 return [("name", "string"), ("position", "coordinates"),
342 ("state_source", "puzzler")]
343
344
345class Box(GameObject):
346 def __init__(self, space, position):
347 body = make_body(10, 10000, position, damping=0.5)
348 self.shape = pymunk.Poly(
349 body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
350 self.shape.friction = 0.5
351 self.shape.collision_type = COLLISION_TYPE_FURNITURE
352 super(Box, self).__init__(
353 SingleShapePhysicser(space, self.shape),
354 render.ImageRenderer(resources.get_image('objects', 'crate.png')),
355 )
356
357 @classmethod
358 def requires(cls):
359 return [("name", "string"), ("position", "coordinates"),
360 ("state_source", "puzzler")]
361
362
363class SokoBox(GameObject):
364 def __init__(self, space, position):
365 body = make_body(1, pymunk.inf, position, 0.1)
366 self.shape = pymunk.Poly(
367 body, [(-40, -40), (40, -40), (40, 40), (-40, 40)])
368 self.shape.friction = 2.0
369 self.shape.collision_type = COLLISION_TYPE_FURNITURE
370 super(SokoBox, self).__init__(
371 SingleShapePhysicser(space, self.shape),
372 render.ImageRenderer(
373 resources.get_image('objects', 'sokobox.png')),
374 )
375
376 @classmethod
377 def requires(cls):
378 return [("name", "string"), ("position", "coordinates"),
379 ("state_source", "puzzler")]
380
381
382class BaseDoor(GameObject):
383 zorder = ZORDER_FLOOR
384 is_open = True
385
386 def __init__(self, space, position, destination, dest_pos, angle,
387 renderer, condition):
388 body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5)
389 self.shape = pymunk.Circle(body, 30)
390 self.shape.collision_type = COLLISION_TYPE_DOOR
391 self.shape.body.angle = float(angle) / 180 * math.pi
392 self.shape.sensor = True
393 self.destination = destination
394 self.dest_pos = tuple(dest_pos)
395 super(BaseDoor, self).__init__(
396 SingleShapePhysicser(space, self.shape),
397 renderer,
398 puzzle.ParentAttrPuzzler('is_open'),
399 interactible=environment.Interactible(
400 environment.Action(self._post_door_event, condition)),
401 )
402
403 def _post_door_event(self, protagonist):
404 self.door_opened()
405 DoorEvent.post(self.destination, self.dest_pos)
406
407 def door_opened(self):
408 sound.play_sound('robotstep2.ogg')
409
410
411class Door(BaseDoor):
412 def __init__(self, space, position, destination, dest_pos, angle):
413 super(Door, self).__init__(
414 space, position, destination, dest_pos, angle,
415 render.ImageRenderer(resources.get_image('objects', 'door.png')),
416 environment.YesCondition(),
417 )
418
419 @classmethod
420 def requires(cls):
421 return [("name", "string"), ("position", "coordinates"),
422 ("destination", "level name"), ("dest_pos", "coordinate"),
423 ("angle", "degrees")]
424
425
426class RestartGameDoor(Door):
427 def _post_door_event(self, protagonist):
428 protagonist.world.reset()
429 super(RestartGameDoor, self)._post_door_event(protagonist)
430
431
432class ContinueGameDoor(Door):
433 def _post_door_event(self, protagonist):
434 world = protagonist.world
435 if world.level[0]:
436 DoorEvent.post(world.level[0], world.level[1])
437 else:
438 # New game?
439 super(ContinueGameDoor, self)._post_door_event(protagonist)
440
441
442def make_overlay_image(image_name, angle):
443 transforms = ()
444 if angle != 0:
445 transforms = (rotator(-angle),)
446 return resources.get_image('objects', image_name, transforms=transforms)
447
448
449class PuzzleDoor(BaseDoor):
450 def __init__(self, space, position, destination, dest_pos, angle,
451 key_state):
452 self._key_state = key_state
453 overlay = ImageOverlay(make_overlay_image('lock.png', angle))
454 super(PuzzleDoor, self).__init__(
455 space, position, destination, dest_pos, angle,
456 render.ImageStateRenderer({
457 True: resources.get_image('objects', 'door.png'),
458 False: resources.get_image(
459 'objects', 'door.png', transforms=(overlay,)),
460 }),
461 environment.FunctionCondition(lambda p: self.is_open),
462 )
463
464 @property
465 def is_open(self):
466 if self._stored_state['is_open']:
467 return True
468 return self.puzzler.glue.get_state_of(self._key_state)
469
470 def door_opened(self):
471 self._stored_state['is_open'] = True
472 super(PuzzleDoor, self).door_opened()
473
474 def set_stored_state_dict(self, stored_state):
475 self._stored_state = stored_state
476 self._stored_state.setdefault('is_open', False)
477 return True
478
479 @classmethod
480 def requires(cls):
481 return [("name", "string"), ("position", "coordinates"),
482 ("destination", "level name"), ("dest_pos", "coordinate"),
483 ("angle", "degrees"),
484 ("key_state", "puzzler")]
485
486
487class KeyedDoor(BaseDoor):
488 def __init__(self, space, position, destination, dest_pos, angle,
489 key_item=None):
490 self._key_item = key_item
491 overlay = ImageOverlay(
492 make_overlay_image('%s.png' % (key_item,), angle))
493 super(KeyedDoor, self).__init__(
494 space, position, destination, dest_pos, angle,
495 render.ImageRenderer(resources.get_image(
496 'objects', 'door.png', transforms=(overlay,))),
497 environment.ItemRequiredCondition(key_item),
498 )
499
500 @classmethod
501 def requires(cls):
502 return [("name", "string"), ("position", "coordinates"),
503 ("destination", "level name"), ("dest_pos", "coordinate"),
504 ("angle", "degrees"), ("key_item", "item name")]
505
506
507class Hatch(GameObject):
508 zorder = ZORDER_FLOOR
509
510 def __init__(self, space, end1, end2, key_state=None):
511 a = pymunk.Vec2d(end1)
512 b = pymunk.Vec2d(end2)
513 offset = b - a
514 offset.length /= 2
515 mid = (a + offset).int_tuple
516 body = make_body(None, None, mid)
517 self.shape = pymunk.Segment(
518 body, body.world_to_local(tuple(end1)),
519 body.world_to_local(tuple(end2)), 7)
520 self.shape.collision_type = COLLISION_TYPE_DOOR
521 if key_state is None:
522 puzzler = puzzle.YesPuzzler()
523 else:
524 puzzler = puzzle.StateProxyPuzzler(key_state)
525 super(Hatch, self).__init__(
526 SingleShapePhysicser(space, self.shape),
527 render.HatchRenderer(),
528 puzzler,
529 )
530
531 def collide_with_protagonist(self, protagonist):
532 if self.puzzler.get_state():
533 # Reject the collision, we can walk through.
534 return False
535 return True
536
537 collide_with_furniture = collide_with_protagonist
538
539 @classmethod
540 def requires(cls):
541 return [("name", "string"), ("end1", "coordinates"),
542 ("end2", "coordinates"), ("key_state", "puzzler")]
543
544 # The level knows that hatches are magical
545 @classmethod
546 def movable(cls):
547 return True
548
549
550class KeyedHatch(GameObject):
551 zorder = ZORDER_FLOOR
552
553 def __init__(self, space, end1, end2, key_item):
554 a = pymunk.Vec2d(end1)
555 b = pymunk.Vec2d(end2)
556 offset = b - a
557 offset.length /= 2
558 mid = (a + offset).int_tuple
559 body = make_body(None, None, mid)
560 self.shape = pymunk.Segment(
561 body, body.world_to_local(tuple(end1)),
562 body.world_to_local(tuple(end2)), 7)
563 self.shape.collision_type = COLLISION_TYPE_DOOR
564 self._key_item = key_item
565 super(KeyedHatch, self).__init__(
566 SingleShapePhysicser(space, self.shape),
567 render.KeyedHatchRenderer(
568 resources.get_image(
569 'objects', '%s.png' % (key_item,),
570 transforms=(scaler((32, 32)),))),
571 puzzle.ParentAttrPuzzler('is_open'),
572 )
573 self.add_timer('door_open', 0.1)
574
575 @property
576 def is_open(self):
577 return self.check_timer('door_open')
578
579 def collide_with_protagonist(self, protagonist):
580 if protagonist.has_item(self._key_item):
581 self.start_timer('door_open')
582 return False
583 return True
584
585 @classmethod
586 def requires(cls):
587 return [("name", "string"), ("end1", "coordinates"),
588 ("end2", "coordinates"), ("key_item", "item name")]
589
590 # The level knows that hatches are magical
591 @classmethod
592 def movable(cls):
593 return True
594
595
596class ToggleSwitch(GameObject):
597 zorder = ZORDER_LOW
598
599 def __init__(self, space, position):
600 body = make_body(None, None, position)
601 self.shape = pymunk.Circle(body, 20)
602 self.shape.sensor = True
603 super(ToggleSwitch, self).__init__(
604 SingleShapePhysicser(space, self.shape),
605 render.ImageStateRenderer({
606 True: resources.get_image('objects', 'lever.png'),
607 False: resources.get_image(
608 'objects', 'lever.png', transforms=(FLIP_H,)),
609 }),
610 puzzle.ParentAttrPuzzler('toggle_on'),
611 interactible=environment.Interactible(
612 environment.Action(self._toggle)),
613 )
614
615 @property
616 def toggle_on(self):
617 return self._stored_state['toggle_on']
618
619 def _toggle(self, protagonist):
620 self._stored_state['toggle_on'] = not self.toggle_on
621
622 def set_stored_state_dict(self, stored_state):
623 self._stored_state = stored_state
624 # We start in the "off" position.
625 self._stored_state.setdefault('toggle_on', False)
626 return True
627
628 @classmethod
629 def requires(cls):
630 return [("name", "string"), ("position", "coordinates")]
631
632
633class Bullet(GameObject):
634 def __init__(self, space, position, impulse, damage, bullet_type,
635 source_collision_type):
636 body = make_body(1, pymunk.inf, position)
637 body.angle = impulse.angle
638 self.last_position = position
639 self.shape = pymunk.Circle(body, 2)
640 self.shape.sensor = True
641 self.shape.collision_type = COLLISION_TYPE_PROJECTILE
642 self.damage = damage
643 self.type = bullet_type
644 self.source_collision_type = source_collision_type
645 super(Bullet, self).__init__(
646 SingleShapePhysicser(space, self.shape),
647 render.ImageRenderer(resources.get_image(
648 'objects', '%s.png' % self.type)),
649 )
650 self.physicser.apply_impulse(impulse)
651
652 def update(self, dt):
653 super(Bullet, self).update(dt)
654 position = (self.physicser.position.x, self.physicser.position.y)
655 r = self.get_space().segment_query(self.last_position, position)
656 self.last_position = position
657 for collision in r:
658 shape = collision.shape
659 if (shape.collision_type == self.source_collision_type
660 or shape == self.physicser.get_shape()
661 or shape.sensor):
662 continue
663 if hasattr(shape, 'physicser'):
664 shape.physicser.game_object.hit(self)
665 self.physicser.remove_from_space()
666 return Result(remove=[self])
667
668
669class ClawAttack(GameObject):
670 def __init__(self, space, pos, vector, damage):
671 body = make_body(1, pymunk.inf,
672 (pos[0] + (vector.length * math.cos(vector.angle)),
673 pos[1] + (vector.length * math.sin(vector.angle))))
674 body.angle = vector.angle
675 self.shape = pymunk.Circle(body, 30)
676 self.shape.sensor = True
677 self.shape.collision_type = COLLISION_TYPE_WEREWOLF_ATTACK
678 self.damage = damage
679 super(ClawAttack, self).__init__(
680 SingleShapePhysicser(space, self.shape),
681 render.ImageRenderer(resources.get_image(
682 'objects', 'werewolf_SW_claw_attack.png',
683 transforms=(FLIP_H,))),
684 )
685
686 def update(self, dt):
687 super(ClawAttack, self).update(dt)
688 if self.lifetime > 0.1:
689 self.physicser.remove_from_space()
690 return Result(remove=[self])
691
692
693class HostileTerrain(GameObject):
694 zorder = ZORDER_FLOOR
695 damage = None
696 tiles = []
697 tile_alpha = 255
698 tile_frame_ticks = 3
699 # How often to hit the player
700 rate = 5
701
702 def __init__(self, space, position, outline):
703 body = make_body(10, pymunk.inf, position)
704 # Adjust shape relative to position
705 shape_outline = [(p[0] - position[0], p[1] - position[1]) for
706 p in outline]
707 self.shape = pymunk.Poly(body, shape_outline)
708 self._ticks = 0
709 self.shape.collision_type = COLLISION_TYPE_SWITCH
710 self.shape.sensor = True
711 renderer = self._fix_image(outline)
712 super(HostileTerrain, self).__init__(
713 SingleShapePhysicser(space, self.shape),
714 renderer)
715
716 def _fix_image(self, outline):
717 if len(self.tiles) > 1:
718 tile_images = [resources.get_image('tiles', x)
719 for x in self.tiles]
720 renderer = render.TimedTiledRenderer(outline, tile_images,
721 self.tile_frame_ticks,
722 self.tile_alpha)
723 else:
724 tile_image = resources.get_image('tiles', self.tiles[0])
725 renderer = render.TiledRenderer(outline, tile_image,
726 self.tile_alpha)
727 return renderer
728
729 def update_image(self, new_outline):
730 self.renderer = self._fix_image(new_outline)
731
732 def collide_with_protagonist(self, protagonist):
733 # We're called every frame we're colliding, so
734 # There are timing issues with stepping on and
735 # off terrian, but as long as the rate is reasonably
736 # low, they shouldn't impact gameplay
737 if self._ticks == 0:
738 self.apply_effect(protagonist)
739 self._ticks += 1
740 if self._ticks > self.rate:
741 self._ticks = 0
742
743 def apply_effect(self, protagonist):
744 protagonist.lose_health(self.damage)
745
746 @classmethod
747 def requires(cls):
748 return [("name", "string"), ("position", "coordinates"),
749 ("outline", "polygon (convex)")]
750
751
752class AcidFloor(HostileTerrain):
753 damage = 1
754 tiles = ['acid.png', 'acid2.png', 'acid3.png']
755 tile_alpha = 200
756 tile_frame_ticks = 10
757
758
759class ForceWolfFloor(HostileTerrain):
760 tiles = ['moonlight.png']
761 rate = 0
762 tile_alpha = 150
763 zorder = ZORDER_HIGH
764
765 def apply_effect(self, protagonist):
766 protagonist.force_wolf_form()
767
768
769class GravityWell(GameObject):
770 zorder = ZORDER_FLOOR
771 # How often to hit the player
772 rate = 5
773
774 def __init__(self, space, position, radius, force):
775 body = make_body(None, None, position)
776 # Adjust shape relative to position
777 self._radius = radius
778 self.shape = pymunk.Circle(body, radius)
779 self.centre = pymunk.Circle(body, 10)
780 self.centre.friction = pymunk.inf
781 self._ticks = 0
782 self.force = force
783 self.shape.collision_type = COLLISION_TYPE_SWITCH
784 self.shape.sensor = True
785 super(GravityWell, self).__init__(
786 MultiShapePhysicser(space, self.shape, self.centre),
787 render.ImageRenderer(resources.get_image(
788 'objects', 'gravity_well.png')),
789 )
790
791 def collide_with_protagonist(self, protagonist):
792 # We're called every frame we're colliding, so
793 # There are timing issues with stepping on and
794 # off terrian, but as long as the rate is reasonably
795 # low, they shouldn't impact gameplay
796 self.apply_effect(protagonist)
797
798 def collide_with_furniture(self, furniture):
799 # We're called every frame we're colliding, so
800 # There are timing issues with stepping on and
801 # off terrian, but as long as the rate is reasonably
802 # low, they shouldn't impact gameplay
803 self.apply_effect(furniture)
804
805 def apply_effect(self, object_to_move):
806 movement = self.physicser.position - object_to_move.physicser.position
807 local_force = self.force * math.sqrt(
808 object_to_move.get_shape().body.mass)
809 movement.length = local_force
810 object_to_move.environmental_movement(movement)
811
812 @classmethod
813 def requires(cls):
814 return [("name", "string"), ("position", "coordinates"),
815 ("radius", "int"), ("force", "int")]
816
817
818class SheepPen(GameObject):
819 zorder = ZORDER_FLOOR
820
821 def __init__(self, space, position, outline, sheep_count):
822 body = make_body(None, None, position)
823 # Adjust shape relative to position
824 shape_outline = [(p[0] - position[0], p[1] - position[1]) for
825 p in outline]
826 self.shape = pymunk.Poly(body, shape_outline)
827 self.shape.collision_type = COLLISION_TYPE_SHEEP_PEN
828 self.shape.sensor = True
829 super(SheepPen, self).__init__(
830 SingleShapePhysicser(space, self.shape),
831 render.Renderer(),
832 puzzle.MultiCollidePuzzler(sheep_count, COLLISION_TYPE_SHEEP),
833 )
834
835 @classmethod
836 def requires(cls):
837 return [("name", "string"), ("position", "coordinates"),
838 ("outline", "polygon (convex)"), ("sheep_count", "int")]
Note: See TracBrowser for help on using the repository browser.