source: tools/area_editor.py@ 284:26682f1b8c59

Last change on this file since 284:26682f1b8c59 was 280:7bb6296024c4, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Add current mode hints

  • Property exe set to *
File size: 33.0 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, None)
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, parent):
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 self._parent = parent
349
350 def _level_coordinates(self, pos):
351 # Move positions to level values
352 if not pos:
353 return (0, 0)
354 return pos[0] + self.pos[0], pos[1] + self.pos[1]
355
356 def _move_view(self, offset):
357 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
358 if new_pos[0] < 0:
359 new_pos[0] = self.pos[0]
360 elif new_pos[0] > self.level.x - SCREEN[0]:
361 new_pos[0] = self.pos[0]
362 if new_pos[1] < 0:
363 new_pos[1] = self.pos[1]
364 elif new_pos[1] > self.level.y - SCREEN[1]:
365 new_pos[1] = self.pos[1]
366 self.pos = tuple(new_pos)
367
368 def set_objects(self, value):
369 if self._draw_objects != value:
370 self._draw_objects = value
371 self.invalidate()
372
373 def set_enemies(self, value):
374 if self._draw_enemies != value:
375 self._draw_enemies = value
376 self.invalidate()
377
378 def draw(self, surface):
379 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
380 and len(self.level.polygons[self.cur_poly])):
381 # We have an active polygon
382 mouse_pos = self._level_coordinates(self.mouse_pos)
383 elif self._draw_lines:
384 # Interior wall mode
385 mouse_pos = self._level_coordinates(self.mouse_pos)
386 else:
387 mouse_pos = None
388 level_surface = level.draw(mouse_pos, self.cur_poly, self.filled_mode,
389 self._draw_lines, self._start_pos)
390 if self._draw_objects:
391 for thing in self.level.drawables:
392 if not isinstance(thing, ne.Enemy):
393 thing.render(level_surface)
394 if self._draw_enemies:
395 for thing in self.level.drawables:
396 if isinstance(thing, ne.Enemy):
397 thing.render(level_surface)
398 surface_area = pygame.rect.Rect(self.pos, SCREEN)
399 surface.blit(level_surface, (0, 0), surface_area)
400
401 def change_poly(self, new_poly):
402 self.cur_poly = new_poly
403 self._draw_lines = False
404 if self.cur_poly is not None:
405 self._parent.reset_lit_buttons()
406 self.filled_mode = False
407
408 def line_mode(self):
409 self.cur_poly = None
410 self._parent.reset_lit_buttons()
411 self._draw_lines = True
412 self.filled_mode = False
413 self._start_pos = None
414
415 def key_down(self, ev):
416 if ev.key == pgl.K_LEFT:
417 self._move_view((-10, 0))
418 elif ev.key == pgl.K_RIGHT:
419 self._move_view((10, 0))
420 elif ev.key == pgl.K_UP:
421 self._move_view((0, -10))
422 elif ev.key == pgl.K_DOWN:
423 self._move_view((0, 10))
424 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
425 self.change_poly(ev.key - pgl.K_0)
426 elif ev.key == pgl.K_0:
427 self.change_poly(None)
428 elif ev.key == pgl.K_d and self.cur_poly:
429 self.level.delete_point(self.cur_poly)
430 elif ev.key == pgl.K_f:
431 self.set_filled()
432 elif ev.key == pgl.K_c:
433 self.close_poly()
434
435 def set_filled(self):
436 closed, _ = self.level.all_closed()
437 if closed:
438 self.cur_poly = None
439 self._parent.reset_lit_buttons()
440 self.filled_mode = True
441 self._draw_lines = False
442 else:
443 alert('Not all polygons closed, so not filling')
444
445 def mouse_move(self, ev):
446 old_pos = self.mouse_pos
447 self.mouse_pos = ev.pos
448 if old_pos != self.mouse_pos and (self.cur_poly or self._draw_lines):
449 self.invalidate()
450
451 def mouse_drag(self, ev):
452 if self._mouse_drag:
453 old_pos = self.mouse_pos
454 self.mouse_pos = ev.pos
455 diff = (-self.mouse_pos[0] + old_pos[0],
456 -self.mouse_pos[1] + old_pos[1])
457 self._move_view(diff)
458 self.invalidate()
459
460 def mouse_down(self, ev):
461 if self.sel_mode and ev.button == 1:
462 corrected_pos = ev.pos[0] + self.pos[0], ev.pos[1] + self.pos[1]
463 obj = self.level.find_obj_at_pos(corrected_pos)
464 if obj is not None:
465 self._edit_selected(obj)
466 elif ev.button == 1:
467 if self._draw_lines:
468 if self._start_pos is None:
469 self._start_pos = ev.pos
470 else:
471 self.level.add_line(self._start_pos, ev.pos)
472 self._start_pos = None
473 else:
474 print "Click: %r" % (
475 self.level.point_to_pymunk(
476 self._level_coordinates(ev.pos)),)
477 if ev.button == 4: # Scroll up
478 self._move_view((0, -10))
479 elif ev.button == 5: # Scroll down
480 self._move_view((0, 10))
481 elif ev.button == 6: # Scroll left
482 self._move_view((-10, 0))
483 elif ev.button == 7: # Scroll right
484 self._move_view((10, 0))
485 elif self.cur_poly and ev.button == 1:
486 # Add a point
487 self.level.add_point(self.cur_poly,
488 self._level_coordinates(ev.pos))
489 elif ev.button == 3:
490 self._mouse_drag = True
491
492 def mouse_up(self, ev):
493 if ev.button == 3:
494 self._mouse_drag = False
495
496 def close_poly(self):
497 if self.cur_poly is None:
498 return
499 if self.level.close_poly(self.cur_poly):
500 alert("Successfully closed the polygon")
501 self.change_poly(None)
502 else:
503 alert("Failed to close the polygon")
504
505 def _edit_class(self, classname, cls, data):
506 # Dialog for class properties
507 dialog = EditClassDialog(classname, cls, data)
508 if dialog.present() == 'OK':
509 return dialog.get_data()
510 return None
511
512 def _edit_selected(self, obj):
513 data = self.level.lookup[obj]
514 cls = obj.__class__
515 classname = obj.__class__.__name__
516 dialog = EditClassDialog(classname, cls, data, True)
517 res = dialog.present()
518 if res == 'OK':
519 edited = dialog.get_data()
520 if edited is not None:
521 for target in [self.level._game_objects, self.level._enemies]:
522 if data in target:
523 self.level.try_new_object(classname, target,
524 edited, data)
525 break
526 elif res == 'Delete':
527 for target in [self.level._game_objects, self.level._enemies]:
528 if data in target:
529 target.remove(data)
530 self.level.reset_objs()
531 break
532
533 def _make_edit_dialog(self, entries):
534 # Dialog to hold the editor
535 edit_box = Dialog()
536 edit_box.rect = pygame.rect.Rect(0, 0, 700, 500)
537 table = ObjectTable(entries)
538 edit_box.add(table)
539 buttons = []
540 for text in ['OK', 'Delete', 'Cancel']:
541 but = Button(text, action=lambda x=text: edit_box.dismiss(x))
542 buttons.append(but)
543 row = Row(buttons)
544 row.rect = pygame.rect.Rect(250, 450, 700, 50)
545 edit_box.add(row)
546 edit_box.get_selection = lambda: table.get_selection()
547 return edit_box
548
549 def edit_objects(self):
550 edit_box = self._make_edit_dialog(self.level._game_objects)
551 res = edit_box.present()
552 choice = edit_box.get_selection()
553 if choice is None:
554 return
555 if res == 'OK':
556 cls = self.level.get_class(choice['classname'])
557 edited = self._edit_class(choice['classname'], cls, choice)
558 if edited is not None:
559 self.level.try_new_object(choice["classname"],
560 self.level._game_objects,
561 edited, choice)
562 elif res == 'Delete':
563 self.level._game_objects.remove(choice)
564 self.level.reset_objs()
565
566 def edit_enemies(self):
567 edit_box = self._make_edit_dialog(self.level._enemies)
568 res = edit_box.present()
569 choice = edit_box.get_selection()
570 if choice is None:
571 return
572 if res == 'OK':
573 cls = self.level.get_class(choice['classname'], ne)
574 edited = self._edit_class(choice['classname'], cls, choice)
575 if edited is not None:
576 self.level.try_new_object(choice["classname"],
577 self.level._enemies, edited, choice)
578 elif res == 'Delete':
579 self.level._enemies.remove(choice)
580 self.level.reset_objs()
581
582 def _make_choice_dialog(self, classes):
583 # Dialog to hold the editor
584 data = []
585 for cls_name, cls in classes:
586 data.append({"classname": cls_name, "class": cls})
587 choice_box = Dialog()
588 choice_box.rect = pygame.rect.Rect(0, 0, 700, 500)
589 table = ObjectTable(data)
590 choice_box.add(table)
591 buttons = []
592 for text in ['OK', 'Cancel']:
593 but = Button(text, action=lambda x=text: choice_box.dismiss(x))
594 buttons.append(but)
595 row = Row(buttons)
596 row.rect = pygame.rect.Rect(250, 450, 700, 50)
597 choice_box.add(row)
598 choice_box.get_selection = lambda: table.get_selection()
599 return choice_box
600
601 def add_game_object(self):
602 classes = ngo.get_editable_game_objects()
603 choose = self._make_choice_dialog(classes)
604 res = choose.present()
605 choice = choose.get_selection()
606 if res == 'OK' and choice is not None:
607 classname = choice['classname']
608 cls = choice['class']
609 new_cls = self._edit_class(classname, cls, None)
610 if new_cls is not None:
611 self.level.try_new_object(classname, self.level._game_objects,
612 new_cls, None)
613
614 def add_enemy(self):
615 classes = ne.get_editable_enemies()
616 choose = self._make_choice_dialog(classes)
617 res = choose.present()
618 choice = choose.get_selection()
619 if res == 'OK' and choice is not None:
620 classname = choice['classname']
621 cls = choice['class']
622 new_cls = self._edit_class(classname, cls, None)
623 if new_cls is not None:
624 self.level.try_new_object(classname, self.level._enemies,
625 new_cls, None)
626
627 def add_puzzler(self):
628 classes = np.get_editable_puzzlers()
629 choose = self._make_choice_dialog(classes)
630 res = choose.present()
631 choice = choose.get_selection()
632 if res == 'OK' and choice is not None:
633 classname = choice['classname']
634 cls = choice['class']
635 new_cls = self._edit_class(classname, cls, None)
636 if new_cls is not None:
637 self.level.try_new_object(classname, self.level._game_objects,
638 new_cls, None)
639
640
641class HighLightButton(Button):
642 """Button with highlight support"""
643 def __init__(self, text, parent, **kwds):
644 super(HighLightButton, self).__init__(text, **kwds)
645 self._parent = parent
646
647 def highlight(self):
648 self.border_color = pygame.color.Color('red')
649
650 def reset(self):
651 self.border_color = self.fg_color
652
653
654class PolyButton(HighLightButton):
655 """Button for coosing the correct polygon"""
656
657 def __init__(self, index, level_widget, parent):
658 if index is not None:
659 text = "Draw: %s" % index
660 else:
661 text = 'Exit Draw Mode'
662 super(PolyButton, self).__init__(text, parent)
663 self.index = index
664 self.level_widget = level_widget
665
666 def action(self):
667 self.level_widget.change_poly(self.index)
668 self._parent.reset_lit_buttons()
669 if self.index is not None:
670 self.highlight()
671
672
673class EditorApp(RootWidget):
674
675 def __init__(self, level, surface):
676 super(EditorApp, self).__init__(surface)
677 self.level = level
678 self.level_widget = LevelWidget(self.level, self)
679 self.add(self.level_widget)
680
681 self._dMenus = {}
682
683 self._light_buttons = []
684
685 self._make_draw_menu()
686 self._make_objects_menu()
687
688 self._menu_mode = 'drawing'
689 self._populate_menu()
690
691 def _make_draw_menu(self):
692 widgets = []
693
694 # Add poly buttons
695 y = 15
696 for poly in range(1, 7):
697 but = PolyButton(poly, self.level_widget, self)
698 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 2 - MENU_PAD,
699 MENU_BUTTON_HEIGHT)
700 if poly % 2:
701 but.rect.move_ip(MENU_LEFT, y)
702 else:
703 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 2 - MENU_HALF_PAD,
704 y)
705 y += MENU_BUTTON_HEIGHT + MENU_PAD
706 self._light_buttons.append(but)
707 widgets.append(but)
708
709 end_poly_but = PolyButton(None, self.level_widget, self)
710 end_poly_but.rect = BUTTON_RECT.copy()
711 end_poly_but.rect.move_ip(MENU_LEFT, y)
712 widgets.append(end_poly_but)
713 y += MENU_BUTTON_HEIGHT + MENU_PAD
714
715 self.draw_line_but = HighLightButton("Draw interior wall", self,
716 action=self.set_line_mode)
717 self.draw_line_but.rect = BUTTON_RECT.copy()
718 self.draw_line_but.rect.move_ip(MENU_LEFT, y)
719 widgets.append(self.draw_line_but)
720 self._light_buttons.append(self.draw_line_but)
721 y += MENU_BUTTON_HEIGHT + MENU_PAD
722
723 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
724 fill_but.rect = BUTTON_RECT.copy()
725 fill_but.rect.move_ip(MENU_LEFT, y)
726 widgets.append(fill_but)
727 y += MENU_BUTTON_HEIGHT + MENU_PAD
728
729 close_poly_but = Button('Close Polygon',
730 action=self.level_widget.close_poly)
731 close_poly_but.rect = BUTTON_RECT.copy()
732 close_poly_but.rect.move_ip(MENU_LEFT, y)
733 widgets.append(close_poly_but)
734 y += MENU_BUTTON_HEIGHT + MENU_PAD
735
736 white = pygame.color.Color("white")
737 self.show_objs = CheckBox(fg_color=white)
738 self.show_objs.rect = CHECK_RECT.copy()
739 self.show_objs.rect.move_ip(MENU_LEFT, y)
740 label = Label("Show Objects", fg_color=white)
741 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
742 widgets.append(self.show_objs)
743 widgets.append(label)
744 y += label.rect.height + MENU_PAD
745
746 self.show_enemies = CheckBox(fg_color=white)
747 self.show_enemies.rect = CHECK_RECT.copy()
748 self.show_enemies.rect.move_ip(MENU_LEFT, y)
749 label = Label("Show enemy start pos", fg_color=white)
750 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
751 widgets.append(self.show_enemies)
752 widgets.append(label)
753 y += label.rect.height + MENU_PAD
754
755 y += MENU_PAD
756 switch_but = Button('Switch to Objects', action=self.switch_to_objects)
757 switch_but.rect = BUTTON_RECT.copy()
758 switch_but.rect.move_ip(MENU_LEFT, y)
759 widgets.append(switch_but)
760 y += switch_but.rect.height + MENU_PAD
761
762 save_but = Button('Save Level', action=self.save)
763 save_but.rect = BUTTON_RECT.copy()
764 save_but.rect.move_ip(MENU_LEFT, y)
765 widgets.append(save_but)
766 y += MENU_BUTTON_HEIGHT + MENU_PAD
767
768 y += MENU_PAD
769 quit_but = Button('Quit', action=self.quit)
770 quit_but.rect = BUTTON_RECT.copy()
771 quit_but.rect.move_ip(MENU_LEFT, y)
772 widgets.append(quit_but)
773
774 self._dMenus['drawing'] = widgets
775
776 def _make_objects_menu(self):
777 widgets = []
778
779 # Add poly buttons
780 y = 15
781
782 edit_objs_but = Button('Edit Objects',
783 action=self.level_widget.edit_objects)
784 edit_objs_but.rect = BUTTON_RECT.copy()
785 edit_objs_but.rect.move_ip(MENU_LEFT, y)
786 widgets.append(edit_objs_but)
787 y += MENU_BUTTON_HEIGHT + MENU_PAD
788
789 edir_enemies_but = Button('Edit Enemies',
790 action=self.level_widget.edit_enemies)
791 edir_enemies_but.rect = BUTTON_RECT.copy()
792 edir_enemies_but.rect.move_ip(MENU_LEFT, y)
793 widgets.append(edir_enemies_but)
794 y += MENU_BUTTON_HEIGHT + MENU_PAD
795
796 add_obj_but = Button('Add Game Object',
797 action=self.level_widget.add_game_object)
798 add_obj_but.rect = BUTTON_RECT.copy()
799 add_obj_but.rect.move_ip(MENU_LEFT, y)
800 widgets.append(add_obj_but)
801 y += MENU_BUTTON_HEIGHT + MENU_PAD
802
803 add_puzzle_but = Button('Add Puzzler',
804 action=self.level_widget.add_puzzler)
805 add_puzzle_but.rect = BUTTON_RECT.copy()
806 add_puzzle_but.rect.move_ip(MENU_LEFT, y)
807 widgets.append(add_puzzle_but)
808 y += MENU_BUTTON_HEIGHT + MENU_PAD
809
810 add_enemy_but = Button('Add Enemy',
811 action=self.level_widget.add_enemy)
812 add_enemy_but.rect = BUTTON_RECT.copy()
813 add_enemy_but.rect.move_ip(MENU_LEFT, y)
814 widgets.append(add_enemy_but)
815 y += MENU_BUTTON_HEIGHT + MENU_PAD
816
817 y += MENU_PAD
818 self.sel_mode_but = HighLightButton('Select Object', self,
819 action=self.sel_mode)
820 self.sel_mode_but.rect = BUTTON_RECT.copy()
821 self.sel_mode_but.rect.move_ip(MENU_LEFT, y)
822 widgets.append(self.sel_mode_but)
823 self._light_buttons.append(self.sel_mode_but)
824 y += MENU_BUTTON_HEIGHT + MENU_PAD
825
826 y += MENU_PAD
827 switch_but = Button('Switch to Drawing', action=self.switch_to_draw)
828 switch_but.rect = BUTTON_RECT.copy()
829 switch_but.rect.move_ip(MENU_LEFT, y)
830 widgets.append(switch_but)
831 y += switch_but.rect.height + MENU_PAD
832
833 save_but = Button('Save Level', action=self.save)
834 save_but.rect = BUTTON_RECT.copy()
835 save_but.rect.move_ip(MENU_LEFT, y)
836 widgets.append(save_but)
837 y += MENU_BUTTON_HEIGHT + MENU_PAD
838
839 y += MENU_PAD
840 quit_but = Button('Quit', action=self.quit)
841 quit_but.rect = BUTTON_RECT.copy()
842 quit_but.rect.move_ip(MENU_LEFT, y)
843 widgets.append(quit_but)
844
845 self._dMenus['objects'] = widgets
846
847 def key_down(self, ev):
848 if ev.key == pgl.K_ESCAPE:
849 self.quit()
850 elif ev.key == pgl.K_s:
851 self.save()
852 else:
853 self.level_widget.key_down(ev)
854
855 def save(self):
856 closed, messages = self.level.all_closed()
857 if closed:
858 self.level.save()
859 # display success
860 alert("Level %s saved successfully." % self.level.name)
861 else:
862 # display errors
863 alert("Failed to save level.\n\n%s" % '\n'.join(messages))
864
865 def switch_to_draw(self):
866 if self._menu_mode != 'drawing':
867 self._clear_menu()
868 self._menu_mode = 'drawing'
869 self._populate_menu()
870
871 def switch_to_objects(self):
872 if self._menu_mode != 'objects':
873 self._clear_menu()
874 self._menu_mode = 'objects'
875 self._populate_menu()
876
877 def _clear_menu(self):
878 for widget in self._dMenus[self._menu_mode]:
879 self.remove(widget)
880
881 def reset_lit_buttons(self):
882 for but in self._light_buttons:
883 but.reset()
884
885 def _populate_menu(self):
886 self.level_widget.change_poly(None)
887 self.level_widget.sel_mode = False
888 for widget in self._dMenus[self._menu_mode]:
889 self.add(widget)
890 self.invalidate()
891
892 def set_line_mode(self):
893 self.level_widget.line_mode()
894 self.draw_line_but.highlight()
895
896 def sel_mode(self):
897 self.level_widget.sel_mode = not self.level_widget.sel_mode
898 if self.level_widget.sel_mode:
899 self.sel_mode_but.highlight()
900 else:
901 self.sel_mode_but.reset()
902
903 def mouse_move(self, ev):
904 self.level_widget.mouse_move(ev)
905
906 def draw(self, surface):
907 # Update checkbox state
908 if self._menu_mode == 'drawing':
909 self.level_widget.set_objects(self.show_objs.value)
910 self.level_widget.set_enemies(self.show_enemies.value)
911 else:
912 self.level_widget.set_objects(True)
913 self.level_widget.set_enemies(True)
914 super(EditorApp, self).draw(surface)
915
916
917if __name__ == "__main__":
918 if len(sys.argv) not in [2, 4]:
919 print 'Please supply a levelname or levelname and level size'
920 sys.exit()
921 # Need to ensure we have defaults for rendering
922 parse_args([])
923 pygame.display.init()
924 pygame.font.init()
925 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
926 pgl.SWSURFACE)
927 if len(sys.argv) == 2:
928 level = EditorLevel(sys.argv[1])
929 level.load(pymunk.Space())
930 elif len(sys.argv) == 4:
931 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
932 pygame.display.set_caption('Nagslang Area Editor')
933 pygame.key.set_repeat(200, 100)
934 app = EditorApp(level, pygame.display.get_surface())
935 app.run()
Note: See TracBrowser for help on using the repository browser.