source: tools/area_editor.py@ 251:611370331bd1

Last change on this file since 251:611370331bd1 was 251:611370331bd1, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Add framework of edit object dialog

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