Example #1
0
		def create_node(instance=None, point=None, layer=None):
			""" creates a node of one of these types:
			
				- attached to an instance
				- attached to an instance with offset
				- attached to a point
			
				FIXME:
					- add location node
			
			@type:	instance:	object
			@param	instance:	fife instance object
			@type	point:		tuple
			@param	point:		x,y,z tuple
			@type	layer:		object
			@param	layer:		fife layer object
			"""
			node = None
			if not layer: return node
			
			# node at layer coordinates
			if point and not instance:
				point = fife.Point(point[0], point[1])
				node = fife.RendererNode(point);
			# node with offset
			if instance and point:
				node = fife.RendererNode(instance, layer, fife.Point(point[0], point[1]))
			# node attached to instance
			if instance and not point:
				node = fife.RendererNode(instance, layer)

			if node:
				node.thisown = 0
				
			return node			
    def __render_icon(self, instance, group, res, amount):
        """ This renders the icon. It calculates the position of the icon.
		Most parts of this were copied from horizons/world/managers/statusiconmanager.py
		"""
        # TODO: Try to unify the __render methods of this class and statusiconmanager.py!
        self.renderer.removeAll(group)

        pos = instance.position
        # self.run[group] is used for the moving up animation
        # use -50 here to get some more offset in height
        bg_rel = fife.Point(0, -50 - self.run[group])
        rel = fife.Point(-14, -50 - self.run[group])
        self.run[group] += self.animation_steps

        loc = fife.Location(self.layer)
        loc.setExactLayerCoordinates(
            fife.ExactModelCoordinate(
                pos.origin.x + float(pos.width) / 4,
                pos.origin.y + float(pos.height) / 4,
            ))

        bg_node = fife.RendererNode(loc, bg_rel)
        node = fife.RendererNode(loc, rel)

        bg_image = horizons.globals.fife.imagemanager.load(self.background)
        res_icon = horizons.globals.fife.imagemanager.load(
            get_res_icon_path(res))
        font = horizons.globals.fife.pychanmanager.getFont('mainmenu')

        self.renderer.addImage(group, bg_node, bg_image)
        self.renderer.resizeImage(group, node, res_icon, 24, 24)
        self.renderer.addText(group, node, font,
                              ' ' * 9 + '{amount:>2d}'.format(amount=amount))
Example #3
0
 def reset(self):
     """Reset image to original image"""
     # reload
     self.rendertarget.removeAll()
     size = self.minimap.get_size()
     self.rendertarget.addQuad(self.minimap._get_render_name("background"),
                               fife.Point(0, 0), fife.Point(0, size[1]),
                               fife.Point(size[0], size[1]),
                               fife.Point(size[0], 0),
                               *Minimap.COLORS["water"])
Example #4
0
    def mouseDragged(self, evt):
        if evt.getButton() == fife.MouseEvent.LEFT and hasattr(
                self, 'select_begin'):
            x, y = self.select_begin
            xx, yy = evt.getX(), evt.getY()
            do_multi = ((x - xx)**2 + (y - yy)**2) >= 10  # from 3px (3*3 + 1)
            self.session.view.renderer['GenericRenderer'].removeAll(
                self.__class__._SELECTION_RECTANGLE_NAME)
            if do_multi:
                # draw a rectangle
                xmin, xmax = min(x, xx), max(x, xx)
                ymin, ymax = min(y, yy), max(y, yy)
                a = fife.Point(xmin, ymin)
                b = fife.Point(xmax, ymin)
                c = fife.Point(xmax, ymax)
                d = fife.Point(xmin, ymax)
                self._draw_rect_line(a, b)
                self._draw_rect_line(b, c)
                self._draw_rect_line(d, c)
                self._draw_rect_line(d, a)
                area = fife.Rect(xmin, ymin, xmax - xmin, ymax - ymin)
            else:
                area = fife.ScreenPoint(xx, yy)
            instances = self.session.view.cam.getMatchingInstances(
                area, self.session.view.layers[LAYERS.OBJECTS],
                False)  # False for accurate

            # get selection components
            instances = (self.fife_instance_to_uh_instance(i)
                         for i in instances)
            instances = [i for i in instances if i is not None]

            # We only consider selectable items when dragging a selection box.
            instances = self.filter_selectable(instances)

            # If there is at least one player unit, we don't select any enemies.
            # This applies to both buildings and ships.
            if any(
                (self.is_owned_by_player(instance) for instance in instances)):
                instances = self.filter_owner(instances)

            self._update_selection(instances, do_multi)

        elif evt.getButton() == fife.MouseEvent.RIGHT:
            pass
        else:
            super(SelectionTool, self).mouseDragged(evt)
            return
        evt.consume()
