def repair_unit(self, unit, price): if self.game.blue.budget > price: self.game.blue.budget -= price unit.alive = True GameUpdateSignal.get_instance().updateGame(self.game) # Remove destroyed units in the vicinity destroyed_units = self.game.get_destroyed_units() for d in destroyed_units: p = Point(d["x"], d["z"], self.game.theater.terrain) if p.distance_to_point(unit.position) < 15: destroyed_units.remove(d) logging.info("Removed destroyed units " + str(d)) logging.info(f"Repaired unit: {unit.unit_name}") self.update_game()
def repair_unit(self, group, unit, price): if self.game.budget > price: self.game.budget -= price group.units_losts = [u for u in group.units_losts if u.id != unit.id] group.units.append(unit) GameUpdateSignal.get_instance().updateGame(self.game) # Remove destroyed units in the vicinity destroyed_units = self.game.get_destroyed_units() for d in destroyed_units: p = Point(d["x"], d["z"]) if p.distance_to_point(unit.position) < 15: destroyed_units.remove(d) logging.info("Removed destroyed units " + str(d)) logging.info("Repaired unit : " + str(unit.id) + " " + str(unit.type)) self.do_refresh_layout()
def __init__( self, target: Point, home: Point, coalition: Coalition, ) -> None: self._target = target self.threat_zone = coalition.opponent.threat_zone.all self.home = ShapelyPoint(home.x, home.y) max_ip_distance = coalition.doctrine.max_ingress_distance min_ip_distance = coalition.doctrine.min_ingress_distance # The minimum distance between the home location and the IP. min_distance_from_home = nautical_miles(5) # The distance that is expected to be needed between the beginning of the attack # and weapon release. This buffers the threat zone to give a 5nm window between # the edge of the "safe" zone and the actual threat so that "safe" IPs are less # likely to end up with the attacker entering a threatened area. attack_distance_buffer = nautical_miles(5) home_threatened = coalition.opponent.threat_zone.threatened(home) shapely_target = ShapelyPoint(target.x, target.y) home_to_target_distance = meters(home.distance_to_point(target)) self.home_bubble = self.home.buffer(home_to_target_distance.meters).difference( self.home.buffer(min_distance_from_home.meters) ) # If the home zone is not threatened and home is within LAR, constrain the max # range to the home-to-target distance to prevent excessive backtracking. # # If the home zone *is* threatened, we need to back out of the zone to # rendezvous anyway. if not home_threatened and ( min_ip_distance < home_to_target_distance < max_ip_distance ): max_ip_distance = home_to_target_distance max_ip_bubble = shapely_target.buffer(max_ip_distance.meters) min_ip_bubble = shapely_target.buffer(min_ip_distance.meters) self.ip_bubble = max_ip_bubble.difference(min_ip_bubble) # The intersection of the home bubble and IP bubble will be all the points that # are within the valid IP range that are not farther from home than the target # is. However, if the origin airfield is threatened but there are safe # placements for the IP, we should not constrain to the home zone. In this case # we'll either end up with a safe zone outside the home zone and pick the # closest point in to to home (minimizing backtracking), or we'll have no safe # IP anywhere within range of the target, and we'll later pick the IP nearest # the edge of the threat zone. if home_threatened: self.permissible_zone = self.ip_bubble else: self.permissible_zone = self.ip_bubble.intersection(self.home_bubble) if self.permissible_zone.is_empty: # If home is closer to the target than the min range, there will not be an # IP solution that's close enough to home, in which case we need to ignore # the home bubble. self.permissible_zone = self.ip_bubble safe_zones = self.permissible_zone.difference( self.threat_zone.buffer(attack_distance_buffer.meters) ) if not isinstance(safe_zones, MultiPolygon): safe_zones = MultiPolygon([safe_zones]) self.safe_zones = safe_zones
def distance_to_pixels(self, distance: Distance) -> int: p1 = Point(0, 0) p2 = Point(0, distance.meters) p1a = Point(*self._transform_point(p1)) p2a = Point(*self._transform_point(p2)) return int(p1a.distance_to_point(p2a))
def destination_in_range(self, destination: Point) -> bool: move_distance = meters( destination.distance_to_point(self.control_point.position) ) return move_distance <= MAX_SHIP_DISTANCE
def __init__(self, target: Point, home: Point, ip: Point, coalition: Coalition) -> None: self._target = target # Normal join placement is based on the path from home to the IP. If no path is # found it means that the target is on a direct path. In that case we instead # want to enforce that the join point is: # # * Not closer to the target than the IP. # * Not too close to the home airfield. # * Not threatened. # * A minimum distance from the IP. # * Not too sharp a turn at the ingress point. self.ip = ShapelyPoint(ip.x, ip.y) self.threat_zone = coalition.opponent.threat_zone.all self.home = ShapelyPoint(home.x, home.y) self.ip_bubble = self.ip.buffer( coalition.doctrine.join_distance.meters) ip_distance = ip.distance_to_point(target) self.target_bubble = ShapelyPoint(target.x, target.y).buffer(ip_distance) # The minimum distance between the home location and the IP. min_distance_from_home = nautical_miles(5) self.home_bubble = self.home.buffer(min_distance_from_home.meters) excluded_zones = shapely.ops.unary_union( [self.ip_bubble, self.target_bubble, self.threat_zone]) if not isinstance(excluded_zones, MultiPolygon): excluded_zones = MultiPolygon([excluded_zones]) self.excluded_zones = excluded_zones ip_heading = target.heading_between_point(ip) # Arbitrarily large since this is later constrained by the map boundary, and # we'll be picking a location close to the IP anyway. Just used to avoid real # distance calculations to project to the map edge. large_distance = nautical_miles(400).meters turn_limit = 40 ip_limit_ccw = ip.point_from_heading(ip_heading - turn_limit, large_distance) ip_limit_cw = ip.point_from_heading(ip_heading + turn_limit, large_distance) ip_direction_limit_wedge = Polygon([ (ip.x, ip.y), (ip_limit_ccw.x, ip_limit_ccw.y), (ip_limit_cw.x, ip_limit_cw.y), ]) permissible_zones = ip_direction_limit_wedge.difference( self.excluded_zones).difference(self.home_bubble) if permissible_zones.is_empty: permissible_zones = MultiPolygon([]) if not isinstance(permissible_zones, MultiPolygon): permissible_zones = MultiPolygon([permissible_zones]) self.permissible_zones = permissible_zones preferred_lines = ip_direction_limit_wedge.intersection( self.excluded_zones.boundary).difference(self.home_bubble) if preferred_lines.is_empty: preferred_lines = MultiLineString([]) if not isinstance(preferred_lines, MultiLineString): preferred_lines = MultiLineString([preferred_lines]) self.preferred_lines = preferred_lines
def __init__( self, target: Point, home: Point, ip: Point, join: Point, coalition: Coalition, theater: ConflictTheater, ) -> None: self._target = target # Hold points are placed one of two ways. Either approach guarantees: # # * Safe hold point. # * Minimum distance to the join point. # * Not closer to the target than the join point. # # 1. As near the join point as possible with a specific distance from the # departure airfield. This prevents loitering directly above the airfield but # also keeps the hold point close to the departure airfield. # # 2. Alternatively, if the entire home zone is excluded by the above criteria, # as neat the departure airfield as possible within a minimum distance from # the join point, with a restricted turn angle at the join point. This # handles the case where we need to backtrack from the departure airfield and # the join point to place the hold point, but the turn angle limit restricts # the maximum distance of the backtrack while maintaining the direction of # the flight plan. self.threat_zone = coalition.opponent.threat_zone.all self.home = ShapelyPoint(home.x, home.y) self.join = ShapelyPoint(join.x, join.y) self.join_bubble = self.join.buffer( coalition.doctrine.push_distance.meters) join_to_target_distance = join.distance_to_point(target) self.target_bubble = ShapelyPoint( target.x, target.y).buffer(join_to_target_distance) self.home_bubble = self.home.buffer( coalition.doctrine.hold_distance.meters) excluded_zones = shapely.ops.unary_union( [self.join_bubble, self.target_bubble, self.threat_zone]) if not isinstance(excluded_zones, MultiPolygon): excluded_zones = MultiPolygon([excluded_zones]) self.excluded_zones = excluded_zones join_heading = ip.heading_between_point(join) # Arbitrarily large since this is later constrained by the map boundary, and # we'll be picking a location close to the IP anyway. Just used to avoid real # distance calculations to project to the map edge. large_distance = nautical_miles(400).meters turn_limit = 40 join_limit_ccw = join.point_from_heading(join_heading - turn_limit, large_distance) join_limit_cw = join.point_from_heading(join_heading + turn_limit, large_distance) join_direction_limit_wedge = Polygon([ (join.x, join.y), (join_limit_ccw.x, join_limit_ccw.y), (join_limit_cw.x, join_limit_cw.y), ]) permissible_zones = (coalition.nav_mesh.map_bounds( theater).intersection(join_direction_limit_wedge).difference( self.excluded_zones).difference(self.home_bubble)) if not isinstance(permissible_zones, MultiPolygon): permissible_zones = MultiPolygon([permissible_zones]) self.permissible_zones = permissible_zones self.preferred_lines = self.home_bubble.boundary.difference( self.excluded_zones)
def km_to_pixel(self, km): p1 = Point(0, 0) p2 = Point(0, 1000 * km) p1a = Point(*self._transform_point(p1)) p2a = Point(*self._transform_point(p2)) return p1a.distance_to_point(p2a)