source: tools/area_editor.py@ 262:521f73061872

Last change on this file since 262:521f73061872 was 262:521f73061872, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Better reporting of object errors. Fix incorrect assumption about ordering

  • Property exe set to *
File size: 29.4 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> <ysiz>
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
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.puzzle as np
39
40# layout constants
41MENU_BUTTON_HEIGHT = 35
42MENU_PAD = 6
43MENU_HALF_PAD = MENU_PAD // 2
44MENU_LEFT = SCREEN[0] + MENU_HALF_PAD
45MENU_WIDTH = 200 - MENU_PAD
46
47BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
48CHECK_RECT = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
49 MENU_BUTTON_HEIGHT // 2)
50
51
52class EditorLevel(Level):
53
54 def __init__(self, name, x=800, y=600):
55 super(EditorLevel, self).__init__(name)
56 self.x = x
57 self.y = y
58
59 def round_point(self, pos):
60 return (10 * (pos[0] // 10), 10 * (pos[1] // 10))
61
62 def point_to_pymunk(self, pos):
63 # inverse of point_to_pygame
64 # (this is also the same as point_to_pygame, but a additional
65 # function for sanity later in pyweek).
66 return (pos[0], self.y - pos[1])
67
68 def add_point(self, poly_index, pos):
69 self.polygons.setdefault(poly_index, [])
70 if not self.polygons[poly_index]:
71 point = self.point_to_pymunk(self.round_point(pos))
72 self.polygons[poly_index].append(point)
73 else:
74 add_pos = self.fix_poly_angle(poly_index, pos)
75 self.polygons[poly_index].append(add_pos)
76
77 def _fix_angle(self, point1, pos):
78 # We want the line (point1 to pos) to be an angle of
79 # 0, 45, 90, 135, 180, 225, 270, 305
80 # However, we only need to consider half the circle
81 # This is a hack to approximate the right thing
82 pos0 = (pos[0], point1[1])
83 pos90 = (point1[0], pos[1])
84 dist = max(abs(point1[0] - pos[0]), abs(point1[1] - pos[1]))
85 pos45 = (point1[0] + dist, point1[1] + dist)
86 pos135 = (point1[0] + dist, point1[1] - dist)
87 pos225 = (point1[0] - dist, point1[1] - dist)
88 pos305 = (point1[0] - dist, point1[1] + dist)
89 min_dist = 9999999
90 new_pos = point1
91 for cand in [pos0, pos90, pos45, pos135, pos225, pos305]:
92 dist = (pos[0] - cand[0]) ** 2 + (pos[1] - cand[1]) ** 2
93 if dist < min_dist:
94 new_pos = cand
95 min_dist = dist
96 return self.point_to_pymunk(new_pos)
97
98 def fix_line_angle(self, start_pos, pos):
99 start_pos = self.round_point(start_pos)
100 pos = self.round_point(pos)
101 return self._fix_angle(start_pos, pos)
102
103 def fix_poly_angle(self, index, pos):
104 # Last point
105 point1 = self.point_to_pygame(self.polygons[index][-1])
106 pos = self.round_point(pos)
107 return self._fix_angle(point1, pos)
108
109 def delete_point(self, index):
110 if index in self.polygons and len(self.polygons[index]) > 0:
111 self.polygons[index].pop()
112
113 def close_poly(self, index):
114 """Attempts to close the current polygon.
115
116 We allow a small additional step to close the polygon, but
117 it's limited as it's a magic point addition"""
118 if len(self.polygons[index]) < 2:
119 # Too small
120 return False
121 first = self.polygons[index][0]
122 if self.fix_poly_angle(index, self.point_to_pygame(first)) == first:
123 self.add_point(index, self.point_to_pygame(first))
124 return True
125 candidates = [(first[0] + 10 * i, first[1]) for
126 i in (-3, -2, -1, 1, 2, 3)]
127 candidates.extend([(first[0], first[1] + 10 * i) for
128 i in (-3, -2, -1, 1, 2, 3)])
129 candidates.extend([(first[0] + 10 * i, first[1] + 10 * i) for
130 i in (-3, -2, -1, 1, 2, 3)])
131 candidates.extend([(first[0] + 10 * i, first[1] - 10 * i) for
132 i in (-3, -2, -1, 1, 2, 3)])
133 min_dist = 99999
134 poss = None
135 for cand in candidates:
136 if self.fix_poly_angle(index, self.point_to_pygame(cand)) == cand:
137 dist = (first[0] - cand[0]) ** 2 + (first[1] - cand[1]) ** 2
138 if dist < min_dist:
139 poss = cand
140 if poss is not None:
141 self.add_point(index, self.point_to_pygame(poss))
142 self.add_point(index, self.point_to_pygame(first))
143 return True
144 return False
145
146 def add_line(self, start_pos, end_pos):
147 endpoint = self.fix_line_angle(start_pos, end_pos)
148 startpoint = self.point_to_pymunk(self.round_point(start_pos))
149 self.lines.append([startpoint, endpoint])
150
151 def draw(self, mouse_pos, mouse_poly, filled, draw_cand_line, start_pos):
152 self._draw_background(True)
153 # Draw polygons as needed for the editor
154 if filled:
155 self._draw_exterior(True)
156 for index, polygon in self.polygons.items():
157 color = POLY_COLORS[index]
158 if len(polygon) > 1:
159 pointlist = [self.point_to_pygame(p) for p in polygon]
160 pygame.draw.lines(self._surface, color, False, pointlist, 2)
161 if index == mouse_poly and mouse_pos:
162 endpoint = self.fix_poly_angle(index, mouse_pos)
163 pygame.draw.line(self._surface, color,
164 self.point_to_pygame(polygon[-1]),
165 self.point_to_pygame(endpoint))
166 for line in self.lines:
167 pointlist = [self.point_to_pygame(p) for p in line]
168 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 2)
169 if draw_cand_line and start_pos and mouse_pos:
170 endpoint = self.fix_line_angle(start_pos, mouse_pos)
171 pointlist = [self.round_point(start_pos),
172 self.point_to_pygame(endpoint)]
173 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 1)
174 return self._surface.copy()
175
176 def reset_objs(self):
177 # Reset the object state - needed when changing stuff
178 self.drawables = []
179 self.overlay_drawables = []
180 self._glue = np.PuzzleGlue()
181 for game_object_dict in self._game_objects:
182 self._create_game_object(pymunk.Space(), **game_object_dict)
183 for enemy_dict in self._enemies:
184 self._create_enemy(pymunk.Space(), **enemy_dict)
185
186 def get_class(self, classname, mod=None):
187 # Get the class given the classname
188 modules = {
189 'game_object': ngo,
190 'enemies': ne,
191 'puzzle': np,
192 }
193 if '.' in classname:
194 modname, classname = classname.split('.')
195 mod = modules[modname]
196 if mod is None:
197 mod = ngo
198 return getattr(mod, classname)
199
200 def try_new_object(self, classname, target, new, old=None):
201 if old in target:
202 target.remove(old)
203 try:
204 target.append(new)
205 self.reset_objs()
206 return True
207 except Exception as e:
208 target.remove(new)
209 if old is not None:
210 target.append(old)
211 self.reset_objs()
212 alert("Failed to update object %s: %s" % (classname, e))
213 return False
214
215
216class ObjectTable(TableView):
217
218 columns = [TableColumn("Object", 690, 'l', '%r')]
219
220 def __init__(self, data):
221 super(ObjectTable, self).__init__(height=450)
222 self.data = data
223 self.selected_row = -1
224
225 def num_rows(self):
226 return len(self.data)
227
228 def row_data(self, i):
229 data = self.data[i]
230 if 'name' in data:
231 return ('%s (%s)' % (data['classname'], data['name']), )
232 return (data['classname'], )
233
234 def row_is_selected(self, i):
235 return self.selected_row == i
236
237 def click_row(self, i, ev):
238 self.selected_row = i
239
240 def get_selection(self):
241 if self.selected_row >= 0:
242 return self.data[self.selected_row]
243 return None
244
245
246class EditClassDialog(Dialog):
247
248 def __init__(self, classname, cls, data):
249 super(EditClassDialog, self).__init__()
250 self.classname = classname
251 self.rect = pygame.rect.Rect(0, 0, 800, 550)
252 title = Label("Editing %s" % classname)
253 title.rect = pygame.rect.Rect(100, 10, 600, 25)
254 self.add(title)
255 self.requires = cls.requires()
256 y = 40
257 self.fields = {}
258 index = 0
259 for requirement, hint in self.requires:
260 label = Label(requirement)
261 label.rect = pygame.rect.Rect(40, y, 200, 25)
262 self.add(label)
263 field = TextField()
264 field.rect = pygame.rect.Rect(220, y, 400, 25)
265 self.add(field)
266 if data is not None:
267 if requirement in data:
268 field.set_text('%s' % data[requirement])
269 elif 'args' in data and requirement != 'name':
270 # NB: The ordering assumptions in requires should make
271 # this safe, but it's really, really, really fragile
272 try:
273 field.set_text('%s' % data['args'][index])
274 index += 1
275 except IndexError:
276 # Assumed to be arguments with the default value
277 pass
278 self.fields[requirement] = field
279 hintlabel = Label(hint)
280 hintlabel.rect = pygame.rect.Rect(640, y, 100, 25)
281 self.add(hintlabel)
282 y += 30
283 buttons = []
284 for text in ['OK', 'Cancel']:
285 but = Button(text, action=lambda x=text: self.dismiss(x))
286 buttons.append(but)
287 row = Row(buttons)
288 row.rect = pygame.rect.Rect(250, 500, 700, 50)
289 self.add(row)
290
291 def get_data(self):
292 result = {}
293 result['classname'] = self.classname
294 args = []
295 # We arrange to bounce this through yaml'ish to convert
296 # stuff to the expected type
297 for val, _ in self.requires:
298 text = self.fields[val].get_text()
299 if not text:
300 # skip empty fields
301 continue
302 if val == 'name':
303 result['name'] = text
304 else:
305 args.append(' - ' + text)
306 data = "args:\n" + '\n'.join(args)
307 result['args'] = load_s(data)['args']
308 return result
309
310
311class LevelWidget(Widget):
312
313 def __init__(self, level):
314 super(LevelWidget, self).__init__(pygame.rect.Rect(0, 0,
315 SCREEN[0], SCREEN[1]))
316 self.level = level
317 self.pos = (0, 0)
318 self.filled_mode = False
319 self.mouse_pos = None
320 self.cur_poly = None
321 self._mouse_drag = False
322 self._draw_objects = False
323 self._draw_enemies = False
324 self._draw_lines = False
325 self._start_pos = None
326
327 def _level_coordinates(self, pos):
328 # Move positions to level values
329 if not pos:
330 return (0, 0)
331 return pos[0] + self.pos[0], pos[1] + self.pos[1]
332
333 def _move_view(self, offset):
334 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
335 if new_pos[0] < 0:
336 new_pos[0] = self.pos[0]
337 elif new_pos[0] > self.level.x - SCREEN[0]:
338 new_pos[0] = self.pos[0]
339 if new_pos[1] < 0:
340 new_pos[1] = self.pos[1]
341 elif new_pos[1] > self.level.y - SCREEN[1]:
342 new_pos[1] = self.pos[1]
343 self.pos = tuple(new_pos)
344
345 def set_objects(self, value):
346 if self._draw_objects != value:
347 self._draw_objects = value
348 self.invalidate()
349
350 def set_enemies(self, value):
351 if self._draw_enemies != value:
352 self._draw_enemies = value
353 self.invalidate()
354
355 def draw(self, surface):
356 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
357 and len(self.level.polygons[self.cur_poly])):
358 # We have an active polygon
359 mouse_pos = self._level_coordinates(self.mouse_pos)
360 elif self._draw_lines:
361 # Interior wall mode
362 mouse_pos = self._level_coordinates(self.mouse_pos)
363 else:
364 mouse_pos = None
365 level_surface = level.draw(mouse_pos, self.cur_poly, self.filled_mode,
366 self._draw_lines, self._start_pos)
367 if self._draw_objects:
368 for thing in self.level.drawables:
369 if not isinstance(thing, ne.Enemy):
370 thing.render(level_surface)
371 if self._draw_enemies:
372 for thing in self.level.drawables:
373 if isinstance(thing, ne.Enemy):
374 thing.render(level_surface)
375 surface_area = pygame.rect.Rect(self.pos, SCREEN)
376 surface.blit(level_surface, (0, 0), surface_area)
377
378 def change_poly(self, new_poly):
379 self.cur_poly = new_poly
380 self._draw_lines = False
381 if self.cur_poly is not None:
382 self.filled_mode = False
383
384 def line_mode(self):
385 self.cur_poly = None
386 self._draw_lines = True
387 self.filled_mode = False
388 self._start_pos = None
389
390 def key_down(self, ev):
391 if ev.key == pgl.K_LEFT:
392 self._move_view((-10, 0))
393 elif ev.key == pgl.K_RIGHT:
394 self._move_view((10, 0))
395 elif ev.key == pgl.K_UP:
396 self._move_view((0, -10))
397 elif ev.key == pgl.K_DOWN:
398 self._move_view((0, 10))
399 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
400 self.change_poly(ev.key - pgl.K_0)
401 elif ev.key == pgl.K_0:
402 self.change_poly(None)
403 elif ev.key == pgl.K_d and self.cur_poly:
404 self.level.delete_point(self.cur_poly)
405 elif ev.key == pgl.K_f:
406 self.set_filled()
407 elif ev.key == pgl.K_c:
408 self.close_poly()
409
410 def set_filled(self):
411 closed, _ = self.level.all_closed()
412 if closed:
413 self.cur_poly = None
414 self.filled_mode = True
415 self._draw_lines = False
416 else:
417 alert('Not all polygons closed, so not filling')
418
419 def mouse_move(self, ev):
420 old_pos = self.mouse_pos
421 self.mouse_pos = ev.pos
422 if old_pos != self.mouse_pos and (self.cur_poly or self._draw_lines):
423 self.invalidate()
424
425 def mouse_drag(self, ev):
426 if self._mouse_drag:
427 old_pos = self.mouse_pos
428 self.mouse_pos = ev.pos
429 diff = (-self.mouse_pos[0] + old_pos[0],
430 -self.mouse_pos[1] + old_pos[1])
431 self._move_view(diff)
432 self.invalidate()
433
434 def mouse_down(self, ev):
435 if ev.button == 1:
436 if self._draw_lines:
437 if self._start_pos is None:
438 self._start_pos = ev.pos
439 else:
440 self.level.add_line(self._start_pos, ev.pos)
441 self._start_pos = None
442 else:
443 print "Click: %r" % (
444 self.level.point_to_pymunk(
445 self._level_coordinates(ev.pos)),)
446 if ev.button == 4: # Scroll up
447 self._move_view((0, -10))
448 elif ev.button == 5: # Scroll down
449 self._move_view((0, 10))
450 elif ev.button == 6: # Scroll left
451 self._move_view((-10, 0))
452 elif ev.button == 7: # Scroll right
453 self._move_view((10, 0))
454 elif self.cur_poly and ev.button == 1:
455 # Add a point
456 self.level.add_point(self.cur_poly,
457 self._level_coordinates(ev.pos))
458 elif ev.button == 3:
459 self._mouse_drag = True
460
461 def mouse_up(self, ev):
462 if ev.button == 3:
463 self._mouse_drag = False
464
465 def close_poly(self):
466 if self.cur_poly is None:
467 return
468 if self.level.close_poly(self.cur_poly):
469 alert("Successfully closed the polygon")
470 self.change_poly(None)
471 else:
472 alert("Failed to close the polygon")
473
474 def _edit_class(self, classname, cls, data):
475 # Dialog for class properties
476 dialog = EditClassDialog(classname, cls, data)
477 if dialog.present() == 'OK':
478 return dialog.get_data()
479 return None
480
481 def _make_edit_dialog(self, entries):
482 # Dialog to hold the editor
483 edit_box = Dialog()
484 edit_box.rect = pygame.rect.Rect(0, 0, 700, 500)
485 table = ObjectTable(entries)
486 edit_box.add(table)
487 buttons = []
488 for text in ['OK', 'Delete', 'Cancel']:
489 but = Button(text, action=lambda x=text: edit_box.dismiss(x))
490 buttons.append(but)
491 row = Row(buttons)
492 row.rect = pygame.rect.Rect(250, 450, 700, 50)
493 edit_box.add(row)
494 edit_box.get_selection = lambda: table.get_selection()
495 return edit_box
496
497 def edit_objects(self):
498 edit_box = self._make_edit_dialog(self.level._game_objects)
499 res = edit_box.present()
500 choice = edit_box.get_selection()
501 if choice is None:
502 return
503 if res == 'OK':
504 cls = self.level.get_class(choice['classname'])
505 edited = self._edit_class(choice['classname'], cls, choice)
506 if edited is not None:
507 self.level.try_new_object(choice["classname"],
508 self.level._game_objects,
509 edited, choice)
510 elif res == 'Delete':
511 self.level._game_objects.remove(choice)
512 self.level.reset_objs()
513
514 def edit_enemies(self):
515 edit_box = self._make_edit_dialog(self.level._enemies)
516 res = edit_box.present()
517 choice = edit_box.get_selection()
518 if choice is None:
519 return
520 if res == 'OK':
521 cls = self.level.get_class(choice['classname'], ne)
522 edited = self._edit_class(choice['classname'], cls, choice)
523 if edited is not None:
524 self.level.try_new_object(choice["classname"],
525 self.level._enemies, edited, choice)
526 elif res == 'Delete':
527 self.level._enemies.remove(choice)
528 self.level.reset_objs()
529
530 def _make_choice_dialog(self, classes):
531 # Dialog to hold the editor
532 data = []
533 for cls_name, cls in classes:
534 data.append({"classname": cls_name, "class": cls})
535 choice_box = Dialog()
536 choice_box.rect = pygame.rect.Rect(0, 0, 700, 500)
537 table = ObjectTable(data)
538 choice_box.add(table)
539 buttons = []
540 for text in ['OK', 'Cancel']:
541 but = Button(text, action=lambda x=text: choice_box.dismiss(x))
542 buttons.append(but)
543 row = Row(buttons)
544 row.rect = pygame.rect.Rect(250, 450, 700, 50)
545 choice_box.add(row)
546 choice_box.get_selection = lambda: table.get_selection()
547 return choice_box
548
549 def add_game_object(self):
550 classes = ngo.get_editable_game_objects()
551 choose = self._make_choice_dialog(classes)
552 res = choose.present()
553 choice = choose.get_selection()
554 if res == 'OK' and choice is not None:
555 classname = choice['classname']
556 cls = choice['class']
557 new_cls = self._edit_class(classname, cls, None)
558 if new_cls is not None:
559 self.level.try_new_object(classname, self.level._game_objects,
560 new_cls, None)
561
562 def add_enemy(self):
563 classes = ne.get_editable_enemies()
564 choose = self._make_choice_dialog(classes)
565 res = choose.present()
566 choice = choose.get_selection()
567 if res == 'OK' and choice is not None:
568 classname = choice['classname']
569 cls = choice['class']
570 new_cls = self._edit_class(classname, cls, None)
571 if new_cls is not None:
572 self.level.try_new_object(classname, self.level._enemies,
573 new_cls, None)
574
575 def add_puzzler(self):
576 classes = np.get_editable_puzzlers()
577 choose = self._make_choice_dialog(classes)
578 res = choose.present()
579 choice = choose.get_selection()
580 if res == 'OK' and choice is not None:
581 classname = choice['classname']
582 cls = choice['class']
583 new_cls = self._edit_class(classname, cls, None)
584 if new_cls is not None:
585 self.level.try_new_object(classname, self.level._game_objects,
586 new_cls, None)
587
588
589class PolyButton(Button):
590 """Button for coosing the correct polygon"""
591
592 def __init__(self, index, level_widget):
593 if index is not None:
594 text = "Draw: %s" % index
595 else:
596 text = 'Exit Draw Mode'
597 super(PolyButton, self).__init__(text)
598 self.index = index
599 self.level_widget = level_widget
600
601 def action(self):
602 self.level_widget.change_poly(self.index)
603
604
605class EditorApp(RootWidget):
606
607 def __init__(self, level, surface):
608 super(EditorApp, self).__init__(surface)
609 self.level = level
610 self.level_widget = LevelWidget(self.level)
611 self.add(self.level_widget)
612
613 self._dMenus = {}
614
615 self._make_draw_menu()
616 self._make_objects_menu()
617
618 self._menu_mode = 'drawing'
619 self._populate_menu()
620
621 def _make_draw_menu(self):
622 widgets = []
623
624 # Add poly buttons
625 y = 15
626 for poly in range(1, 7):
627 but = PolyButton(poly, self.level_widget)
628 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 2 - MENU_PAD,
629 MENU_BUTTON_HEIGHT)
630 if poly % 2:
631 but.rect.move_ip(MENU_LEFT, y)
632 else:
633 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 2 - MENU_HALF_PAD,
634 y)
635 y += MENU_BUTTON_HEIGHT + MENU_PAD
636 widgets.append(but)
637
638 end_poly_but = PolyButton(None, self.level_widget)
639 end_poly_but.rect = BUTTON_RECT.copy()
640 end_poly_but.rect.move_ip(MENU_LEFT, y)
641 widgets.append(end_poly_but)
642 y += MENU_BUTTON_HEIGHT + MENU_PAD
643
644 draw_line = Button("Draw interior wall", self.level_widget.line_mode)
645 draw_line.rect = BUTTON_RECT.copy()
646 draw_line.rect.move_ip(MENU_LEFT, y)
647 widgets.append(draw_line)
648 y += MENU_BUTTON_HEIGHT + MENU_PAD
649
650 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
651 fill_but.rect = BUTTON_RECT.copy()
652 fill_but.rect.move_ip(MENU_LEFT, y)
653 widgets.append(fill_but)
654 y += MENU_BUTTON_HEIGHT + MENU_PAD
655
656 close_poly_but = Button('Close Polygon',
657 action=self.level_widget.close_poly)
658 close_poly_but.rect = BUTTON_RECT.copy()
659 close_poly_but.rect.move_ip(MENU_LEFT, y)
660 widgets.append(close_poly_but)
661 y += MENU_BUTTON_HEIGHT + MENU_PAD
662
663 white = pygame.color.Color("white")
664 self.show_objs = CheckBox(fg_color=white)
665 self.show_objs.rect = CHECK_RECT.copy()
666 self.show_objs.rect.move_ip(MENU_LEFT, y)
667 label = Label("Show Objects", fg_color=white)
668 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
669 widgets.append(self.show_objs)
670 widgets.append(label)
671 y += label.rect.height + MENU_PAD
672
673 self.show_enemies = CheckBox(fg_color=white)
674 self.show_enemies.rect = CHECK_RECT.copy()
675 self.show_enemies.rect.move_ip(MENU_LEFT, y)
676 label = Label("Show enemy start pos", fg_color=white)
677 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
678 widgets.append(self.show_enemies)
679 widgets.append(label)
680 y += label.rect.height + MENU_PAD
681
682 y += MENU_PAD
683 switch_but = Button('Switch to Objects', action=self.switch_to_objects)
684 switch_but.rect = BUTTON_RECT.copy()
685 switch_but.rect.move_ip(MENU_LEFT, y)
686 widgets.append(switch_but)
687 y += switch_but.rect.height + MENU_PAD
688
689 save_but = Button('Save Level', action=self.save)
690 save_but.rect = BUTTON_RECT.copy()
691 save_but.rect.move_ip(MENU_LEFT, y)
692 widgets.append(save_but)
693 y += MENU_BUTTON_HEIGHT + MENU_PAD
694
695 y += MENU_PAD
696 quit_but = Button('Quit', action=self.quit)
697 quit_but.rect = BUTTON_RECT.copy()
698 quit_but.rect.move_ip(MENU_LEFT, y)
699 widgets.append(quit_but)
700
701 self._dMenus['drawing'] = widgets
702
703 def _make_objects_menu(self):
704 widgets = []
705
706 # Add poly buttons
707 y = 15
708
709 edit_objs_but = Button('Edit Objects',
710 action=self.level_widget.edit_objects)
711 edit_objs_but.rect = BUTTON_RECT.copy()
712 edit_objs_but.rect.move_ip(MENU_LEFT, y)
713 widgets.append(edit_objs_but)
714 y += MENU_BUTTON_HEIGHT + MENU_PAD
715
716 edir_enemies_but = Button('Edit Enemies',
717 action=self.level_widget.edit_enemies)
718 edir_enemies_but.rect = BUTTON_RECT.copy()
719 edir_enemies_but.rect.move_ip(MENU_LEFT, y)
720 widgets.append(edir_enemies_but)
721 y += MENU_BUTTON_HEIGHT + MENU_PAD
722
723 add_obj_but = Button('Add Game Object',
724 action=self.level_widget.add_game_object)
725 add_obj_but.rect = BUTTON_RECT.copy()
726 add_obj_but.rect.move_ip(MENU_LEFT, y)
727 widgets.append(add_obj_but)
728 y += MENU_BUTTON_HEIGHT + MENU_PAD
729
730 add_puzzle_but = Button('Add Puzzler',
731 action=self.level_widget.add_puzzler)
732 add_puzzle_but.rect = BUTTON_RECT.copy()
733 add_puzzle_but.rect.move_ip(MENU_LEFT, y)
734 widgets.append(add_puzzle_but)
735 y += MENU_BUTTON_HEIGHT + MENU_PAD
736
737 add_enemy_but = Button('Add Enemy',
738 action=self.level_widget.add_enemy)
739 add_enemy_but.rect = BUTTON_RECT.copy()
740 add_enemy_but.rect.move_ip(MENU_LEFT, y)
741 widgets.append(add_enemy_but)
742 y += MENU_BUTTON_HEIGHT + MENU_PAD
743
744 y += MENU_PAD
745 switch_but = Button('Switch to Drawing', action=self.switch_to_draw)
746 switch_but.rect = BUTTON_RECT.copy()
747 switch_but.rect.move_ip(MENU_LEFT, y)
748 widgets.append(switch_but)
749 y += switch_but.rect.height + MENU_PAD
750
751 save_but = Button('Save Level', action=self.save)
752 save_but.rect = BUTTON_RECT.copy()
753 save_but.rect.move_ip(MENU_LEFT, y)
754 widgets.append(save_but)
755 y += MENU_BUTTON_HEIGHT + MENU_PAD
756
757 y += MENU_PAD
758 quit_but = Button('Quit', action=self.quit)
759 quit_but.rect = BUTTON_RECT.copy()
760 quit_but.rect.move_ip(MENU_LEFT, y)
761 widgets.append(quit_but)
762
763 self._dMenus['objects'] = widgets
764
765 def key_down(self, ev):
766 if ev.key == pgl.K_ESCAPE:
767 self.quit()
768 elif ev.key == pgl.K_s:
769 self.save()
770 else:
771 self.level_widget.key_down(ev)
772
773 def save(self):
774 closed, messages = self.level.all_closed()
775 if closed:
776 self.level.save()
777 # display success
778 alert("Level %s saved successfully." % self.level.name)
779 else:
780 # display errors
781 alert("Failed to save level.\n\n%s" % '\n'.join(messages))
782
783 def switch_to_draw(self):
784 if self._menu_mode != 'drawing':
785 self._clear_menu()
786 self._menu_mode = 'drawing'
787 self._populate_menu()
788
789 def switch_to_objects(self):
790 if self._menu_mode != 'objects':
791 self._clear_menu()
792 self._menu_mode = 'objects'
793 self._populate_menu()
794
795 def _clear_menu(self):
796 for widget in self._dMenus[self._menu_mode]:
797 self.remove(widget)
798
799 def _populate_menu(self):
800 self.level_widget.change_poly(None)
801 for widget in self._dMenus[self._menu_mode]:
802 self.add(widget)
803 self.invalidate()
804
805 def mouse_move(self, ev):
806 self.level_widget.mouse_move(ev)
807
808 def draw(self, surface):
809 # Update checkbox state
810 if self._menu_mode == 'drawing':
811 self.level_widget.set_objects(self.show_objs.value)
812 self.level_widget.set_enemies(self.show_enemies.value)
813 else:
814 self.level_widget.set_objects(True)
815 self.level_widget.set_enemies(True)
816 super(EditorApp, self).draw(surface)
817
818
819if __name__ == "__main__":
820 if len(sys.argv) not in [2, 4]:
821 print 'Please supply a levelname or levelname and level size'
822 sys.exit()
823 # Need to ensure we have defaults for rendering
824 parse_args([])
825 pygame.display.init()
826 pygame.font.init()
827 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
828 pgl.SWSURFACE)
829 if len(sys.argv) == 2:
830 level = EditorLevel(sys.argv[1])
831 level.load(pymunk.Space())
832 elif len(sys.argv) == 4:
833 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
834 pygame.display.set_caption('Nagslang Area Editor')
835 pygame.key.set_repeat(200, 100)
836 app = EditorApp(level, pygame.display.get_surface())
837 app.run()
Note: See TracBrowser for help on using the repository browser.