source: tools/area_editor.py@ 198:05c2c592ce2e

Last change on this file since 198:05c2c592ce2e was 198:05c2c592ce2e, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Add dummy check boxes

  • Property exe set to *
File size: 13.6 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
28
29from nagslang.constants import SCREEN
30from nagslang.level import Level, POLY_COLORS
31
32
33# layout constants
34MENU_BUTTON_HEIGHT = 35
35MENU_PAD = 6
36MENU_HALF_PAD = MENU_PAD // 2
37MENU_LEFT = SCREEN[0] + MENU_HALF_PAD
38MENU_WIDTH = 200 - MENU_PAD
39
40
41class EditorLevel(Level):
42
43 def __init__(self, name, x=800, y=600):
44 super(EditorLevel, self).__init__(name)
45 self.x = x
46 self.y = y
47
48 def round_point(self, pos):
49 return (10 * (pos[0] // 10), 10 * (pos[1] // 10))
50
51 def point_to_pymunk(self, pos):
52 # inverse of point_to_pygame
53 # (this is also the same as point_to_pygame, but a additional
54 # function for sanity later in pyweek).
55 return (pos[0], self.y - pos[1])
56
57 def add_point(self, poly_index, pos):
58 self.polygons.setdefault(poly_index, [])
59 if not self.polygons[poly_index]:
60 point = self.point_to_pymunk(self.round_point(pos))
61 self.polygons[poly_index].append(point)
62 else:
63 add_pos = self.fix_angle(poly_index, pos)
64 self.polygons[poly_index].append(add_pos)
65
66 def fix_angle(self, index, pos):
67 # Last point
68 point1 = self.point_to_pygame(self.polygons[index][-1])
69 pos = self.round_point(pos)
70 # We want the line (point1 to pos) to be an angle of
71 # 0, 45, 90, 135, 180, 225, 270, 305
72 # However, we only need to consider half the circle
73 # This is a hack to approximate the right thing
74 pos0 = (pos[0], point1[1])
75 pos90 = (point1[0], pos[1])
76 dist = max(abs(point1[0] - pos[0]), abs(point1[1] - pos[1]))
77 pos45 = (point1[0] + dist, point1[1] + dist)
78 pos135 = (point1[0] + dist, point1[1] - dist)
79 pos225 = (point1[0] - dist, point1[1] - dist)
80 pos305 = (point1[0] - dist, point1[1] + dist)
81 min_dist = 9999999
82 new_pos = point1
83 for cand in [pos0, pos90, pos45, pos135, pos225, pos305]:
84 dist = (pos[0] - cand[0]) ** 2 + (pos[1] - cand[1]) ** 2
85 if dist < min_dist:
86 new_pos = cand
87 min_dist = dist
88 return self.point_to_pymunk(new_pos)
89
90 def delete_point(self, index):
91 if index in self.polygons and len(self.polygons[index]) > 0:
92 self.polygons[index].pop()
93
94 def close_poly(self, index):
95 """Attempts to close the current polygon.
96
97 We allow a small additional step to close the polygon, but
98 it's limited as it's a magic point addition"""
99 if len(self.polygons[index]) < 2:
100 # Too small
101 return False
102 first = self.polygons[index][0]
103 if self.fix_angle(index, self.point_to_pygame(first)) == first:
104 self.add_point(index, self.point_to_pygame(first))
105 return True
106 candidates = [(first[0] + 10 * i, first[1]) for
107 i in (-3, -2, -1, 1, 2, 3)]
108 candidates.extend([(first[0], first[1] + 10 * i) for
109 i in (-3, -2, -1, 1, 2, 3)])
110 candidates.extend([(first[0] + 10 * i, first[1] + 10 * i) for
111 i in (-3, -2, -1, 1, 2, 3)])
112 candidates.extend([(first[0] + 10 * i, first[1] - 10 * i) for
113 i in (-3, -2, -1, 1, 2, 3)])
114 min_dist = 99999
115 poss = None
116 for cand in candidates:
117 if self.fix_angle(index, self.point_to_pygame(cand)) == cand:
118 dist = (first[0] - cand[0]) ** 2 + (first[1] - cand[1]) ** 2
119 if dist < min_dist:
120 poss = cand
121 if poss is not None:
122 self.add_point(index, self.point_to_pygame(poss))
123 self.add_point(index, self.point_to_pygame(first))
124 return True
125 return False
126
127 def draw(self, surface, topleft, mouse_pos, mouse_poly, filled):
128 self._draw_background(True)
129 # Draw polygons as needed for the editor
130 if filled:
131 self._draw_exterior(True)
132 for index, polygon in self.polygons.items():
133 color = POLY_COLORS[index]
134 if len(polygon) > 1:
135 pointlist = [self.point_to_pygame(p) for p in polygon]
136 pygame.draw.lines(self._surface, color, False, pointlist, 2)
137 if index == mouse_poly and mouse_pos:
138 endpoint = self.fix_angle(index, mouse_pos)
139 pygame.draw.line(self._surface, color,
140 self.point_to_pygame(polygon[-1]),
141 self.point_to_pygame(endpoint))
142 surface_area = pygame.rect.Rect(topleft, SCREEN)
143 surface.blit(self._surface, (0, 0), surface_area)
144
145
146class LevelWidget(Widget):
147
148 def __init__(self, level):
149 super(LevelWidget, self).__init__(pygame.rect.Rect(0, 0,
150 SCREEN[0], SCREEN[1]))
151 self.level = level
152 self.pos = (0, 0)
153 self.filled_mode = False
154 self.mouse_pos = None
155 self.cur_poly = None
156 self._mouse_drag = False
157
158 def _level_coordinates(self, pos):
159 # Move positions to level values
160 if not pos:
161 return (0, 0)
162 return pos[0] + self.pos[0], pos[1] + self.pos[1]
163
164 def _move_view(self, offset):
165 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
166 if new_pos[0] < 0:
167 new_pos[0] = self.pos[0]
168 elif new_pos[0] > self.level.x - SCREEN[0]:
169 new_pos[0] = self.pos[0]
170 if new_pos[1] < 0:
171 new_pos[1] = self.pos[1]
172 elif new_pos[1] > self.level.y - SCREEN[1]:
173 new_pos[1] = self.pos[1]
174 self.pos = tuple(new_pos)
175
176 def draw(self, surface):
177 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
178 and len(self.level.polygons[self.cur_poly])):
179 # We have an active polygon
180 mouse_pos = self._level_coordinates(self.mouse_pos)
181 else:
182 mouse_pos = None
183 level.draw(surface, self.pos, mouse_pos, self.cur_poly,
184 self.filled_mode)
185
186 def change_poly(self, new_poly):
187 self.cur_poly = new_poly
188 if self.cur_poly is not None:
189 self.filled_mode = False
190
191 def key_down(self, ev):
192 if ev.key == pgl.K_LEFT:
193 self._move_view((-10, 0))
194 elif ev.key == pgl.K_RIGHT:
195 self._move_view((10, 0))
196 elif ev.key == pgl.K_UP:
197 self._move_view((0, -10))
198 elif ev.key == pgl.K_DOWN:
199 self._move_view((0, 10))
200 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
201 self.change_poly(ev.key - pgl.K_0)
202 elif ev.key == pgl.K_0:
203 self.change_poly(None)
204 elif ev.key == pgl.K_d and self.cur_poly:
205 self.level.delete_point(self.cur_poly)
206 elif ev.key == pgl.K_f:
207 self.set_filled()
208 elif ev.key == pgl.K_c:
209 self.close_poly()
210
211 def set_filled(self):
212 closed, _ = self.level.all_closed()
213 if closed:
214 self.cur_poly = None
215 self.filled_mode = True
216 else:
217 alert('Not all polygons closed, so not filling')
218
219 def mouse_move(self, ev):
220 old_pos = self.mouse_pos
221 self.mouse_pos = ev.pos
222 if self.cur_poly and old_pos != self.mouse_pos:
223 self.invalidate()
224
225 def mouse_drag(self, ev):
226 if self._mouse_drag:
227 old_pos = self.mouse_pos
228 self.mouse_pos = ev.pos
229 diff = (-self.mouse_pos[0] + old_pos[0],
230 -self.mouse_pos[1] + old_pos[1])
231 self._move_view(diff)
232 self.invalidate()
233
234 def mouse_down(self, ev):
235 if ev.button == 1:
236 print "Click: %r" % (
237 self.level.point_to_pymunk(self._level_coordinates(ev.pos)),)
238 if ev.button == 4: # Scroll up
239 self._move_view((0, -10))
240 elif ev.button == 5: # Scroll down
241 self._move_view((0, 10))
242 elif ev.button == 6: # Scroll left
243 self._move_view((-10, 0))
244 elif ev.button == 7: # Scroll right
245 self._move_view((10, 0))
246 elif self.cur_poly and ev.button == 1:
247 # Add a point
248 self.level.add_point(self.cur_poly,
249 self._level_coordinates(ev.pos))
250 elif ev.button == 3:
251 self._mouse_drag = True
252
253 def mouse_up(self, ev):
254 if ev.button == 3:
255 self._mouse_drag = False
256
257 def close_poly(self):
258 if self.cur_poly is None:
259 return
260 if self.level.close_poly(self.cur_poly):
261 alert("Successfully closed the polygon")
262 self.change_poly(None)
263 else:
264 alert("Failed to close the polygon")
265
266
267class PolyButton(Button):
268 """Button for coosing the correct polygon"""
269
270 def __init__(self, index, level_widget):
271 if index is not None:
272 text = "Draw: %s" % index
273 else:
274 text = 'Exit Draw Mode'
275 super(PolyButton, self).__init__(text)
276 self.index = index
277 self.level_widget = level_widget
278
279 def action(self):
280 self.level_widget.change_poly(self.index)
281
282
283class EditorApp(RootWidget):
284
285 def __init__(self, level, surface):
286 super(EditorApp, self).__init__(surface)
287 self.level = level
288 self.level_widget = LevelWidget(self.level)
289 self.add(self.level_widget)
290
291 # Add poly buttons
292 y = 15
293 for poly in range(1, 7):
294 but = PolyButton(poly, self.level_widget)
295 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 2 - MENU_PAD,
296 MENU_BUTTON_HEIGHT)
297 if poly % 2:
298 but.rect.move_ip(MENU_LEFT, y)
299 else:
300 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 2 - MENU_HALF_PAD,
301 y)
302 y += MENU_BUTTON_HEIGHT + MENU_PAD
303 self.add(but)
304
305 button_rect = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
306
307 check_rect = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
308 MENU_BUTTON_HEIGHT // 2)
309
310 end_poly_but = PolyButton(None, self.level_widget)
311 end_poly_but.rect = button_rect.copy()
312 end_poly_but.rect.move_ip(MENU_LEFT, y)
313 self.add(end_poly_but)
314 y += MENU_BUTTON_HEIGHT + MENU_PAD
315
316 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
317 fill_but.rect = button_rect.copy()
318 fill_but.rect.move_ip(MENU_LEFT, y)
319 self.add(fill_but)
320 y += MENU_BUTTON_HEIGHT + MENU_PAD
321
322 save_but = Button('Save Level', action=self.save)
323 save_but.rect = button_rect.copy()
324 save_but.rect.move_ip(MENU_LEFT, y)
325 self.add(save_but)
326 y += MENU_BUTTON_HEIGHT + MENU_PAD
327
328 close_poly_but = Button('Close Polygon',
329 action=self.level_widget.close_poly)
330 close_poly_but.rect = button_rect.copy()
331 close_poly_but.rect.move_ip(MENU_LEFT, y)
332 self.add(close_poly_but)
333 y += MENU_BUTTON_HEIGHT + MENU_PAD
334
335 white = pygame.color.Color("white")
336 self.show_objs = CheckBox(fg_color=white)
337 self.show_objs.rect = check_rect.copy()
338 self.show_objs.rect.move_ip(MENU_LEFT, y)
339 label = Label("Show Objects", fg_color=white)
340 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
341 self.add(self.show_objs)
342 self.add(label)
343 y += label.rect.height + MENU_PAD
344
345 self.show_enemies = CheckBox(fg_color=white)
346 self.show_enemies.rect = check_rect.copy()
347 self.show_enemies.rect.move_ip(MENU_LEFT, y)
348 label = Label("Show enemy start pos", fg_color=white)
349 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
350 self.add(self.show_enemies)
351 self.add(label)
352 y += label.rect.height + MENU_PAD
353
354 quit_but = Button('Quit', action=self.quit)
355 quit_but.rect = button_rect.copy()
356 quit_but.rect.move_ip(MENU_LEFT, y)
357 self.add(quit_but)
358
359 def key_down(self, ev):
360 if ev.key == pgl.K_ESCAPE:
361 self.quit()
362 elif ev.key == pgl.K_s:
363 self.save()
364 else:
365 self.level_widget.key_down(ev)
366
367 def save(self):
368 closed, messages = self.level.all_closed()
369 if closed:
370 self.level.save()
371 # display success
372 alert("Level %s saved successfully." % self.level.name)
373 else:
374 # display errors
375 alert("Failed to save level.\n\n%s" % '\n'.join(messages))
376
377 def mouse_move(self, ev):
378 self.level_widget.mouse_move(ev)
379
380
381if __name__ == "__main__":
382 if len(sys.argv) not in [2, 4]:
383 print 'Please supply a levelname or levelname and level size'
384 sys.exit()
385 pygame.display.init()
386 pygame.font.init()
387 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
388 pgl.SWSURFACE)
389 if len(sys.argv) == 2:
390 level = EditorLevel(sys.argv[1])
391 level.load(pymunk.Space())
392 elif len(sys.argv) == 4:
393 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
394 pygame.display.set_caption('Nagslang Area Editor')
395 pygame.key.set_repeat(200, 100)
396 app = EditorApp(level, pygame.display.get_surface())
397 app.run()
Note: See TracBrowser for help on using the repository browser.