source: tools/area_editor.py@ 407:314ddad2d6d2

Last change on this file since 407:314ddad2d6d2 was 407:314ddad2d6d2, checked in by Neil Muller <drnlmuller@…>, 9 years ago

Move points button

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