source: tools/area_editor.py@ 544:ea396ebc7a92

Last change on this file since 544:ea396ebc7a92 was 544:ea396ebc7a92, checked in by Neil Muller <drnlmuller@…>, 9 years ago

Less leaky object movement

  • Property exe set to *
File size: 48.2 KB
Line 
1#!/usr/bin/env python
2
3# The basic area editor
4#
5# To edit an existing level, use
6# editor levelname
7#
8# To create a new level:
9#
10# editor levelname <xsize> <ysize>
11# (size specified in pixels
12#
13
14import os
15import sys
16
17import pygame
18import pygame.locals as pgl
19
20sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
21
22import pymunk
23
24from albow.root import RootWidget
25from albow.widget import Widget
26from albow.controls import Button, Label, CheckBox
27from albow.dialogs import alert, Dialog, ask
28from albow.layout import Row
29from albow.fields import TextField
30from albow.table_view import TableView, TableColumn
31
32from nagslang.options import parse_args
33from nagslang.constants import SCREEN
34from nagslang.level import Level, POLY_COLORS, LINE_COLOR
35from nagslang.yamlish import load_s
36import nagslang.enemies as ne
37import nagslang.game_object as ngo
38import nagslang.collectable as collectable
39import nagslang.puzzle as np
40
41# layout constants
42MENU_BUTTON_HEIGHT = 35
43MENU_PAD = 4
44MENU_HALF_PAD = MENU_PAD // 2
45MENU_LEFT = SCREEN[0] + MENU_HALF_PAD
46MENU_WIDTH = 200 - MENU_PAD
47MENU_HALF_WIDTH = MENU_WIDTH // 2 - MENU_HALF_PAD
48
49BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
50HALF_BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_HALF_WIDTH, MENU_BUTTON_HEIGHT)
51CHECK_RECT = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
52 MENU_BUTTON_HEIGHT // 2)
53
54
55class TestWorld(object):
56
57 def __init__(self):
58 self.level_state = {}
59 self.inventory = {}
60
61
62def distance(tup1, tup2):
63 return (tup1[0] - tup2[0]) ** 2 + (tup1[1] - tup2[1]) ** 2
64
65
66class EditorLevel(Level):
67
68 def __init__(self, name, x=800, y=600):
69 world = TestWorld()
70 super(EditorLevel, self).__init__(name, world)
71 self.x = x
72 self.y = y
73 # Lookup initiliasition info from the objects
74 self.lookup = {}
75 self._move_poly = None
76
77 def _in_bounds(self, pos):
78 if pos[0] < 0 or pos[0] >= self.x:
79 return False
80 if pos[1] < 0 or pos[1] >= self.y:
81 return False
82 return True
83
84 def load(self, space):
85 super(EditorLevel, self).load(space)
86 # Needed to fill in the lookup dict
87 self.reset_objs()
88
89 def point_to_pygame(self, pos):
90 # inverse of point_to_pymunk
91 # (this is also the same as point_to_pymunk, but an additional
92 # function for sanity later in pyweek).
93 return (pos[0], self.y - pos[1])
94
95 def point_to_pymunk(self, pos):
96 # inverse of point_to_pygame
97 # (this is also the same as point_to_pygame, but an additional
98 # function for sanity later in pyweek).
99 return (pos[0], self.y - pos[1])
100
101 def add_point(self, poly_index, pos):
102 self.polygons.setdefault(poly_index, [])
103 if not self._in_bounds(pos):
104 return False
105 if not self.polygons[poly_index]:
106 point = self.point_to_pymunk(pos)
107 self.polygons[poly_index].append(point)
108 else:
109 add_pos = self.point_to_pymunk(pos)
110 self.polygons[poly_index].append(add_pos)
111 return True
112
113 def delete_point(self, index):
114 if index in self.polygons and len(self.polygons[index]) > 0:
115 self.polygons[index].pop()
116
117 def close_poly(self, index):
118 """Attempts to close the current polygon.
119
120 We allow a small additional step to close the polygon, but
121 it's limited as it's a magic point addition"""
122 if len(self.polygons[index]) < 2:
123 # Too small
124 return False
125 first = self.polygons[index][0]
126 if self.add_point(index, self.point_to_pygame(first)):
127 return True
128 return False
129
130 def add_line(self, start_pos, end_pos):
131 startpoint = self.point_to_pymunk(start_pos)
132 endpoint = self.point_to_pymunk(end_pos)
133 self.lines.append([startpoint, endpoint])
134
135 def draw(self, mouse_pos, mouse_poly, filled, draw_cand_line, start_pos,
136 move_point_mode, move_poly_mode, move_point, zoom_factor,
137 level_widget):
138 self._draw_background(True)
139 # Draw polygons as needed for the editor
140 line_width = int(2 * zoom_factor)
141 if filled:
142 self._draw_exterior(True)
143 for index, polygon in self.polygons.items():
144 color = POLY_COLORS.get(index, pygame.color.THECOLORS['black'])
145 if move_point_mode and index == self._move_poly and move_point:
146 pointlist = [p for p in polygon]
147 pointlist = [self.point_to_pygame(p) if p != move_point else
148 mouse_pos for p in pointlist]
149 pygame.draw.lines(self._surface, color, False, pointlist,
150 line_width)
151 break
152 if move_poly_mode and index == self._move_poly and move_point:
153 new_point = self.point_to_pymunk(mouse_pos)
154 pointlist = [self.point_to_pygame(p)
155 for p in self.translate_poly(
156 polygon, move_point, new_point)]
157 pygame.draw.lines(self._surface, color, False, pointlist,
158 line_width)
159 #break
160 if len(polygon) > 1:
161 pointlist = [self.point_to_pygame(p) for p in polygon]
162 pygame.draw.lines(self._surface, color, False, pointlist,
163 line_width)
164 if index == mouse_poly and mouse_pos and len(polygon) > 0:
165 endpoint = self.point_to_pymunk(mouse_pos)
166 pygame.draw.line(self._surface, color,
167 self.point_to_pygame(polygon[-1]),
168 self.point_to_pygame(endpoint),
169 line_width // 2)
170 line_found = False # Hack for sane behaviour if lines overlap
171 for line in self.lines:
172 pointlist = [self.point_to_pygame(p) for p in line]
173 if move_point_mode and not self._move_poly and not line_found:
174 if move_point in line:
175 line_found = True
176 pointlist.remove(self.point_to_pygame(move_point))
177 pointlist.append(mouse_pos)
178 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist,
179 line_width)
180 if draw_cand_line and start_pos and mouse_pos:
181 endpoint = level_widget.snap_to_grid(mouse_pos)
182 endpoint = self.point_to_pymunk(endpoint)
183 pointlist = [start_pos,
184 self.point_to_pygame(endpoint)]
185 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 1)
186 return self._surface
187
188 def reset_objs(self):
189 # Reset the object state - needed when changing stuff
190 old_drawable = self.drawables[:]
191 self.drawables = []
192 self.overlay_drawables = []
193 self._glue = np.PuzzleGlue()
194 for game_object_dict in self._game_objects:
195 obj = self._create_game_object(pymunk.Space(), **game_object_dict)
196 self.lookup[obj] = game_object_dict
197 for enemy_dict in self._enemies:
198 obj = self._create_enemy(pymunk.Space(), **enemy_dict)
199 self.lookup[obj] = enemy_dict
200
201 def get_class(self, classname, mod=None):
202 # Get the class given the classname
203 modules = {
204 'game_object': ngo,
205 'collectable': collectable,
206 'enemies': ne,
207 'puzzle': np,
208 }
209 if '.' in classname:
210 modname, classname = classname.split('.')
211 mod = modules[modname]
212 if mod is None:
213 mod = ngo
214 return getattr(mod, classname)
215
216 def try_new_object(self, classname, target, new, old=None):
217 if old in target:
218 target.remove(old)
219 try:
220 target.append(new)
221 self.reset_objs()
222 test_surface = pygame.surface.Surface((self.x, self.y))
223 # Check for initialisation stuff that happens in render
224 for thing in self.drawables:
225 if not isinstance(thing, ne.Enemy):
226 thing.render(test_surface)
227 return True
228 except Exception as e:
229 target.remove(new)
230 if old is not None:
231 target.append(old)
232 self.reset_objs()
233 alert("Failed to update object %s: %s" % (classname, e))
234 return False
235
236 def find_obj_at_pos(self, mouse_pos):
237 pymunk_pos = self.point_to_pymunk(mouse_pos)
238 # Search visible objects
239 for obj in self.drawables:
240 if obj.get_shape().point_query(pymunk_pos):
241 return obj
242 return None
243
244 def find_vertex(self, mouse_pos):
245 # search for vertexes closest to where we've killed
246 mindist = 1000
247 move_point = None
248 search_point = self.point_to_pymunk(mouse_pos)
249 for index, polygon in self.polygons.items():
250 for point in polygon:
251 dist = distance(point, search_point)
252 if dist < mindist:
253 mindist = dist
254 move_point = point
255 self._move_poly = index
256 # Also check lines
257 for line in self.lines:
258 for point in line:
259 dist = distance(point, search_point)
260 if dist < mindist:
261 mindist = dist
262 move_point = point
263 self._move_poly = None
264 return move_point
265
266 def translate_poly(self, poly, move_point, new_point):
267 dx = new_point[0] - move_point[0]
268 dy = new_point[1] - move_point[1]
269 new_poly = [(p[0] + dx, p[1] + dy) for p in poly]
270 return new_poly
271
272 def replace_poly(self, move_point, new_point):
273 new_point = self.point_to_pymunk(new_point)
274 if self._move_poly:
275 self.polygons[self._move_poly] = self.translate_poly(
276 self.polygons[self._move_poly], move_point, new_point)
277
278 def replace_vertex(self, old_point, new_point):
279 new_point = self.point_to_pymunk(new_point)
280 if self._move_poly:
281 new_polygon = [p if p != old_point else new_point for p in
282 self.polygons[self._move_poly]]
283 self.polygons[self._move_poly] = new_polygon
284 else:
285 for line in self.lines:
286 if old_point in line:
287 line.remove(old_point)
288 line.append(new_point)
289 break
290
291
292class ObjectTable(TableView):
293
294 columns = [TableColumn("Object", 690, 'l', '%r')]
295
296 def __init__(self, data):
297 super(ObjectTable, self).__init__(height=450)
298 self.data = sorted(data,
299 key=lambda d: (d.get('classname'), d.get('name')))
300 self.selected_row = -1
301
302 def num_rows(self):
303 return len(self.data)
304
305 def row_data(self, i):
306 data = self.data[i]
307 if 'name' in data:
308 return ('%s (%s)' % (data['classname'], data['name']), )
309 return (data['classname'], )
310
311 def row_is_selected(self, i):
312 return self.selected_row == i
313
314 def click_row(self, i, ev):
315 self.selected_row = i
316
317 def get_selection(self):
318 if self.selected_row >= 0:
319 return self.data[self.selected_row]
320 return None
321
322
323class EditClassDialog(Dialog):
324
325 def __init__(self, classname, cls, data, level_widget,
326 delete=False):
327 super(EditClassDialog, self).__init__()
328 self.level_widget = level_widget
329 self.classname = classname
330 self.rect = pygame.rect.Rect(0, 0, 900, 550)
331 title = Label("Editing %s" % classname)
332 title.rect = pygame.rect.Rect(100, 10, 600, 25)
333 self.add(title)
334 self.requires = cls.requires()
335 y = 40
336 self.fields = {}
337 index = 0
338 self.poly_field = None
339 self.needs_cleanup = False
340 for requirement, hint in self.requires:
341 label = Label(requirement)
342 label.rect = pygame.rect.Rect(40, y, 200, 25)
343 self.add(label)
344 field = TextField()
345 field.rect = pygame.rect.Rect(220, y, 400, 25)
346 self.add(field)
347 if data is not None:
348 if requirement in data:
349 field.set_text('%s' % data[requirement])
350 elif 'args' in data and requirement != 'name':
351 # NB: The ordering assumptions in requires should make
352 # this safe, but it's really, really, really fragile
353 try:
354 field.set_text('%s' % data['args'][index])
355 index += 1
356 except IndexError:
357 # Assumed to be arguments with the default value
358 pass
359 if hint.startswith('polygon'):
360 self.poly_field = field
361 self.fields[requirement] = field
362 hintlabel = Label(hint)
363 hintlabel.rect = pygame.rect.Rect(640, y, 250, 25)
364 self.add(hintlabel)
365 y += 30
366 if self.poly_field:
367 y += 40
368 label = Label("Polygon to use:")
369 label.rect = pygame.rect.Rect(40, y, 200, 25)
370 self.add(label)
371 self.poly_choice = TextField()
372 self.poly_choice.rect = pygame.Rect(280, y, 400, 25)
373 self.add(self.poly_choice)
374 y += 30
375 button = Button('Use Polygon X', action=self.get_poly)
376 button.rect = pygame.rect.Rect(350, y, 250, 30)
377 self.add(button)
378 buttons = []
379 if delete:
380 labels = ['OK', 'Delete', 'Cancel']
381 else:
382 labels = ['OK', 'Cancel']
383 for text in labels:
384 but = Button(text, action=lambda x=text: self.dismiss(x))
385 buttons.append(but)
386 row = Row(buttons)
387 row.rect = pygame.rect.Rect(250, 500, 700, 50)
388 self.add(row)
389
390 def get_poly(self):
391 try:
392 try:
393 index = int(self.poly_choice.get_text())
394 except ValueError:
395 index = 0
396 data = self.level_widget.level.polygons[index][:]
397 except KeyError:
398 data = []
399 if data:
400 # We unclose the polygon, because that's what pymunk
401 # wants
402 if data[0] == data[-1] and len(data) > 1:
403 data.pop()
404 data = [list(x) for x in data]
405 self.needs_cleanup = True
406 self.poly_field.set_text('%s' % data)
407
408 def cleanup(self):
409 if self.needs_cleanup:
410 self.level_widget.level.polygons[6] = []
411 self.level_widget.invalidate()
412
413 def get_data(self):
414 result = {}
415 result['classname'] = self.classname
416 args = []
417 # We arrange to bounce this through yaml'ish to convert
418 # stuff to the expected type
419 for val, _ in self.requires:
420 text = self.fields[val].get_text()
421 if not text:
422 # skip empty fields
423 continue
424 if val == 'name':
425 result['name'] = text
426 elif self.fields[val] == self.poly_field and text:
427 # Evil, but faster than good
428 try:
429 l = eval(text)
430 except Exception:
431 alert("Invalid polygon %s" % text)
432 self.needs_cleanup = False
433 return None
434 # Check for convexity
435 if not pymunk.util.is_convex(l):
436 alert("Invalid polygon %s - not convex" % text)
437 return None
438 if not pymunk.util.is_clockwise(l):
439 l.reverse()
440 if not pymunk.util.is_clockwise(l):
441 alert("Invalid polygon %s - unable to make clockwise"
442 % text)
443 return None
444 args.append(' - - %s' % l[0])
445 for coord in l[1:]:
446 args.append(' - %s' % coord)
447 else:
448 args.append(' - ' + text)
449 data = "args:\n" + '\n'.join(args)
450 result['args'] = load_s(data)['args']
451 return result
452
453
454class LevelWidget(Widget):
455
456 def __init__(self, level, parent):
457 super(LevelWidget, self).__init__(pygame.rect.Rect(0, 0,
458 SCREEN[0], SCREEN[1]))
459 self.level = level
460 self.pos = (0, 0)
461 self.filled_mode = False
462 self.mouse_pos = None
463 self.cur_poly = None
464 self._mouse_drag = False
465 self._draw_objects = False
466 self._draw_enemies = False
467 self._draw_lines = False
468 self.grid_size = 1
469 self.sel_mode = False
470 self.move_obj_mode = False
471 self.move_obj = None
472 self._start_pos = None
473 self._parent = parent
474 self._move_point_mode = False
475 self._move_poly_mode = False
476 self._move_point = False
477 self._zoom_factor = 1.0
478
479 def _level_coordinates(self, pos):
480 # Move positions to level values
481 if not pos:
482 return (0, 0)
483 # Apply zoom_factor
484 pos = pos[0] + self.pos[0], pos[1] + self.pos[1]
485 zoomed = (pos[0] * self._zoom_factor, pos[1] * self._zoom_factor)
486 return int(zoomed[0]), int(zoomed[1])
487
488 def _move_view(self, offset):
489 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
490 if new_pos[0] < 0:
491 new_pos[0] = self.pos[0]
492 elif new_pos[0] > self.level.x - SCREEN[0]:
493 new_pos[0] = self.pos[0]
494 if new_pos[1] < 0:
495 new_pos[1] = self.pos[1]
496 elif new_pos[1] > self.level.y - SCREEN[1]:
497 new_pos[1] = self.pos[1]
498 self.pos = tuple(new_pos)
499
500 def inc_grid_size(self, amount):
501 self.grid_size = max(1, self.grid_size + amount)
502 if self.grid_size > 1:
503 self.grid_size = self.grid_size - (self.grid_size % 5)
504
505 def snap_to_grid(self, pos):
506 x = pos[0] - (pos[0] % self.grid_size)
507 y = pos[1] - (pos[1] % self.grid_size)
508 return (x, y)
509
510 def set_objects(self, value):
511 if self._draw_objects != value:
512 self._draw_objects = value
513 self.invalidate()
514
515 def set_enemies(self, value):
516 if self._draw_enemies != value:
517 self._draw_enemies = value
518 self.invalidate()
519
520 def zoom_out(self):
521 self._zoom_factor = self._zoom_factor * 2.0
522 if self._zoom_factor > 8:
523 self._zoom_factor = 8
524
525 def zoom_in(self):
526 self._zoom_factor = self._zoom_factor // 2.0
527 if self._zoom_factor < 1:
528 self._zoom_factor = 1
529
530 def draw(self, surface):
531 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
532 and len(self.level.polygons[self.cur_poly])):
533 # We have an active polygon
534 mouse_pos = self._level_coordinates(self.mouse_pos)
535 mouse_pos = self.snap_to_grid(mouse_pos)
536 elif self._draw_lines:
537 # Interior wall mode
538 mouse_pos = self._level_coordinates(self.mouse_pos)
539 mouse_pos = self.snap_to_grid(mouse_pos)
540 elif self._move_point_mode or self._move_poly_mode:
541 mouse_pos = self._level_coordinates(self.mouse_pos)
542 mouse_pos = self.snap_to_grid(mouse_pos)
543 else:
544 mouse_pos = None
545 level_surface = level.draw(mouse_pos, self.cur_poly, self.filled_mode,
546 self._draw_lines, self._start_pos,
547 self._move_point_mode, self._move_poly_mode,
548 self._move_point, self._zoom_factor, self)
549 if self._draw_objects:
550 for thing in self.level.drawables:
551 if not isinstance(thing, ne.Enemy):
552 thing.render(level_surface)
553 if self._draw_enemies:
554 for thing in self.level.drawables:
555 if isinstance(thing, ne.Enemy):
556 thing.render(level_surface)
557 surface_area = pygame.rect.Rect(self.pos, SCREEN)
558 zoomed_surface = level_surface.copy()
559 if self._zoom_factor != 1:
560 zoomed_surface = pygame.transform.scale(
561 level_surface,
562 (int(level_surface.get_width() / self._zoom_factor),
563 int(level_surface.get_height() / self._zoom_factor)))
564 surface.blit(zoomed_surface, (0, 0), surface_area)
565
566 def change_poly(self, new_poly):
567 self.cur_poly = new_poly
568 self._draw_lines = False
569 self._move_point_mode = False
570 if self.cur_poly is not None:
571 self._parent.reset_lit_buttons()
572 self.filled_mode = False
573
574 def line_mode(self):
575 self.cur_poly = None
576 self._parent.reset_lit_buttons()
577 self._draw_lines = True
578 self.filled_mode = False
579 self._start_pos = None
580 self._move_point_mode = False
581 self._move_poly_mode = False
582
583 def key_down(self, ev):
584 if ev.key == pgl.K_LEFT:
585 self._move_view((-10, 0))
586 elif ev.key == pgl.K_RIGHT:
587 self._move_view((10, 0))
588 elif ev.key == pgl.K_UP:
589 self._move_view((0, -10))
590 elif ev.key == pgl.K_DOWN:
591 self._move_view((0, 10))
592 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
593 self.change_poly(ev.key - pgl.K_0)
594 elif ev.key == pgl.K_0:
595 self.change_poly(None)
596 elif ev.key == pgl.K_d and self.cur_poly:
597 self.level.delete_point(self.cur_poly)
598 elif ev.key == pgl.K_f:
599 self.set_filled()
600 elif ev.key == pgl.K_c:
601 self.close_poly()
602
603 def set_move_poly_mode(self):
604 self._draw_lines = False
605 self._move_point_mode = False
606 self._move_poly_mode = True
607 self.filled_mode = False
608 self._parent.reset_lit_buttons()
609 self._move_point = None
610
611 def set_move_mode(self):
612 self._draw_lines = False
613 self._move_point_mode = True
614 self._move_poly_mode = False
615 self.filled_mode = False
616 self._parent.reset_lit_buttons()
617 self._move_point = None
618
619 def set_filled(self):
620 closed, _ = self.level.all_closed()
621 if closed:
622 self.cur_poly = None
623 self._parent.reset_lit_buttons()
624 self.filled_mode = True
625 self._draw_lines = False
626 self._move_point_mode = False
627 self._move_poly_mode = False
628 else:
629 alert('Not all polygons closed, so not filling')
630
631 def mouse_move(self, ev):
632 old_pos = self.mouse_pos
633 self.mouse_pos = ev.pos
634 if self.move_obj:
635 corrected_pos = self._level_coordinates(ev.pos)
636 snapped_pos = self.snap_to_grid(corrected_pos)
637 self._move_obj(self.move_obj, snapped_pos)
638 if old_pos != self.mouse_pos and (self.cur_poly or self._draw_lines
639 or self._move_point_mode
640 or self._move_poly_mode):
641 self.invalidate()
642
643 def mouse_drag(self, ev):
644 if self._mouse_drag:
645 old_pos = self.mouse_pos
646 self.mouse_pos = ev.pos
647 diff = (-self.mouse_pos[0] + old_pos[0],
648 -self.mouse_pos[1] + old_pos[1])
649 self._move_view(diff)
650 self.invalidate()
651
652 def mouse_down(self, ev):
653 corrected_pos = self._level_coordinates(ev.pos)
654 snapped_pos = self.snap_to_grid(corrected_pos)
655 if self.sel_mode and ev.button == 1:
656 obj = self.level.find_obj_at_pos(corrected_pos)
657 if obj is not None:
658 self._edit_selected(obj)
659 elif self.move_obj_mode and ev.button == 1 and not self.move_obj:
660 obj = self.level.find_obj_at_pos(corrected_pos)
661 if obj is not None:
662 if obj.movable():
663 self.move_obj = obj
664 data = self.level.lookup[obj]
665 elif self.move_obj_mode and ev.button == 1 and self.move_obj:
666 self._update_pos(self.move_obj, snapped_pos)
667 self.move_obj = None
668 elif self._move_poly_mode and ev.button == 1:
669 if self._move_point:
670 # Place the current point
671 self.level.replace_poly(self._move_point, snapped_pos)
672 self._move_point = None
673 self.invalidate()
674 else:
675 # find the current point
676 self._move_point = self.level.find_vertex(corrected_pos)
677 elif self._move_point_mode and ev.button == 1:
678 if self._move_point:
679 # Place the current point
680 self.level.replace_vertex(self._move_point, snapped_pos)
681 self._move_point = None
682 self.invalidate()
683 else:
684 # find the current point
685 self._move_point = self.level.find_vertex(corrected_pos)
686 elif ev.button == 1:
687 if self._draw_lines:
688 if self._start_pos is None:
689 self._start_pos = snapped_pos
690 else:
691 self.level.add_line(self._start_pos, snapped_pos)
692 self._start_pos = None
693 else:
694 print "Click: %r" % (
695 self.level.point_to_pymunk(corrected_pos),)
696 if ev.button == 4: # Scroll up
697 self._move_view((0, -10))
698 elif ev.button == 5: # Scroll down
699 self._move_view((0, 10))
700 elif ev.button == 6: # Scroll left
701 self._move_view((-10, 0))
702 elif ev.button == 7: # Scroll right
703 self._move_view((10, 0))
704 elif self.cur_poly and ev.button == 1:
705 # Add a point
706 if not self.level.add_point(self.cur_poly, snapped_pos):
707 alert("Failed to place point")
708 elif ev.button == 3:
709 self._mouse_drag = True
710
711 def mouse_up(self, ev):
712 if ev.button == 3:
713 self._mouse_drag = False
714
715 def close_poly(self):
716 if self.cur_poly is None:
717 return
718 if self.level.close_poly(self.cur_poly):
719 alert("Successfully closed the polygon")
720 self.change_poly(None)
721 else:
722 alert("Failed to close the polygon")
723
724 def _edit_class(self, classname, cls, data):
725 # Dialog for class properties
726 dialog = EditClassDialog(classname, cls, data, self)
727 if dialog.present() == 'OK':
728 return dialog
729 return None
730
731 def _edit_selected(self, obj):
732 data = self.level.lookup[obj]
733 cls = obj.__class__
734 classname = obj.__class__.__name__
735 dialog = EditClassDialog(classname, cls, data, self, True)
736 res = dialog.present()
737 if res == 'OK':
738 edited = dialog.get_data()
739 if edited is not None:
740 for target in [self.level._game_objects, self.level._enemies]:
741 if data in target:
742 if self.level.try_new_object(classname, target,
743 edited, data):
744 dialog.cleanup()
745 break
746 elif res == 'Delete':
747 for target in [self.level._game_objects, self.level._enemies]:
748 if data in target:
749 target.remove(data)
750 self.level.reset_objs()
751 break
752
753 def _make_edit_dialog(self, entries):
754 # Dialog to hold the editor
755 edit_box = Dialog()
756 edit_box.rect = pygame.rect.Rect(0, 0, 700, 500)
757 table = ObjectTable(entries)
758 edit_box.add(table)
759 buttons = []
760 for text in ['OK', 'Delete', 'Cancel']:
761 but = Button(text, action=lambda x=text: edit_box.dismiss(x))
762 buttons.append(but)
763 row = Row(buttons)
764 row.rect = pygame.rect.Rect(250, 450, 700, 50)
765 edit_box.add(row)
766 edit_box.get_selection = lambda: table.get_selection()
767 return edit_box
768
769 def edit_objects(self):
770 edit_box = self._make_edit_dialog(self.level._game_objects)
771 res = edit_box.present()
772 choice = edit_box.get_selection()
773 if choice is None:
774 return
775 if res == 'OK':
776 cls = self.level.get_class(choice['classname'])
777 edit_dlg = self._edit_class(choice['classname'], cls, choice)
778 if edit_dlg is not None:
779 edited = edit_dlg.get_data()
780 if self.level.try_new_object(choice["classname"],
781 self.level._game_objects,
782 edited, choice):
783 edit_dlg.cleanup()
784 elif res == 'Delete':
785 self.level._game_objects.remove(choice)
786 self.level.reset_objs()
787
788 def edit_enemies(self):
789 edit_box = self._make_edit_dialog(self.level._enemies)
790 res = edit_box.present()
791 choice = edit_box.get_selection()
792 if choice is None:
793 return
794 if res == 'OK':
795 cls = self.level.get_class(choice['classname'], ne)
796 edit_dlg = self._edit_class(choice['classname'], cls, choice)
797 if edit_dlg is not None:
798 edited = edit_dlg.get_data()
799 if self.level.try_new_object(choice["classname"],
800 self.level._enemies,
801 edited, choice):
802 edit_dlg.cleanup()
803 elif res == 'Delete':
804 self.level._enemies.remove(choice)
805 self.level.reset_objs()
806
807 def _make_choice_dialog(self, classes):
808 # Dialog to hold the editor
809 data = []
810 for cls_name, cls in classes:
811 data.append({"classname": cls_name, "class": cls})
812 choice_box = Dialog()
813 choice_box.rect = pygame.rect.Rect(0, 0, 700, 500)
814 table = ObjectTable(data)
815 choice_box.add(table)
816 buttons = []
817 for text in ['OK', 'Cancel']:
818 but = Button(text, action=lambda x=text: choice_box.dismiss(x))
819 buttons.append(but)
820 row = Row(buttons)
821 row.rect = pygame.rect.Rect(250, 450, 700, 50)
822 choice_box.add(row)
823 choice_box.get_selection = lambda: table.get_selection()
824 return choice_box
825
826 def add_game_object(self):
827 classes = ngo.get_editable_game_objects()
828 classes.extend(("collectable.%s" % cls_name, cls)
829 for cls_name, cls
830 in collectable.get_editable_game_objects())
831 choose = self._make_choice_dialog(classes)
832 res = choose.present()
833 choice = choose.get_selection()
834 if res == 'OK' and choice is not None:
835 classname = choice['classname']
836 cls = choice['class']
837 edit_dlg = self._edit_class(classname, cls, None)
838 if edit_dlg is not None:
839 new_cls = edit_dlg.get_data()
840 if self.level.try_new_object(classname,
841 self.level._game_objects,
842 new_cls, None):
843 edit_dlg.cleanup()
844
845 def add_enemy(self):
846 classes = ne.get_editable_enemies()
847 choose = self._make_choice_dialog(classes)
848 res = choose.present()
849 choice = choose.get_selection()
850 if res == 'OK' and choice is not None:
851 classname = choice['classname']
852 cls = choice['class']
853 edit_dlg = self._edit_class(classname, cls, None)
854 if edit_dlg is not None:
855 new_cls = edit_dlg.get_data()
856 if self.level.try_new_object(classname, self.level._enemies,
857 new_cls, None):
858 edit_dlg.cleanup()
859
860 def add_puzzler(self):
861 classes = np.get_editable_puzzlers()
862 choose = self._make_choice_dialog(classes)
863 res = choose.present()
864 choice = choose.get_selection()
865 if res == 'OK' and choice is not None:
866 classname = choice['classname']
867 cls = choice['class']
868 edit_dlg = self._edit_class(classname, cls, None)
869 if edit_dlg is not None:
870 new_cls = edit_dlg.get_data()
871 if self.level.try_new_object(classname,
872 self.level._game_objects,
873 new_cls, None):
874 edit_dlg.cleanup()
875
876 def _move_obj(self, obj, new_pos):
877 new_coords = self.level.point_to_pymunk(new_pos)
878 shape = obj.get_shape()
879 shape.body.position = (new_coords[0], new_coords[1])
880 self.invalidate()
881
882 def _update_pos(self, obj, new_pos, final=False):
883 data = self.level.lookup[obj]
884 new_coords = self.level.point_to_pymunk(new_pos)
885 args = data['args']
886 old_coords = list(args[0])
887 args[0][0] = new_coords[0]
888 args[0][1] = new_coords[1]
889 param_defs = obj.requires()[1:] # chop off name
890 for i, (_key, key_type) in enumerate(param_defs):
891 if i > len(args):
892 break
893 if key_type == "polygon (convex)":
894 args[i] = self.level.translate_poly(
895 args[i], old_coords, new_coords)
896 self.level.reset_objs()
897 self.invalidate()
898
899
900class HighLightButton(Button):
901 """Button with highlight support"""
902 def __init__(self, text, parent, **kwds):
903 super(HighLightButton, self).__init__(text, **kwds)
904 self._parent = parent
905
906 def highlight(self):
907 self.border_color = pygame.color.Color('red')
908
909 def reset(self):
910 self.border_color = self.fg_color
911
912
913class PolyButton(HighLightButton):
914 """Button for coosing the correct polygon"""
915
916 def __init__(self, index, level_widget, parent):
917 if index is not None:
918 text = "P %s" % index
919 else:
920 text = 'Exit Draw Mode'
921 super(PolyButton, self).__init__(text, parent)
922 self.index = index
923 self.level_widget = level_widget
924
925 def action(self):
926 self.level_widget.change_poly(self.index)
927 self._parent.reset_lit_buttons()
928 if self.index is not None:
929 self.highlight()
930
931
932class GridSizeLabel(Label):
933 """Label and setter for grid size."""
934
935 def __init__(self, level_widget, **kwds):
936 self.level_widget = level_widget
937 super(GridSizeLabel, self).__init__(self.grid_text(), **kwds)
938
939 def grid_text(self):
940 return "Grid size: %d" % self.level_widget.grid_size
941
942 def inc_grid_size(self, amount):
943 self.level_widget.inc_grid_size(amount)
944 self.set_text(self.grid_text())
945
946
947class SnapButton(Button):
948 """Button for increasing or decreasing snap-to-grid size."""
949
950 def __init__(self, grid_size_label, parent, inc_amount):
951 self.grid_size_label = grid_size_label
952 self.inc_amount = inc_amount
953 text = "Grid %s%d" % (
954 '-' if inc_amount < 0 else '+',
955 abs(inc_amount))
956 self._parent = parent
957 super(SnapButton, self).__init__(text)
958
959 def action(self):
960 self.grid_size_label.inc_grid_size(self.inc_amount)
961
962
963class EditorApp(RootWidget):
964
965 def __init__(self, level, surface):
966 super(EditorApp, self).__init__(surface)
967 self.level = level
968 self.level_widget = LevelWidget(self.level, self)
969 self.add(self.level_widget)
970
971 self._dMenus = {}
972
973 self._light_buttons = []
974
975 self._make_draw_menu()
976 self._make_objects_menu()
977
978 self._menu_mode = 'drawing'
979 self._populate_menu()
980
981 self._zoom = 1
982
983 def _make_draw_menu(self):
984 widgets = []
985
986 white = pygame.color.Color("white")
987
988 # Add poly buttons
989 y = 5
990 for poly in range(1, 10):
991 but = PolyButton(poly, self.level_widget, self)
992 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 3 - MENU_PAD,
993 MENU_BUTTON_HEIGHT)
994 index = poly % 3
995 if index == 1:
996 but.rect.move_ip(MENU_LEFT, y)
997 elif index == 2:
998 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 3 - MENU_HALF_PAD,
999 y)
1000 else:
1001 left = MENU_LEFT + 2 * MENU_WIDTH // 3 - MENU_HALF_PAD
1002 but.rect.move_ip(left, y)
1003 y += MENU_BUTTON_HEIGHT + MENU_PAD
1004 self._light_buttons.append(but)
1005 widgets.append(but)
1006
1007 end_poly_but = PolyButton(None, self.level_widget, self)
1008 end_poly_but.rect = BUTTON_RECT.copy()
1009 end_poly_but.rect.move_ip(MENU_LEFT, y)
1010 widgets.append(end_poly_but)
1011 y += MENU_BUTTON_HEIGHT + MENU_PAD
1012
1013 self.move_point_but = HighLightButton("Mv Point", self,
1014 action=self.move_point)
1015 self.move_point_but.rect = HALF_BUTTON_RECT.copy()
1016 self.move_point_but.rect.move_ip(MENU_LEFT, y)
1017 widgets.append(self.move_point_but)
1018 self._light_buttons.append(self.move_point_but)
1019
1020 self.move_poly_but = HighLightButton("Mv Poly", self,
1021 action=self.move_poly)
1022 self.move_poly_but.rect = HALF_BUTTON_RECT.copy()
1023 self.move_poly_but.rect.move_ip(MENU_LEFT + MENU_HALF_WIDTH, y)
1024 widgets.append(self.move_poly_but)
1025 self._light_buttons.append(self.move_poly_but)
1026
1027 y += MENU_BUTTON_HEIGHT + MENU_PAD
1028
1029 # grid size widgets
1030 grid_size_label = GridSizeLabel(
1031 self.level_widget, width=BUTTON_RECT.width,
1032 align="c", fg_color=white)
1033 grid_size_label.rect.move_ip(MENU_LEFT, y)
1034 widgets.append(grid_size_label)
1035 y += grid_size_label.rect.height + MENU_PAD
1036 inc_snap_but = SnapButton(grid_size_label, self, 5)
1037 inc_snap_but.rect = HALF_BUTTON_RECT.copy()
1038 inc_snap_but.rect.move_ip(MENU_LEFT, y)
1039 widgets.append(inc_snap_but)
1040 dec_snap_but = SnapButton(grid_size_label, self, -5)
1041 dec_snap_but.rect = HALF_BUTTON_RECT.copy()
1042 dec_snap_but.rect.move_ip(
1043 MENU_LEFT + MENU_HALF_WIDTH, y)
1044 widgets.append(dec_snap_but)
1045 y += MENU_BUTTON_HEIGHT + MENU_PAD
1046
1047 self.draw_line_but = HighLightButton("Draw interior wall", self,
1048 action=self.set_line_mode)
1049 self.draw_line_but.rect = BUTTON_RECT.copy()
1050 self.draw_line_but.rect.move_ip(MENU_LEFT, y)
1051 widgets.append(self.draw_line_but)
1052 self._light_buttons.append(self.draw_line_but)
1053 y += MENU_BUTTON_HEIGHT + MENU_PAD
1054
1055 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
1056 fill_but.rect = BUTTON_RECT.copy()
1057 fill_but.rect.move_ip(MENU_LEFT, y)
1058 widgets.append(fill_but)
1059 y += MENU_BUTTON_HEIGHT + MENU_PAD
1060
1061 close_poly_but = Button('Close Polygon',
1062 action=self.level_widget.close_poly)
1063 close_poly_but.rect = BUTTON_RECT.copy()
1064 close_poly_but.rect.move_ip(MENU_LEFT, y)
1065 widgets.append(close_poly_but)
1066 y += MENU_BUTTON_HEIGHT + MENU_PAD
1067
1068 self.show_objs = CheckBox(fg_color=white)
1069 self.show_objs.rect = CHECK_RECT.copy()
1070 self.show_objs.rect.move_ip(MENU_LEFT, y)
1071 label = Label("Show Objects", fg_color=white)
1072 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
1073 widgets.append(self.show_objs)
1074 widgets.append(label)
1075 y += label.rect.height + MENU_PAD
1076
1077 self.show_enemies = CheckBox(fg_color=white)
1078 self.show_enemies.rect = CHECK_RECT.copy()
1079 self.show_enemies.rect.move_ip(MENU_LEFT, y)
1080 label = Label("Show enemy start pos", fg_color=white)
1081 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
1082 widgets.append(self.show_enemies)
1083 widgets.append(label)
1084 y += label.rect.height + MENU_PAD
1085
1086 y += MENU_PAD
1087 switch_but = Button('Switch to Objects', action=self.switch_to_objects)
1088 switch_but.rect = BUTTON_RECT.copy()
1089 switch_but.rect.move_ip(MENU_LEFT, y)
1090 widgets.append(switch_but)
1091 y += switch_but.rect.height + MENU_PAD
1092
1093 save_but = Button('Save Level', action=self.save)
1094 save_but.rect = BUTTON_RECT.copy()
1095 save_but.rect.move_ip(MENU_LEFT, y)
1096 widgets.append(save_but)
1097 y += MENU_BUTTON_HEIGHT + MENU_PAD
1098
1099 zoom_out = Button('Zoom out', action=self.level_widget.zoom_out)
1100 zoom_out.rect = BUTTON_RECT.copy()
1101 zoom_out.rect.width = zoom_out.rect.width // 2
1102 zoom_out.rect.move_ip(MENU_LEFT, y)
1103 widgets.append(zoom_out)
1104
1105 zoom_in = Button('Zoom in', action=self.level_widget.zoom_in)
1106 zoom_in.rect = BUTTON_RECT.copy()
1107 zoom_in.width = zoom_in.width // 2
1108 zoom_in.rect.move_ip(MENU_LEFT + zoom_out.width, y)
1109 widgets.append(zoom_in)
1110
1111 y = SCREEN[1] - MENU_BUTTON_HEIGHT - MENU_PAD
1112 quit_but = Button('Quit', action=self.do_quit)
1113 quit_but.rect = BUTTON_RECT.copy()
1114 quit_but.rect.move_ip(MENU_LEFT, y)
1115 widgets.append(quit_but)
1116
1117 self._dMenus['drawing'] = widgets
1118
1119 def _make_objects_menu(self):
1120 widgets = []
1121
1122 # Add poly buttons
1123 y = 15
1124
1125 edit_objs_but = Button('Edit Objects',
1126 action=self.level_widget.edit_objects)
1127 edit_objs_but.rect = BUTTON_RECT.copy()
1128 edit_objs_but.rect.move_ip(MENU_LEFT, y)
1129 widgets.append(edit_objs_but)
1130 y += MENU_BUTTON_HEIGHT + MENU_PAD
1131
1132 edir_enemies_but = Button('Edit Enemies',
1133 action=self.level_widget.edit_enemies)
1134 edir_enemies_but.rect = BUTTON_RECT.copy()
1135 edir_enemies_but.rect.move_ip(MENU_LEFT, y)
1136 widgets.append(edir_enemies_but)
1137 y += MENU_BUTTON_HEIGHT + MENU_PAD
1138
1139 add_obj_but = Button('Add Game Object',
1140 action=self.level_widget.add_game_object)
1141 add_obj_but.rect = BUTTON_RECT.copy()
1142 add_obj_but.rect.move_ip(MENU_LEFT, y)
1143 widgets.append(add_obj_but)
1144 y += MENU_BUTTON_HEIGHT + MENU_PAD
1145
1146 add_puzzle_but = Button('Add Puzzler',
1147 action=self.level_widget.add_puzzler)
1148 add_puzzle_but.rect = BUTTON_RECT.copy()
1149 add_puzzle_but.rect.move_ip(MENU_LEFT, y)
1150 widgets.append(add_puzzle_but)
1151 y += MENU_BUTTON_HEIGHT + MENU_PAD
1152
1153 add_enemy_but = Button('Add Enemy',
1154 action=self.level_widget.add_enemy)
1155 add_enemy_but.rect = BUTTON_RECT.copy()
1156 add_enemy_but.rect.move_ip(MENU_LEFT, y)
1157 widgets.append(add_enemy_but)
1158 y += MENU_BUTTON_HEIGHT + MENU_PAD
1159
1160 y += MENU_PAD
1161 self.sel_mode_but = HighLightButton('Select Object', self,
1162 action=self.sel_mode)
1163 self.sel_mode_but.rect = BUTTON_RECT.copy()
1164 self.sel_mode_but.rect.move_ip(MENU_LEFT, y)
1165 widgets.append(self.sel_mode_but)
1166 self._light_buttons.append(self.sel_mode_but)
1167 y += MENU_BUTTON_HEIGHT + MENU_PAD
1168
1169 y += MENU_PAD
1170 self.move_obj_mode_but = HighLightButton('Move Object', self,
1171 action=self.move_obj_mode)
1172 self.move_obj_mode_but.rect = BUTTON_RECT.copy()
1173 self.move_obj_mode_but.rect.move_ip(MENU_LEFT, y)
1174 widgets.append(self.move_obj_mode_but)
1175 self._light_buttons.append(self.move_obj_mode_but)
1176 y += MENU_BUTTON_HEIGHT + MENU_PAD
1177
1178 y += MENU_PAD
1179 switch_but = Button('Switch to Drawing', action=self.switch_to_draw)
1180 switch_but.rect = BUTTON_RECT.copy()
1181 switch_but.rect.move_ip(MENU_LEFT, y)
1182 widgets.append(switch_but)
1183 y += switch_but.rect.height + MENU_PAD
1184
1185 save_but = Button('Save Level', action=self.save)
1186 save_but.rect = BUTTON_RECT.copy()
1187 save_but.rect.move_ip(MENU_LEFT, y)
1188 widgets.append(save_but)
1189 y += MENU_BUTTON_HEIGHT + MENU_PAD
1190
1191 zoom_out = Button('Zoom out', action=self.level_widget.zoom_out)
1192 zoom_out.rect = BUTTON_RECT.copy()
1193 zoom_out.rect.width = zoom_out.rect.width // 2
1194 zoom_out.rect.move_ip(MENU_LEFT, y)
1195 widgets.append(zoom_out)
1196
1197 zoom_in = Button('Zoom in', action=self.level_widget.zoom_in)
1198 zoom_in.rect = BUTTON_RECT.copy()
1199 zoom_in.width = zoom_in.width // 2
1200 zoom_in.rect.move_ip(MENU_LEFT + zoom_out.width, y)
1201 widgets.append(zoom_in)
1202 y += MENU_BUTTON_HEIGHT + MENU_PAD
1203
1204 y = SCREEN[1] - MENU_BUTTON_HEIGHT - MENU_PAD
1205 quit_but = Button('Quit', action=self.do_quit)
1206 quit_but.rect = BUTTON_RECT.copy()
1207 quit_but.rect.move_ip(MENU_LEFT, y)
1208 widgets.append(quit_but)
1209
1210 self._dMenus['objects'] = widgets
1211
1212 def key_down(self, ev):
1213 if ev.key == pgl.K_ESCAPE:
1214 self.do_quit()
1215 elif ev.key == pgl.K_s:
1216 self.save()
1217 else:
1218 self.level_widget.key_down(ev)
1219
1220 def do_quit(self):
1221 res = ask("Really Quit?")
1222 if res == "OK":
1223 self.quit()
1224
1225 def save(self):
1226 closed, messages = self.level.all_closed()
1227 if closed:
1228 self.level.save()
1229 # display success
1230 alert("Level %s saved successfully." % self.level.name)
1231 else:
1232 # display errors
1233 alert("Failed to save level.\n\n%s" % '\n'.join(messages))
1234
1235 def switch_to_draw(self):
1236 if self._menu_mode != 'drawing':
1237 self._clear_menu()
1238 self._menu_mode = 'drawing'
1239 self._populate_menu()
1240
1241 def switch_to_objects(self):
1242 if self._menu_mode != 'objects':
1243 self._clear_menu()
1244 self._menu_mode = 'objects'
1245 self._populate_menu()
1246
1247 def _clear_menu(self):
1248 for widget in self._dMenus[self._menu_mode]:
1249 self.remove(widget)
1250
1251 def reset_lit_buttons(self):
1252 for but in self._light_buttons:
1253 but.reset()
1254
1255 def _populate_menu(self):
1256 self.level_widget.change_poly(None)
1257 self.level_widget.sel_mode = False
1258 self.level_widget.move_obj_mode = False
1259 self.level_widget.move_obj = None
1260 for widget in self._dMenus[self._menu_mode]:
1261 self.add(widget)
1262 self.invalidate()
1263
1264 def set_line_mode(self):
1265 self.level_widget.line_mode()
1266 self.draw_line_but.highlight()
1267
1268 def sel_mode(self):
1269 self.level_widget.sel_mode = not self.level_widget.sel_mode
1270 if self.level_widget.sel_mode:
1271 self.move_obj_mode_but.reset()
1272 self.sel_mode_but.highlight()
1273 self.level_widget.move_obj_mode = False
1274 self.level_widget.move_obj = None
1275 else:
1276 self.sel_mode_but.reset()
1277
1278 def move_obj_mode(self):
1279 self.level_widget.move_obj_mode = not self.level_widget.move_obj_mode
1280 if self.level_widget.move_obj_mode:
1281 self.sel_mode_but.reset()
1282 self.move_obj_mode_but.highlight()
1283 self.level_widget.sel_mode = False
1284 else:
1285 self.move_obj_mode_but.reset()
1286
1287 def mouse_move(self, ev):
1288 self.level_widget.mouse_move(ev)
1289
1290 def move_point(self):
1291 self.level_widget.set_move_mode()
1292 self.move_point_but.highlight()
1293
1294 def move_poly(self):
1295 self.level_widget.set_move_poly_mode()
1296 self.move_poly_but.highlight()
1297
1298 def draw(self, surface):
1299 # Update checkbox state
1300 if self._menu_mode == 'drawing':
1301 self.level_widget.set_objects(self.show_objs.value)
1302 self.level_widget.set_enemies(self.show_enemies.value)
1303 else:
1304 self.level_widget.set_objects(True)
1305 self.level_widget.set_enemies(True)
1306 super(EditorApp, self).draw(surface)
1307
1308
1309if __name__ == "__main__":
1310 if len(sys.argv) not in [2, 4]:
1311 print 'Please supply a levelname or levelname and level size'
1312 sys.exit()
1313 # Need to ensure we have defaults for rendering
1314 parse_args([])
1315 pygame.display.init()
1316 pygame.font.init()
1317 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
1318 pgl.SWSURFACE)
1319 if len(sys.argv) == 2:
1320 level = EditorLevel(sys.argv[1])
1321 level.load(pymunk.Space())
1322 elif len(sys.argv) == 4:
1323 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
1324 pygame.display.set_caption('Nagslang Area Editor')
1325 pygame.key.set_repeat(200, 100)
1326 app = EditorApp(level, pygame.display.get_surface())
1327 app.run()
Note: See TracBrowser for help on using the repository browser.