source: tools/area_editor.py@ 261:db7c8e74efb4

Last change on this file since 261:db7c8e74efb4 was 255:d4928d4a661a, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Object editing

  • Property exe set to *
File size: 29.7 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, 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:
208 target.remove(new)
209 if old is not None:
210 target.append(old)
211 self.reset_objs()
212 return False
213
214
215class ObjectTable(TableView):
216
217 columns = [TableColumn("Object", 690, 'l', '%r')]
218
219 def __init__(self, data):
220 super(ObjectTable, self).__init__(height=450)
221 self.data = data
222 self.selected_row = -1
223
224 def num_rows(self):
225 return len(self.data)
226
227 def row_data(self, i):
228 data = self.data[i]
229 if 'name' in data:
230 return ('%s (%s)' % (data['classname'], data['name']), )
231 return (data['classname'], )
232
233 def row_is_selected(self, i):
234 return self.selected_row == i
235
236 def click_row(self, i, ev):
237 self.selected_row = i
238
239 def get_selection(self):
240 if self.selected_row >= 0:
241 return self.data[self.selected_row]
242 return None
243
244
245class EditClassDialog(Dialog):
246
247 def __init__(self, classname, cls, data):
248 super(EditClassDialog, self).__init__()
249 self.classname = classname
250 self.rect = pygame.rect.Rect(0, 0, 800, 550)
251 title = Label("Editing %s" % classname)
252 title.rect = pygame.rect.Rect(100, 10, 600, 25)
253 self.add(title)
254 requires = cls.requires()
255 y = 40
256 self.fields = {}
257 index = 0
258 for requirement, hint in requires:
259 label = Label(requirement)
260 label.rect = pygame.rect.Rect(40, y, 200, 25)
261 self.add(label)
262 field = TextField()
263 field.rect = pygame.rect.Rect(220, y, 400, 25)
264 self.add(field)
265 if data is not None:
266 if requirement in data:
267 field.set_text('%s' % data[requirement])
268 elif 'args' in data and requirement != 'name':
269 # NB: The ordering assumptions in requires should make
270 # this safe, but it's really, really, really fragile
271 try:
272 field.set_text('%s' % data['args'][index])
273 index += 1
274 except IndexError:
275 # Assumed to be arguments with the default value
276 pass
277 self.fields[requirement] = field
278 hintlabel = Label(hint)
279 hintlabel.rect = pygame.rect.Rect(640, y, 100, 25)
280 self.add(hintlabel)
281 y += 30
282 buttons = []
283 for text in ['OK', 'Cancel']:
284 but = Button(text, action=lambda x=text: self.dismiss(x))
285 buttons.append(but)
286 row = Row(buttons)
287 row.rect = pygame.rect.Rect(250, 500, 700, 50)
288 self.add(row)
289
290 def get_data(self):
291 result = {}
292 result['classname'] = self.classname
293 args = []
294 # We arrange to bounce this through yaml'ish to convert
295 # stuff to the expected type
296 for val in self.fields:
297 text = self.fields[val].get_text()
298 if not text:
299 # skip empty fields
300 continue
301 if val == 'name':
302 result['name'] = text
303 else:
304 args.append(' - ' + text)
305 data = "args:\n" + '\n'.join(args)
306 result['args'] = load_s(data)['args']
307 return result
308
309
310class LevelWidget(Widget):
311
312 def __init__(self, level):
313 super(LevelWidget, self).__init__(pygame.rect.Rect(0, 0,
314 SCREEN[0], SCREEN[1]))
315 self.level = level
316 self.pos = (0, 0)
317 self.filled_mode = False
318 self.mouse_pos = None
319 self.cur_poly = None
320 self._mouse_drag = False
321 self._draw_objects = False
322 self._draw_enemies = False
323 self._draw_lines = False
324 self._start_pos = None
325
326 def _level_coordinates(self, pos):
327 # Move positions to level values
328 if not pos:
329 return (0, 0)
330 return pos[0] + self.pos[0], pos[1] + self.pos[1]
331
332 def _move_view(self, offset):
333 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
334 if new_pos[0] < 0:
335 new_pos[0] = self.pos[0]
336 elif new_pos[0] > self.level.x - SCREEN[0]:
337 new_pos[0] = self.pos[0]
338 if new_pos[1] < 0:
339 new_pos[1] = self.pos[1]
340 elif new_pos[1] > self.level.y - SCREEN[1]:
341 new_pos[1] = self.pos[1]
342 self.pos = tuple(new_pos)
343
344 def set_objects(self, value):
345 if self._draw_objects != value:
346 self._draw_objects = value
347 self.invalidate()
348
349 def set_enemies(self, value):
350 if self._draw_enemies != value:
351 self._draw_enemies = value
352 self.invalidate()
353
354 def draw(self, surface):
355 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
356 and len(self.level.polygons[self.cur_poly])):
357 # We have an active polygon
358 mouse_pos = self._level_coordinates(self.mouse_pos)
359 elif self._draw_lines:
360 # Interior wall mode
361 mouse_pos = self._level_coordinates(self.mouse_pos)
362 else:
363 mouse_pos = None
364 level_surface = level.draw(mouse_pos, self.cur_poly, self.filled_mode,
365 self._draw_lines, self._start_pos)
366 if self._draw_objects:
367 for thing in self.level.drawables:
368 if not isinstance(thing, ne.Enemy):
369 thing.render(level_surface)
370 if self._draw_enemies:
371 for thing in self.level.drawables:
372 if isinstance(thing, ne.Enemy):
373 thing.render(level_surface)
374 surface_area = pygame.rect.Rect(self.pos, SCREEN)
375 surface.blit(level_surface, (0, 0), surface_area)
376
377 def change_poly(self, new_poly):
378 self.cur_poly = new_poly
379 self._draw_lines = False
380 if self.cur_poly is not None:
381 self.filled_mode = False
382
383 def line_mode(self):
384 self.cur_poly = None
385 self._draw_lines = True
386 self.filled_mode = False
387 self._start_pos = None
388
389 def key_down(self, ev):
390 if ev.key == pgl.K_LEFT:
391 self._move_view((-10, 0))
392 elif ev.key == pgl.K_RIGHT:
393 self._move_view((10, 0))
394 elif ev.key == pgl.K_UP:
395 self._move_view((0, -10))
396 elif ev.key == pgl.K_DOWN:
397 self._move_view((0, 10))
398 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
399 self.change_poly(ev.key - pgl.K_0)
400 elif ev.key == pgl.K_0:
401 self.change_poly(None)
402 elif ev.key == pgl.K_d and self.cur_poly:
403 self.level.delete_point(self.cur_poly)
404 elif ev.key == pgl.K_f:
405 self.set_filled()
406 elif ev.key == pgl.K_c:
407 self.close_poly()
408
409 def set_filled(self):
410 closed, _ = self.level.all_closed()
411 if closed:
412 self.cur_poly = None
413 self.filled_mode = True
414 self._draw_lines = False
415 else:
416 alert('Not all polygons closed, so not filling')
417
418 def mouse_move(self, ev):
419 old_pos = self.mouse_pos
420 self.mouse_pos = ev.pos
421 if old_pos != self.mouse_pos and (self.cur_poly or self._draw_lines):
422 self.invalidate()
423
424 def mouse_drag(self, ev):
425 if self._mouse_drag:
426 old_pos = self.mouse_pos
427 self.mouse_pos = ev.pos
428 diff = (-self.mouse_pos[0] + old_pos[0],
429 -self.mouse_pos[1] + old_pos[1])
430 self._move_view(diff)
431 self.invalidate()
432
433 def mouse_down(self, ev):
434 if ev.button == 1:
435 if self._draw_lines:
436 if self._start_pos is None:
437 self._start_pos = ev.pos
438 else:
439 self.level.add_line(self._start_pos, ev.pos)
440 self._start_pos = None
441 else:
442 print "Click: %r" % (
443 self.level.point_to_pymunk(
444 self._level_coordinates(ev.pos)),)
445 if ev.button == 4: # Scroll up
446 self._move_view((0, -10))
447 elif ev.button == 5: # Scroll down
448 self._move_view((0, 10))
449 elif ev.button == 6: # Scroll left
450 self._move_view((-10, 0))
451 elif ev.button == 7: # Scroll right
452 self._move_view((10, 0))
453 elif self.cur_poly and ev.button == 1:
454 # Add a point
455 self.level.add_point(self.cur_poly,
456 self._level_coordinates(ev.pos))
457 elif ev.button == 3:
458 self._mouse_drag = True
459
460 def mouse_up(self, ev):
461 if ev.button == 3:
462 self._mouse_drag = False
463
464 def close_poly(self):
465 if self.cur_poly is None:
466 return
467 if self.level.close_poly(self.cur_poly):
468 alert("Successfully closed the polygon")
469 self.change_poly(None)
470 else:
471 alert("Failed to close the polygon")
472
473 def _edit_class(self, classname, cls, data):
474 # Dialog for class properties
475 dialog = EditClassDialog(classname, cls, data)
476 if dialog.present() == 'OK':
477 return dialog.get_data()
478 return None
479
480 def _make_edit_dialog(self, entries):
481 # Dialog to hold the editor
482 edit_box = Dialog()
483 edit_box.rect = pygame.rect.Rect(0, 0, 700, 500)
484 table = ObjectTable(entries)
485 edit_box.add(table)
486 buttons = []
487 for text in ['OK', 'Delete', 'Cancel']:
488 but = Button(text, action=lambda x=text: edit_box.dismiss(x))
489 buttons.append(but)
490 row = Row(buttons)
491 row.rect = pygame.rect.Rect(250, 450, 700, 50)
492 edit_box.add(row)
493 edit_box.get_selection = lambda: table.get_selection()
494 return edit_box
495
496 def edit_objects(self):
497 edit_box = self._make_edit_dialog(self.level._game_objects)
498 res = edit_box.present()
499 choice = edit_box.get_selection()
500 if choice is None:
501 return
502 if res == 'OK':
503 cls = self.level.get_class(choice['classname'])
504 edited = self._edit_class(choice['classname'], cls, choice)
505 if edited is not None:
506 if not self.level.try_new_object(self.level._game_objects,
507 edited, choice):
508 alert('Failed to update GameObject %s'
509 % choice['classname'])
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 if not self.level.try_new_object(self.level._enemies,
525 edited, choice):
526 alert('Failed to update Enemy %s'
527 % choice['classname'])
528 elif res == 'Delete':
529 self.level._enemies.remove(choice)
530 self.level.reset_objs()
531
532 def _make_choice_dialog(self, classes):
533 # Dialog to hold the editor
534 data = []
535 for cls_name, cls in classes:
536 data.append({"classname": cls_name, "class": cls})
537 choice_box = Dialog()
538 choice_box.rect = pygame.rect.Rect(0, 0, 700, 500)
539 table = ObjectTable(data)
540 choice_box.add(table)
541 buttons = []
542 for text in ['OK', 'Cancel']:
543 but = Button(text, action=lambda x=text: choice_box.dismiss(x))
544 buttons.append(but)
545 row = Row(buttons)
546 row.rect = pygame.rect.Rect(250, 450, 700, 50)
547 choice_box.add(row)
548 choice_box.get_selection = lambda: table.get_selection()
549 return choice_box
550
551 def add_game_object(self):
552 classes = ngo.get_editable_game_objects()
553 choose = self._make_choice_dialog(classes)
554 res = choose.present()
555 choice = choose.get_selection()
556 if res == 'OK' and choice is not None:
557 classname = choice['classname']
558 cls = choice['class']
559 new_cls = self._edit_class(classname, cls, None)
560 if new_cls is not None:
561 if not self.level.try_new_object(self.level._game_objects,
562 new_cls, None):
563 alert('Failed to add GameObject %s' % classname)
564
565 def add_enemy(self):
566 classes = ne.get_editable_enemies()
567 choose = self._make_choice_dialog(classes)
568 res = choose.present()
569 choice = choose.get_selection()
570 if res == 'OK' and choice is not None:
571 classname = choice['classname']
572 cls = choice['class']
573 new_cls = self._edit_class(classname, cls, None)
574 if new_cls is not None:
575 if not self.level.try_new_object(self.level._enemies,
576 new_cls, None):
577 alert('Failed to add Enemy %s' % classname)
578
579 def add_puzzler(self):
580 classes = np.get_editable_puzzlers()
581 choose = self._make_choice_dialog(classes)
582 res = choose.present()
583 choice = choose.get_selection()
584 if res == 'OK' and choice is not None:
585 classname = choice['classname']
586 cls = choice['class']
587 new_cls = self._edit_class(classname, cls, None)
588 if new_cls is not None:
589 if not self.level.try_new_object(self.level._game_objects,
590 new_cls, None):
591 alert('Failed to add Puzzler %s' % classname)
592
593
594class PolyButton(Button):
595 """Button for coosing the correct polygon"""
596
597 def __init__(self, index, level_widget):
598 if index is not None:
599 text = "Draw: %s" % index
600 else:
601 text = 'Exit Draw Mode'
602 super(PolyButton, self).__init__(text)
603 self.index = index
604 self.level_widget = level_widget
605
606 def action(self):
607 self.level_widget.change_poly(self.index)
608
609
610class EditorApp(RootWidget):
611
612 def __init__(self, level, surface):
613 super(EditorApp, self).__init__(surface)
614 self.level = level
615 self.level_widget = LevelWidget(self.level)
616 self.add(self.level_widget)
617
618 self._dMenus = {}
619
620 self._make_draw_menu()
621 self._make_objects_menu()
622
623 self._menu_mode = 'drawing'
624 self._populate_menu()
625
626 def _make_draw_menu(self):
627 widgets = []
628
629 # Add poly buttons
630 y = 15
631 for poly in range(1, 7):
632 but = PolyButton(poly, self.level_widget)
633 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 2 - MENU_PAD,
634 MENU_BUTTON_HEIGHT)
635 if poly % 2:
636 but.rect.move_ip(MENU_LEFT, y)
637 else:
638 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 2 - MENU_HALF_PAD,
639 y)
640 y += MENU_BUTTON_HEIGHT + MENU_PAD
641 widgets.append(but)
642
643 end_poly_but = PolyButton(None, self.level_widget)
644 end_poly_but.rect = BUTTON_RECT.copy()
645 end_poly_but.rect.move_ip(MENU_LEFT, y)
646 widgets.append(end_poly_but)
647 y += MENU_BUTTON_HEIGHT + MENU_PAD
648
649 draw_line = Button("Draw interior wall", self.level_widget.line_mode)
650 draw_line.rect = BUTTON_RECT.copy()
651 draw_line.rect.move_ip(MENU_LEFT, y)
652 widgets.append(draw_line)
653 y += MENU_BUTTON_HEIGHT + MENU_PAD
654
655 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
656 fill_but.rect = BUTTON_RECT.copy()
657 fill_but.rect.move_ip(MENU_LEFT, y)
658 widgets.append(fill_but)
659 y += MENU_BUTTON_HEIGHT + MENU_PAD
660
661 close_poly_but = Button('Close Polygon',
662 action=self.level_widget.close_poly)
663 close_poly_but.rect = BUTTON_RECT.copy()
664 close_poly_but.rect.move_ip(MENU_LEFT, y)
665 widgets.append(close_poly_but)
666 y += MENU_BUTTON_HEIGHT + MENU_PAD
667
668 white = pygame.color.Color("white")
669 self.show_objs = CheckBox(fg_color=white)
670 self.show_objs.rect = CHECK_RECT.copy()
671 self.show_objs.rect.move_ip(MENU_LEFT, y)
672 label = Label("Show Objects", fg_color=white)
673 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
674 widgets.append(self.show_objs)
675 widgets.append(label)
676 y += label.rect.height + MENU_PAD
677
678 self.show_enemies = CheckBox(fg_color=white)
679 self.show_enemies.rect = CHECK_RECT.copy()
680 self.show_enemies.rect.move_ip(MENU_LEFT, y)
681 label = Label("Show enemy start pos", fg_color=white)
682 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
683 widgets.append(self.show_enemies)
684 widgets.append(label)
685 y += label.rect.height + MENU_PAD
686
687 y += MENU_PAD
688 switch_but = Button('Switch to Objects', action=self.switch_to_objects)
689 switch_but.rect = BUTTON_RECT.copy()
690 switch_but.rect.move_ip(MENU_LEFT, y)
691 widgets.append(switch_but)
692 y += switch_but.rect.height + MENU_PAD
693
694 save_but = Button('Save Level', action=self.save)
695 save_but.rect = BUTTON_RECT.copy()
696 save_but.rect.move_ip(MENU_LEFT, y)
697 widgets.append(save_but)
698 y += MENU_BUTTON_HEIGHT + MENU_PAD
699
700 y += MENU_PAD
701 quit_but = Button('Quit', action=self.quit)
702 quit_but.rect = BUTTON_RECT.copy()
703 quit_but.rect.move_ip(MENU_LEFT, y)
704 widgets.append(quit_but)
705
706 self._dMenus['drawing'] = widgets
707
708 def _make_objects_menu(self):
709 widgets = []
710
711 # Add poly buttons
712 y = 15
713
714 edit_objs_but = Button('Edit Objects',
715 action=self.level_widget.edit_objects)
716 edit_objs_but.rect = BUTTON_RECT.copy()
717 edit_objs_but.rect.move_ip(MENU_LEFT, y)
718 widgets.append(edit_objs_but)
719 y += MENU_BUTTON_HEIGHT + MENU_PAD
720
721 edir_enemies_but = Button('Edit Enemies',
722 action=self.level_widget.edit_enemies)
723 edir_enemies_but.rect = BUTTON_RECT.copy()
724 edir_enemies_but.rect.move_ip(MENU_LEFT, y)
725 widgets.append(edir_enemies_but)
726 y += MENU_BUTTON_HEIGHT + MENU_PAD
727
728 add_obj_but = Button('Add Game Object',
729 action=self.level_widget.add_game_object)
730 add_obj_but.rect = BUTTON_RECT.copy()
731 add_obj_but.rect.move_ip(MENU_LEFT, y)
732 widgets.append(add_obj_but)
733 y += MENU_BUTTON_HEIGHT + MENU_PAD
734
735 add_puzzle_but = Button('Add Puzzler',
736 action=self.level_widget.add_puzzler)
737 add_puzzle_but.rect = BUTTON_RECT.copy()
738 add_puzzle_but.rect.move_ip(MENU_LEFT, y)
739 widgets.append(add_puzzle_but)
740 y += MENU_BUTTON_HEIGHT + MENU_PAD
741
742 add_enemy_but = Button('Add Enemy',
743 action=self.level_widget.add_enemy)
744 add_enemy_but.rect = BUTTON_RECT.copy()
745 add_enemy_but.rect.move_ip(MENU_LEFT, y)
746 widgets.append(add_enemy_but)
747 y += MENU_BUTTON_HEIGHT + MENU_PAD
748
749 y += MENU_PAD
750 switch_but = Button('Switch to Drawing', action=self.switch_to_draw)
751 switch_but.rect = BUTTON_RECT.copy()
752 switch_but.rect.move_ip(MENU_LEFT, y)
753 widgets.append(switch_but)
754 y += switch_but.rect.height + MENU_PAD
755
756 save_but = Button('Save Level', action=self.save)
757 save_but.rect = BUTTON_RECT.copy()
758 save_but.rect.move_ip(MENU_LEFT, y)
759 widgets.append(save_but)
760 y += MENU_BUTTON_HEIGHT + MENU_PAD
761
762 y += MENU_PAD
763 quit_but = Button('Quit', action=self.quit)
764 quit_but.rect = BUTTON_RECT.copy()
765 quit_but.rect.move_ip(MENU_LEFT, y)
766 widgets.append(quit_but)
767
768 self._dMenus['objects'] = widgets
769
770 def key_down(self, ev):
771 if ev.key == pgl.K_ESCAPE:
772 self.quit()
773 elif ev.key == pgl.K_s:
774 self.save()
775 else:
776 self.level_widget.key_down(ev)
777
778 def save(self):
779 closed, messages = self.level.all_closed()
780 if closed:
781 self.level.save()
782 # display success
783 alert("Level %s saved successfully." % self.level.name)
784 else:
785 # display errors
786 alert("Failed to save level.\n\n%s" % '\n'.join(messages))
787
788 def switch_to_draw(self):
789 if self._menu_mode != 'drawing':
790 self._clear_menu()
791 self._menu_mode = 'drawing'
792 self._populate_menu()
793
794 def switch_to_objects(self):
795 if self._menu_mode != 'objects':
796 self._clear_menu()
797 self._menu_mode = 'objects'
798 self._populate_menu()
799
800 def _clear_menu(self):
801 for widget in self._dMenus[self._menu_mode]:
802 self.remove(widget)
803
804 def _populate_menu(self):
805 self.level_widget.change_poly(None)
806 for widget in self._dMenus[self._menu_mode]:
807 self.add(widget)
808 self.invalidate()
809
810 def mouse_move(self, ev):
811 self.level_widget.mouse_move(ev)
812
813 def draw(self, surface):
814 # Update checkbox state
815 if self._menu_mode == 'drawing':
816 self.level_widget.set_objects(self.show_objs.value)
817 self.level_widget.set_enemies(self.show_enemies.value)
818 else:
819 self.level_widget.set_objects(True)
820 self.level_widget.set_enemies(True)
821 super(EditorApp, self).draw(surface)
822
823
824if __name__ == "__main__":
825 if len(sys.argv) not in [2, 4]:
826 print 'Please supply a levelname or levelname and level size'
827 sys.exit()
828 # Need to ensure we have defaults for rendering
829 parse_args([])
830 pygame.display.init()
831 pygame.font.init()
832 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
833 pgl.SWSURFACE)
834 if len(sys.argv) == 2:
835 level = EditorLevel(sys.argv[1])
836 level.load(pymunk.Space())
837 elif len(sys.argv) == 4:
838 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
839 pygame.display.set_caption('Nagslang Area Editor')
840 pygame.key.set_repeat(200, 100)
841 app = EditorApp(level, pygame.display.get_surface())
842 app.run()
Note: See TracBrowser for help on using the repository browser.