Merge branch 'master' of ctpug.org.za:tabakrolletjie
[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             size = "16" if self._age < 10 else "32" if self._age < 20 else "64"
47             self._img = loader.load_image(size, name)
48         return self._img
49
50     def tick(self, gamestate, space, moulds):
51         """Grow and / or Die"""
52
53         self._age += 1
54
55         # we regain a health every tick, so we heal in the dark
56         if self._health < 100:
57             self._health += 1
58
59         refresh = False
60
61         if (self._age % 15) == 0 and len(moulds) < 1000:
62             # Spawn a new child, if we can
63             spawn = True
64             choice = random.randint(0, 4)
65             if choice == 0:
66                 pos = self.position + (0, 24)
67             elif choice == 1:
68                 pos = self.position + (24, 0)
69             elif choice == 2:
70                 pos = self.position + (-24, 0)
71             else:
72                 pos = self.position + (0, -24)
73             # check for bounds
74             if pos[0] < 0 or pos[0] >= SCREEN_SIZE[0]:
75                 spawn = False
76             if pos[1] < 0 or pos[1] >= SCREEN_SIZE[1]:
77                 spawn = False
78             # Check for free space
79             # We allow some overlap, hence not checking full radius
80             query = space.point_query(pos, 8, MOULD_FILTER)
81             if query:
82                 # for x in query:
83                 #     if not isinstance(x.shape.body, Mould):
84                 #         print x.shape, x.shape.body
85                 spawn = False
86             if spawn:
87                 child = Mould(gamestate, space, pos)
88                 child._health = self._health
89                 moulds.append(child)
90                 refresh = True
91
92         if self._age in (10, 20):
93             # Segment grows in size
94             refresh = True
95             self._img = None # invalidate cached image
96
97         if self._age > 120:
98             # We die of old age
99             space.remove(self, self._shape)
100             moulds.remove(self)
101             refresh = True
102         return refresh
103
104     def damage(self, light_color, intensity, space, moulds):
105         """Take damage for light, adjusted for resistances."""
106         self._health -= 3
107         if self._health <= 0 and self._age <= 120:
108             # We die of damage
109             space.remove(self, self._shape)
110             moulds.remove(self)
111             return True
112         return False
113
114
115 class Boyd(object):
116
117     def __init__(self, gamestate, space):
118         seed = Mould(gamestate, space, (350, 370))
119         self._moulds = [seed]
120         self._image = pygame.surface.Surface(SCREEN_SIZE)
121         self._image.convert_alpha(pygame.display.get_surface())
122         self._draw_moulds()
123
124     def _draw_moulds(self):
125         self._image.fill((0, 0, 0, 0))
126         for m in self._moulds:
127             self._image.blit(m.get_image(),
128                              m.pygame_pos(self._image), None,
129                              0)
130
131     def tick(self, gamestate, space, lights):
132         redraw = False
133         # Handle spawn events
134         for mould in self._moulds[:]:
135             # Handle updates
136             if mould.tick(gamestate, space, self._moulds):
137                 redraw = True
138             # Check for damage
139             lit_by = lights.light_query(mould._shape)
140             for light in lit_by:
141                 # Todo: extract colour and intensity from light
142                 if mould.damage(None, None, space, self._moulds):
143                     redraw = True
144                     break  # we only die once
145         if redraw:
146             self._draw_moulds()
147
148     def render(self, surface):
149         """Draw ourselves"""
150         surface.blit(self._image, (0, 0), None, pgl.BLEND_RGBA_ADD)