Package horizons :: Package gui :: Package widgets :: Module minimap
[hide private]
[frames] | no frames]

Source Code for Module horizons.gui.widgets.minimap

  1  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  2  # team@unknown-horizons.org 
  3  # This file is part of Unknown Horizons. 
  4  # 
  5  # Unknown Horizons is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 2 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # This program is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with this program; if not, write to the 
 17  # Free Software Foundation, Inc., 
 18  # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 19  # ################################################### 
 20   
 21   
 22  import itertools 
 23  import math 
 24  from math import cos, sin 
 25  from typing import List 
 26   
 27  from fife import fife 
 28   
 29  import horizons.globals 
 30  from horizons.command.unit import Act 
 31  from horizons.component.namedcomponent import NamedComponent 
 32  from horizons.extscheduler import ExtScheduler 
 33  from horizons.util.shapes import Circle, Point, Rect 
34 35 36 -def get_minimap_color(world_coords, world, island_color, water_color):
37 """Return the color that the minimap would have for given world coordinates 38 39 The color is a (r, g, b) tuple. 40 The world_coords should be a (x, y) tuple of integer coordinates, giving 41 the location on the real_world map. 42 43 If you only have the minimap coords you have to use 44 _MinimapTransform.minimap_to_world() first 45 46 island_color is the color used for unclaimed parts of islands 47 """ 48 49 full_map = world.full_map 50 51 # check what's at the covered_area 52 if world_coords in full_map: 53 # this pixel is an island 54 tile = full_map[world_coords] 55 settlement = tile.settlement 56 if settlement is None: 57 # island without settlement 58 if tile.id <= 0: 59 color = water_color 60 else: 61 color = island_color 62 else: 63 # pixel belongs to a player 64 color = settlement.owner.color.to_tuple() 65 else: 66 color = water_color 67 return color
68
69 70 -def iter_minimap_points_colors(location, world, island_color, water_color):
71 """Return an iterator over the pixels of a minimap of the given world. 72 73 For every pixel, a tuple ((x, y), (r, g, b)) is returned. These are the x and y 74 coordinated and the color of the pixel in RGB. 75 76 This function is not used anymore in the in-game minimap, but it is necessary 77 for the minimap preview. 78 """ 79 80 transform = _MinimapTransform(world.map_dimensions, location, 0, False) 81 82 for x, y in transform.iter_points(): 83 world_coords = transform.minimap_to_world((x, y)) 84 85 color = get_minimap_color(world_coords, world, island_color, water_color) 86 87 yield ((x, y), color)
88
89 90 -class Minimap:
91 """A basic minimap. 92 93 USAGE: 94 Minimap can be drawn via GenericRenderer on an arbitrary position (determined by rect in ctor) 95 or 96 via Pychan Icon. In this case, the rect parameter only determines the size, the 97 Minimap will scroll by default on clicks, overwrite on_click if you don't want that. 98 99 TODO: 100 * Remove renderer when used in icon node 101 * Clear up distinction of coords where the minimap image or screen is the origin 102 * Create a minimap tag for pychan 103 ** Handle clicks, remove overlay icon 104 """ 105 COLORS = { 106 "island": (137, 117, 87), 107 "cam": (1, 1, 1), 108 "water": (198, 188, 165), 109 "highlight": (255, 0, 0), # for events 110 } 111 112 WAREHOUSE_IMAGE = "content/gui/icons/minimap/warehouse.png" 113 SHIP_NEUTRAL = "content/gui/icons/minimap/ship_neutral.png" 114 SHIP_PIRATE = "content/gui/icons/minimap/pirate.png" 115 GROUND_UNIT_IMAGE = "content/gui/icons/minimap/groundunit.png" 116 117 SHIP_DOT_UPDATE_INTERVAL = 0.5 # seconds 118 119 # Alpha-ordering determines the order: 120 RENDER_NAMES = { 121 "background": "c", 122 "base": "d", # islands, etc. 123 "warehouse": "e", 124 "ship": "f", 125 "cam": "g", 126 "ship_route": "h", 127 "highlight": "l" 128 } 129 130 __minimap_id_counter = itertools.count() 131 __ship_route_counter = itertools.count() 132 # all active instances 133 _instances = [] # type: List[Minimap] 134 135 _dummy_fife_point = fife.Point(0, 0) # use when you quickly need a temporary point 136
137 - def __init__(self, position, session, view, targetrenderer, imagemanager, renderer=None, world=None, 138 cam_border=True, use_rotation=True, on_click=None, preview=False, tooltip=None, mousearea=None):
139 """ 140 @param position: a Rect or a Pychan Icon, where we will draw to 141 @param world: World object or fake thereof 142 @param view: View object for cam control. Can be None to disable this 143 @param renderer: renderer to be used if position isn't an icon 144 @param targetrenderer: fife target renderer for drawing on icons 145 @param imagemanager: fife imagemanager for drawing on icons 146 @param cam_border: boolean, whether to draw the cam border 147 @param use_rotation: boolean, whether to use rotation 148 @param on_click: function taking 1 argument or None for scrolling 149 @param preview: flag, whether to only show the map as preview 150 @param tooltip: always show this tooltip when cursor hovers over minimap 151 152 NOTE: Preview generation in a different process overwrites this method. 153 """ 154 if isinstance(position, Rect): 155 self.location = position 156 self.renderer = renderer 157 else: # assume icon 158 self.location = Rect.init_from_topleft_and_size(0, 0, position.width, position.height) 159 self.icon = position 160 if mousearea is None: 161 mousearea = self.icon 162 self.use_overlay_icon(mousearea) 163 164 # FIXME PY3 width / height of icon is sometimes zero. Why? 165 if self.location.height == 0 or self.location.width == 0: 166 self.location = Rect.init_from_topleft_and_size(0, 0, 128, 128) 167 168 self.session = session 169 self.world = world 170 self.view = view 171 self.fixed_tooltip = tooltip 172 173 self.click_handler = on_click if on_click is not None else self.default_on_click 174 175 self.cam_border = cam_border 176 self.use_rotation = use_rotation 177 self.preview = preview 178 179 self.location_center = self.location.center 180 181 self._id = str(next(self.__class__.__minimap_id_counter)) # internal identifier, used for allocating resources 182 183 self._image_size_cache = {} # internal detail 184 185 self.imagemanager = imagemanager 186 187 self.minimap_image = _MinimapImage(self, targetrenderer) 188 189 self.transform = None
190
191 - def end(self):
192 self.disable() 193 self.world = None 194 self.session = None 195 self.renderer = None
196
197 - def disable(self):
198 """Due to the way the minimap works, there isn't really a show/hide, 199 but you can disable it with this and enable again with draw(). 200 Stops all updates.""" 201 ExtScheduler().rem_all_classinst_calls(self) 202 if self.view is not None: 203 self.view.discard_change_listener(self.update_cam) 204 205 if self in self.__class__._instances: 206 self.__class__._instances.remove(self)
207
208 - def draw(self):
209 """Recalculates and draws the whole minimap of self.session.world or world. 210 The world you specified is reused for every operation until the next draw(). 211 """ 212 if self.world is None and self.session.world is not None: 213 self.world = self.session.world # in case minimap has been constructed before the world 214 if not self.world.inited: 215 return # don't draw while loading 216 if self.transform is None: 217 self.transform = _MinimapTransform(self.world.map_dimensions, 218 self.location, 219 0, 220 self.use_rotation) 221 self.update_rotation() 222 223 self.__class__._instances.append(self) 224 225 # update cam when view updates 226 if self.view is not None and not self.view.has_change_listener(self.update_cam): 227 self.view.add_change_listener(self.update_cam) 228 229 if not hasattr(self, "icon"): 230 # add to global generic renderer with id specific to this instance 231 self.renderer.removeAll("minimap_image" + self._id) 232 self.minimap_image.reset() 233 # NOTE: this is for the generic renderer interface, the offrenderer has slightly different methods 234 node = fife.RendererNode(fife.Point(self.location.center.x, self.location.center.y)) 235 self.renderer.addImage("minimap_image" + self._id, node, self.minimap_image.image, False) 236 237 else: 238 # attach image to pychan icon (recommended) 239 self.minimap_image.reset() 240 self.icon.image = fife.GuiImage(self.minimap_image.image) 241 242 self.update_cam() 243 self._recalculate() 244 if not self.preview: 245 self._timed_update(force=True) 246 ExtScheduler().rem_all_classinst_calls(self) 247 ExtScheduler().add_new_object(self._timed_update, self, 248 self.SHIP_DOT_UPDATE_INTERVAL, -1)
249
250 - def draw_data(self, data):
251 """Display data from dump_data""" 252 # only icon mode for now 253 self.minimap_image.reset() 254 self.icon.image = fife.GuiImage(self.minimap_image.image) 255 256 self.minimap_image.set_drawing_enabled() 257 rt = self.minimap_image.rendertarget 258 render_name = self._get_render_name("base") 259 draw_point = rt.addPoint 260 point = fife.Point() 261 262 for x, y, r, g, b in data: 263 point.set(x, y) 264 draw_point(render_name, point, r, g, b)
265
266 - def _get_render_name(self, key):
267 return self.RENDER_NAMES[key] + self._id
268
269 - def update_cam(self):
270 """Redraw camera border.""" 271 if not self.cam_border or self.view is None: # needs view 272 return 273 if self.world is None or not self.world.inited: 274 return # don't draw while loading 275 self.minimap_image.set_drawing_enabled() 276 self.minimap_image.rendertarget.removeAll(self._get_render_name("cam")) 277 # draw rect for current screen 278 displayed_area = self.view.get_displayed_area() 279 280 minimap_corners_as_point = [] 281 for (x, y) in displayed_area: 282 coords = self.transform.world_to_minimap((x, y)) 283 minimap_corners_as_point.append(fife.Point(coords[0], coords[1])) 284 285 for i in range(0, 4): 286 self.minimap_image.rendertarget.addLine(self._get_render_name("cam"), 287 minimap_corners_as_point[i], 288 minimap_corners_as_point[(i + 1) % 4], 289 *self.COLORS["cam"])
290 291 @classmethod
292 - def update(cls, tup):
293 for minimap in cls._instances: 294 minimap._update(tup)
295
296 - def _update(self, tup):
297 """Recalculate and redraw minimap for real world coord tup 298 @param tup: (x, y)""" 299 if self.world is None or not self.world.inited: 300 return # don't draw while loading 301 if tup is not None: 302 tup = self.transform.world_to_minimap(tup) 303 304 self._recalculate(tup)
305
306 - def use_overlay_icon(self, icon):
307 """Configures icon so that clicks get mapped here. 308 The current gui requires, that the minimap is drawn behind an icon.""" 309 self.overlay_icon = icon 310 icon.mapEvents({ 311 icon.name + '/mousePressed': self._on_click, 312 icon.name + '/mouseDragged': self._on_drag, 313 icon.name + '/mouseEntered': self._mouse_entered, 314 icon.name + '/mouseMoved': self._mouse_moved, 315 icon.name + '/mouseExited': self._mouse_exited, 316 })
317
318 - def default_on_click(self, event, drag):
319 """Handler for clicks (pressed and dragged) 320 Scrolls screen to the point, where the cursor points to on the minimap. 321 Overwrite this method to your convenience. 322 """ 323 if self.preview: 324 return # we don't do anything in this mode 325 button = event.getButton() 326 map_coords = event.map_coords 327 if button == fife.MouseEvent.RIGHT: 328 if drag: 329 return 330 for i in self.session.selected_instances: 331 if i.movable: 332 Act(i, *map_coords).execute(self.session) 333 elif button == fife.MouseEvent.LEFT: 334 if self.view is None: 335 print("Warning: Can't handle minimap clicks since we have no view object") 336 else: 337 self.view.center(*map_coords)
338
339 - def _on_click(self, event):
340 if self.world is not None: # supply world coords if there is a world 341 event.map_coords = self._get_event_coords(event) 342 if event.map_coords: 343 self.click_handler(event, drag=False) 344 else: 345 self.click_handler(event, drag=True)
346
347 - def _on_drag(self, event):
348 if self.world is not None: # supply world coords if there is a world 349 event.map_coords = self._get_event_coords(event) 350 if event.map_coords: 351 self.click_handler(event, drag=True) 352 else: 353 self.click_handler(event, drag=True)
354
355 - def _get_event_coords(self, event):
356 """Returns position of event as uh map coordinate tuple or None""" 357 mouse_position = Point(event.getX(), event.getY()) 358 if not hasattr(self, "icon"): 359 icon_pos = Point(*self.overlay_icon.getAbsolutePos()) 360 abs_mouse_position = icon_pos + mouse_position 361 if not self.location.contains(abs_mouse_position): 362 # mouse click was on icon but not actually on minimap 363 return None 364 world_position = self.transform.minimap_to_world((event.getX(), event.getY())) 365 if self.world.map_dimensions.contains_tuple(world_position): 366 return world_position
367
368 - def _mouse_entered(self, event):
369 self._show_tooltip(event)
370
371 - def _mouse_moved(self, event):
372 self._show_tooltip(event)
373
374 - def _mouse_exited(self, event):
375 if hasattr(self, "icon"): # only supported for icon mode atm 376 self.icon.hide_tooltip()
377
378 - def _show_tooltip(self, event):
379 if not hasattr(self, "icon"): 380 # only supported for icon mode atm 381 return 382 if self.fixed_tooltip is not None: 383 self.icon.helptext = self.fixed_tooltip 384 self.icon.position_tooltip(event) 385 #self.icon.show_tooltip() 386 else: 387 coords = self._get_event_coords(event) 388 if not coords: # no valid/relevant event location 389 self.icon.hide_tooltip() 390 return 391 392 tile = self.world.get_tile(Point(*coords)) 393 if tile is not None and tile.settlement is not None: 394 new_helptext = tile.settlement.get_component(NamedComponent).name 395 if self.icon.helptext != new_helptext: 396 self.icon.helptext = new_helptext 397 self.icon.show_tooltip() 398 else: 399 self.icon.position_tooltip(event) 400 else: 401 # mouse not over relevant part of the minimap 402 self.icon.hide_tooltip()
403
404 - def highlight(self, tup, factor=1.0, speed=1.0, finish_callback=None, color=(0, 0, 0)):
405 """Try to get the users attention on a certain point of the minimap. 406 @param tup: world coords 407 @param factor: float indicating importance of event 408 @param speed: animation speed as factor 409 @param finish_callback: executed when animation finishes 410 @param color: color of anim, (r,g,b), r,g,b of [0,255] 411 @return duration of full animation in seconds""" 412 tup = self.transform.world_to_minimap(tup) 413 414 # grow the circle from MIN_RAD to MAX_RAD and back with STEPS steps, where the 415 # interval between steps is INTERVAL seconds 416 MIN_RAD = int(3 * factor) # pixel 417 MAX_RAD = int(12 * factor) # pixel 418 STEPS = int(20 * factor) 419 INTERVAL = (math.pi / 16) * factor 420 421 def high(i=0): 422 i += 1 423 render_name = self._get_render_name("highlight") + str(tup) 424 self.minimap_image.set_drawing_enabled() 425 self.minimap_image.rendertarget.removeAll(render_name) 426 if i > STEPS: 427 if finish_callback: 428 finish_callback() 429 return 430 part = i # grow bigger 431 if i > STEPS // 2: # after the first half 432 part = STEPS - i # become smaller 433 434 radius = MIN_RAD + int((float(part) / (STEPS // 2)) * (MAX_RAD - MIN_RAD)) 435 436 draw_point = self.minimap_image.rendertarget.addPoint 437 for x, y in Circle(Point(*tup), radius=radius).get_border_coordinates(): 438 draw_point(render_name, fife.Point(x, y), *color) 439 440 ExtScheduler().add_new_object(lambda: high(i), self, INTERVAL, loops=1)
441 442 high() 443 return STEPS * INTERVAL
444
445 - def show_unit_path(self, unit):
446 """Show the path a unit is moving along""" 447 path = unit.path.path 448 if path is None: # show at least the position 449 path = [unit.position.to_tuple()] 450 451 # the path always contains the full path, the unit might be somewhere in it 452 position_of_unit_in_path = 0 453 unit_pos = unit.position.to_tuple() 454 for i, pos in enumerate(path): 455 if pos == unit_pos: 456 position_of_unit_in_path = i 457 break 458 459 # display units one ahead if possible, it looks nicer if the unit is moving 460 if len(path) > 1 and position_of_unit_in_path + 1 < len(path): 461 position_of_unit_in_path += 1 462 path = path[position_of_unit_in_path:] 463 464 # draw every step-th coord 465 step = 1 466 relevant_coords = [path[0]] 467 for i in range(step, len(path), step): 468 relevant_coords.append(path[i]) 469 relevant_coords.append(path[-1]) 470 471 # get coords, actual drawing 472 self.minimap_image.set_drawing_enabled() 473 p = fife.Point(0, 0) 474 render_name = self._get_render_name("ship_route") + str(next(self.__class__.__ship_route_counter)) 475 color = unit.owner.color.to_tuple() 476 last_coord = None 477 draw_point = self.minimap_image.rendertarget.addPoint 478 for i in relevant_coords: 479 coord = self.transform.world_to_minimap(i) 480 if last_coord is not None and \ 481 sum(abs(last_coord[i] - coord[i]) for i in (0, 1)) < 2: # 2 is min dist in pixels 482 continue 483 last_coord = coord 484 p.x = coord[0] 485 p.y = coord[1] 486 draw_point(render_name, p, *color) 487 488 def cleanup(): 489 self.minimap_image.set_drawing_enabled() 490 self.minimap_image.rendertarget.removeAll(render_name)
491 492 speed = 1.0 + math.sqrt(5) / 2 493 self.highlight(path[-1], factor=0.4, speed=speed, finish_callback=cleanup, color=color) 494 495 return True 496
497 - def _recalculate(self, where=None):
498 """Calculate which pixel of the minimap should display what and draw it 499 @param where: minimap coords. If this is given only that pixel will be redrawn 500 """ 501 self.minimap_image.set_drawing_enabled() 502 503 rt = self.minimap_image.rendertarget 504 render_name = self._get_render_name("base") 505 506 if where is None: 507 rt.removeAll(render_name) 508 points = self.transform.iter_points() 509 else: 510 points = [where] 511 512 # Is this really worth it? 513 draw_point = rt.addPoint 514 515 fife_point = fife.Point(0, 0) 516 island_color = self.COLORS["island"] 517 water_color = self.COLORS["water"] 518 519 for (x, y) in points: 520 521 world_coords = self.transform.minimap_to_world((x, y)) 522 color = get_minimap_color(world_coords, self.world, island_color, water_color) 523 fife_point.set(x, y) 524 draw_point(render_name, fife_point, *color)
525
526 - def _timed_update(self, force=False):
527 """Regular updates for domains we can't or don't want to keep track of.""" 528 # OPTIMIZATION NOTE: There can be pretty many ships. 529 # Don't rely on the loop being rarely executed! 530 # update ship icons 531 self.minimap_image.set_drawing_enabled() 532 render_name = self._get_render_name("ship") 533 self.minimap_image.rendertarget.removeAll(render_name) 534 # Make use of these dummy points instead of creating fife.Point instances 535 # (which are consuming a lot of resources). 536 dummy_point0 = fife.Point(0, 0) 537 dummy_point1 = fife.Point(0, 0) 538 for ship in self.world.ships: 539 if not ship.in_ship_map: 540 continue # no fisher ships, etc 541 coord = self.transform.world_to_minimap(ship.position.to_tuple()) 542 color = ship.owner.color.to_tuple() 543 # set correct icon 544 if ship.owner is self.session.world.pirate: 545 ship_icon_path = self.__class__.SHIP_PIRATE 546 else: 547 ship_icon_path = self.__class__.SHIP_NEUTRAL 548 ship_icon = self.imagemanager.load(ship_icon_path) 549 dummy_point1.set(coord[0], coord[1]) 550 self.minimap_image.rendertarget.addImage(render_name, dummy_point1, ship_icon) 551 if ship.owner.regular_player: 552 # add the 'flag' over the ship icon, with the color of the owner 553 dummy_point0.set(coord[0] - 5, coord[1] - 5) 554 dummy_point1.set(coord[0], coord[1] - 5) 555 self.minimap_image.rendertarget.addLine(render_name, dummy_point0, 556 dummy_point1, color[0], color[1], color[2]) 557 dummy_point0.set(coord[0] - 6, coord[1] - 6) 558 dummy_point1.set(coord[0], coord[1] - 6) 559 self.minimap_image.rendertarget.addLine(render_name, dummy_point0, 560 dummy_point1, color[0], color[1], color[2]) 561 dummy_point0.set(coord[0] - 4, coord[1] - 4) 562 dummy_point1.set(coord[0], coord[1] - 4) 563 self.minimap_image.rendertarget.addLine(render_name, dummy_point0, 564 dummy_point1, color[0], color[1], color[2]) 565 # add black border around the flag 566 dummy_point0.set(coord[0] - 6, coord[1] - 7) 567 dummy_point1.set(coord[0], coord[1] - 7) 568 self.minimap_image.rendertarget.addLine(render_name, dummy_point0, dummy_point1, 0, 0, 0) 569 dummy_point0.set(coord[0] - 4, coord[1] - 3) 570 dummy_point1.set(coord[0], coord[1] - 4) 571 self.minimap_image.rendertarget.addLine(render_name, dummy_point0, dummy_point1, 0, 0, 0) 572 dummy_point0.set(coord[0] - 6, coord[1] - 7) 573 dummy_point1.set(coord[0] - 4, coord[1] - 3) 574 self.minimap_image.rendertarget.addLine(render_name, dummy_point0, dummy_point1, 0, 0, 0) 575 576 # TODO: nicer selected view 577 dummy_point0.set(coord[0], coord[1]) 578 draw_point = self.minimap_image.rendertarget.addPoint 579 if ship in self.session.selected_instances: 580 draw_point(render_name, dummy_point0, *Minimap.COLORS["water"]) 581 for x_off, y_off in ((-2, 0), (+2, 0), (0, -2), (0, +2)): 582 dummy_point1.set(coord[0] + x_off, coord[1] + y_off) 583 draw_point(render_name, dummy_point1, *color) 584 585 # draw settlement warehouses if something has changed 586 settlements = self.world.settlements 587 # save only worldids as to not introduce actual coupling 588 cur_settlements = set(i.worldid for i in settlements) 589 if force or \ 590 (not hasattr(self, "_last_settlements") or cur_settlements != self._last_settlements): 591 # update necessary 592 warehouse_render_name = self._get_render_name("warehouse") 593 self.minimap_image.rendertarget.removeAll(warehouse_render_name) 594 for settlement in settlements: 595 coord = settlement.warehouse.position.center.to_tuple() 596 coord = self.transform.world_to_minimap(coord) 597 self._update_image(self.__class__.WAREHOUSE_IMAGE, 598 warehouse_render_name, 599 coord) 600 self._last_settlements = cur_settlements
601
602 - def _update_image(self, img_path, name, coord_tuple):
603 """Updates image as part of minimap (e.g. when it has moved)""" 604 img = self.imagemanager.load(img_path) 605 606 size_tuple = self._image_size_cache.get(img_path) 607 if size_tuple is None: 608 ratio = sum(self.transform.world_to_minimap_ratio) / 2.0 609 ratio = max(1.0, ratio) 610 size_tuple = int(img.getWidth() / ratio), int(img.getHeight() / ratio) 611 self._image_size_cache[img_path] = size_tuple 612 new_width, new_height = size_tuple 613 p = self.__class__._dummy_fife_point 614 p.set(*coord_tuple) 615 # resizeImage also means draw 616 self.minimap_image.rendertarget.resizeImage(name, p, img, new_width, new_height)
617
618 - def update_rotation(self):
619 # ensure the minimap rotation matches the main view rotation 620 if self.view is None: 621 return 622 self.transform.set_rotation(self.view.cam.getRotation()) 623 self.draw()
624
625 - def get_size(self):
626 return (self.location.width, self.location.height)
627
628 629 -class _MinimapTransform:
630
631 - def __init__(self, world_dimensions, location, rotation=0, use_rotation=True):
632 """ 633 @param world_dimensions: Rect, specifying the size of the world 634 @param location: Rect, specifying the place and size of the area to draw to 635 @param rotation: integer giving minimap rotation in degrees. 636 Might act weird with other values than 45, 135, 225 and 315 637 @param use_rotation: boolean, if this is false no rotation is used 638 """ 639 self.world_dimensions = world_dimensions 640 self.location = location 641 self.rotation = rotation 642 self.use_rotation = use_rotation 643 self._update_parameters()
644
645 - def set_rotation(self, rotation):
646 """ 647 @param rotation: integer giving rotation in degrees 648 the rotation only does something when use_rotation is True 649 """ 650 self.rotation = rotation 651 self._update_parameters()
652
653 - def set_use_rotation(self, use_rotation):
654 self.use_rotation = use_rotation 655 self._update_parameters()
656
657 - def world_to_minimap(self, coords):
658 """Complete coord transformation, batteries included. 659 @param coords: tuple of two numbers representing a coordinate in the world 660 """ 661 662 x, y = coords 663 664 # center on 0,0 665 x -= self.world_dimensions.center.x 666 y -= self.world_dimensions.center.y 667 668 # rotate 669 x_ = x * self._cos_rotation - y * self._sin_rotation 670 y_ = x * self._sin_rotation + y * self._cos_rotation 671 672 # scale to minimap size and translate to correct position 673 x = x_ * self._world_minimap_ratio_x + self.location.center.x 674 y = y_ * self._world_minimap_ratio_y + self.location.center.y 675 676 return (int(x), int(y))
677
678 - def minimap_to_world(self, coords):
679 """ 680 @param coords: tuple of two numbers representing a point in the minimap 681 """ 682 x, y = coords 683 684 # center on 0,0 and scale to world size 685 x = (x - self.location.center.x) / self._world_minimap_ratio_x 686 y = (y - self.location.center.y) / self._world_minimap_ratio_y 687 688 # rotate 689 x_ = x * self._cos_rotation + y * self._sin_rotation 690 y_ = -x * self._sin_rotation + y * self._cos_rotation 691 692 # undo centering and translate to correct position 693 x = x_ + self.world_dimensions.center.x 694 y = y_ + self.world_dimensions.center.y 695 696 return (int(x), int(y))
697
698 - def _update_parameters(self):
699 """ Update the transformation parameters. 700 This is only executed at the begin and when the map rotation changes. 701 This should improve performance """ 702 self._rotation_rad = self._get_rotation() 703 self._sin_rotation = sin(self._rotation_rad) 704 self._cos_rotation = cos(self._rotation_rad) 705 706 self._scale_correction = max(abs(self._sin_rotation), abs(self._cos_rotation)) 707 708 self._world_minimap_ratio_x = self.location.width / self.world_dimensions.width * self._scale_correction 709 self._world_minimap_ratio_y = self.location.height / self.world_dimensions.height * self._scale_correction 710 self.world_to_minimap_ratio = (self._world_minimap_ratio_x, self._world_minimap_ratio_y)
711
712 - def _get_rotation(self):
713 # keep track of rotation at any time, but only apply 714 # if it's actually used 715 if self.use_rotation: 716 # convert to radians 717 return -self.rotation / 180 * math.pi 718 else: 719 return 0
720
721 - def iter_points(self):
722 """Return an iterator over the pixels of a minimap of the given world. 723 724 For every pixel, a tuple (x, y) is returned. These are the x and y 725 coordinates. 726 """ 727 location = self.location 728 729 # loop through map coordinates 730 # TODO: make this work with any rotation 731 if self.use_rotation: 732 for x_i in range(0, location.width): 733 x = x_i + location.left 734 for y_i in range(self.get_min_y(x), self.get_max_y(x)): 735 y = y_i + location.top 736 yield (x, y) 737 else: 738 for x in range(location.left, location.width + location.left): 739 for y in range(location.top, location.height + location.top): 740 yield (x, y)
741
742 - def get_min_y(self, x):
743 if self.use_rotation: 744 return int(abs(x / self.location.width - 0.5) * self.location.height) 745 else: 746 return 0
747
748 - def get_max_y(self, x):
749 return self.location.height - self.get_min_y(x)
750
751 - def get_min_x(self, y):
752 if self.use_rotation: 753 return int(abs(y / self.location.height - 0.5) * self.location.width) 754 else: 755 return 0
756
757 - def get_max_x(self, y):
758 return self.location.height - self.get_min_x(y)
759
760 761 -class _MinimapImage:
762 """Encapsulates handling of fife Image. 763 Provides: 764 - self.rendertarget: instance of fife.RenderTarget 765 """
766 - def __init__(self, minimap, targetrenderer):
767 self.minimap = minimap 768 self.targetrenderer = targetrenderer 769 size = self.minimap.get_size() 770 self.image = self.minimap.imagemanager.loadBlank(size[0], size[1]) 771 self.rendertarget = targetrenderer.createRenderTarget(self.image) 772 self.set_drawing_enabled()
773
774 - def reset(self):
775 """Reset image to original image""" 776 # reload 777 self.rendertarget.removeAll() 778 size = self.minimap.get_size() 779 self.rendertarget.addQuad(self.minimap._get_render_name("background"), 780 fife.Point(0, 0), 781 fife.Point(0, size[1]), 782 fife.Point(size[0], size[1]), 783 fife.Point(size[0], 0), 784 *Minimap.COLORS["water"])
785
786 - def set_drawing_enabled(self):
787 """Always call this.""" 788 targetname = self.rendertarget.getTarget().getName() 789 self.targetrenderer.setRenderTarget(targetname, False, 0)
790