source: nagslang/game_object.py@ 591:8dcf5176ffd8

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

Door sound.

File size: 25.5 KB
RevLine 
[81]1import pymunk
[93]2import pymunk.pygame_util
[81]3
[263]4import math
5
[281]6from nagslang import environment
[201]7from nagslang import puzzle
[207]8from nagslang import render
[519]9from nagslang.mutators import FLIP_H, ImageOverlay, rotator
[107]10from nagslang.constants import (
[318]11 COLLISION_TYPE_DOOR, COLLISION_TYPE_FURNITURE, COLLISION_TYPE_PROJECTILE,
[444]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)
[155]15from nagslang.resources import resources
[180]16from nagslang.events import DoorEvent
[591]17from nagslang.sound import sound
[81]18
[82]19
[385]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
[393]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
[385]35
[235]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
[59]44class Physicser(object):
[93]45 def __init__(self, space):
[123]46 self._space = space
47
48 def get_space(self):
49 return self._space
50
[276]51 def set_space(self, new_space):
52 self._space = new_space
53
[123]54 def set_game_object(self, game_object):
55 self.game_object = game_object
56
57 def get_shape(self):
58 raise NotImplementedError()
[93]59
60 def add_to_space(self):
[215]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)
[59]65
[93]66 def remove_from_space(self):
[215]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)
[59]71
[93]72 def get_render_position(self, surface):
[215]73 pos = self.get_shape().body.position
74 return pymunk.pygame_util.to_pygame(pos, surface)
[63]75
[93]76 def get_angle(self):
[215]77 return self.get_shape().body.angle
78
[217]79 def get_velocity(self):
80 return self.get_shape().body.velocity
81
[216]82 def _get_position(self):
[215]83 return self.get_shape().body.position
84
[216]85 def _set_position(self, position):
[215]86 self.get_shape().body.position = position
[93]87
[216]88 position = property(_get_position, _set_position)
89
[93]90 def apply_impulse(self, j, r=(0, 0)):
[215]91 return self.get_shape().body.apply_impulse(j, r)
[59]92
93
94class SingleShapePhysicser(Physicser):
[93]95 def __init__(self, space, shape):
96 super(SingleShapePhysicser, self).__init__(space)
[59]97 self._shape = shape
[186]98 shape.physicser = self
[59]99
[123]100 def get_shape(self):
101 return self._shape
102
[59]103
[427]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
[133]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)
[145]140 body.position = tuple(position)
[133]141 if damping is not None:
142 body.damping = damping
143 body.velocity_func = damping_velocity_func
144 return body
145
146
[59]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
[162]153 zorder = ZORDER_LOW
[218]154 is_moving = False # `True` if a movement animation should play.
[162]155
[281]156 def __init__(self, physicser, renderer, puzzler=None, overlay=None,
157 interactible=None):
[333]158 self.lifetime = 0
[93]159 self.physicser = physicser
[520]160 if physicser is not None:
161 physicser.set_game_object(self)
162 self.physicser.add_to_space()
[59]163 self.renderer = renderer
[123]164 renderer.set_game_object(self)
[81]165 self.puzzler = puzzler
[123]166 if puzzler is not None:
167 puzzler.set_game_object(self)
[191]168 self.overlay = overlay
169 if overlay is not None:
170 self.overlay.set_game_object(self)
[281]171 self.interactible = interactible
172 if interactible is not None:
173 self.interactible.set_game_object(self)
[371]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
[59]187
[346]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
[123]198 def get_space(self):
199 return self.physicser.get_space()
200
201 def get_shape(self):
202 return self.physicser.get_shape()
203
[93]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()
[59]209
[229]210 def get_facing_direction(self):
211 """Used by rendererd that care what direction an object is facing.
212 """
213 return None
214
[59]215 def render(self, surface):
[123]216 return self.renderer.render(surface)
[81]217
[333]218 def update(self, dt):
219 self.lifetime += dt
[371]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)
[333]224 self.renderer.update(dt)
[143]225
[302]226 def hit(self, weapon):
227 '''Was hit with a weapon (such as a bullet)'''
228 pass
229
[256]230 def collide_with_protagonist(self, protagonist):
[186]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 """
[192]236 return True
[186]237
[319]238 def collide_with_furniture(self, furniture):
239 return True
240
[333]241 def collide_with_claw_attack(self, claw_attack):
242 return True
243
[427]244 def environmental_movement(self, vec):
245 self.physicser.apply_impulse(vec)
[416]246
[235]247 @classmethod
248 def requires(cls):
249 """Hints for the level editor"""
250 return [("name", "string")]
251
[477]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
[81]261
262class FloorSwitch(GameObject):
[162]263 zorder = ZORDER_FLOOR
264
[93]265 def __init__(self, space, position):
[145]266 body = make_body(None, None, position)
[81]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__(
[93]271 SingleShapePhysicser(space, self.shape),
[207]272 render.ImageStateRenderer({
[162]273 True: resources.get_image('objects', 'sensor_on.png'),
274 False: resources.get_image('objects', 'sensor_off.png'),
275 }),
[201]276 puzzle.CollidePuzzler(*SWITCH_PUSHERS),
[81]277 )
278
[235]279 @classmethod
280 def requires(cls):
281 return [("name", "string"), ("position", "coordinates")]
282
[106]283
[191]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),
[207]293 render.ImageRenderer(resources.get_image('objects', 'note.png')),
[201]294 puzzle.CollidePuzzler(),
[222]295 render.TextOverlay(message),
[191]296 )
297
[235]298 @classmethod
299 def requires(cls):
300 return [("name", "string"), ("position", "coordinates"),
301 ("message", "text")]
302
[191]303
[520]304class EphemeralNote(GameObject):
[521]305 def __init__(self, message, timeout, **kwargs):
306 kwargs.setdefault('bg_colour', (255, 180, 180, 192))
[520]307 super(EphemeralNote, self).__init__(
308 None,
309 render.NullRenderer(),
310 puzzle.YesPuzzler(),
[521]311 render.TextOverlay(message, **kwargs),
[520]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
[106]322class FloorLight(GameObject):
[162]323 zorder = ZORDER_FLOOR
324
[106]325 def __init__(self, space, position, state_source):
[145]326 body = make_body(None, None, position)
[106]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),
[207]332 render.ImageStateRenderer({
[162]333 True: resources.get_image('objects', 'light_on.png'),
334 False: resources.get_image('objects', 'light_off.png'),
335 }),
[201]336 puzzle.StateProxyPuzzler(state_source),
[106]337 )
[133]338
[235]339 @classmethod
340 def requires(cls):
341 return [("name", "string"), ("position", "coordinates"),
342 ("state_source", "puzzler")]
343
[133]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)])
[208]350 self.shape.friction = 0.5
[318]351 self.shape.collision_type = COLLISION_TYPE_FURNITURE
[133]352 super(Box, self).__init__(
353 SingleShapePhysicser(space, self.shape),
[207]354 render.ImageRenderer(resources.get_image('objects', 'crate.png')),
[133]355 )
[176]356
[235]357 @classmethod
358 def requires(cls):
359 return [("name", "string"), ("position", "coordinates"),
360 ("state_source", "puzzler")]
361
[176]362
[560]363class SokoBox(GameObject):
364 def __init__(self, space, position):
[584]365 body = make_body(1, pymunk.inf, position, 0.1)
[560]366 self.shape = pymunk.Poly(
367 body, [(-40, -40), (40, -40), (40, 40), (-40, 40)])
[584]368 self.shape.friction = 2.0
[560]369 self.shape.collision_type = COLLISION_TYPE_FURNITURE
[563]370 super(SokoBox, self).__init__(
[560]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
[359]382class BaseDoor(GameObject):
[176]383 zorder = ZORDER_FLOOR
[359]384 is_open = True
[176]385
[263]386 def __init__(self, space, position, destination, dest_pos, angle,
[359]387 renderer, condition):
[176]388 body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5)
[281]389 self.shape = pymunk.Circle(body, 30)
[176]390 self.shape.collision_type = COLLISION_TYPE_DOOR
[264]391 self.shape.body.angle = float(angle) / 180 * math.pi
[176]392 self.shape.sensor = True
393 self.destination = destination
394 self.dest_pos = tuple(dest_pos)
[359]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):
[569]404 self.door_opened()
[359]405 DoorEvent.post(self.destination, self.dest_pos)
406
[569]407 def door_opened(self):
[591]408 sound.play_sound('robotstep2.ogg')
[569]409
[359]410
411class Door(BaseDoor):
412 def __init__(self, space, position, destination, dest_pos, angle):
[176]413 super(Door, self).__init__(
[359]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
[437]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
[510]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)
[437]440
441
[519]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
[359]449class PuzzleDoor(BaseDoor):
450 def __init__(self, space, position, destination, dest_pos, angle,
451 key_state):
452 self._key_state = key_state
[519]453 overlay = ImageOverlay(make_overlay_image('lock.png', angle))
[359]454 super(PuzzleDoor, self).__init__(
455 space, position, destination, dest_pos, angle,
[346]456 render.ImageStateRenderer({
457 True: resources.get_image('objects', 'door.png'),
[390]458 False: resources.get_image(
459 'objects', 'door.png', transforms=(overlay,)),
[346]460 }),
[359]461 environment.FunctionCondition(lambda p: self.is_open),
[176]462 )
463
[346]464 @property
465 def is_open(self):
[569]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
[591]472 super(PuzzleDoor, self).door_opened()
[346]473
474 def set_stored_state_dict(self, stored_state):
475 self._stored_state = stored_state
[359]476 self._stored_state.setdefault('is_open', False)
477 return True
[346]478
[235]479 @classmethod
480 def requires(cls):
481 return [("name", "string"), ("position", "coordinates"),
482 ("destination", "level name"), ("dest_pos", "coordinate"),
[263]483 ("angle", "degrees"),
[359]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
[364]491 overlay = ImageOverlay(
[519]492 make_overlay_image('%s.png' % (key_item,), angle))
[359]493 super(KeyedDoor, self).__init__(
494 space, position, destination, dest_pos, angle,
[364]495 render.ImageRenderer(resources.get_image(
496 'objects', 'door.png', transforms=(overlay,))),
[359]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")]
[235]505
[224]506
[558]507class Hatch(GameObject):
[224]508 zorder = ZORDER_FLOOR
509
510 def __init__(self, space, end1, end2, key_state=None):
511 body = make_body(None, None, (0, 0))
[488]512 self.shape = pymunk.Segment(body, tuple(end1), tuple(end2), 7)
[224]513 self.shape.collision_type = COLLISION_TYPE_DOOR
514 if key_state is None:
515 puzzler = puzzle.YesPuzzler()
516 else:
517 puzzler = puzzle.StateProxyPuzzler(key_state)
[558]518 super(Hatch, self).__init__(
[224]519 SingleShapePhysicser(space, self.shape),
[558]520 render.HatchRenderer(),
[224]521 puzzler,
522 )
523
[256]524 def collide_with_protagonist(self, protagonist):
[224]525 if self.puzzler.get_state():
526 # Reject the collision, we can walk through.
527 return False
528 return True
[235]529
[319]530 collide_with_furniture = collide_with_protagonist
531
[235]532 @classmethod
533 def requires(cls):
534 return [("name", "string"), ("end1", "coordinates"),
535 ("end2", "coordinates"), ("key_state", "puzzler")]
[261]536
[570]537 # The level knows that bulkheads are magical
538 @classmethod
539 def movable(cls):
540 return True
541
[261]542
[282]543class ToggleSwitch(GameObject):
544 zorder = ZORDER_LOW
545
546 def __init__(self, space, position):
547 body = make_body(None, None, position)
548 self.shape = pymunk.Circle(body, 20)
549 self.shape.sensor = True
550 super(ToggleSwitch, self).__init__(
551 SingleShapePhysicser(space, self.shape),
[296]552 render.ImageStateRenderer({
553 True: resources.get_image('objects', 'lever.png'),
554 False: resources.get_image(
555 'objects', 'lever.png', transforms=(FLIP_H,)),
556 }),
[282]557 puzzle.ParentAttrPuzzler('toggle_on'),
558 interactible=environment.Interactible(
559 environment.Action(self._toggle)),
560 )
561
[346]562 @property
563 def toggle_on(self):
564 return self._stored_state['toggle_on']
565
[282]566 def _toggle(self, protagonist):
[346]567 self._stored_state['toggle_on'] = not self.toggle_on
568
569 def set_stored_state_dict(self, stored_state):
570 self._stored_state = stored_state
571 # We start in the "off" position.
572 self._stored_state.setdefault('toggle_on', False)
573 return True
[282]574
575 @classmethod
576 def requires(cls):
577 return [("name", "string"), ("position", "coordinates")]
578
579
[261]580class Bullet(GameObject):
[363]581 def __init__(self, space, position, impulse, damage, bullet_type,
[305]582 source_collision_type):
[261]583 body = make_body(1, pymunk.inf, position)
[363]584 body.angle = impulse.angle
[293]585 self.last_position = position
[286]586 self.shape = pymunk.Circle(body, 2)
[293]587 self.shape.sensor = True
[261]588 self.shape.collision_type = COLLISION_TYPE_PROJECTILE
[305]589 self.damage = damage
[363]590 self.type = bullet_type
[293]591 self.source_collision_type = source_collision_type
[261]592 super(Bullet, self).__init__(
593 SingleShapePhysicser(space, self.shape),
[363]594 render.ImageRenderer(resources.get_image(
595 'objects', '%s.png' % self.type)),
[261]596 )
597 self.physicser.apply_impulse(impulse)
[286]598
[333]599 def update(self, dt):
600 super(Bullet, self).update(dt)
[297]601 position = (self.physicser.position.x, self.physicser.position.y)
[293]602 r = self.get_space().segment_query(self.last_position, position)
603 self.last_position = position
604 for collision in r:
[302]605 shape = collision.shape
606 if (shape.collision_type == self.source_collision_type
607 or shape == self.physicser.get_shape()
608 or shape.sensor):
[293]609 continue
[302]610 if hasattr(shape, 'physicser'):
611 shape.physicser.game_object.hit(self)
[293]612 self.physicser.remove_from_space()
[385]613 return Result(remove=[self])
[293]614
[286]615
[312]616class ClawAttack(GameObject):
[356]617 def __init__(self, space, pos, vector, damage):
618 body = make_body(1, pymunk.inf,
619 (pos[0] + (vector.length * math.cos(vector.angle)),
[362]620 pos[1] + (vector.length * math.sin(vector.angle))))
[333]621 body.angle = vector.angle
[312]622 self.shape = pymunk.Circle(body, 30)
623 self.shape.sensor = True
624 self.shape.collision_type = COLLISION_TYPE_WEREWOLF_ATTACK
[333]625 self.damage = damage
[312]626 super(ClawAttack, self).__init__(
627 SingleShapePhysicser(space, self.shape),
[333]628 render.ImageRenderer(resources.get_image(
629 'objects', 'werewolf_SW_claw_attack.png',
630 transforms=(FLIP_H,))),
[312]631 )
632
[333]633 def update(self, dt):
634 super(ClawAttack, self).update(dt)
[335]635 if self.lifetime > 0.1:
[312]636 self.physicser.remove_from_space()
[385]637 return Result(remove=[self])
[351]638
639
640class HostileTerrain(GameObject):
641 zorder = ZORDER_FLOOR
642 damage = None
[401]643 tiles = []
[362]644 tile_alpha = 255
[401]645 tile_frame_ticks = 3
[351]646 # How often to hit the player
647 rate = 5
648
649 def __init__(self, space, position, outline):
650 body = make_body(10, pymunk.inf, position)
651 # Adjust shape relative to position
652 shape_outline = [(p[0] - position[0], p[1] - position[1]) for
653 p in outline]
654 self.shape = pymunk.Poly(body, shape_outline)
655 self._ticks = 0
656 self.shape.collision_type = COLLISION_TYPE_SWITCH
657 self.shape.sensor = True
[548]658 renderer = self._fix_image(outline)
659 super(HostileTerrain, self).__init__(
660 SingleShapePhysicser(space, self.shape),
661 renderer)
662
663 def _fix_image(self, outline):
[401]664 if len(self.tiles) > 1:
665 tile_images = [resources.get_image('tiles', x)
666 for x in self.tiles]
667 renderer = render.TimedTiledRenderer(outline, tile_images,
668 self.tile_frame_ticks,
669 self.tile_alpha)
670 else:
671 tile_image = resources.get_image('tiles', self.tiles[0])
672 renderer = render.TiledRenderer(outline, tile_image,
673 self.tile_alpha)
[548]674 return renderer
675
676 def update_image(self, new_outline):
677 self.renderer = self._fix_image(new_outline)
[351]678
679 def collide_with_protagonist(self, protagonist):
680 # We're called every frame we're colliding, so
681 # There are timing issues with stepping on and
682 # off terrian, but as long as the rate is reasonably
683 # low, they shouldn't impact gameplay
684 if self._ticks == 0:
[357]685 self.apply_effect(protagonist)
[351]686 self._ticks += 1
687 if self._ticks > self.rate:
688 self._ticks = 0
689
[357]690 def apply_effect(self, protagonist):
691 protagonist.lose_health(self.damage)
692
[351]693 @classmethod
694 def requires(cls):
695 return [("name", "string"), ("position", "coordinates"),
[354]696 ("outline", "polygon (convex)")]
[351]697
698
699class AcidFloor(HostileTerrain):
700 damage = 1
[402]701 tiles = ['acid.png', 'acid2.png', 'acid3.png']
[377]702 tile_alpha = 200
[402]703 tile_frame_ticks = 10
[357]704
705
706class ForceWolfFloor(HostileTerrain):
[401]707 tiles = ['moonlight.png']
[357]708 rate = 0
[362]709 tile_alpha = 150
710 zorder = ZORDER_HIGH
[357]711
712 def apply_effect(self, protagonist):
713 protagonist.force_wolf_form()
[416]714
[418]715
[416]716class GravityWell(GameObject):
717 zorder = ZORDER_FLOOR
718 # How often to hit the player
719 rate = 5
720
[427]721 def __init__(self, space, position, radius, force):
722 body = make_body(None, None, position)
[416]723 # Adjust shape relative to position
[427]724 self._radius = radius
725 self.shape = pymunk.Circle(body, radius)
726 self.centre = pymunk.Circle(body, 10)
727 self.centre.friction = pymunk.inf
[416]728 self._ticks = 0
729 self.force = force
730 self.shape.collision_type = COLLISION_TYPE_SWITCH
731 self.shape.sensor = True
732 super(GravityWell, self).__init__(
[427]733 MultiShapePhysicser(space, self.shape, self.centre),
[416]734 render.ImageRenderer(resources.get_image(
735 'objects', 'gravity_well.png')),
736 )
737
738 def collide_with_protagonist(self, protagonist):
739 # We're called every frame we're colliding, so
740 # There are timing issues with stepping on and
741 # off terrian, but as long as the rate is reasonably
742 # low, they shouldn't impact gameplay
[427]743 self.apply_effect(protagonist)
[416]744
745 def collide_with_furniture(self, furniture):
746 # We're called every frame we're colliding, so
747 # There are timing issues with stepping on and
748 # off terrian, but as long as the rate is reasonably
749 # low, they shouldn't impact gameplay
[427]750 self.apply_effect(furniture)
[416]751
[417]752 def apply_effect(self, object_to_move):
[427]753 movement = self.physicser.position - object_to_move.physicser.position
[575]754 local_force = self.force * math.sqrt(
755 object_to_move.get_shape().body.mass)
[429]756 movement.length = local_force
[575]757 object_to_move.environmental_movement(movement)
[416]758
759 @classmethod
760 def requires(cls):
761 return [("name", "string"), ("position", "coordinates"),
[567]762 ("radius", "int"), ("force", "int")]
[444]763
764
765class SheepPen(GameObject):
766 zorder = ZORDER_FLOOR
767
768 def __init__(self, space, position, outline, sheep_count):
769 body = make_body(None, None, position)
770 # Adjust shape relative to position
771 shape_outline = [(p[0] - position[0], p[1] - position[1]) for
772 p in outline]
773 self.shape = pymunk.Poly(body, shape_outline)
774 self.shape.collision_type = COLLISION_TYPE_SHEEP_PEN
775 self.shape.sensor = True
776 super(SheepPen, self).__init__(
777 SingleShapePhysicser(space, self.shape),
778 render.Renderer(),
779 puzzle.MultiCollidePuzzler(sheep_count, COLLISION_TYPE_SHEEP),
780 )
781
782 @classmethod
783 def requires(cls):
784 return [("name", "string"), ("position", "coordinates"),
785 ("outline", "polygon (convex)"), ("sheep_count", "int")]
Note: See TracBrowser for help on using the repository browser.