Refactor mould drawing code. Use shape_query for lighting information
[tabakrolletjie.git] / tabakrolletjie / enemies.py
1 # Boyd, the friendly, misunderstood turnip loving, light hating space mould
2
3 import random
4
5 import pymunk
6 import pymunk.pygame_util
7 import pygame.draw
8 import pygame.surface
9 import pygame.display
10
11 import pygame.locals as pgl
12
13 from .constants import SCREEN_SIZE, MOULD_CATEGORY, OBSTACLE_CATEGORY
14 from .loader import loader
15
16 MOULD_FILTER = pymunk.ShapeFilter(
17     mask=MOULD_CATEGORY | OBSTACLE_CATEGORY,
18     categories=MOULD_CATEGORY)
19
20
21 class Mould(pymunk.Body):
22     """A segment of Boyd"""
23
24     def __init__(self, gamestate, space, pos):
25         super(Mould, self).__init__(0, 0, pymunk.Body.STATIC)
26         self.position = pos
27         self._shape = pymunk.Circle(self, 16)
28         space.add(self, self._shape)
29         self._shape.filter = MOULD_FILTER
30         self._resistances = {}
31         self._age = 0
32         self._img = None
33         self._health = 500
34
35     def pygame_pos(self, surface):
36         """Convert to pygame coordinates and offset position so
37            our position is the centre of the image."""
38         # The odd sign combination is because of the pymunk / pygame
39         # transform, but we do it this way to exploit Vec2d math magic
40         return pymunk.pygame_util.to_pygame(self.position + (-16, 16), surface)
41
42     def get_image(self):
43         if not self._img:
44             name = random.choice(
45                 ('mouldA.png', 'mouldB.png', 'mouldC.png'))
46             self._img = loader.load_image("32", name)
47         return self._img
48
49     def tick(self, gamestate, space, moulds):
50         """Grow and / or Die"""
51         self._age += 1
52
53         # we regain a health every tick, so we heal in the dark
54         if self._health < 100:
55             self._health += 1
56
57         refresh = False
58
59         if (self._age % 15) == 0 and len(moulds) < 1000:
60             # Spawn a new child, if we can
61             spawn = True
62             choice = random.randint(0, 4)
63             if choice == 0:
64                 pos = self.position + (0, 24)
65             elif choice == 1:
66                 pos = self.position + (24, 0)
67             elif choice == 2:
68                 pos = self.position + (-24, 0)
69             else:
70                 pos = self.position + (0, -24)
71             # check for bounds
72             if pos[0] < 0 or pos[0] >= SCREEN_SIZE[0]:
73                 spawn = False
74             if pos[1] < 0 or pos[1] >= SCREEN_SIZE[1]:
75                 spawn = False
76             # Check for free space
77             # We allow some overlap, hence not checking full radius
78             query = space.point_query(pos, 8, MOULD_FILTER)
79             if query:
80                 # for x in query:
81                 #     if not isinstance(x.shape.body, Mould):
82                 #         print x.shape, x.shape.body
83                 spawn = False
84             if spawn:
85                 child = Mould(gamestate, space, pos)
86                 child._health = self._health
87                 moulds.append(child)
88                 refresh = True
89
90         if self._age > 120:
91             # We die of old age
92             space.remove(self, self._shape)
93             moulds.remove(self)
94             refresh = True
95         return refresh
96
97     def damage(self, light_color, intensity, space, moulds):
98         """Take damage for light, adjusted for resistances."""
99         self._health -= 3
100         if self._health <= 0 and self._age <= 120:
101             # We die of damage
102             space.remove(self, self._shape)
103             moulds.remove(self)
104             return True
105         return False
106
107
108 class Boyd(object):
109
110     def __init__(self, gamestate, space):
111         seed = Mould(gamestate, space, (350, 370))
112         self._moulds = [seed]
113         self._image = pygame.surface.Surface(SCREEN_SIZE)
114         self._image.convert_alpha(pygame.display.get_surface())
115         self._draw_moulds()
116
117     def _draw_moulds(self):
118         self._image.fill((0, 0, 0, 0))
119         for m in self._moulds:
120             self._image.blit(m.get_image(),
121                              m.pygame_pos(self._image), None,
122                              pgl.BLEND_RGBA_ADD)
123
124     def tick(self, gamestate, space, lights):
125         redraw = False
126         # Handle spawn events
127         for mould in self._moulds[:]:
128             # Handle updates
129             if mould.tick(gamestate, space, self._moulds):
130                 redraw = True
131             # Check for damage
132             lit_by = lights.light_query(mould._shape)
133             for light in lit_by:
134                 # Todo: extract colour and intensity from light
135                 if mould.damage(None, None, space, self._moulds):
136                     redraw = True
137                     break  # we only die once
138         if redraw:
139             self._draw_moulds()
140
141     def render(self, surface):
142         """Draw ourselves"""
143         surface.blit(self._image, (0, 0), None, pgl.BLEND_RGBA_ADD)