Example #5
0
        def high(i=0):
            i += 1
            render_name = self._get_render_name("highlight") + str(tup)
            self.minimap_image.set_drawing_enabled()
            self.minimap_image.rendertarget.removeAll(render_name)
            if i > STEPS:
                if finish_callback:
                    finish_callback()
                return
            part = i  # grow bigger
            if i > STEPS // 2:  # after the first half
                part = STEPS - i  # become smaller

            radius = MIN_RAD + int(
                (float(part) / (STEPS // 2)) * (MAX_RAD - MIN_RAD))

            draw_point = self.minimap_image.rendertarget.addPoint
            for x, y in Circle(Point(*tup),
                               radius=radius).get_border_coordinates():
                draw_point(render_name, fife.Point(x, y), *color)

            ExtScheduler().add_new_object(lambda: high(i),
                                          self,
                                          INTERVAL,
                                          loops=1)
    def __render_status(self, instance, status):
        status_string = self.get_status_string(instance)

        # Clean icons
        self.renderer.removeAll(status_string)

        # pixel-offset on screen (will be constant across zoom-levels)
        rel = fife.Point(0, -30)

        pos = instance.position

        # trial and error has brought me to this (it's supposed to hit the center)
        loc = fife.Location(self.layer)
        loc.setExactLayerCoordinates(
            fife.ExactModelCoordinate(
                pos.origin.x + float(pos.width) / 4,
                pos.origin.y + float(pos.height) / 4,
            ))

        node = fife.RendererNode(loc, rel)

        try:  # to load an animation
            anim = horizons.globals.fife.animationloader.loadResource(
                status.icon)
            self.renderer.addAnimation(status_string, node, anim)
        except ValueError:
            img = horizons.globals.fife.imagemanager.load(status.icon)
            self.renderer.addImage(status_string, node, img)
Example #7
0
    def _recalculate(self, where=None):
        """Calculate which pixel of the minimap should display what and draw it
		@param where: minimap coords. If this is given only that pixel will be redrawn
		"""
        self.minimap_image.set_drawing_enabled()

        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")

        if where is None:
            rt.removeAll(render_name)
            points = self.transform.iter_points()
        else:
            points = [where]

        # Is this really worth it?
        draw_point = rt.addPoint

        fife_point = fife.Point(0, 0)
        island_color = self.COLORS["island"]
        water_color = self.COLORS["water"]

        for (x, y) in points:

            world_coords = self.transform.minimap_to_world((x, y))
            color = get_minimap_color(world_coords, self.world, island_color,
                                      water_color)
            fife_point.set(x, y)
            draw_point(render_name, fife_point, *color)
Example #8
0
 def update_cam(self):
     """Redraw camera border."""
     if self.world is None or not self.world.inited:
         return  # don't draw while loading
     self.renderer.removeAll("minimap_cam_border")
     # draw rect for current screen
     displayed_area = self.session.view.get_displayed_area()
     minimap_corners_as_renderer_node = []
     for corner in displayed_area.get_corners():
         # check if the corners are outside of the screen
         corner = list(corner)
         if corner[0] > self.world.max_x:
             corner[0] = self.world.max_x
         if corner[0] < self.world.min_x:
             corner[0] = self.world.min_x
         if corner[1] > self.world.max_y:
             corner[1] = self.world.max_y
         if corner[1] < self.world.min_y:
             corner[1] = self.world.min_y
         corner = tuple(corner)
         minimap_coords = self._get_rotated_coords(
             self._world_coord_to_minimap_coord(corner))
         minimap_corners_as_renderer_node.append( fife.GenericRendererNode( \
           fife.Point(*minimap_coords) ) )
     for i in xrange(0, 3):
         self.renderer.addLine("minimap_cam_border", minimap_corners_as_renderer_node[i], \
                          minimap_corners_as_renderer_node[i+1], *self.colors[self.cam_border])
     # close the rect
     self.renderer.addLine("minimap_cam_border", minimap_corners_as_renderer_node[3], \
                      minimap_corners_as_renderer_node[0], *self.colors[self.cam_border])
Example #9
0
    def update_cam(self):
        """Redraw camera border."""
        if not self.cam_border or self.view is None:  # needs view
            return
        if self.world is None or not self.world.inited:
            return  # don't draw while loading
        use_rotation = self._get_rotation_setting()
        self.minimap_image.set_drawing_enabled()
        self.minimap_image.rendertarget.removeAll(self._get_render_name("cam"))
        # draw rect for current screen
        displayed_area = self.view.get_displayed_area()
        minimap_corners_as_point = []
        for corner in displayed_area.get_corners():
            # check if the corners are outside of the screen
            corner = list(corner)
            if corner[0] > self.world.max_x:
                corner[0] = self.world.max_x
            if corner[0] < self.world.min_x:
                corner[0] = self.world.min_x
            if corner[1] > self.world.max_y:
                corner[1] = self.world.max_y
            if corner[1] < self.world.min_y:
                corner[1] = self.world.min_y
            corner = tuple(corner)

            coords = self._world_to_minimap(corner, use_rotation)
            minimap_corners_as_point.append(fife.Point(coords[0], coords[1]))

        for i in xrange(0, 4):
            self.minimap_image.rendertarget.addLine(
                self._get_render_name("cam"), minimap_corners_as_point[i],
                minimap_corners_as_point[(i + 1) % 4], *self.COLORS["cam"])
Example #10
0
    def _recalculate(self, where=None):
        """Calculate which pixel of the minimap should display what and draw it
		@param where: Rect of minimap coords. Defaults to self.location
		"""
        self.minimap_image.set_drawing_enabled()

        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")

        if where is None:
            rt.removeAll(render_name)

        location_left = self.location.left
        location_top = self.location.top
        draw_point = rt.addPoint
        fife_point = fife.Point(0, 0)
        use_rotation = self._get_rotation_setting()

        for (x, y), color in iter_minimap_points(self.location, self.world,
                                                 self.COLORS["island"],
                                                 self.COLORS["water"], where):
            if use_rotation:
                # inlined _get_rotated_coords
                rot_x, rot_y = self._rotate(
                    (location_left + x, location_top + y), self._rotations)
                fife_point.set(rot_x - location_left, rot_y - location_top)
            else:
                fife_point.set(x, y)
            draw_point(render_name, fife_point, *color)
Example #11
0
    def show_unit_path(self, unit):
        """Show the path a unit is moving along"""
        path = unit.path.path
        if path is None:  # show at least the position
            path = [unit.position.to_tuple()]

        # the path always contains the full path, the unit might be somewhere in it
        position_of_unit_in_path = 0
        unit_pos = unit.position.to_tuple()
        for i, pos in enumerate(path):
            if pos == unit_pos:
                position_of_unit_in_path = i
                break

        # display units one ahead if possible, it looks nicer if the unit is moving
        if len(path) > 1 and position_of_unit_in_path + 1 < len(path):
            position_of_unit_in_path += 1  #
        path = path[position_of_unit_in_path:]

        # draw every step-th coord
        step = 1
        relevant_coords = [path[0]]
        for i in range(step, len(path), step):
            relevant_coords.append(path[i])
        relevant_coords.append(path[-1])

        # get coords, actual drawing
        use_rotation = self._get_rotation_setting()
        self.minimap_image.set_drawing_enabled()
        p = fife.Point(0, 0)
        render_name = self._get_render_name("ship_route") + str(
            next(self.__class__.__ship_route_counter))
        color = unit.owner.color.to_tuple()
        last_coord = None
        draw_point = self.minimap_image.rendertarget.addPoint
        for i in relevant_coords:
            coord = self._world_to_minimap(i, use_rotation)
            if last_coord is not None and \
               sum(abs(last_coord[i] - coord[i]) for i in (0, 1)) < 2:  # 2 is min dist in pixels
                continue
            last_coord = coord
            p.x = coord[0]
            p.y = coord[1]
            draw_point(render_name, p, *color)

        def cleanup():
            self.minimap_image.set_drawing_enabled()
            self.minimap_image.rendertarget.removeAll(render_name)

        speed = 1.0 + math.sqrt(5) / 2
        self.highlight(path[-1],
                       factor=0.4,
                       speed=speed,
                       finish_callback=cleanup,
                       color=color)

        return True
Example #12
0
    def mouseDragged(self, event):
        instancerenderer = fife.InstanceRenderer.getInstance(
            self._test._camera)
        instancerenderer.removeAllColored()
        instancerenderer.removeAllOutlines()

        if event.getButton() == fife.MouseEvent.LEFT and hasattr(
                self, 'select_begin'):
            do_multi = ((self.select_begin[0] - event.getX())**2 +
                        (self.select_begin[1] - event.getY())**
                        2) >= 10  # from 3px (3*3 + 1)
            genericrenderer = fife.GenericRenderer.getInstance(
                self._test._camera)
            genericrenderer.removeAll("selection")
            if do_multi:
                # draw a rectangle
                a = fife.Point(min(self.select_begin[0], event.getX()), \
                        min(self.select_begin[1], event.getY()))
                b = fife.Point(max(self.select_begin[0], event.getX()), \
                        min(self.select_begin[1], event.getY()))
                c = fife.Point(max(self.select_begin[0], event.getX()), \
                        max(self.select_begin[1], event.getY()))
                d = fife.Point(min(self.select_begin[0], event.getX()), \
                        max(self.select_begin[1], event.getY()))
                genericrenderer.addLine("selection", \
                      fife.RendererNode(a), fife.RendererNode(b), 200, 200, 200)
                genericrenderer.addLine("selection", \
                      fife.RendererNode(b), fife.RendererNode(c), 200, 200, 200)
                genericrenderer.addLine("selection", \
                      fife.RendererNode(d), fife.RendererNode(c), 200, 200, 200)
                genericrenderer.addLine("selection", \
                      fife.RendererNode(a), fife.RendererNode(d), 200, 200, 200)

            instances = self._test._camera.getMatchingInstances(\
             fife.Rect(min(self.select_begin[0], event.getX()), \
                  min(self.select_begin[1], event.getY()), \
                  abs(event.getX() - self.select_begin[0]), \
                  abs(event.getY() - self.select_begin[1])) if do_multi else fife.ScreenPoint(event.getX(), event.getY()),
             self._test._actorlayer,
             0) # False for accurate

            for instance in instances:
                instancerenderer.addColored(instance, 250, 50, 250)
                instancerenderer.addOutlined(instance, 255, 255, 0, 2)
	def _send_hover_instance_upate(self):
		"""Broadcast update with new instances below mouse (hovered).
		At most called in a certain interval, not after every mouse move in
		order to prevent delays."""
		self._hover_instances_update_scheduled = False
		where = fife.Point(self.__class__.last_event_pos.x, self.__class__.last_event_pos.y)

		instances = set(self.get_hover_instances(where))
		# only send when there were actual changes
		if instances != set(self.__class__.last_hover_instances):
			self.__class__.last_hover_instances = WeakList(instances)
			HoverInstancesChanged.broadcast(self, instances)
Example #14
0
 def draw_health(self):
     """Draws the units current health as a healthbar over the unit."""
     renderer = self.session.view.renderer['GenericRenderer']
     renderer.removeAll("health_" + str(self.worldid))
     zoom = self.session.view.get_zoom()
     height = int(5 * zoom)
     width = int(50 * zoom)
     y_pos = int(self.health_bar_y * zoom)
     mid_node_up = fife.GenericRendererNode(self._instance, \
            fife.Point(-width/2+int(((self.health/self.max_health)*width)),\
                                            y_pos-height)
                                 )
     mid_node_down = fife.GenericRendererNode(self._instance, \
                                              fife.Point(
                                                  -width/2+int(((self.health/self.max_health)*width))
                                                  ,y_pos)
                                              )
     if self.health != 0:
         renderer.addQuad("health_" + str(self.worldid), \
                         fife.GenericRendererNode(self._instance, \
                                                  fife.Point(-width/2, y_pos-height)), \
                         mid_node_up, \
                         mid_node_down, \
                         fife.GenericRendererNode(self._instance, fife.Point(-width/2, y_pos)), \
                         0, 255, 0)
     if self.health != self.max_health:
         renderer.addQuad("health_" + str(self.worldid), mid_node_up, \
                          fife.GenericRendererNode(self._instance, fife.Point(width/2, y_pos-height)), \
                          fife.GenericRendererNode(self._instance, fife.Point(width/2, y_pos)), \
                          mid_node_down, 255, 0, 0)
Example #15
0
 def findPoint(self, event):
     point = None
     pos = -1
     thickness = self.point_graph.thickness
     points = self.point_graph.coordinates
     rec = fife.Rect(event.getX() - thickness,
                     event.getY() - thickness, thickness * 2, thickness * 2)
     for i, p in enumerate(points):
         if rec.contains(fife.Point(p.x, p.y)):
             point = p
             pos = i
             break
     return (point, pos)
Example #16
0
    def draw_data(self, data):
        """Display data from dump_data"""
        # only icon mode for now
        self.minimap_image.reset()
        self.icon.image = fife.GuiImage(self.minimap_image.image)

        self.minimap_image.set_drawing_enabled()
        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")
        drawPoint = rt.addPoint
        point = fife.Point()
        for x, y, r, g, b in json.loads(data):
            point.set(x, y)
            drawPoint(render_name, point, r, g, b)
Example #17
0
    def update_cam(self):
        """Redraw camera border."""
        if not self.cam_border or self.view is None:  # needs view
            return
        if self.world is None or not self.world.inited:
            return  # don't draw while loading
        self.minimap_image.set_drawing_enabled()
        self.minimap_image.rendertarget.removeAll(self._get_render_name("cam"))
        # draw rect for current screen
        displayed_area = self.view.get_displayed_area()
        minimap_corners_as_point = []
        for (x, y) in displayed_area:
            coords = self.transform.world_to_minimap((x, y))
            minimap_corners_as_point.append(fife.Point(coords[0], coords[1]))

        for i in range(0, 4):
            self.minimap_image.rendertarget.addLine(
                self._get_render_name("cam"), minimap_corners_as_point[i],
                minimap_corners_as_point[(i + 1) % 4], *self.COLORS["cam"])
Example #18
0
    def draw(self):
        """Recalculates and draws the whole minimap of self.session.world or world.
		The world you specified is reused for every operation until the next draw().
		"""
        if self.world is None and self.session.world is not None:
            self.world = self.session.world  # in case minimap has been constructed before the world
        if not self.world.inited:
            return  # don't draw while loading
        if self.transform is None:
            self.transform = _MinimapTransform(self.world.map_dimensions,
                                               self.location, 0,
                                               self._get_rotation_setting())
            self.update_rotation()

        self.__class__._instances.append(self)

        # update cam when view updates
        if self.view is not None and not self.view.has_change_listener(
                self.update_cam):
            self.view.add_change_listener(self.update_cam)

        if not hasattr(self, "icon"):
            # add to global generic renderer with id specific to this instance
            self.renderer.removeAll("minimap_image" + self._id)
            self.minimap_image.reset()
            # NOTE: this is for the generic renderer interface, the offrenderer has slightly different methods
            node = fife.RendererNode(
                fife.Point(self.location.center.x, self.location.center.y))
            self.renderer.addImage("minimap_image" + self._id, node,
                                   self.minimap_image.image, False)

        else:
            # attach image to pychan icon (recommended)
            self.minimap_image.reset()
            self.icon.image = fife.GuiImage(self.minimap_image.image)

        self.update_cam()
        self._recalculate()
        if not self.preview:
            self._timed_update(force=True)
            ExtScheduler().rem_all_classinst_calls(self)
            ExtScheduler().add_new_object(self._timed_update, self,
                                          self.SHIP_DOT_UPDATE_INTERVAL, -1)
Example #19
0
    def draw_data(self, data):
        """Display data from dump_data"""
        # only icon mode for now
        self.minimap_image.reset()
        self.icon.image = fife.GuiImage(self.minimap_image.image)

        self.minimap_image.set_drawing_enabled()
        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")
        draw_point = rt.addPoint
        point = fife.Point()

        # XXX There have been reports about `data` containing Fife debug
        # output (e.g. #2193). As temporary workaround, we try to only
        # parse what looks like valid json in there and ignore the rest.
        found_json = re.findall(r'\[\[.*\]\]', data)[0]

        for x, y, r, g, b in json.loads(found_json):
            point.set(x, y)
            draw_point(render_name, point, r, g, b)
Example #20
0
	def draw_health(self, remove_only=False, auto_remove=False):
		"""Draws the units current health as a healthbar over the unit."""
		if not self.has_component(HealthComponent):
			return
		render_name = "health_" + str(self.worldid)
		renderer = self.session.view.renderer['GenericRenderer']
		renderer.removeAll(render_name)
		if remove_only or (auto_remove and not self._last_draw_health_call_on_damage):
			# only remove on auto_remove if this health was actually displayed as reacton to _on_damage
			# else we might remove something that the user still wants
			self._health_displayed = False
			return
		self._last_draw_health_call_on_damage = False
		self._health_displayed = True
		health_component = self.get_component(HealthComponent)
		health = health_component.health
		max_health = health_component.max_health
		zoom = self.session.view.zoom
		height = int(5 * zoom)
		width = int(50 * zoom)
		y_pos = int(self.health_bar_y * zoom)
		relative_x = int((width * health) // max_health - (width // 2))
		# mid_node is the coord separating healthy (green) and damaged (red) quads
		mid_node_top = fife.RendererNode(self._instance, fife.Point(relative_x, y_pos - height))
		mid_node_btm = fife.RendererNode(self._instance, fife.Point(relative_x, y_pos))

		left_upper = fife.RendererNode(self._instance, fife.Point(-width // 2, y_pos - height))
		right_upper = fife.RendererNode(self._instance, fife.Point(width // 2, y_pos - height))
		left_lower = fife.RendererNode(self._instance, fife.Point(-width // 2, y_pos))
		right_lower = fife.RendererNode(self._instance, fife.Point(width // 2, y_pos))

		if health > 0: # draw healthy part of health bar
			renderer.addQuad(render_name,
			                 left_upper,
			                 left_lower,
			                 mid_node_btm,
			                 mid_node_top,
			                 0, 255, 0)
		if health < max_health: # draw damaged part
			renderer.addQuad(render_name,
			                 mid_node_top,
			                 mid_node_btm,
			                 right_lower,
			                 right_upper,
			                 255, 0, 0)
Example #21
0
class Minimap(object):
    """A basic minimap.

	USAGE:
	Minimap can be drawn via GenericRenderer on an arbitrary position (determined by rect in ctor)
	or
	via Pychan Icon. In this case, the rect parameter only determines the size, the
	Minimap will scroll by default on clicks, overwrite on_click if you don't want that.

	TODO:
	* Remove renderer when used in icon node
	* Clear up distinction of coords where the minimap image or screen is the origin
	* Create a minimap tag for pychan
	** Handle clicks, remove overlay icon
	"""
    COLORS = {
        "island": (137, 117, 87),
        "cam": (1, 1, 1),
        "water": (198, 188, 165),
        "highlight": (255, 0, 0),  # for events
    }

    WAREHOUSE_IMAGE = "content/gui/icons/minimap/warehouse.png"
    SHIP_NEUTRAL = "content/gui/icons/minimap/ship_neutral.png"
    SHIP_PIRATE = "content/gui/icons/minimap/pirate.png"

    SHIP_DOT_UPDATE_INTERVAL = 0.5  # seconds

    RENDER_NAMES = {  # alpha-ordering determines the order
        "background": "c",
        "base": "d",  # islands, etc.
        "warehouse": "e",
        "ship": "f",
        "cam": "g",
        "ship_route": "h",
        "highlight": "l"
    }

    __minimap_id_counter = itertools.count()
    __ship_route_counter = itertools.count()
    _instances = []  # all active instances

    _dummy_fife_point = fife.Point(
        0, 0)  # use when you quickly need a temporary point

    def __init__(self,
                 position,
                 session,
                 view,
                 targetrenderer,
                 imagemanager,
                 renderer=None,
                 world=None,
                 cam_border=True,
                 use_rotation=True,
                 on_click=None,
                 preview=False,
                 tooltip=None):
        """
		@param position: a Rect or a Pychan Icon, where we will draw to
		@param world: World object or fake thereof
		@param view: View object for cam control. Can be None to disable this
		@param renderer: renderer to be used if position isn't an icon
		@param targetrenderer: fife target rendererfor drawing on icons
		@param imagemanager: fife imagemanager for drawing on icons
		@param cam_border: boolean, whether to draw the cam border
		@param use_rotation: boolean, whether to use rotation (it must also be enabled in the settings)
		@param on_click: function taking 1 argument or None for scrolling
		@param preview: flag, whether to only show the map as preview
		@param tooltip: always show this tooltip when cursor hovers over minimap
		"""
        if isinstance(position, Rect):
            self.location = position
            self.renderer = renderer
        else:  # assume icon
            self.location = Rect.init_from_topleft_and_size(
                0, 0, position.width, position.height)
            self.icon = position
            self.use_overlay_icon(self.icon)
        self.session = session
        self.world = world
        if self.world:
            self._update_world_to_minimap_ratio()
        self.view = view
        self.rotation = 0
        self.fixed_tooltip = tooltip

        if on_click is not None:
            self.on_click = on_click

        self.cam_border = cam_border
        self.use_rotation = use_rotation
        self.preview = preview

        self.location_center = self.location.center

        self._id = str(self.__class__.__minimap_id_counter.next()
                       )  # internal identifier, used for allocating resources

        self._image_size_cache = {}  # internal detail

        self.imagemanager = imagemanager

        self.minimap_image = _MinimapImage(self, targetrenderer)

        #import random
        #ExtScheduler().add_new_object(lambda : self.highlight( (50+random.randint(-50,50), random.randint(-50,50) + 50 )), self, 2, loops=-1)

        self._rotation_setting = horizons.globals.fife.get_uh_setting(
            "MinimapRotation")
        if self.use_rotation:
            MinimapRotationSettingChanged.subscribe(
                self._on_rotation_setting_change)

    def end(self):
        self.disable()
        self.world = None
        self.session = None
        self.renderer = None
        if self.use_rotation:
            MinimapRotationSettingChanged.unsubscribe(
                self._on_rotation_setting_change)

    def disable(self):
        """Due to the way the minimap works, there isn't really a show/hide,
		but you can disable it with this and enable again with draw().
		Stops all updates."""
        ExtScheduler().rem_all_classinst_calls(self)
        if self.view is not None and self.view.has_change_listener(
                self.update_cam):
            self.view.remove_change_listener(self.update_cam)

        if self in self.__class__._instances:
            self.__class__._instances.remove(self)

    def draw(self):
        """Recalculates and draws the whole minimap of self.session.world or world.
		The world you specified is reused for every operation until the next draw().
		@param recalculate: do a full recalculation
		"""
        if self.world is None and self.session.world is not None:
            self.world = self.session.world  # in case minimap has been constructed before the world
            self._update_world_to_minimap_ratio()
        if not self.world.inited:
            return  # don't draw while loading

        self.__class__._instances.append(self)

        # update cam when view updates
        if self.view is not None and not self.view.has_change_listener(
                self.update_cam):
            self.view.add_change_listener(self.update_cam)

        if not hasattr(self, "icon"):
            # add to global generic renderer with id specific to this instance
            self.renderer.removeAll("minimap_image" + self._id)
            self.minimap_image.reset()
            # NOTE: this is for the generic renderer interface, the offrenderer has slightly different methods
            node = fife.RendererNode(
                fife.Point(self.location.center.x, self.location.center.y))
            self.renderer.addImage("minimap_image" + self._id, node,
                                   self.minimap_image.image, False)

        else:
            # attach image to pychan icon (recommended)
            self.minimap_image.reset()
            self.icon.image = fife.GuiImage(self.minimap_image.image)

        self.update_cam()
        self._recalculate()
        if not self.preview:
            self._timed_update(force=True)
            ExtScheduler().rem_all_classinst_calls(self)
            ExtScheduler().add_new_object(self._timed_update, self,
                                          self.SHIP_DOT_UPDATE_INTERVAL, -1)

    def dump_data(self):
        """Returns a string representing the minimap data"""
        return self._recalculate(dump_data=True)

    def draw_data(self, data):
        """Display data from dump_data"""
        # only icon mode for now
        self.minimap_image.reset()
        self.icon.image = fife.GuiImage(self.minimap_image.image)

        self.minimap_image.set_drawing_enabled()
        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")
        drawPoint = rt.addPoint
        point = fife.Point()
        for x, y, r, g, b in json.loads(data):
            point.set(x, y)
            drawPoint(render_name, point, r, g, b)

    def _get_render_name(self, key):
        return self.RENDER_NAMES[key] + self._id

    def update_cam(self):
        """Redraw camera border."""
        if not self.cam_border or self.view is None:  # needs view
            return
        if self.world is None or not self.world.inited:
            return  # don't draw while loading
        use_rotation = self._get_rotation_setting()
        self.minimap_image.set_drawing_enabled()
        self.minimap_image.rendertarget.removeAll(self._get_render_name("cam"))
        # draw rect for current screen
        displayed_area = self.view.get_displayed_area()
        minimap_corners_as_point = []
        for corner in displayed_area.get_corners():
            # check if the corners are outside of the screen
            corner = list(corner)
            if corner[0] > self.world.max_x:
                corner[0] = self.world.max_x
            if corner[0] < self.world.min_x:
                corner[0] = self.world.min_x
            if corner[1] > self.world.max_y:
                corner[1] = self.world.max_y
            if corner[1] < self.world.min_y:
                corner[1] = self.world.min_y
            corner = tuple(corner)

            coords = self._world_to_minimap(corner, use_rotation)
            minimap_corners_as_point.append(fife.Point(coords[0], coords[1]))

        for i in xrange(0, 4):
            self.minimap_image.rendertarget.addLine(
                self._get_render_name("cam"), minimap_corners_as_point[i],
                minimap_corners_as_point[(i + 1) % 4], *self.COLORS["cam"])

    @classmethod
    def update(cls, tup):
        for minimap in cls._instances:
            minimap._update(tup)

    def _update(self, tup):
        """Recalculate and redraw minimap for real world coord tup
		@param tup: (x, y)"""
        if self.world is None or not self.world.inited:
            return  # don't draw while loading
        minimap_point = self._world_to_minimap(tup,
                                               self._get_rotation_setting())
        world_to_minimap = self._world_to_minimap_ratio
        # TODO: remove this remnant of the old implementation, perhaps by refactoring recalculate()
        minimap_point = (
            minimap_point[0] + self.location.left,
            minimap_point[1] + self.location.top,
        )
        rect = Rect.init_from_topleft_and_size(
            minimap_point[0], minimap_point[1],
            int(round(1 / world_to_minimap[0])) + 1,
            int(round(1 / world_to_minimap[1])) + 1)
        self._recalculate(rect)

    def use_overlay_icon(self, icon):
        """Configures icon so that clicks get mapped here.
		The current gui requires, that the minimap is drawn behind an icon."""
        self.overlay_icon = icon
        icon.mapEvents({
            icon.name + '/mousePressed': self._on_click,
            icon.name + '/mouseDragged': self._on_drag,
            icon.name + '/mouseEntered': self._mouse_entered,
            icon.name + '/mouseMoved': self._mouse_moved,
            icon.name + '/mouseExited': self._mouse_exited,
        })

    def on_click(self, event, drag):
        """Handler for clicks (pressed and dragged)
		Scrolls screen to the point, where the cursor points to on the minimap.
		Overwrite this method to your convenience.
		"""
        if self.preview:
            return  # we don't do anything in this mode
        map_coords = event.map_coords
        moveable_selecteds = [
            i for i in self.session.selected_instances if i.movable
        ]
        if moveable_selecteds and event.getButton() == fife.MouseEvent.RIGHT:
            if drag:
                return
            for i in moveable_selecteds:
                Act(i, *map_coords).execute(self.session)
        elif event.getButton() == fife.MouseEvent.LEFT:
            if self.view is None:
                print "Warning: Can't handle minimap clicks since we have no view object"
            else:
                self.view.center(*map_coords)

    def _on_click(self, event):
        if self.world is not None:  # supply world coords if there is a world
            event.map_coords = self._get_event_coords(event)
            if event.map_coords:
                self.on_click(event, drag=False)
        else:
            self.on_click(event, drag=True)

    def _on_drag(self, event):
        if self.world is not None:  # supply world coords if there is a world
            event.map_coords = self._get_event_coords(event)
            if event.map_coords:
                self.on_click(event, drag=True)
        else:
            self.on_click(event, drag=True)

    def _get_event_coords(self, event):
        """Returns position of event as uh map coordinate tuple or None"""
        mouse_position = Point(event.getX(), event.getY())
        if not hasattr(self, "icon"):
            icon_pos = Point(*self.overlay_icon.getAbsolutePos())
            abs_mouse_position = icon_pos + mouse_position
            if not self.location.contains(abs_mouse_position):
                # mouse click was on icon but not actually on minimap
                return
            abs_mouse_position = abs_mouse_position.to_tuple()
        else:
            abs_mouse_position = mouse_position.to_tuple()
        if self._get_rotation_setting():
            abs_mouse_position = self._get_from_rotated_coords(
                abs_mouse_position)
        return self._minimap_coords_to_world_coords(abs_mouse_position)

    def _mouse_entered(self, event):
        self._show_tooltip(event)

    def _mouse_moved(self, event):
        self._show_tooltip(event)

    def _mouse_exited(self, event):
        if hasattr(self, "icon"):  # only supported for icon mode atm
            self.icon.hide_tooltip()

    def _show_tooltip(self, event):
        if hasattr(self, "icon"):  # only supported for icon mode atm
            if self.fixed_tooltip is not None:
                self.icon.helptext = self.fixed_tooltip
                self.icon.position_tooltip(event)
                #self.icon.show_tooltip()
            else:
                coords = self._get_event_coords(event)
                if not coords:  # no valid/relevant event location
                    self.icon.hide_tooltip()
                    return

                tile = self.world.get_tile(Point(*coords))
                if tile is not None and tile.settlement is not None:
                    new_helptext = tile.settlement.get_component(
                        NamedComponent).name
                    if self.icon.helptext != new_helptext:
                        self.icon.helptext = new_helptext
                        self.icon.show_tooltip()
                    else:
                        self.icon.position_tooltip(event)
                else:
                    # mouse not over relevant part of the minimap
                    self.icon.hide_tooltip()

    def highlight(self,
                  tup,
                  factor=1.0,
                  speed=1.0,
                  finish_callback=None,
                  color=(0, 0, 0)):
        """Try to get the users attention on a certain point of the minimap.
		@param tuple: world coords
		@param factor: float indicating importance of event
		@param speed: animation speed as factor
		@param finish_callback: executed when animation finishes
		@param color: color of anim, (r,g,b), r,g,b of [0,255]
		@return duration of full animation in seconds"""
        tup = self._world_to_minimap(tup, self._get_rotation_setting())

        # grow the circle from MIN_RAD to MAX_RAD and back with STEPS steps, where the
        # interval between steps is INTERVAL seconds
        MIN_RAD = int(3 * factor)  # pixel
        MAX_RAD = int(12 * factor)  # pixel
        STEPS = int(20 * factor)
        INTERVAL = (math.pi / 16) * factor

        def high(i=0):
            i += 1
            render_name = self._get_render_name("highlight") + str(tup)
            self.minimap_image.set_drawing_enabled()
            self.minimap_image.rendertarget.removeAll(render_name)
            if i > STEPS:
                if finish_callback:
                    finish_callback()
                return
            part = i  # grow bigger
            if i > STEPS // 2:  # after the first half
                part = STEPS - i  # become smaller

            radius = MIN_RAD + int(
                (float(part) / (STEPS // 2)) * (MAX_RAD - MIN_RAD))

            for x, y in Circle(Point(*tup),
                               radius=radius).get_border_coordinates():
                self.minimap_image.rendertarget.addPoint(
                    render_name, fife.Point(x, y), *color)

            ExtScheduler().add_new_object(lambda: high(i),
                                          self,
                                          INTERVAL,
                                          loops=1)

        high()
        return STEPS * INTERVAL

    def show_unit_path(self, unit):
        """Show the path a unit is moving along"""
        path = unit.path.path
        if path is None:  # show at least the position
            path = [unit.position.to_tuple()]

        # the path always contains the full path, the unit might be somewhere in it
        position_of_unit_in_path = 0
        unit_pos = unit.position.to_tuple()
        for i in xrange(len(path)):
            if path[i] == unit_pos:
                position_of_unit_in_path = i
                break

        # display units one ahead if possible, it looks nicer if the unit is moving
        if len(path) > 1 and position_of_unit_in_path + 1 < len(path):
            position_of_unit_in_path += 1  #
        path = path[position_of_unit_in_path:]

        # draw every step-th coord
        step = 1
        relevant_coords = [path[0]]
        for i in xrange(step, len(path), step):
            relevant_coords.append(path[i])
        relevant_coords.append(path[-1])

        # get coords, actual drawing
        use_rotation = self._get_rotation_setting()
        self.minimap_image.set_drawing_enabled()
        p = fife.Point(0, 0)
        render_name = self._get_render_name("ship_route") + str(
            self.__class__.__ship_route_counter.next())
        color = unit.owner.color.to_tuple()
        last_coord = None
        for i in relevant_coords:
            coord = self._world_to_minimap(i, use_rotation)
            if last_coord is not None and \
               sum( abs(last_coord[i] - coord[i]) for i in (0, 1) ) < 2: # 2 is min dist in pixels
                continue
            last_coord = coord
            p.x = coord[0]
            p.y = coord[1]
            self.minimap_image.rendertarget.addPoint(render_name, p, *color)

        def cleanup():
            self.minimap_image.set_drawing_enabled()
            self.minimap_image.rendertarget.removeAll(render_name)

        self.highlight(path[-1],
                       factor=0.4,
                       speed=((1.0 + math.sqrt(5) / 2)),
                       finish_callback=cleanup,
                       color=color)

        return True

    def _recalculate(self, where=None, dump_data=False):
        """Calculate which pixel of the minimap should display what and draw it
		@param where: Rect of minimap coords. Defaults to self.location
		@param dump_data: Don't draw but return calculated data"""
        self.minimap_image.set_drawing_enabled()

        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")

        if where is None:
            where = self.location
            rt.removeAll(render_name)

        # calculate which area of the real map is mapped to which pixel on the minimap
        pixel_per_coord_x, pixel_per_coord_y = self._world_to_minimap_ratio

        # calculate values here so we don't have to do it in the loop
        pixel_per_coord_x_half_as_int = int(pixel_per_coord_x / 2)
        pixel_per_coord_y_half_as_int = int(pixel_per_coord_y / 2)

        world_min_x = self.world.min_x
        world_min_y = self.world.min_y
        island_col = self.COLORS["island"]
        water_col = self.COLORS["water"]
        location_left = self.location.left
        location_top = self.location.top
        if dump_data:
            data = []
            drawPoint = lambda name, fife_point, r, g, b: data.append(
                (fife_point.x, fife_point.y, r, g, b))
        else:
            drawPoint = rt.addPoint
        fife_point = fife.Point(0, 0)

        use_rotation = self._get_rotation_setting()
        full_map = self.world.full_map

        # loop through map coordinates, assuming (0, 0) is the origin of the minimap
        # this faciliates calculating the real world coords
        for x in xrange(where.left - self.location.left,
                        where.left + where.width - self.location.left):
            for y in xrange(where.top - self.location.top,
                            where.top + where.height - self.location.top):
                """
				This code should be here, but since python can't do inlining, we have to inline
				ourselves for performance reasons
				covered_area = Rect.init_from_topleft_and_size(
				  int(x * pixel_per_coord_x)+world_min_x, \
				  int(y * pixel_per_coord_y)+world_min_y), \
				  int(pixel_per_coord_x), int(pixel_per_coord_y))
				real_map_point = covered_area.center
				"""
                # use center of the rect that the pixel covers
                real_map_x = int(
                    x * pixel_per_coord_x
                ) + world_min_x + pixel_per_coord_x_half_as_int
                real_map_y = int(
                    y * pixel_per_coord_y
                ) + world_min_y + pixel_per_coord_y_half_as_int
                real_map_coords = (real_map_x, real_map_y)

                # check what's at the covered_area
                if real_map_coords in full_map:
                    # this pixel is an island
                    tile = full_map[real_map_coords]
                    settlement = tile.settlement
                    if settlement is None:
                        # island without settlement
                        if tile.id <= 0:
                            color = water_col
                        else:
                            color = island_col
                    else:
                        # pixel belongs to a player
                        color = settlement.owner.color.to_tuple()
                else:
                    color = water_col

                if use_rotation:
                    # inlined _get_rotated_coords
                    rot_x, rot_y = self._rotate(
                        (location_left + x, location_top + y), self._rotations)
                    fife_point.set(rot_x - location_left, rot_y - location_top)
                else:
                    fife_point.set(x, y)

                drawPoint(render_name, fife_point, *color)

        if dump_data:
            return json.dumps(data)

    def _timed_update(self, force=False):
        """Regular updates for domains we can't or don't want to keep track of."""
        # OPTIMISATION NOTE: there can be pretty many ships, don't rely on the loop being rarely executed
        # update ship icons
        self.minimap_image.set_drawing_enabled()
        render_name = self._get_render_name("ship")
        self.minimap_image.rendertarget.removeAll(render_name)
        use_rotation = self._get_rotation_setting()
        # make use of this dummy points instead of creating a fife.point instances which are consuming a lot of resources
        dummy_point0 = fife.Point(0, 0)
        dummy_point1 = fife.Point(0, 0)
        for ship in self.world.ships:
            if not ship.in_ship_map:
                continue  # no fisher ships, etc
            coord = self._world_to_minimap(ship.position.to_tuple(),
                                           use_rotation)
            color = ship.owner.color.to_tuple()
            # set correct icon
            if ship.owner is self.session.world.pirate:
                ship_icon_path = self.__class__.SHIP_PIRATE
            else:
                ship_icon_path = self.__class__.SHIP_NEUTRAL
            ship_icon = self.imagemanager.load(ship_icon_path)
            dummy_point1.set(coord[0], coord[1])
            self.minimap_image.rendertarget.addImage(render_name, dummy_point1,
                                                     ship_icon)
            if ship.owner.regular_player is True:
                # add the 'flag' over the ship icon, with the color of the owner
                dummy_point0.set(coord[0] - 5, coord[1] - 5)
                dummy_point1.set(coord[0], coord[1] - 5)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                dummy_point0.set(coord[0] - 6, coord[1] - 6)
                dummy_point1.set(coord[0], coord[1] - 6)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                dummy_point0.set(coord[0] - 4, coord[1] - 4)
                dummy_point1.set(coord[0], coord[1] - 4)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                # add black border around the flag
                dummy_point0.set(coord[0] - 6, coord[1] - 7)
                dummy_point1.set(coord[0], coord[1] - 7)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)
                dummy_point0.set(coord[0] - 4, coord[1] - 3)
                dummy_point1.set(coord[0], coord[1] - 4)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)
                dummy_point0.set(coord[0] - 6, coord[1] - 7)
                dummy_point1.set(coord[0] - 4, coord[1] - 3)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)

            # TODO: nicer selected view
            dummy_point0.set(coord[0], coord[1])
            if ship in self.session.selected_instances:
                self.minimap_image.rendertarget.addPoint(
                    render_name, dummy_point0, *Minimap.COLORS["water"])
                for x_off, y_off in ((-2, 0), (+2, 0), (0, -2), (0, +2)):
                    dummy_point1.set(coord[0] + x_off, coord[1] + y_off)
                    self.minimap_image.rendertarget.addPoint(
                        render_name, dummy_point1, *color)

        # draw settlement warehouses if something has changed
        settlements = self.world.settlements
        # save only worldids as to not introduce actual coupling
        cur_settlements = set(i.worldid for i in settlements)
        if force or \
           (not hasattr(self, "_last_settlements") or cur_settlements != self._last_settlements):
            # update necessary
            warehouse_render_name = self._get_render_name("warehouse")
            self.minimap_image.rendertarget.removeAll(warehouse_render_name)
            for settlement in settlements:
                coord = settlement.warehouse.position.center.to_tuple()
                coord = self._world_to_minimap(coord, use_rotation)
                self._update_image(self.__class__.WAREHOUSE_IMAGE,
                                   warehouse_render_name, coord)
            self._last_settlements = cur_settlements

    def _update_image(self, img_path, name, coord_tuple):
        """Updates image as part of minimap (e.g. when it has moved)"""
        img = self.imagemanager.load(img_path)

        size_tuple = self._image_size_cache.get(img_path)
        if size_tuple is None:
            ratio = sum(self._world_to_minimap_ratio) / 2.0
            ratio = max(1.0, ratio)
            size_tuple = int(img.getWidth() / ratio), int(img.getHeight() /
                                                          ratio)
            self._image_size_cache[img_path] = size_tuple
        new_width, new_height = size_tuple
        p = self.__class__._dummy_fife_point
        p.set(*coord_tuple)
        # resizeImage also means draw
        self.minimap_image.rendertarget.resizeImage(name, p, img, new_width,
                                                    new_height)

    def rotate_right(self):
        # keep track of rotation at any time, but only apply
        # if it's actually used
        self.rotation -= 1
        self.rotation %= 4
        if self._get_rotation_setting():
            self.draw()

    def rotate_left(self):
        # see above
        self.rotation += 1
        self.rotation %= 4
        if self._get_rotation_setting():
            self.draw()

    ## CALC UTILITY
    def _world_to_minimap(self, coords, use_rotation):
        """Complete coord transformation, batteries included.
		The methods below are for more specialised purposes."""
        coords = self._world_coords_to_minimap_coords(coords)

        if use_rotation:
            coords = self._get_rotated_coords(coords)
        # transform from screen coords to minimap coords
        coords = (coords[0] - self.location.left,
                  coords[1] - self.location.top)
        return coords

    def _get_rotation_setting(self):
        if not self.use_rotation:
            return False
        return self._rotation_setting

    def _on_rotation_setting_change(self, message):
        self._rotation_setting = horizons.globals.fife.get_uh_setting(
            "MinimapRotation")
        self.draw()

    _rotations = {0: 0, 1: 3 * math.pi / 2, 2: math.pi, 3: math.pi / 2}

    def _get_rotated_coords(self, tup):
        """Rotates according to current rotation settings.
		Input coord must be relative to screen origin, not minimap origin"""
        return self._rotate(tup, self._rotations)

    _from_rotations = {0: 0, 1: math.pi / 2, 2: math.pi, 3: 3 * math.pi / 2}

    def _get_from_rotated_coords(self, tup):
        return self._rotate(tup, self._from_rotations)

    def _rotate(self, tup, rotations):
        rotation = rotations[self.rotation]

        x = tup[0]
        y = tup[1]

        # rotate around center of minimap
        x -= self.location_center.x
        y -= self.location_center.y

        new_x = x * cos(rotation) - y * sin(rotation)
        new_y = x * sin(rotation) + y * cos(rotation)

        new_x += self.location_center.x
        new_y += self.location_center.y

        new_x = int(round(new_x))
        new_y = int(round(new_y))

        #some points may get out of range
        new_x = max(self.location.left, new_x)
        new_x = min(self.location.right, new_x)
        new_y = max(self.location.top, new_y)
        new_y = min(self.location.bottom, new_y)

        return (new_x, new_y)

    def _update_world_to_minimap_ratio(self):
        world_height = self.world.map_dimensions.height
        world_width = self.world.map_dimensions.width
        minimap_height = self.location.height
        minimap_width = self.location.width
        pixel_per_coord_x = float(world_width) / minimap_width
        pixel_per_coord_y = float(world_height) / minimap_height
        self._world_to_minimap_ratio = (pixel_per_coord_x, pixel_per_coord_y)

    def _world_coords_to_minimap_coords(self, tup):
        """Calculates which pixel in the minimap contains a coord in the real map.
		@param tup: (x, y) as ints
		@return tuple"""
        pixel_per_coord_x, pixel_per_coord_y = self._world_to_minimap_ratio
        return (
            int(round(float(tup[0] - self.world.min_x) / pixel_per_coord_x)) +
            self.location.left,
            int(round(float(tup[1] - self.world.min_y) / pixel_per_coord_y)) +
            self.location.top)

    def _minimap_coords_to_world_coords(self, tup):
        """Inverse to _world_coords_to_minimap_coords"""
        pixel_per_coord_x, pixel_per_coord_y = self._world_to_minimap_ratio
        return (int(round((tup[0] - self.location.left) * pixel_per_coord_x)) +
                self.world.min_x,
                int(round((tup[1] - self.location.top) * pixel_per_coord_y)) +
                self.world.min_y)

    def get_size(self):
        return (self.location.height, self.location.width)
Example #22
0
    def __create_render_node(self, agent=None, layer=None, location=None,
                             point=None):
        """Creatss a fife.RendererNode.

        Arguments:
            agent: The name of the agent the light should be attached too. If
            empty or None this will be ignored. Please note that the layer and
            location have to be set if this is empty or None.

            layer: The name of the layer the light originates from. Lights will
            illuminate lower layers, but not higher ones. If empty or None this
            will be ignored.

            location: The relative or absolute location of the light depending
            on whether the agent was set or not. A list with two or three
            values.
            If None this will be ignored.

            point: The relative or absolute window position of the light as
            a list with 2 values or a fife.Point.
            This differs from location as it is in pixels and (0, 0) is the
            upper left position of the window.
        """
        if agent is not None and agent != "":
            entity = self.__application.world.get_entity(agent)
            if entity in self.entities:
                fifeagent = getattr(entity, FifeAgent.registered_as)
                agent = fifeagent.instance
            else:
                raise TypeError("The map %s has no entity %s" % (self.name,
                                                                 agent))
        else:
            agent = None
        if layer is not None and layer != "":
            map_layer = self.get_layer(layer)
            if map_layer is None:
                raise TypeError("No such layer: %s" % (layer))
            layer = map_layer
        elif agent is not None:
            layer = fifeagent.layer
        else:
            layer = None
        if location:
            if layer is not None:
                coords = fife.DoublePoint3D(*location)
                location = fife.Location()
                location.setLayer(layer)
                location.setMapCoordinates(coords)
            else:
                raise TypeError(
                    "The location was set, but not agent or layer.")
        else:
            location = None
        if point is not None and not isinstance(point, fife.Point):
            point = fife.Point(*point)
        arguments = []
        if agent is not None:
            arguments.append(agent)
        if location is not None:
            arguments.append(location)
        if layer is not None:
            arguments.append(layer)
            self.get_light_renderer().addActiveLayer(layer)
        if point is not None:
            arguments.append(point)
        if not arguments:
            raise TypeError("A light needs either an agent"
                            ", a location and a layer"
                            ", or a point")
        node = fife.RendererNode(*arguments)
        return node
Example #23
0
    def _timed_update(self, force=False):
        """Regular updates for domains we can't or don't want to keep track of."""
        # OPTIMIZATION NOTE: There can be pretty many ships.
        # Don't rely on the loop being rarely executed!
        # update ship icons
        self.minimap_image.set_drawing_enabled()
        render_name = self._get_render_name("ship")
        self.minimap_image.rendertarget.removeAll(render_name)
        # Make use of these dummy points instead of creating fife.Point instances
        # (which are consuming a lot of resources).
        dummy_point0 = fife.Point(0, 0)
        dummy_point1 = fife.Point(0, 0)
        for ship in self.world.ships:
            if not ship.in_ship_map:
                continue  # no fisher ships, etc
            coord = self.transform.world_to_minimap(ship.position.to_tuple())
            color = ship.owner.color.to_tuple()
            # set correct icon
            if ship.owner is self.session.world.pirate:
                ship_icon_path = self.__class__.SHIP_PIRATE
            else:
                ship_icon_path = self.__class__.SHIP_NEUTRAL
            ship_icon = self.imagemanager.load(ship_icon_path)
            dummy_point1.set(coord[0], coord[1])
            self.minimap_image.rendertarget.addImage(render_name, dummy_point1,
                                                     ship_icon)
            if ship.owner.regular_player:
                # add the 'flag' over the ship icon, with the color of the owner
                dummy_point0.set(coord[0] - 5, coord[1] - 5)
                dummy_point1.set(coord[0], coord[1] - 5)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                dummy_point0.set(coord[0] - 6, coord[1] - 6)
                dummy_point1.set(coord[0], coord[1] - 6)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                dummy_point0.set(coord[0] - 4, coord[1] - 4)
                dummy_point1.set(coord[0], coord[1] - 4)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                # add black border around the flag
                dummy_point0.set(coord[0] - 6, coord[1] - 7)
                dummy_point1.set(coord[0], coord[1] - 7)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)
                dummy_point0.set(coord[0] - 4, coord[1] - 3)
                dummy_point1.set(coord[0], coord[1] - 4)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)
                dummy_point0.set(coord[0] - 6, coord[1] - 7)
                dummy_point1.set(coord[0] - 4, coord[1] - 3)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)

            # TODO: nicer selected view
            dummy_point0.set(coord[0], coord[1])
            draw_point = self.minimap_image.rendertarget.addPoint
            if ship in self.session.selected_instances:
                draw_point(render_name, dummy_point0, *Minimap.COLORS["water"])
                for x_off, y_off in ((-2, 0), (+2, 0), (0, -2), (0, +2)):
                    dummy_point1.set(coord[0] + x_off, coord[1] + y_off)
                    draw_point(render_name, dummy_point1, *color)

        # draw settlement warehouses if something has changed
        settlements = self.world.settlements
        # save only worldids as to not introduce actual coupling
        cur_settlements = set(i.worldid for i in settlements)
        if force or \
           (not hasattr(self, "_last_settlements") or cur_settlements != self._last_settlements):
            # update necessary
            warehouse_render_name = self._get_render_name("warehouse")
            self.minimap_image.rendertarget.removeAll(warehouse_render_name)
            for settlement in settlements:
                coord = settlement.warehouse.position.center.to_tuple()
                coord = self.transform.world_to_minimap(coord)
                self._update_image(self.__class__.WAREHOUSE_IMAGE,
                                   warehouse_render_name, coord)
            self._last_settlements = cur_settlements
Example #24
0
    def _recalculate(self, where=None, dump_data=False):
        """Calculate which pixel of the minimap should display what and draw it
		@param where: Rect of minimap coords. Defaults to self.location
		@param dump_data: Don't draw but return calculated data"""
        self.minimap_image.set_drawing_enabled()

        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")

        if where is None:
            where = self.location
            rt.removeAll(render_name)

        # calculate which area of the real map is mapped to which pixel on the minimap
        pixel_per_coord_x, pixel_per_coord_y = self._world_to_minimap_ratio

        # calculate values here so we don't have to do it in the loop
        pixel_per_coord_x_half_as_int = int(pixel_per_coord_x / 2)
        pixel_per_coord_y_half_as_int = int(pixel_per_coord_y / 2)

        world_min_x = self.world.min_x
        world_min_y = self.world.min_y
        island_col = self.COLORS["island"]
        water_col = self.COLORS["water"]
        location_left = self.location.left
        location_top = self.location.top
        if dump_data:
            data = []
            drawPoint = lambda name, fife_point, r, g, b: data.append(
                (fife_point.x, fife_point.y, r, g, b))
        else:
            drawPoint = rt.addPoint
        fife_point = fife.Point(0, 0)

        use_rotation = self._get_rotation_setting()
        full_map = self.world.full_map

        # loop through map coordinates, assuming (0, 0) is the origin of the minimap
        # this faciliates calculating the real world coords
        for x in xrange(where.left - self.location.left,
                        where.left + where.width - self.location.left):
            for y in xrange(where.top - self.location.top,
                            where.top + where.height - self.location.top):
                """
				This code should be here, but since python can't do inlining, we have to inline
				ourselves for performance reasons
				covered_area = Rect.init_from_topleft_and_size(
				  int(x * pixel_per_coord_x)+world_min_x, \
				  int(y * pixel_per_coord_y)+world_min_y), \
				  int(pixel_per_coord_x), int(pixel_per_coord_y))
				real_map_point = covered_area.center
				"""
                # use center of the rect that the pixel covers
                real_map_x = int(
                    x * pixel_per_coord_x
                ) + world_min_x + pixel_per_coord_x_half_as_int
                real_map_y = int(
                    y * pixel_per_coord_y
                ) + world_min_y + pixel_per_coord_y_half_as_int
                real_map_coords = (real_map_x, real_map_y)

                # check what's at the covered_area
                if real_map_coords in full_map:
                    # this pixel is an island
                    tile = full_map[real_map_coords]
                    settlement = tile.settlement
                    if settlement is None:
                        # island without settlement
                        if tile.id <= 0:
                            color = water_col
                        else:
                            color = island_col
                    else:
                        # pixel belongs to a player
                        color = settlement.owner.color.to_tuple()
                else:
                    color = water_col

                if use_rotation:
                    # inlined _get_rotated_coords
                    rot_x, rot_y = self._rotate(
                        (location_left + x, location_top + y), self._rotations)
                    fife_point.set(rot_x - location_left, rot_y - location_top)
                else:
                    fife_point.set(x, y)

                drawPoint(render_name, fife_point, *color)

        if dump_data:
            return json.dumps(data)
Example #25
0
class Minimap:
    """A basic minimap.

	USAGE:
	Minimap can be drawn via GenericRenderer on an arbitrary position (determined by rect in ctor)
	or
	via Pychan Icon. In this case, the rect parameter only determines the size, the
	Minimap will scroll by default on clicks, overwrite on_click if you don't want that.

	TODO:
	* Remove renderer when used in icon node
	* Clear up distinction of coords where the minimap image or screen is the origin
	* Create a minimap tag for pychan
	** Handle clicks, remove overlay icon
	"""
    COLORS = {
        "island": (137, 117, 87),
        "cam": (1, 1, 1),
        "water": (198, 188, 165),
        "highlight": (255, 0, 0),  # for events
    }

    WAREHOUSE_IMAGE = "content/gui/icons/minimap/warehouse.png"
    SHIP_NEUTRAL = "content/gui/icons/minimap/ship_neutral.png"
    SHIP_PIRATE = "content/gui/icons/minimap/pirate.png"
    GROUND_UNIT_IMAGE = "content/gui/icons/minimap/groundunit.png"

    SHIP_DOT_UPDATE_INTERVAL = 0.5  # seconds

    # Alpha-ordering determines the order:
    RENDER_NAMES = {
        "background": "c",
        "base": "d",  # islands, etc.
        "warehouse": "e",
        "ship": "f",
        "cam": "g",
        "ship_route": "h",
        "highlight": "l",
    }

    __minimap_id_counter = itertools.count()
    __ship_route_counter = itertools.count()
    # all active instances
    _instances = []  # type: List[Minimap]

    _dummy_fife_point = fife.Point(
        0, 0)  # use when you quickly need a temporary point

    def __init__(self,
                 position,
                 session,
                 view,
                 targetrenderer,
                 imagemanager,
                 renderer=None,
                 world=None,
                 cam_border=True,
                 use_rotation=True,
                 on_click=None,
                 preview=False,
                 tooltip=None):
        """
		@param position: a Rect or a Pychan Icon, where we will draw to
		@param world: World object or fake thereof
		@param view: View object for cam control. Can be None to disable this
		@param renderer: renderer to be used if position isn't an icon
		@param targetrenderer: fife target renderer for drawing on icons
		@param imagemanager: fife imagemanager for drawing on icons
		@param cam_border: boolean, whether to draw the cam border
		@param use_rotation: boolean, whether to use rotation (it must also be enabled in the settings)
		@param on_click: function taking 1 argument or None for scrolling
		@param preview: flag, whether to only show the map as preview
		@param tooltip: always show this tooltip when cursor hovers over minimap

		NOTE: Preview generation in a different process overwrites this method.
		"""
        if isinstance(position, Rect):
            self.location = position
            self.renderer = renderer
        else:  # assume icon
            self.location = Rect.init_from_topleft_and_size(
                0, 0, position.width, position.height)
            self.icon = position
            self.use_overlay_icon(self.icon)

        # FIXME PY3 width / height of icon is sometimes zero. Why?
        if self.location.height == 0 or self.location.width == 0:
            self.location = Rect.init_from_topleft_and_size(0, 0, 128, 128)

        self.session = session
        self.world = world
        self.view = view
        self.fixed_tooltip = tooltip

        self.click_handler = on_click if on_click is not None else self.default_on_click

        self.cam_border = cam_border
        self.use_rotation = use_rotation
        self.preview = preview

        self.location_center = self.location.center

        self._id = str(next(self.__class__.__minimap_id_counter)
                       )  # internal identifier, used for allocating resources

        self._image_size_cache = {}  # internal detail

        self.imagemanager = imagemanager

        self.minimap_image = _MinimapImage(self, targetrenderer)

        self._rotation_setting = horizons.globals.fife.get_uh_setting(
            "MinimapRotation")
        if self.use_rotation:
            SettingChanged.subscribe(self._on_setting_changed)

        self.transform = None

    def end(self):
        self.disable()
        self.world = None
        self.session = None
        self.renderer = None
        if self.use_rotation:
            SettingChanged.unsubscribe(self._on_setting_changed)

    def disable(self):
        """Due to the way the minimap works, there isn't really a show/hide,
		but you can disable it with this and enable again with draw().
		Stops all updates."""
        ExtScheduler().rem_all_classinst_calls(self)
        if self.view is not None:
            self.view.discard_change_listener(self.update_cam)

        if self in self.__class__._instances:
            self.__class__._instances.remove(self)

    def draw(self):
        """Recalculates and draws the whole minimap of self.session.world or world.
		The world you specified is reused for every operation until the next draw().
		"""
        if self.world is None and self.session.world is not None:
            self.world = self.session.world  # in case minimap has been constructed before the world
        if not self.world.inited:
            return  # don't draw while loading
        if self.transform is None:
            self.transform = _MinimapTransform(self.world.map_dimensions,
                                               self.location, 0,
                                               self._get_rotation_setting())
            self.update_rotation()

        self.__class__._instances.append(self)

        # update cam when view updates
        if self.view is not None and not self.view.has_change_listener(
                self.update_cam):
            self.view.add_change_listener(self.update_cam)

        if not hasattr(self, "icon"):
            # add to global generic renderer with id specific to this instance
            self.renderer.removeAll("minimap_image" + self._id)
            self.minimap_image.reset()
            # NOTE: this is for the generic renderer interface, the offrenderer has slightly different methods
            node = fife.RendererNode(
                fife.Point(self.location.center.x, self.location.center.y))
            self.renderer.addImage("minimap_image" + self._id, node,
                                   self.minimap_image.image, False)

        else:
            # attach image to pychan icon (recommended)
            self.minimap_image.reset()
            self.icon.image = fife.GuiImage(self.minimap_image.image)

        self.update_cam()
        self._recalculate()
        if not self.preview:
            self._timed_update(force=True)
            ExtScheduler().rem_all_classinst_calls(self)
            ExtScheduler().add_new_object(self._timed_update, self,
                                          self.SHIP_DOT_UPDATE_INTERVAL, -1)

    def draw_data(self, data):
        """Display data from dump_data"""
        # only icon mode for now
        self.minimap_image.reset()
        self.icon.image = fife.GuiImage(self.minimap_image.image)

        self.minimap_image.set_drawing_enabled()
        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")
        draw_point = rt.addPoint
        point = fife.Point()

        for x, y, r, g, b in data:
            point.set(x, y)
            draw_point(render_name, point, r, g, b)

    def _get_render_name(self, key):
        return self.RENDER_NAMES[key] + self._id

    def update_cam(self):
        """Redraw camera border."""
        if not self.cam_border or self.view is None:  # needs view
            return
        if self.world is None or not self.world.inited:
            return  # don't draw while loading
        self.minimap_image.set_drawing_enabled()
        self.minimap_image.rendertarget.removeAll(self._get_render_name("cam"))
        # draw rect for current screen
        displayed_area = self.view.get_displayed_area()
        minimap_corners_as_point = []
        for (x, y) in displayed_area:
            coords = self.transform.world_to_minimap((x, y))
            minimap_corners_as_point.append(fife.Point(coords[0], coords[1]))

        for i in range(0, 4):
            self.minimap_image.rendertarget.addLine(
                self._get_render_name("cam"), minimap_corners_as_point[i],
                minimap_corners_as_point[(i + 1) % 4], *self.COLORS["cam"])

    @classmethod
    def update(cls, tup):
        for minimap in cls._instances:
            minimap._update(tup)

    def _update(self, tup):
        """Recalculate and redraw minimap for real world coord tup
		@param tup: (x, y)"""
        if self.world is None or not self.world.inited:
            return  # don't draw while loading
        minimap_point = self.transform.world_to_minimap(tup)

        self._recalculate(minimap_point)

    def use_overlay_icon(self, icon):
        """Configures icon so that clicks get mapped here.
		The current gui requires, that the minimap is drawn behind an icon."""
        self.overlay_icon = icon
        icon.mapEvents({
            icon.name + '/mousePressed': self._on_click,
            icon.name + '/mouseDragged': self._on_drag,
            icon.name + '/mouseEntered': self._mouse_entered,
            icon.name + '/mouseMoved': self._mouse_moved,
            icon.name + '/mouseExited': self._mouse_exited,
        })

    def default_on_click(self, event, drag):
        """Handler for clicks (pressed and dragged)
		Scrolls screen to the point, where the cursor points to on the minimap.
		Overwrite this method to your convenience.
		"""
        if self.preview:
            return  # we don't do anything in this mode
        button = event.getButton()
        map_coords = event.map_coords
        if button == fife.MouseEvent.RIGHT:
            if drag:
                return
            for i in self.session.selected_instances:
                if i.movable:
                    Act(i, *map_coords).execute(self.session)
        elif button == fife.MouseEvent.LEFT:
            if self.view is None:
                print(
                    "Warning: Can't handle minimap clicks since we have no view object"
                )
            else:
                self.view.center(*map_coords)

    def _on_click(self, event):
        if self.world is not None:  # supply world coords if there is a world
            event.map_coords = self._get_event_coords(event)
            if event.map_coords:
                self.click_handler(event, drag=False)
        else:
            self.click_handler(event, drag=True)

    def _on_drag(self, event):
        if self.world is not None:  # supply world coords if there is a world
            event.map_coords = self._get_event_coords(event)
            if event.map_coords:
                self.click_handler(event, drag=True)
        else:
            self.click_handler(event, drag=True)

    def _get_event_coords(self, event):
        """Returns position of event as uh map coordinate tuple or None"""
        mouse_position = Point(event.getX(), event.getY())
        if not hasattr(self, "icon"):
            icon_pos = Point(*self.overlay_icon.getAbsolutePos())
            abs_mouse_position = icon_pos + mouse_position
            if not self.location.contains(abs_mouse_position):
                # mouse click was on icon but not actually on minimap
                return None
        return self.transform.minimap_to_world((event.getX(), event.getY()))

    def _mouse_entered(self, event):
        self._show_tooltip(event)

    def _mouse_moved(self, event):
        self._show_tooltip(event)

    def _mouse_exited(self, event):
        if hasattr(self, "icon"):  # only supported for icon mode atm
            self.icon.hide_tooltip()

    def _show_tooltip(self, event):
        if not hasattr(self, "icon"):
            # only supported for icon mode atm
            return
        if self.fixed_tooltip is not None:
            self.icon.helptext = self.fixed_tooltip
            self.icon.position_tooltip(event)
            #self.icon.show_tooltip()
        else:
            coords = self._get_event_coords(event)
            if not coords:  # no valid/relevant event location
                self.icon.hide_tooltip()
                return

            tile = self.world.get_tile(Point(*coords))
            if tile is not None and tile.settlement is not None:
                new_helptext = tile.settlement.get_component(
                    NamedComponent).name
                if self.icon.helptext != new_helptext:
                    self.icon.helptext = new_helptext
                    self.icon.show_tooltip()
                else:
                    self.icon.position_tooltip(event)
            else:
                # mouse not over relevant part of the minimap
                self.icon.hide_tooltip()

    def highlight(self,
                  tup,
                  factor=1.0,
                  speed=1.0,
                  finish_callback=None,
                  color=(0, 0, 0)):
        """Try to get the users attention on a certain point of the minimap.
		@param tup: world coords
		@param factor: float indicating importance of event
		@param speed: animation speed as factor
		@param finish_callback: executed when animation finishes
		@param color: color of anim, (r,g,b), r,g,b of [0,255]
		@return duration of full animation in seconds"""
        tup = self.transform.world_to_minimap(tup)

        # grow the circle from MIN_RAD to MAX_RAD and back with STEPS steps, where the
        # interval between steps is INTERVAL seconds
        MIN_RAD = int(3 * factor)  # pixel
        MAX_RAD = int(12 * factor)  # pixel
        STEPS = int(20 * factor)
        INTERVAL = (math.pi / 16) * factor

        def high(i=0):
            i += 1
            render_name = self._get_render_name("highlight") + str(tup)
            self.minimap_image.set_drawing_enabled()
            self.minimap_image.rendertarget.removeAll(render_name)
            if i > STEPS:
                if finish_callback:
                    finish_callback()
                return
            part = i  # grow bigger
            if i > STEPS // 2:  # after the first half
                part = STEPS - i  # become smaller

            radius = MIN_RAD + int(
                (float(part) / (STEPS // 2)) * (MAX_RAD - MIN_RAD))

            draw_point = self.minimap_image.rendertarget.addPoint
            for x, y in Circle(Point(*tup),
                               radius=radius).get_border_coordinates():
                draw_point(render_name, fife.Point(x, y), *color)

            ExtScheduler().add_new_object(lambda: high(i),
                                          self,
                                          INTERVAL,
                                          loops=1)

        high()
        return STEPS * INTERVAL

    def show_unit_path(self, unit):
        """Show the path a unit is moving along"""
        path = unit.path.path
        if path is None:  # show at least the position
            path = [unit.position.to_tuple()]

        # the path always contains the full path, the unit might be somewhere in it
        position_of_unit_in_path = 0
        unit_pos = unit.position.to_tuple()
        for i, pos in enumerate(path):
            if pos == unit_pos:
                position_of_unit_in_path = i
                break

        # display units one ahead if possible, it looks nicer if the unit is moving
        if len(path) > 1 and position_of_unit_in_path + 1 < len(path):
            position_of_unit_in_path += 1
        path = path[position_of_unit_in_path:]

        # draw every step-th coord
        step = 1
        relevant_coords = [path[0]]
        for i in range(step, len(path), step):
            relevant_coords.append(path[i])
        relevant_coords.append(path[-1])

        # get coords, actual drawing
        self.minimap_image.set_drawing_enabled()
        p = fife.Point(0, 0)
        render_name = self._get_render_name("ship_route") + str(
            next(self.__class__.__ship_route_counter))
        color = unit.owner.color.to_tuple()
        last_coord = None
        draw_point = self.minimap_image.rendertarget.addPoint
        for i in relevant_coords:
            coord = self.transform.world_to_minimap(i)
            if last_coord is not None and \
               sum(abs(last_coord[i] - coord[i]) for i in (0, 1)) < 2:  # 2 is min dist in pixels
                continue
            last_coord = coord
            p.x = coord[0]
            p.y = coord[1]
            draw_point(render_name, p, *color)

        def cleanup():
            self.minimap_image.set_drawing_enabled()
            self.minimap_image.rendertarget.removeAll(render_name)

        speed = 1.0 + math.sqrt(5) / 2
        self.highlight(path[-1],
                       factor=0.4,
                       speed=speed,
                       finish_callback=cleanup,
                       color=color)

        return True

    def _recalculate(self, where=None):
        """Calculate which pixel of the minimap should display what and draw it
		@param where: minimap coords. If this is given only that pixel will be redrawn
		"""
        self.minimap_image.set_drawing_enabled()

        rt = self.minimap_image.rendertarget
        render_name = self._get_render_name("base")

        if where is None:
            rt.removeAll(render_name)
            points = self.transform.iter_points()
        else:
            points = [where]

        # Is this really worth it?
        draw_point = rt.addPoint

        fife_point = fife.Point(0, 0)
        island_color = self.COLORS["island"]
        water_color = self.COLORS["water"]

        for (x, y) in points:

            world_coords = self.transform.minimap_to_world((x, y))
            color = get_minimap_color(world_coords, self.world, island_color,
                                      water_color)
            fife_point.set(x, y)
            draw_point(render_name, fife_point, *color)

    def _timed_update(self, force=False):
        """Regular updates for domains we can't or don't want to keep track of."""
        # OPTIMIZATION NOTE: There can be pretty many ships.
        # Don't rely on the loop being rarely executed!
        # update ship icons
        self.minimap_image.set_drawing_enabled()
        render_name = self._get_render_name("ship")
        self.minimap_image.rendertarget.removeAll(render_name)
        # Make use of these dummy points instead of creating fife.Point instances
        # (which are consuming a lot of resources).
        dummy_point0 = fife.Point(0, 0)
        dummy_point1 = fife.Point(0, 0)
        for ship in self.world.ships:
            if not ship.in_ship_map:
                continue  # no fisher ships, etc
            coord = self.transform.world_to_minimap(ship.position.to_tuple())
            color = ship.owner.color.to_tuple()
            # set correct icon
            if ship.owner is self.session.world.pirate:
                ship_icon_path = self.__class__.SHIP_PIRATE
            else:
                ship_icon_path = self.__class__.SHIP_NEUTRAL
            ship_icon = self.imagemanager.load(ship_icon_path)
            dummy_point1.set(coord[0], coord[1])
            self.minimap_image.rendertarget.addImage(render_name, dummy_point1,
                                                     ship_icon)
            if ship.owner.regular_player:
                # add the 'flag' over the ship icon, with the color of the owner
                dummy_point0.set(coord[0] - 5, coord[1] - 5)
                dummy_point1.set(coord[0], coord[1] - 5)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                dummy_point0.set(coord[0] - 6, coord[1] - 6)
                dummy_point1.set(coord[0], coord[1] - 6)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                dummy_point0.set(coord[0] - 4, coord[1] - 4)
                dummy_point1.set(coord[0], coord[1] - 4)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, color[0],
                    color[1], color[2])
                # add black border around the flag
                dummy_point0.set(coord[0] - 6, coord[1] - 7)
                dummy_point1.set(coord[0], coord[1] - 7)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)
                dummy_point0.set(coord[0] - 4, coord[1] - 3)
                dummy_point1.set(coord[0], coord[1] - 4)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)
                dummy_point0.set(coord[0] - 6, coord[1] - 7)
                dummy_point1.set(coord[0] - 4, coord[1] - 3)
                self.minimap_image.rendertarget.addLine(
                    render_name, dummy_point0, dummy_point1, 0, 0, 0)

            # TODO: nicer selected view
            dummy_point0.set(coord[0], coord[1])
            draw_point = self.minimap_image.rendertarget.addPoint
            if ship in self.session.selected_instances:
                draw_point(render_name, dummy_point0, *Minimap.COLORS["water"])
                for x_off, y_off in ((-2, 0), (+2, 0), (0, -2), (0, +2)):
                    dummy_point1.set(coord[0] + x_off, coord[1] + y_off)
                    draw_point(render_name, dummy_point1, *color)

        # draw settlement warehouses if something has changed
        settlements = self.world.settlements
        # save only worldids as to not introduce actual coupling
        cur_settlements = set(i.worldid for i in settlements)
        if force or \
           (not hasattr(self, "_last_settlements") or cur_settlements != self._last_settlements):
            # update necessary
            warehouse_render_name = self._get_render_name("warehouse")
            self.minimap_image.rendertarget.removeAll(warehouse_render_name)
            for settlement in settlements:
                coord = settlement.warehouse.position.center.to_tuple()
                coord = self.transform.world_to_minimap(coord)
                self._update_image(self.__class__.WAREHOUSE_IMAGE,
                                   warehouse_render_name, coord)
            self._last_settlements = cur_settlements

    def _update_image(self, img_path, name, coord_tuple):
        """Updates image as part of minimap (e.g. when it has moved)"""
        img = self.imagemanager.load(img_path)

        size_tuple = self._image_size_cache.get(img_path)
        if size_tuple is None:
            ratio = sum(self.transform.world_to_minimap_ratio) / 2.0
            ratio = max(1.0, ratio)
            size_tuple = int(img.getWidth() / ratio), int(img.getHeight() /
                                                          ratio)
            self._image_size_cache[img_path] = size_tuple
        new_width, new_height = size_tuple
        p = self.__class__._dummy_fife_point
        p.set(*coord_tuple)
        # resizeImage also means draw
        self.minimap_image.rendertarget.resizeImage(name, p, img, new_width,
                                                    new_height)

    def update_rotation(self):
        # ensure the minimap rotation matches the main view rotation
        self.transform.set_rotation(self.view.cam.getRotation())
        self.draw()

    def _get_rotation_setting(self):
        return self.use_rotation and self._rotation_setting

    def _on_setting_changed(self, message):
        if message.setting_name == "MinimapRotation":
            self._rotation_setting = message.new_value
            self.transform.set_use_rotation(self._get_rotation_setting())
            self.draw()

    def get_size(self):
        return (self.location.height, self.location.width)
Example #26
0
 def to_fife_point(self):
     """Returns point as fife.Point"""
     return fife.Point(self.x, self.y)
Example #27
0
    def mouseDragged(self, evt):
        if evt.getButton() == fife.MouseEvent.LEFT and hasattr(
                self, 'select_begin'):
            do_multi = ((self.select_begin[0] - evt.getX())**2 +
                        (self.select_begin[1] - evt.getY())**
                        2) >= 10  # ab 3px (3*3 + 1)
            self.session.view.renderer['GenericRenderer'].removeAll("select")
            if do_multi:
                a = fife.Point(min(self.select_begin[0], evt.getX()), \
                        min(self.select_begin[1], evt.getY()))
                b = fife.Point(max(self.select_begin[0], evt.getX()), \
                        min(self.select_begin[1], evt.getY()))
                c = fife.Point(max(self.select_begin[0], evt.getX()), \
                        max(self.select_begin[1], evt.getY()))
                d = fife.Point(min(self.select_begin[0], evt.getX()), \
                        max(self.select_begin[1], evt.getY()))
                self.session.view.renderer['GenericRenderer'].addLine("select", \
                                                                      fife.GenericRendererNode(a), fife.GenericRendererNode(b), 200, 200, 200)
                self.session.view.renderer['GenericRenderer'].addLine("select", \
                                                                      fife.GenericRendererNode(b), fife.GenericRendererNode(c), 200, 200, 200)
                self.session.view.renderer['GenericRenderer'].addLine("select", \
                                                                      fife.GenericRendererNode(d), fife.GenericRendererNode(c), 200, 200, 200)
                self.session.view.renderer['GenericRenderer'].addLine("select", \
                                                                      fife.GenericRendererNode(a), fife.GenericRendererNode(d), 200, 200, 200)
            selectable = []
            instances = self.session.view.cam.getMatchingInstances(\
             fife.Rect(min(self.select_begin[0], evt.getX()), \
                  min(self.select_begin[1], evt.getY()), \
                  abs(evt.getX() - self.select_begin[0]), \
                  abs(evt.getY() - self.select_begin[1])) if do_multi else fife.ScreenPoint(evt.getX(), evt.getY()), self.session.view.layers[LAYERS.OBJECTS])
            # Only one unit, select anyway
            if len(instances) == 1:
                instance = WorldObject.get_object_by_id(
                    int(instances[0].getId()))
                if instance.is_selectable:
                    selectable.append(instance)
            else:
                for i in instances:
                    instance = WorldObject.get_object_by_id(int(i.getId()))
                    if instance.is_selectable and instance.owner == self.session.world.player:
                        selectable.append(instance)

            if len(selectable) > 1:
                if do_multi:
                    for instance in selectable[:]:  # iterate through copy for safe removal
                        if instance.is_building:
                            selectable.remove(instance)
                else:
                    selectable = [selectable.pop(0)]

            if do_multi:
                selectable = set(self.select_old | frozenset(selectable))
            else:
                selectable = set(self.select_old ^ frozenset(selectable))
            for instance in self.session.selected_instances - selectable:
                instance.deselect()
            for instance in selectable - self.session.selected_instances:
                instance.select()
            self.session.selected_instances = selectable
        elif (evt.getButton() == fife.MouseEvent.RIGHT):
            pass
        else:
            super(SelectionTool, self).mouseDragged(evt)
            return
        evt.consume()
Example #28
0
 def updateLighting(self):
     hour = self.application.world.getHour()
     # optimization: this method is expensive, so only run it:
     # - after loading a new map (self.last_hour is None)
     # - if the hour has changed since the last run
     # - if new instances were added since the last run (a listener sets self.last_hour to None)
     # TODO: in the last case: only update the new instances, not all of them
     if hour == self.last_hour:
         return
     self.last_hour = hour
     day = self.application.world.getDay()
     # tooltip clock
     #self.application.gui.tooltip.printMessage("Day " + str(day)
     #			+ "; Time: " + str(int(hour)) + ":" + str(int((hour%1.0)*60)))
     # day-night cycle
     self.light_renderer.removeAll("pc")
     cycle = customanimations.light_cycle
     for next_hour in cycle:
         if hour < next_hour:
             factor = (hour - prev_hour) / (next_hour - prev_hour)
             # global light
             self.camera.setLightingColor(
                 cycle[prev_hour][0] + factor *
                 (cycle[next_hour][0] - cycle[prev_hour][0]),
                 cycle[prev_hour][1] + factor *
                 (cycle[next_hour][1] - cycle[prev_hour][1]),
                 cycle[prev_hour][2] + factor *
                 (cycle[next_hour][2] - cycle[prev_hour][2]))
             # local lights
             #if self.application.current_character:
             #	if self.application.current_character.visual:
             #		for i in xrange(2):
             #			self.light_renderer.addSimpleLight("pc",
             #				fife.RendererNode(
             #				self.application.current_character.visual.instance),
             #				255, 64, 32, 3.0, 1.875,
             #				int((cycle[prev_hour][3]+factor*
             #				(cycle[next_hour][3]-cycle[prev_hour][3]))*255),
             #				int((cycle[prev_hour][4]+factor*
             #				(cycle[next_hour][4]-cycle[prev_hour][4]))*255),
             #				int((cycle[prev_hour][5]+factor*
             #				(cycle[next_hour][5]-cycle[prev_hour][5]))*255))
             for instance in self.application.maplayer.getInstances():
                 if not instance:
                     print("getInstances() error!", instance)
                     #self.application.maplayer.getInstances()
                     continue
                 if instance.getObject().getId(
                 ) not in customanimations.light_circles:
                     continue
                 circle = customanimations.light_circles[
                     instance.getObject().getId()]
                 if not ((circle[0] < hour < circle[1]) or
                         (hour < circle[1] < circle[0]) or
                         (circle[1] < circle[0] < hour)):
                     continue
                 for i in xrange(2):  # double light power!
                     self.light_renderer.addSimpleLight(
                         group="pc",
                         n=fife.RendererNode(
                             instance,
                             fife.Point(
                                 int(circle[7]),
                                 #int(circle[7] * self.camera.getZoom()),
                                 int(circle[8]))),
                         #int(circle[8] * self.camera.getZoom()))),
                         intensity=255,
                         radius=64,
                         subdivisions=32,
                         xstretch=circle[2],
                         ystretch=circle[3],
                         r=int(circle[4] * 255),
                         g=int(circle[5] * 255),
                         b=int(circle[6] * 255))
             break
         prev_hour = next_hour
     # set transparency for light overlay instances
     for instance in self.application.maplayer.getInstances():
         if not instance:
             print("getInstances() error!",
                   instance)  #self.application.maplayer.getInstances()
             continue
         if instance.getObject().getId(
         ) in customanimations.light_instances:
             hours = customanimations.light_instances[
                 instance.getObject().getId()]
             if ((hours[0] < hour < hours[1])
                     or (hour < hours[1] < hours[0])
                     or (hours[1] < hours[0] < hour)):
                 instance.get2dGfxVisual().setTransparency(0)
             else:
                 instance.get2dGfxVisual().setTransparency(255)