source: nagslang/level.py@ 582:7d8c6e7ffd2b

Last change on this file since 582:7d8c6e7ffd2b was 582:7d8c6e7ffd2b, checked in by Stefano Rivera <stefano@…>, 8 years ago

Music in levels

File size: 9.2 KB
Line 
1import os
2
3import pygame
4import pygame.locals as pgl
5import pymunk
6
7from nagslang import collectable
8from nagslang import game_object as go
9from nagslang import enemies
10from nagslang import puzzle
11from nagslang.utils import (
12 tile_surface, points_to_pygame, extend_line, points_to_lines)
13from nagslang.resources import resources
14from nagslang.yamlish import load, dump
15
16POLY_COLORS = {
17 1: pygame.color.THECOLORS['red'],
18 2: pygame.color.THECOLORS['green'],
19 3: pygame.color.THECOLORS['yellow'],
20 4: pygame.color.THECOLORS['blue'],
21 5: pygame.color.THECOLORS['lightblue'],
22 6: pygame.color.THECOLORS['magenta'],
23 7: pygame.color.THECOLORS['lightgreen'],
24 8: pygame.color.THECOLORS['grey'],
25}
26
27
28LINE_COLOR = pygame.color.THECOLORS['orange']
29
30
31class Level(object):
32 _game_starting_point = None
33
34 def __init__(self, name, world):
35 self.name = name
36 # defaults
37 self.x = 800
38 self.y = 600
39 self.polygons = {}
40 self.lines = []
41 self.world = world
42 self.world.level_state.setdefault(name, {})
43 self.basetile = 'tiles/floor.png'
44 self.music = None
45 self._tile_image = None
46 self._surface = None
47 self._base_surface = None
48 self._exterior = False
49 self._glue = puzzle.PuzzleGlue()
50 self.drawables = []
51 self.overlay_drawables = []
52 self._game_objects = []
53 self._enemies = []
54
55 def _get_data(self):
56 # For overriding in tests.
57 with resources.get_file('levels', self.name) as f:
58 return load(f)
59
60 def _dump_data(self, f):
61 # For manipulation in tests.
62 dump({
63 'size': [self.x, self.y],
64 'base_tile': self.basetile,
65 'polygons': self.polygons,
66 'lines': self.lines,
67 'music': self.music,
68 'game_objects': self._game_objects,
69 'enemies': self._enemies,
70 }, f)
71
72 @classmethod
73 def list_levels(cls):
74 dir_ = resources.get_resource_path('levels')
75 for file_ in os.listdir(dir_):
76 if file_ == 'meta':
77 continue
78 yield file_
79
80 @classmethod
81 def game_starting_point(cls):
82 if not cls._game_starting_point:
83 with resources.get_file('levels', 'meta') as f:
84 data = load(f)
85 cls._game_starting_point = (data['starting_level'],
86 tuple(data['starting_position']))
87 return cls._game_starting_point
88
89 def is_starting_level(self):
90 return self.name == self.game_starting_point()[0]
91
92 def load(self, space):
93 data = self._get_data()
94 self.x, self.y = data['size']
95 self.basetile = data['base_tile']
96 self.music = data['music']
97 for i, points in data['polygons'].iteritems():
98 self.polygons[i] = []
99 for point in points:
100 self.polygons[i].append(tuple(point))
101 self.lines = data.get('lines', [])
102 self._game_objects = data.get('game_objects', [])
103 for game_object_dict in self._game_objects:
104 self._create_game_object(space, **game_object_dict)
105 self._enemies = data.get('enemies', [])
106 for enemy_dict in self._enemies:
107 self._create_enemy(space, **enemy_dict)
108
109 def _create_game_object(self, space, classname, args, name=None):
110 modules = {
111 'collectable': collectable,
112 'game_object': go,
113 'puzzle': puzzle,
114 }
115 if '.' in classname:
116 module, classname = classname.split('.')
117 else:
118 module = 'game_object'
119 cls = getattr(modules[module], classname)
120
121 if module == 'collectable' and name in self.world.inventory:
122 return
123
124 if issubclass(cls, puzzle.Puzzler):
125 gobj = cls(*args)
126 elif issubclass(cls, go.GameObject):
127 gobj = cls(space, *args)
128 level_state = self.world.level_state[self.name]
129 stored_state = level_state.get(name, {})
130 should_save = bool(gobj.set_stored_state_dict(stored_state))
131 if should_save:
132 if name is None:
133 raise Exception(
134 "Unnamed game object wants to save state:" % (gobj,))
135 level_state[name] = stored_state
136 self.drawables.append(gobj)
137 if gobj.overlay:
138 self.overlay_drawables.append(gobj.overlay)
139 else:
140 raise TypeError(
141 "Expected a subclass of Puzzler or GameObject, got %s" % (
142 classname))
143 if name is not None:
144 self._glue.add_component(name, gobj)
145 return gobj
146
147 def _create_enemy(self, space, classname, args, name=None):
148 cls = getattr(enemies, classname)
149 if issubclass(cls, go.GameObject):
150 gobj = cls(space, self.world, *args)
151 self.drawables.append(gobj)
152 else:
153 raise TypeError(
154 "Expected a subclass of GameObject, got %s" % (
155 classname))
156 if name is not None:
157 self._glue.add_component(name, gobj)
158 return gobj
159
160 def all_closed(self):
161 """Check if all the polygons are closed"""
162 closed = True
163 messages = []
164 for index, poly in self.polygons.items():
165 if len(poly) == 0:
166 # We ignore empty polygons
167 continue
168 elif len(poly) == 1:
169 closed = False
170 messages.append("Error: polygon %s too small" % index)
171 elif poly[-1] != poly[0]:
172 closed = False
173 messages.append("Error: polygon %s not closed" % index)
174 return closed, messages
175
176 def save(self):
177 closed, _ = self.all_closed()
178 if not closed:
179 return False
180 with resources.get_file('levels', self.name, mode='w') as f:
181 self._dump_data(f)
182 return True
183
184 def get_size(self):
185 return self.x, self.y
186
187 def set_base_tile(self, new_tile):
188 self.basetile = new_tile
189 self._tile_image = None
190
191 def get_walls(self):
192 walls = self.polygons.values()
193 walls.extend(self.lines)
194 return walls
195
196 def _draw_wall_line(self, points, width, colour, extend):
197 for line in points_to_lines(points):
198 if extend:
199 line = extend_line(
200 pymunk.Vec2d(line[0]), pymunk.Vec2d(line[1]), extend)
201 line = points_to_pygame(self._surface, line)
202 pygame.draw.line(self._surface, colour, line[0], line[1], width)
203
204 def _draw_walls_lines(self, width, colour, extend):
205 for index, polygon in self.polygons.items():
206 self._draw_wall_line(polygon, width, colour, extend)
207 for line in self.lines:
208 self._draw_wall_line(line, width, colour, extend)
209
210 def _draw_walls(self):
211 inner_colour = pygame.color.THECOLORS['red']
212 mid_colour = pygame.color.THECOLORS['orange']
213 outer_colour = pygame.color.THECOLORS['yellow']
214 self._draw_walls_lines(5, outer_colour, 0)
215 self._draw_walls_lines(3, outer_colour, 1)
216 self._draw_walls_lines(3, mid_colour, 0)
217 self._draw_walls_lines(1, inner_colour, 0)
218
219 def get_background(self):
220 if self._surface is None:
221 self._draw_background()
222 self._draw_exterior()
223 # Draw polygons
224 self._draw_walls()
225 return self._surface
226
227 def _draw_exterior(self, force=False):
228 """Fill the exterior of the level with black"""
229 if self._exterior and not force:
230 return
231 white = pygame.color.THECOLORS['white']
232 black = pygame.color.THECOLORS['black']
233 surface = pygame.surface.Surface((self.x, self.y), pgl.SRCALPHA)
234 surface.fill(black)
235 for index, polygon in self.polygons.items():
236 if len(polygon) > 1:
237 pointlist = points_to_pygame(self._surface, polygon)
238 # filled polygons
239 color = white
240 # If a polygon overlaps on of the existing polygons,
241 # it is treated as negative
242 # This is not a complete inversion, since any overlap
243 # triggers this (inversion is easy enough, but the
244 # behaviour doesn't seem useful)
245 # We also only check the vertexes - not breaking this
246 # assumption is left to the level designers
247 surface.lock()
248 for p in pointlist:
249 if surface.get_at(p) == white:
250 color = black
251 surface.unlock()
252 pygame.draw.polygon(surface, color, pointlist, 0)
253 self._surface.blit(surface, (0, 0), special_flags=pgl.BLEND_RGBA_MULT)
254 self._exterior = True
255
256 def _draw_background(self, force=False):
257 if self._tile_image is None:
258 self._tile_image = resources.get_image(self.basetile)
259 if self._surface is not None and not force:
260 # We assume we don't change
261 return self._surface
262 if self._base_surface is None:
263 self._base_surface = tile_surface((self.x, self.y),
264 self._tile_image)
265 self._surface = self._base_surface.copy()
266 return self._surface
Note: See TracBrowser for help on using the repository browser.