source: tools/area_editor.py@ 463:11eaf0d3e612

Last change on this file since 463:11eaf0d3e612 was 463:11eaf0d3e612, checked in by Simon Cross <hodgestar@…>, 9 years ago

Fix boogs.

  • Property exe set to *
File size: 44.1 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> <ysize>
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 = 4
43MENU_HALF_PAD = MENU_PAD // 2
44MENU_LEFT = SCREEN[0] + MENU_HALF_PAD
45MENU_WIDTH = 200 - MENU_PAD
46MENU_HALF_WIDTH = MENU_WIDTH // 2 - MENU_HALF_PAD
47
48BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
49HALF_BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_HALF_WIDTH, MENU_BUTTON_HEIGHT)
50CHECK_RECT = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
51 MENU_BUTTON_HEIGHT // 2)
52
53
54class TestWorld(object):
55
56 def __init__(self):
57 self.level_state = {}
58 self.inventory = {}
59
60
61def distance(tup1, tup2):
62 return (tup1[0] - tup2[0]) ** 2 + (tup1[1] - tup2[1]) ** 2
63
64
65class EditorLevel(Level):
66
67 def __init__(self, name, x=800, y=600):
68 world = TestWorld()
69 super(EditorLevel, self).__init__(name, world)
70 self.x = x
71 self.y = y
72 # Lookup initiliasition info from the objects
73 self.lookup = {}
74 self._move_poly = None
75
76 def _in_bounds(self, pos):
77 if pos[0] < 0 or pos[0] >= self.x:
78 return False
79 if pos[1] < 0 or pos[1] >= self.y:
80 return False
81 return True
82
83 def load(self, space):
84 super(EditorLevel, self).load(space)
85 # Needed to fill in the lookup dict
86 self.reset_objs()
87
88 def point_to_pymunk(self, pos):
89 # inverse of point_to_pygame
90 # (this is also the same as point_to_pygame, but a additional
91 # function for sanity later in pyweek).
92 return (pos[0], self.y - pos[1])
93
94 def add_point(self, poly_index, pos):
95 self.polygons.setdefault(poly_index, [])
96 if not self._in_bounds(pos):
97 return False
98 if not self.polygons[poly_index]:
99 point = self.point_to_pymunk(pos)
100 self.polygons[poly_index].append(point)
101 else:
102 add_pos = self.point_to_pymunk(pos)
103 self.polygons[poly_index].append(add_pos)
104 return True
105
106 def delete_point(self, index):
107 if index in self.polygons and len(self.polygons[index]) > 0:
108 self.polygons[index].pop()
109
110 def close_poly(self, index):
111 """Attempts to close the current polygon.
112
113 We allow a small additional step to close the polygon, but
114 it's limited as it's a magic point addition"""
115 if len(self.polygons[index]) < 2:
116 # Too small
117 return False
118 first = self.polygons[index][0]
119 if self.add_point(index, self.point_to_pygame(first)):
120 return True
121 return False
122
123 def add_line(self, start_pos, end_pos):
124 startpoint = self.point_to_pymunk(start_pos)
125 endpoint = self.point_to_pymunk(end_pos)
126 self.lines.append([startpoint, endpoint])
127
128 def draw(self, mouse_pos, mouse_poly, filled, draw_cand_line, start_pos,
129 move_point_mode, move_poly_mode, move_point, zoom_factor,
130 level_widget):
131 self._draw_background(True)
132 # Draw polygons as needed for the editor
133 line_width = int(2 * zoom_factor)
134 if filled:
135 self._draw_exterior(True)
136 for index, polygon in self.polygons.items():
137 color = POLY_COLORS.get(index, pygame.color.THECOLORS['black'])
138 if move_point_mode and index == self._move_poly and move_point:
139 pointlist = [p for p in polygon]
140 pointlist = [self.point_to_pygame(p) if p != move_point else
141 mouse_pos for p in pointlist]
142 pygame.draw.lines(self._surface, color, False, pointlist,
143 line_width)
144 break
145 if move_poly_mode and index == self._move_poly and move_point:
146 new_point = self.point_to_pymunk(mouse_pos)
147 pointlist = [self.point_to_pygame(p)
148 for p in self.translate_poly(
149 polygon, move_point, new_point)]
150 pygame.draw.lines(self._surface, color, False, pointlist,
151 line_width)
152 #break
153 if len(polygon) > 1:
154 pointlist = [self.point_to_pygame(p) for p in polygon]
155 pygame.draw.lines(self._surface, color, False, pointlist,
156 line_width)
157 if index == mouse_poly and mouse_pos and len(polygon) > 0:
158 endpoint = self.point_to_pymunk(mouse_pos)
159 pygame.draw.line(self._surface, color,
160 self.point_to_pygame(polygon[-1]),
161 self.point_to_pygame(endpoint),
162 line_width // 2)
163 line_found = False # Hack for sane behaviour if lines overlap
164 for line in self.lines:
165 pointlist = [self.point_to_pygame(p) for p in line]
166 if move_point_mode and not self._move_poly and not line_found:
167 if move_point in line:
168 line_found = True
169 pointlist.remove(self.point_to_pygame(move_point))
170 pointlist.append(mouse_pos)
171 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist,
172 line_width)
173 if draw_cand_line and start_pos and mouse_pos:
174 endpoint = level_widget.snap_to_grid(mouse_pos)
175 endpoint = self.point_to_pymunk(endpoint)
176 pointlist = [start_pos,
177 self.point_to_pygame(endpoint)]
178 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 1)
179 return self._surface
180
181 def reset_objs(self):
182 # Reset the object state - needed when changing stuff
183 self.drawables = []
184 self.overlay_drawables = []
185 self._glue = np.PuzzleGlue()
186 for game_object_dict in self._game_objects:
187 obj = self._create_game_object(pymunk.Space(), **game_object_dict)
188 self.lookup[obj] = game_object_dict
189 for enemy_dict in self._enemies:
190 obj = self._create_enemy(pymunk.Space(), **enemy_dict)
191 self.lookup[obj] = enemy_dict
192
193 def get_class(self, classname, mod=None):
194 # Get the class given the classname
195 modules = {
196 'game_object': ngo,
197 'enemies': ne,
198 'puzzle': np,
199 }
200 if '.' in classname:
201 modname, classname = classname.split('.')
202 mod = modules[modname]
203 if mod is None:
204 mod = ngo
205 return getattr(mod, classname)
206
207 def try_new_object(self, classname, target, new, old=None):
208 if old in target:
209 target.remove(old)
210 try:
211 target.append(new)
212 self.reset_objs()
213 return True
214 except Exception as e:
215 target.remove(new)
216 if old is not None:
217 target.append(old)
218 self.reset_objs()
219 alert("Failed to update object %s: %s" % (classname, e))
220 return False
221
222 def find_obj_at_pos(self, mouse_pos):
223 pymunk_pos = self.point_to_pymunk(mouse_pos)
224 # Search visible objects
225 for obj in self.drawables:
226 if obj.get_shape().point_query(pymunk_pos):
227 return obj
228 return None
229
230 def find_vertex(self, mouse_pos):
231 # search for vertexes closest to where we've killed
232 mindist = 1000
233 move_point = None
234 search_point = self.point_to_pymunk(mouse_pos)
235 for index, polygon in self.polygons.items():
236 for point in polygon:
237 dist = distance(point, search_point)
238 if dist < mindist:
239 mindist = dist
240 move_point = point
241 self._move_poly = index
242 # Also check lines
243 for line in self.lines:
244 for point in line:
245 dist = distance(point, search_point)
246 if dist < mindist:
247 mindist = dist
248 move_point = point
249 self._move_poly = None
250 return move_point
251
252 def translate_poly(self, poly, move_point, new_point):
253 dx = new_point[0] - move_point[0]
254 dy = new_point[1] - move_point[1]
255 new_poly = [(p[0] + dx, p[1] + dy) for p in poly]
256 return new_poly
257
258 def replace_poly(self, move_point, new_point):
259 new_point = self.point_to_pymunk(new_point)
260 if self._move_poly:
261 self.polygons[self._move_poly] = self.translate_poly(
262 self.polygons[self._move_poly], move_point, new_point)
263
264 def replace_vertex(self, old_point, new_point):
265 new_point = self.point_to_pymunk(new_point)
266 if self._move_poly:
267 new_polygon = [p if p != old_point else new_point for p in
268 self.polygons[self._move_poly]]
269 self.polygons[self._move_poly] = new_polygon
270 else:
271 for line in self.lines:
272 if old_point in line:
273 line.remove(old_point)
274 line.append(new_point)
275 break
276
277
278class ObjectTable(TableView):
279
280 columns = [TableColumn("Object", 690, 'l', '%r')]
281
282 def __init__(self, data):
283 super(ObjectTable, self).__init__(height=450)
284 self.data = data
285 self.selected_row = -1
286
287 def num_rows(self):
288 return len(self.data)
289
290 def row_data(self, i):
291 data = self.data[i]
292 if 'name' in data:
293 return ('%s (%s)' % (data['classname'], data['name']), )
294 return (data['classname'], )
295
296 def row_is_selected(self, i):
297 return self.selected_row == i
298
299 def click_row(self, i, ev):
300 self.selected_row = i
301
302 def get_selection(self):
303 if self.selected_row >= 0:
304 return self.data[self.selected_row]
305 return None
306
307
308class EditClassDialog(Dialog):
309
310 def __init__(self, classname, cls, data, level_widget,
311 delete=False):
312 super(EditClassDialog, self).__init__()
313 self.level_widget = level_widget
314 self.classname = classname
315 self.rect = pygame.rect.Rect(0, 0, 900, 550)
316 title = Label("Editing %s" % classname)
317 title.rect = pygame.rect.Rect(100, 10, 600, 25)
318 self.add(title)
319 self.requires = cls.requires()
320 y = 40
321 self.fields = {}
322 index = 0
323 self.poly_field = None
324 self.needs_cleanup = False
325 for requirement, hint in self.requires:
326 label = Label(requirement)
327 label.rect = pygame.rect.Rect(40, y, 200, 25)
328 self.add(label)
329 field = TextField()
330 field.rect = pygame.rect.Rect(220, y, 400, 25)
331 self.add(field)
332 if data is not None:
333 if requirement in data:
334 field.set_text('%s' % data[requirement])
335 elif 'args' in data and requirement != 'name':
336 # NB: The ordering assumptions in requires should make
337 # this safe, but it's really, really, really fragile
338 try:
339 field.set_text('%s' % data['args'][index])
340 index += 1
341 except IndexError:
342 # Assumed to be arguments with the default value
343 pass
344 if hint.startswith('polygon'):
345 self.poly_field = field
346 self.fields[requirement] = field
347 hintlabel = Label(hint)
348 hintlabel.rect = pygame.rect.Rect(640, y, 250, 25)
349 self.add(hintlabel)
350 y += 30
351 if self.poly_field:
352 y += 20
353 button = Button('Use Polygon 6', action=self.get_poly)
354 button.rect = pygame.rect.Rect(350, y, 250, 30)
355 self.add(button)
356 buttons = []
357 if delete:
358 labels = ['OK', 'Delete', 'Cancel']
359 else:
360 labels = ['OK', 'Cancel']
361 for text in labels:
362 but = Button(text, action=lambda x=text: self.dismiss(x))
363 buttons.append(but)
364 row = Row(buttons)
365 row.rect = pygame.rect.Rect(250, 500, 700, 50)
366 self.add(row)
367
368 def get_poly(self):
369 try:
370 data = self.level_widget.level.polygons[6][:]
371 except KeyError:
372 data = []
373 if data:
374 # We unclose the polygon, because that's what pymunk
375 # wants
376 if data[0] == data[-1] and len(data) > 1:
377 data.pop()
378 data = [list(x) for x in data]
379 self.needs_cleanup = True
380 self.poly_field.set_text('%s' % data)
381
382 def cleanup(self):
383 if self.needs_cleanup:
384 self.level_widget.level.polygons[6] = []
385 self.level_widget.invalidate()
386
387 def get_data(self):
388 result = {}
389 result['classname'] = self.classname
390 args = []
391 # We arrange to bounce this through yaml'ish to convert
392 # stuff to the expected type
393 for val, _ in self.requires:
394 text = self.fields[val].get_text()
395 if not text:
396 # skip empty fields
397 continue
398 if val == 'name':
399 result['name'] = text
400 elif self.fields[val] == self.poly_field and text:
401 # Evil, but faster than good
402 try:
403 l = eval(text)
404 except Exception:
405 alert("Invalid polygon %s" % text)
406 self.needs_cleanup = False
407 return None
408 # Check for convexity
409 if not pymunk.util.is_convex(l):
410 alert("Invalid polygon %s - not convex" % text)
411 return None
412 if not pymunk.util.is_clockwise(l):
413 l.reverse()
414 if not pymunk.util.is_clockwise(l):
415 alert("Invalid polygon %s - unable to make clockwise"
416 % text)
417 return None
418 args.append(' - - %s' % l[0])
419 for coord in l[1:]:
420 args.append(' - %s' % coord)
421 else:
422 args.append(' - ' + text)
423 data = "args:\n" + '\n'.join(args)
424 result['args'] = load_s(data)['args']
425 return result
426
427
428class LevelWidget(Widget):
429
430 def __init__(self, level, parent):
431 super(LevelWidget, self).__init__(pygame.rect.Rect(0, 0,
432 SCREEN[0], SCREEN[1]))
433 self.level = level
434 self.pos = (0, 0)
435 self.filled_mode = False
436 self.mouse_pos = None
437 self.cur_poly = None
438 self._mouse_drag = False
439 self._draw_objects = False
440 self._draw_enemies = False
441 self._draw_lines = False
442 self.grid_size = 1
443 self.sel_mode = False
444 self._start_pos = None
445 self._parent = parent
446 self._move_point_mode = False
447 self._move_poly_mode = False
448 self._move_point = False
449 self._zoom_factor = 1.0
450
451 def _level_coordinates(self, pos):
452 # Move positions to level values
453 if not pos:
454 return (0, 0)
455 # Apply zoom_factor
456 pos = pos[0] + self.pos[0], pos[1] + self.pos[1]
457 zoomed = (pos[0] * self._zoom_factor, pos[1] * self._zoom_factor)
458 return int(zoomed[0]), int(zoomed[1])
459
460 def _move_view(self, offset):
461 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
462 if new_pos[0] < 0:
463 new_pos[0] = self.pos[0]
464 elif new_pos[0] > self.level.x - SCREEN[0]:
465 new_pos[0] = self.pos[0]
466 if new_pos[1] < 0:
467 new_pos[1] = self.pos[1]
468 elif new_pos[1] > self.level.y - SCREEN[1]:
469 new_pos[1] = self.pos[1]
470 self.pos = tuple(new_pos)
471
472 def inc_grid_size(self, amount):
473 self.grid_size = max(1, self.grid_size + amount)
474 if self.grid_size > 1:
475 self.grid_size = self.grid_size - (self.grid_size % 5)
476
477 def snap_to_grid(self, pos):
478 x = pos[0] - (pos[0] % self.grid_size)
479 y = pos[1] - (pos[1] % self.grid_size)
480 return (x, y)
481
482 def set_objects(self, value):
483 if self._draw_objects != value:
484 self._draw_objects = value
485 self.invalidate()
486
487 def set_enemies(self, value):
488 if self._draw_enemies != value:
489 self._draw_enemies = value
490 self.invalidate()
491
492 def zoom_out(self):
493 self._zoom_factor = self._zoom_factor * 2.0
494 if self._zoom_factor > 8:
495 self._zoom_factor = 8
496
497 def zoom_in(self):
498 self._zoom_factor = self._zoom_factor // 2.0
499 if self._zoom_factor < 1:
500 self._zoom_factor = 1
501
502 def draw(self, surface):
503 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
504 and len(self.level.polygons[self.cur_poly])):
505 # We have an active polygon
506 mouse_pos = self._level_coordinates(self.mouse_pos)
507 mouse_pos = self.snap_to_grid(mouse_pos)
508 elif self._draw_lines:
509 # Interior wall mode
510 mouse_pos = self._level_coordinates(self.mouse_pos)
511 mouse_pos = self.snap_to_grid(mouse_pos)
512 elif self._move_point_mode or self._move_poly_mode:
513 mouse_pos = self._level_coordinates(self.mouse_pos)
514 mouse_pos = self.snap_to_grid(mouse_pos)
515 else:
516 mouse_pos = None
517 level_surface = level.draw(mouse_pos, self.cur_poly, self.filled_mode,
518 self._draw_lines, self._start_pos,
519 self._move_point_mode, self._move_poly_mode,
520 self._move_point, self._zoom_factor, self)
521 if self._draw_objects:
522 for thing in self.level.drawables:
523 if not isinstance(thing, ne.Enemy):
524 thing.render(level_surface)
525 if self._draw_enemies:
526 for thing in self.level.drawables:
527 if isinstance(thing, ne.Enemy):
528 thing.render(level_surface)
529 surface_area = pygame.rect.Rect(self.pos, SCREEN)
530 zoomed_surface = level_surface.copy()
531 if self._zoom_factor != 1:
532 zoomed_surface = pygame.transform.scale(
533 level_surface,
534 (int(level_surface.get_width() / self._zoom_factor),
535 int(level_surface.get_height() / self._zoom_factor)))
536 surface.blit(zoomed_surface, (0, 0), surface_area)
537
538 def change_poly(self, new_poly):
539 self.cur_poly = new_poly
540 self._draw_lines = False
541 self._move_point_mode = False
542 if self.cur_poly is not None:
543 self._parent.reset_lit_buttons()
544 self.filled_mode = False
545
546 def line_mode(self):
547 self.cur_poly = None
548 self._parent.reset_lit_buttons()
549 self._draw_lines = True
550 self.filled_mode = False
551 self._start_pos = None
552 self._move_point_mode = False
553 self._move_poly_mode = False
554
555 def key_down(self, ev):
556 if ev.key == pgl.K_LEFT:
557 self._move_view((-10, 0))
558 elif ev.key == pgl.K_RIGHT:
559 self._move_view((10, 0))
560 elif ev.key == pgl.K_UP:
561 self._move_view((0, -10))
562 elif ev.key == pgl.K_DOWN:
563 self._move_view((0, 10))
564 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
565 self.change_poly(ev.key - pgl.K_0)
566 elif ev.key == pgl.K_0:
567 self.change_poly(None)
568 elif ev.key == pgl.K_d and self.cur_poly:
569 self.level.delete_point(self.cur_poly)
570 elif ev.key == pgl.K_f:
571 self.set_filled()
572 elif ev.key == pgl.K_c:
573 self.close_poly()
574
575 def set_move_poly_mode(self):
576 self._draw_lines = False
577 self._move_point_mode = False
578 self._move_poly_mode = True
579 self.filled_mode = False
580 self._parent.reset_lit_buttons()
581 self._move_point = None
582
583 def set_move_mode(self):
584 self._draw_lines = False
585 self._move_point_mode = True
586 self._move_poly_mode = False
587 self.filled_mode = False
588 self._parent.reset_lit_buttons()
589 self._move_point = None
590
591 def set_filled(self):
592 closed, _ = self.level.all_closed()
593 if closed:
594 self.cur_poly = None
595 self._parent.reset_lit_buttons()
596 self.filled_mode = True
597 self._draw_lines = False
598 self._move_point_mode = False
599 self._move_poly_mode = False
600 else:
601 alert('Not all polygons closed, so not filling')
602
603 def mouse_move(self, ev):
604 old_pos = self.mouse_pos
605 self.mouse_pos = ev.pos
606 if old_pos != self.mouse_pos and (self.cur_poly or self._draw_lines
607 or self._move_point_mode
608 or self._move_poly_mode):
609 self.invalidate()
610
611 def mouse_drag(self, ev):
612 if self._mouse_drag:
613 old_pos = self.mouse_pos
614 self.mouse_pos = ev.pos
615 diff = (-self.mouse_pos[0] + old_pos[0],
616 -self.mouse_pos[1] + old_pos[1])
617 self._move_view(diff)
618 self.invalidate()
619
620 def mouse_down(self, ev):
621 corrected_pos = self._level_coordinates(ev.pos)
622 snapped_pos = self.snap_to_grid(corrected_pos)
623 if self.sel_mode and ev.button == 1:
624 obj = self.level.find_obj_at_pos(corrected_pos)
625 if obj is not None:
626 self._edit_selected(obj)
627 elif self._move_poly_mode and ev.button == 1:
628 if self._move_point:
629 # Place the current point
630 self.level.replace_poly(self._move_point, snapped_pos)
631 self._move_point = None
632 self.invalidate()
633 else:
634 # find the current point
635 self._move_point = self.level.find_vertex(snapped_pos)
636 elif self._move_point_mode and ev.button == 1:
637 if self._move_point:
638 # Place the current point
639 self.level.replace_vertex(self._move_point, snapped_pos)
640 self._move_point = None
641 self.invalidate()
642 else:
643 # find the current point
644 self._move_point = self.level.find_vertex(snapped_pos)
645 elif ev.button == 1:
646 if self._draw_lines:
647 if self._start_pos is None:
648 self._start_pos = corrected_pos
649 else:
650 self.level.add_line(self._start_pos, snapped_pos)
651 self._start_pos = None
652 else:
653 print "Click: %r" % (
654 self.level.point_to_pymunk(corrected_pos),)
655 if ev.button == 4: # Scroll up
656 self._move_view((0, -10))
657 elif ev.button == 5: # Scroll down
658 self._move_view((0, 10))
659 elif ev.button == 6: # Scroll left
660 self._move_view((-10, 0))
661 elif ev.button == 7: # Scroll right
662 self._move_view((10, 0))
663 elif self.cur_poly and ev.button == 1:
664 # Add a point
665 if not self.level.add_point(self.cur_poly, snapped_pos):
666 alert("Failed to place point")
667 elif ev.button == 3:
668 self._mouse_drag = True
669
670 def mouse_up(self, ev):
671 if ev.button == 3:
672 self._mouse_drag = False
673
674 def close_poly(self):
675 if self.cur_poly is None:
676 return
677 if self.level.close_poly(self.cur_poly):
678 alert("Successfully closed the polygon")
679 self.change_poly(None)
680 else:
681 alert("Failed to close the polygon")
682
683 def _edit_class(self, classname, cls, data):
684 # Dialog for class properties
685 dialog = EditClassDialog(classname, cls, data, self)
686 if dialog.present() == 'OK':
687 return dialog
688 return None
689
690 def _edit_selected(self, obj):
691 data = self.level.lookup[obj]
692 cls = obj.__class__
693 classname = obj.__class__.__name__
694 dialog = EditClassDialog(classname, cls, data, self, True)
695 res = dialog.present()
696 if res == 'OK':
697 edited = dialog.get_data()
698 if edited is not None:
699 for target in [self.level._game_objects, self.level._enemies]:
700 if data in target:
701 if self.level.try_new_object(classname, target,
702 edited, data):
703 dialog.cleanup()
704 break
705 elif res == 'Delete':
706 for target in [self.level._game_objects, self.level._enemies]:
707 if data in target:
708 target.remove(data)
709 self.level.reset_objs()
710 break
711
712 def _make_edit_dialog(self, entries):
713 # Dialog to hold the editor
714 edit_box = Dialog()
715 edit_box.rect = pygame.rect.Rect(0, 0, 700, 500)
716 table = ObjectTable(entries)
717 edit_box.add(table)
718 buttons = []
719 for text in ['OK', 'Delete', 'Cancel']:
720 but = Button(text, action=lambda x=text: edit_box.dismiss(x))
721 buttons.append(but)
722 row = Row(buttons)
723 row.rect = pygame.rect.Rect(250, 450, 700, 50)
724 edit_box.add(row)
725 edit_box.get_selection = lambda: table.get_selection()
726 return edit_box
727
728 def edit_objects(self):
729 edit_box = self._make_edit_dialog(self.level._game_objects)
730 res = edit_box.present()
731 choice = edit_box.get_selection()
732 if choice is None:
733 return
734 if res == 'OK':
735 cls = self.level.get_class(choice['classname'])
736 edit_dlg = self._edit_class(choice['classname'], cls, choice)
737 if edit_dlg is not None:
738 edited = edit_dlg.get_data()
739 if self.level.try_new_object(choice["classname"],
740 self.level._game_objects,
741 edited, choice):
742 edit_dlg.cleanup()
743 elif res == 'Delete':
744 self.level._game_objects.remove(choice)
745 self.level.reset_objs()
746
747 def edit_enemies(self):
748 edit_box = self._make_edit_dialog(self.level._enemies)
749 res = edit_box.present()
750 choice = edit_box.get_selection()
751 if choice is None:
752 return
753 if res == 'OK':
754 cls = self.level.get_class(choice['classname'], ne)
755 edit_dlg = self._edit_class(choice['classname'], cls, choice)
756 if edit_dlg is not None:
757 edited = edit_dlg.get_data()
758 if self.level.try_new_object(choice["classname"],
759 self.level._enemies,
760 edited, choice):
761 edit_dlg.cleanup()
762 elif res == 'Delete':
763 self.level._enemies.remove(choice)
764 self.level.reset_objs()
765
766 def _make_choice_dialog(self, classes):
767 # Dialog to hold the editor
768 data = []
769 for cls_name, cls in classes:
770 data.append({"classname": cls_name, "class": cls})
771 choice_box = Dialog()
772 choice_box.rect = pygame.rect.Rect(0, 0, 700, 500)
773 table = ObjectTable(data)
774 choice_box.add(table)
775 buttons = []
776 for text in ['OK', 'Cancel']:
777 but = Button(text, action=lambda x=text: choice_box.dismiss(x))
778 buttons.append(but)
779 row = Row(buttons)
780 row.rect = pygame.rect.Rect(250, 450, 700, 50)
781 choice_box.add(row)
782 choice_box.get_selection = lambda: table.get_selection()
783 return choice_box
784
785 def add_game_object(self):
786 classes = ngo.get_editable_game_objects()
787 choose = self._make_choice_dialog(classes)
788 res = choose.present()
789 choice = choose.get_selection()
790 if res == 'OK' and choice is not None:
791 classname = choice['classname']
792 cls = choice['class']
793 edit_dlg = self._edit_class(classname, cls, None)
794 if edit_dlg is not None:
795 new_cls = edit_dlg.get_data()
796 if self.level.try_new_object(classname,
797 self.level._game_objects,
798 new_cls, None):
799 edit_dlg.cleanup()
800
801 def add_enemy(self):
802 classes = ne.get_editable_enemies()
803 choose = self._make_choice_dialog(classes)
804 res = choose.present()
805 choice = choose.get_selection()
806 if res == 'OK' and choice is not None:
807 classname = choice['classname']
808 cls = choice['class']
809 edit_dlg = self._edit_class(classname, cls, None)
810 if edit_dlg is not None:
811 new_cls = edit_dlg.get_data()
812 if self.level.try_new_object(classname, self.level._enemies,
813 new_cls, None):
814 edit_dlg.cleanup()
815
816 def add_puzzler(self):
817 classes = np.get_editable_puzzlers()
818 choose = self._make_choice_dialog(classes)
819 res = choose.present()
820 choice = choose.get_selection()
821 if res == 'OK' and choice is not None:
822 classname = choice['classname']
823 cls = choice['class']
824 edit_dlg = self._edit_class(classname, cls, None)
825 if edit_dlg is not None:
826 new_cls = edit_dlg.get_data()
827 if self.level.try_new_object(classname,
828 self.level._game_objects,
829 new_cls, None):
830 edit_dlg.cleanup()
831
832
833class HighLightButton(Button):
834 """Button with highlight support"""
835 def __init__(self, text, parent, **kwds):
836 super(HighLightButton, self).__init__(text, **kwds)
837 self._parent = parent
838
839 def highlight(self):
840 self.border_color = pygame.color.Color('red')
841
842 def reset(self):
843 self.border_color = self.fg_color
844
845
846class PolyButton(HighLightButton):
847 """Button for coosing the correct polygon"""
848
849 def __init__(self, index, level_widget, parent):
850 if index is not None:
851 text = "Draw: %s" % index
852 else:
853 text = 'Exit Draw Mode'
854 super(PolyButton, self).__init__(text, parent)
855 self.index = index
856 self.level_widget = level_widget
857
858 def action(self):
859 self.level_widget.change_poly(self.index)
860 self._parent.reset_lit_buttons()
861 if self.index is not None:
862 self.highlight()
863
864
865class GridSizeLabel(Label):
866 """Label and setter for grid size."""
867
868 def __init__(self, level_widget, **kwds):
869 self.level_widget = level_widget
870 super(GridSizeLabel, self).__init__(self.grid_text(), **kwds)
871
872 def grid_text(self):
873 return "Grid size: %d" % self.level_widget.grid_size
874
875 def inc_grid_size(self, amount):
876 self.level_widget.inc_grid_size(amount)
877 self.set_text(self.grid_text())
878
879
880class SnapButton(Button):
881 """Button for increasing or decreasing snap-to-grid size."""
882
883 def __init__(self, grid_size_label, parent, inc_amount):
884 self.grid_size_label = grid_size_label
885 self.inc_amount = inc_amount
886 text = "Grid %s%d" % (
887 '-' if inc_amount < 0 else '+',
888 abs(inc_amount))
889 self._parent = parent
890 super(SnapButton, self).__init__(text)
891
892 def action(self):
893 self.grid_size_label.inc_grid_size(self.inc_amount)
894
895
896class EditorApp(RootWidget):
897
898 def __init__(self, level, surface):
899 super(EditorApp, self).__init__(surface)
900 self.level = level
901 self.level_widget = LevelWidget(self.level, self)
902 self.add(self.level_widget)
903
904 self._dMenus = {}
905
906 self._light_buttons = []
907
908 self._make_draw_menu()
909 self._make_objects_menu()
910
911 self._menu_mode = 'drawing'
912 self._populate_menu()
913
914 self._zoom = 1
915
916 def _make_draw_menu(self):
917 widgets = []
918
919 white = pygame.color.Color("white")
920
921 # Add poly buttons
922 y = 5
923 for poly in range(1, 7):
924 but = PolyButton(poly, self.level_widget, self)
925 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 2 - MENU_PAD,
926 MENU_BUTTON_HEIGHT)
927 if poly % 2:
928 but.rect.move_ip(MENU_LEFT, y)
929 else:
930 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 2 - MENU_HALF_PAD,
931 y)
932 y += MENU_BUTTON_HEIGHT + MENU_PAD
933 self._light_buttons.append(but)
934 widgets.append(but)
935
936 end_poly_but = PolyButton(None, self.level_widget, self)
937 end_poly_but.rect = BUTTON_RECT.copy()
938 end_poly_but.rect.move_ip(MENU_LEFT, y)
939 widgets.append(end_poly_but)
940 y += MENU_BUTTON_HEIGHT + MENU_PAD
941
942 self.move_point_but = HighLightButton("Mv Point", self,
943 action=self.move_point)
944 self.move_point_but.rect = HALF_BUTTON_RECT.copy()
945 self.move_point_but.rect.move_ip(MENU_LEFT, y)
946 widgets.append(self.move_point_but)
947 self._light_buttons.append(self.move_point_but)
948
949 self.move_poly_but = HighLightButton("Mv Poly", self,
950 action=self.move_poly)
951 self.move_poly_but.rect = HALF_BUTTON_RECT.copy()
952 self.move_poly_but.rect.move_ip(MENU_LEFT + MENU_HALF_WIDTH, y)
953 widgets.append(self.move_poly_but)
954 self._light_buttons.append(self.move_poly_but)
955
956 y += MENU_BUTTON_HEIGHT + MENU_PAD
957
958 # grid size widgets
959 grid_size_label = GridSizeLabel(
960 self.level_widget, width=BUTTON_RECT.width,
961 align="c", fg_color=white)
962 grid_size_label.rect.move_ip(MENU_LEFT, y)
963 widgets.append(grid_size_label)
964 y += grid_size_label.rect.height + MENU_PAD
965 inc_snap_but = SnapButton(grid_size_label, self, 5)
966 inc_snap_but.rect = HALF_BUTTON_RECT.copy()
967 inc_snap_but.rect.move_ip(MENU_LEFT, y)
968 widgets.append(inc_snap_but)
969 dec_snap_but = SnapButton(grid_size_label, self, -5)
970 dec_snap_but.rect = HALF_BUTTON_RECT.copy()
971 dec_snap_but.rect.move_ip(
972 MENU_LEFT + MENU_HALF_WIDTH, y)
973 widgets.append(dec_snap_but)
974 y += MENU_BUTTON_HEIGHT + MENU_PAD
975
976 self.draw_line_but = HighLightButton("Draw interior wall", self,
977 action=self.set_line_mode)
978 self.draw_line_but.rect = BUTTON_RECT.copy()
979 self.draw_line_but.rect.move_ip(MENU_LEFT, y)
980 widgets.append(self.draw_line_but)
981 self._light_buttons.append(self.draw_line_but)
982 y += MENU_BUTTON_HEIGHT + MENU_PAD
983
984 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
985 fill_but.rect = BUTTON_RECT.copy()
986 fill_but.rect.move_ip(MENU_LEFT, y)
987 widgets.append(fill_but)
988 y += MENU_BUTTON_HEIGHT + MENU_PAD
989
990 close_poly_but = Button('Close Polygon',
991 action=self.level_widget.close_poly)
992 close_poly_but.rect = BUTTON_RECT.copy()
993 close_poly_but.rect.move_ip(MENU_LEFT, y)
994 widgets.append(close_poly_but)
995 y += MENU_BUTTON_HEIGHT + MENU_PAD
996
997 self.show_objs = CheckBox(fg_color=white)
998 self.show_objs.rect = CHECK_RECT.copy()
999 self.show_objs.rect.move_ip(MENU_LEFT, y)
1000 label = Label("Show Objects", fg_color=white)
1001 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
1002 widgets.append(self.show_objs)
1003 widgets.append(label)
1004 y += label.rect.height + MENU_PAD
1005
1006 self.show_enemies = CheckBox(fg_color=white)
1007 self.show_enemies.rect = CHECK_RECT.copy()
1008 self.show_enemies.rect.move_ip(MENU_LEFT, y)
1009 label = Label("Show enemy start pos", fg_color=white)
1010 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
1011 widgets.append(self.show_enemies)
1012 widgets.append(label)
1013 y += label.rect.height + MENU_PAD
1014
1015 y += MENU_PAD
1016 switch_but = Button('Switch to Objects', action=self.switch_to_objects)
1017 switch_but.rect = BUTTON_RECT.copy()
1018 switch_but.rect.move_ip(MENU_LEFT, y)
1019 widgets.append(switch_but)
1020 y += switch_but.rect.height + MENU_PAD
1021
1022 save_but = Button('Save Level', action=self.save)
1023 save_but.rect = BUTTON_RECT.copy()
1024 save_but.rect.move_ip(MENU_LEFT, y)
1025 widgets.append(save_but)
1026 y += MENU_BUTTON_HEIGHT + MENU_PAD
1027
1028 zoom_out = Button('Zoom out', action=self.level_widget.zoom_out)
1029 zoom_out.rect = BUTTON_RECT.copy()
1030 zoom_out.rect.width = zoom_out.rect.width // 2
1031 zoom_out.rect.move_ip(MENU_LEFT, y)
1032 widgets.append(zoom_out)
1033
1034 zoom_in = Button('Zoom in', action=self.level_widget.zoom_in)
1035 zoom_in.rect = BUTTON_RECT.copy()
1036 zoom_in.width = zoom_in.width // 2
1037 zoom_in.rect.move_ip(MENU_LEFT + zoom_out.width, y)
1038 widgets.append(zoom_in)
1039
1040 y = SCREEN[1] - MENU_BUTTON_HEIGHT - MENU_PAD
1041 quit_but = Button('Quit', action=self.quit)
1042 quit_but.rect = BUTTON_RECT.copy()
1043 quit_but.rect.move_ip(MENU_LEFT, y)
1044 widgets.append(quit_but)
1045
1046 self._dMenus['drawing'] = widgets
1047
1048 def _make_objects_menu(self):
1049 widgets = []
1050
1051 # Add poly buttons
1052 y = 15
1053
1054 edit_objs_but = Button('Edit Objects',
1055 action=self.level_widget.edit_objects)
1056 edit_objs_but.rect = BUTTON_RECT.copy()
1057 edit_objs_but.rect.move_ip(MENU_LEFT, y)
1058 widgets.append(edit_objs_but)
1059 y += MENU_BUTTON_HEIGHT + MENU_PAD
1060
1061 edir_enemies_but = Button('Edit Enemies',
1062 action=self.level_widget.edit_enemies)
1063 edir_enemies_but.rect = BUTTON_RECT.copy()
1064 edir_enemies_but.rect.move_ip(MENU_LEFT, y)
1065 widgets.append(edir_enemies_but)
1066 y += MENU_BUTTON_HEIGHT + MENU_PAD
1067
1068 add_obj_but = Button('Add Game Object',
1069 action=self.level_widget.add_game_object)
1070 add_obj_but.rect = BUTTON_RECT.copy()
1071 add_obj_but.rect.move_ip(MENU_LEFT, y)
1072 widgets.append(add_obj_but)
1073 y += MENU_BUTTON_HEIGHT + MENU_PAD
1074
1075 add_puzzle_but = Button('Add Puzzler',
1076 action=self.level_widget.add_puzzler)
1077 add_puzzle_but.rect = BUTTON_RECT.copy()
1078 add_puzzle_but.rect.move_ip(MENU_LEFT, y)
1079 widgets.append(add_puzzle_but)
1080 y += MENU_BUTTON_HEIGHT + MENU_PAD
1081
1082 add_enemy_but = Button('Add Enemy',
1083 action=self.level_widget.add_enemy)
1084 add_enemy_but.rect = BUTTON_RECT.copy()
1085 add_enemy_but.rect.move_ip(MENU_LEFT, y)
1086 widgets.append(add_enemy_but)
1087 y += MENU_BUTTON_HEIGHT + MENU_PAD
1088
1089 y += MENU_PAD
1090 self.sel_mode_but = HighLightButton('Select Object', self,
1091 action=self.sel_mode)
1092 self.sel_mode_but.rect = BUTTON_RECT.copy()
1093 self.sel_mode_but.rect.move_ip(MENU_LEFT, y)
1094 widgets.append(self.sel_mode_but)
1095 self._light_buttons.append(self.sel_mode_but)
1096 y += MENU_BUTTON_HEIGHT + MENU_PAD
1097
1098 y += MENU_PAD
1099 switch_but = Button('Switch to Drawing', action=self.switch_to_draw)
1100 switch_but.rect = BUTTON_RECT.copy()
1101 switch_but.rect.move_ip(MENU_LEFT, y)
1102 widgets.append(switch_but)
1103 y += switch_but.rect.height + MENU_PAD
1104
1105 save_but = Button('Save Level', action=self.save)
1106 save_but.rect = BUTTON_RECT.copy()
1107 save_but.rect.move_ip(MENU_LEFT, y)
1108 widgets.append(save_but)
1109 y += MENU_BUTTON_HEIGHT + MENU_PAD
1110
1111 zoom_out = Button('Zoom out', action=self.level_widget.zoom_out)
1112 zoom_out.rect = BUTTON_RECT.copy()
1113 zoom_out.rect.width = zoom_out.rect.width // 2
1114 zoom_out.rect.move_ip(MENU_LEFT, y)
1115 widgets.append(zoom_out)
1116
1117 zoom_in = Button('Zoom in', action=self.level_widget.zoom_in)
1118 zoom_in.rect = BUTTON_RECT.copy()
1119 zoom_in.width = zoom_in.width // 2
1120 zoom_in.rect.move_ip(MENU_LEFT + zoom_out.width, y)
1121 widgets.append(zoom_in)
1122 y += MENU_BUTTON_HEIGHT + MENU_PAD
1123
1124 y = SCREEN[1] - MENU_BUTTON_HEIGHT - MENU_PAD
1125 quit_but = Button('Quit', action=self.quit)
1126 quit_but.rect = BUTTON_RECT.copy()
1127 quit_but.rect.move_ip(MENU_LEFT, y)
1128 widgets.append(quit_but)
1129
1130 self._dMenus['objects'] = widgets
1131
1132 def key_down(self, ev):
1133 if ev.key == pgl.K_ESCAPE:
1134 self.quit()
1135 elif ev.key == pgl.K_s:
1136 self.save()
1137 else:
1138 self.level_widget.key_down(ev)
1139
1140 def save(self):
1141 closed, messages = self.level.all_closed()
1142 if closed:
1143 self.level.save()
1144 # display success
1145 alert("Level %s saved successfully." % self.level.name)
1146 else:
1147 # display errors
1148 alert("Failed to save level.\n\n%s" % '\n'.join(messages))
1149
1150 def switch_to_draw(self):
1151 if self._menu_mode != 'drawing':
1152 self._clear_menu()
1153 self._menu_mode = 'drawing'
1154 self._populate_menu()
1155
1156 def switch_to_objects(self):
1157 if self._menu_mode != 'objects':
1158 self._clear_menu()
1159 self._menu_mode = 'objects'
1160 self._populate_menu()
1161
1162 def _clear_menu(self):
1163 for widget in self._dMenus[self._menu_mode]:
1164 self.remove(widget)
1165
1166 def reset_lit_buttons(self):
1167 for but in self._light_buttons:
1168 but.reset()
1169
1170 def _populate_menu(self):
1171 self.level_widget.change_poly(None)
1172 self.level_widget.sel_mode = False
1173 for widget in self._dMenus[self._menu_mode]:
1174 self.add(widget)
1175 self.invalidate()
1176
1177 def set_line_mode(self):
1178 self.level_widget.line_mode()
1179 self.draw_line_but.highlight()
1180
1181 def sel_mode(self):
1182 self.level_widget.sel_mode = not self.level_widget.sel_mode
1183 if self.level_widget.sel_mode:
1184 self.sel_mode_but.highlight()
1185 else:
1186 self.sel_mode_but.reset()
1187
1188 def mouse_move(self, ev):
1189 self.level_widget.mouse_move(ev)
1190
1191 def move_point(self):
1192 self.level_widget.set_move_mode()
1193 self.move_point_but.highlight()
1194
1195 def move_poly(self):
1196 self.level_widget.set_move_poly_mode()
1197 self.move_poly_but.highlight()
1198
1199 def draw(self, surface):
1200 # Update checkbox state
1201 if self._menu_mode == 'drawing':
1202 self.level_widget.set_objects(self.show_objs.value)
1203 self.level_widget.set_enemies(self.show_enemies.value)
1204 else:
1205 self.level_widget.set_objects(True)
1206 self.level_widget.set_enemies(True)
1207 super(EditorApp, self).draw(surface)
1208
1209
1210if __name__ == "__main__":
1211 if len(sys.argv) not in [2, 4]:
1212 print 'Please supply a levelname or levelname and level size'
1213 sys.exit()
1214 # Need to ensure we have defaults for rendering
1215 parse_args([])
1216 pygame.display.init()
1217 pygame.font.init()
1218 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
1219 pgl.SWSURFACE)
1220 if len(sys.argv) == 2:
1221 level = EditorLevel(sys.argv[1])
1222 level.load(pymunk.Space())
1223 elif len(sys.argv) == 4:
1224 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
1225 pygame.display.set_caption('Nagslang Area Editor')
1226 pygame.key.set_repeat(200, 100)
1227 app = EditorApp(level, pygame.display.get_surface())
1228 app.run()
Note: See TracBrowser for help on using the repository browser.