source: tools/area_editor.py@ 275:2abb61878bb1

Last change on this file since 275:2abb61878bb1 was 275:2abb61878bb1, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Add a 'select object' with pop-up for easier editing

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