diff --git a/__init__.py b/__init__.py index c26285f9..8efa047b 100755 --- a/__init__.py +++ b/__init__.py @@ -70,7 +70,8 @@ try: for i in range(0, int(len(conflicttheater.controlpoints) / 2)): conflicttheater.controlpoints[i].captured = True - start_generator.generate_initial(conflicttheater, enemy_name, sams, multiplier) + start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier) + start_generator.generate_groundobjects(conflicttheater) game = Game(player_name=player_name, enemy_name=enemy_name, theater=conflicttheater) diff --git a/game/game.py b/game/game.py index 6401a421..3fee2d3c 100644 --- a/game/game.py +++ b/game/game.py @@ -112,14 +112,18 @@ class Game: enemy_generated_types = [] for player_cp, enemy_cp in self.theater.conflicts(True): - if player_cp.is_global or enemy_cp.is_global: + if enemy_cp.is_global: continue for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items(): - if event_class == FrontlineAttackEvent or event_class == InfantryTransportEvent or event_class == FrontlinePatrolEvent: + if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent]: if not Conflict.has_frontline_between(player_cp, enemy_cp): continue + if player_cp.is_global: + if event_class not in [InterceptEvent, StrikeEvent, NavalInterceptEvent]: + continue + if player_probability == 100 or self._roll(player_probability, player_cp.base.strength): if event_class == NavalInterceptEvent and enemy_cp.radials == LAND: pass diff --git a/game/operation/frontlineattack.py b/game/operation/frontlineattack.py index 381e542e..2d8d8675 100644 --- a/game/operation/frontlineattack.py +++ b/game/operation/frontlineattack.py @@ -47,7 +47,9 @@ class FrontlineAttackOperation(Operation): self.briefinggen.append_frequency("FARP", "127.5 MHz AM") for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])), db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)): - self.airgen.generate_cas_strikegroup(*assigned_units_split(dict), at=farp, escort=False) + self.airgen.generate_cas_strikegroup(*assigned_units_split(dict), + at=farp, + escort=len(planes_flights) == 0) self.briefinggen.title = "Frontline CAS" self.briefinggen.description = "Provide CAS for the ground forces attacking enemy lines. Operation will be considered successful if total number of enemy units will be lower than your own by a factor of 1.5 (i.e. with 12 units from both sides, enemy forces need to be reduced to at least 8), meaning that you (and, probably, your wingmans) should concentrate on destroying the enemy units. Target base strength will be lowered as a result. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." diff --git a/game/operation/strike.py b/game/operation/strike.py index 6b79dede..d937e89d 100644 --- a/game/operation/strike.py +++ b/game/operation/strike.py @@ -52,12 +52,22 @@ class StrikeOperation(Operation): targets.sort(key=lambda x: self.from_cp.position.distance_to_point(x[1])) - self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(self.strikegroup), + planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()} + self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(planes_flights), targets=targets, at=self.attackers_starting_position) - self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position) + heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()} + if heli_flights: + self.briefinggen.append_frequency("FARP", "127.5 MHz AM") + for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])), + db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)): + self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(dict), + targets=targets, + at=farp, + escort=len(planes_flights) == 0) + self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position) self.airgen.generate_barcap(*assigned_units_split(self.interceptors), at=self.defenders_starting_position) self.briefinggen.title = "Strike" diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 8e89dfc4..7b3557cd 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -21,6 +21,10 @@ class AirSupportConflictGenerator: self.conflict = conflict self.game = game + @classmethod + def support_tasks(cls) -> typing.Collection[typing.Type[MainTask]]: + return [Refueling, AWACS] + def generate(self, is_awacs_enabled): player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp tanker_unit = db.find_unittype(Refueling, self.conflict.attackers_side.name)[0] diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 39860aa6..0718ca9d 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -130,19 +130,22 @@ class Conflict: return self.to_cp.size * GROUND_DISTANCE_FACTOR def find_insertion_point(self, other_point: Point) -> Point: - dx = self.position.x - self.tail.x - dy = self.position.y - self.tail.y - dr2 = float(dx ** 2 + dy ** 2) + if self.is_vector: + dx = self.position.x - self.tail.x + dy = self.position.y - self.tail.y + dr2 = float(dx ** 2 + dy ** 2) - lerp = ((other_point.x - self.tail.x) * dx + (other_point.y - self.tail.y) * dy) / dr2 - if lerp < 0: - lerp = 0 - elif lerp > 1: - lerp = 1 + lerp = ((other_point.x - self.tail.x) * dx + (other_point.y - self.tail.y) * dy) / dr2 + if lerp < 0: + lerp = 0 + elif lerp > 1: + lerp = 1 - x = lerp * dx + self.tail.x - y = lerp * dy + self.tail.y - return Point(x, y) + x = lerp * dx + self.tail.x + y = lerp * dy + self.tail.y + return Point(x, y) + else: + return self.position def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> typing.Optional[Point]: return Conflict._find_ground_position(at, max_distance, heading, self.theater) @@ -153,7 +156,10 @@ class Conflict: @classmethod def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Tuple[Point, int]: - distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONTLINE_DISTANCE_STRENGTH_FACTOR * to_cp.base.strength, FRONTLINE_MIN_CP_DISTANCE) + cp_distance = from_cp.position.distance_to_point(to_cp.position) + cp_distance -= cp_distance * to_cp.frontline_offset + cp_distance * from_cp.frontline_offset + + distance = max(cp_distance * FRONTLINE_DISTANCE_STRENGTH_FACTOR * to_cp.base.strength, FRONTLINE_MIN_CP_DISTANCE) heading = to_cp.position.heading_between_point(from_cp.position) return to_cp.position.point_from_heading(heading, distance), heading @@ -172,7 +178,6 @@ class Conflict: if pos: left_position = pos center_position = pos - print("{} - {} {}".format(from_cp, to_cp, center_position)) if left_position is None: left_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, -90), theater) diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index 2264b33a..08354825 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -19,11 +19,15 @@ class GroundObjectsGenerator: self.game = game def generate_farps(self, number_of_units=1) -> typing.Collection[StaticGroup]: - assert self.conflict.is_vector, "FARP could be generated only on frontline conflicts!" - - for i, _ in enumerate(range(0, number_of_units, self.FARP_CAPACITY)): + if self.conflict.is_vector: + center = self.conflict.center heading = self.conflict.heading - 90 - position = self.conflict.find_ground_position(self.conflict.center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE), heading) + else: + center, heading = self.conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp) + heading -= 90 + + position = self.conflict.find_ground_position(center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE), heading) + for i, _ in enumerate(range(0, number_of_units, self.FARP_CAPACITY)): position = position.point_from_heading(0, i * 275) yield self.m.farp( @@ -64,6 +68,10 @@ class GroundObjectsGenerator: else: static_type = fortification_map[ground_object.dcs_identifier] + if not static_type: + print("Didn't find {} in static _map(s)!".format(ground_object.dcs_identifier)) + continue + group = self.m.static_group( country=side, name=ground_object.string_identifier, diff --git a/gen/triggergen.py b/gen/triggergen.py index 38db66a1..7a916eed 100644 --- a/gen/triggergen.py +++ b/gen/triggergen.py @@ -12,9 +12,11 @@ from dcs.action import * from game import db from theater import * +from gen.airsupportgen import AirSupportConflictGenerator from gen import * PUSH_TRIGGER_SIZE = 3000 +PUSH_TRIGGER_ACTIVATION_AGL = 100 REGROUP_ZONE_DISTANCE = 12000 REGROUP_ALT = 5000 @@ -51,6 +53,11 @@ class TriggersGenerator: vehicle_group.late_activation = True activate_by_trigger.append(vehicle_group) + for plane_group in country.plane_group: + if plane_group.task in [x.name for x in AirSupportConflictGenerator.support_tasks()]: + plane_group.late_activation = True + activate_by_trigger.append(plane_group) + conflict_distance = player_cp.position.distance_to_point(self.conflict.position) minimum_radius = max(conflict_distance - TRIGGER_MIN_DISTANCE_FROM_START, TRIGGER_RADIUS_MINIMUM) if minimum_radius < 0: @@ -79,9 +86,6 @@ class TriggersGenerator: if player_cp.position.distance_to_point(group.position) > PUSH_TRIGGER_SIZE * 3: continue - if group.units[0].is_human(): - continue - regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position) pos1 = group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE) @@ -109,7 +113,9 @@ class TriggersGenerator: push_trigger = TriggerOnce(Event.NoEvent, "Push trigger") for group in push_by_trigger: - push_trigger.add_condition(AllOfGroupOutsideZone(group.id, push_trigger_zone.id)) + for unit in group.units: + push_trigger.add_condition(UnitAltitudeHigherAGL(unit.id, PUSH_TRIGGER_ACTIVATION_AGL)) + push_trigger.add_action(AITaskPush(group.id, 1)) message_string = self.mission.string("Task force is in the air, proceed with the objective (activate waypoint 3).") diff --git a/resources/caulandmap.p b/resources/caulandmap.p index 14b19f18..f6e34d8e 100644 Binary files a/resources/caulandmap.p and b/resources/caulandmap.p differ diff --git a/resources/groundobject_templates.p b/resources/groundobject_templates.p new file mode 100644 index 00000000..df95b3a1 Binary files /dev/null and b/resources/groundobject_templates.p differ diff --git a/resources/gulflandmap.p b/resources/gulflandmap.p index d64c975a..98566550 100644 Binary files a/resources/gulflandmap.p and b/resources/gulflandmap.p differ diff --git a/resources/tools/cau_groundobjects.miz b/resources/tools/cau_groundobjects.miz index cc026acc..507aa7e7 100644 Binary files a/resources/tools/cau_groundobjects.miz and b/resources/tools/cau_groundobjects.miz differ diff --git a/resources/tools/cau_terrain.miz b/resources/tools/cau_terrain.miz index 20d0c5db..65610873 100644 Binary files a/resources/tools/cau_terrain.miz and b/resources/tools/cau_terrain.miz differ diff --git a/resources/tools/generate_groundobject_templates.py b/resources/tools/generate_groundobject_templates.py new file mode 100644 index 00000000..2995a9c4 --- /dev/null +++ b/resources/tools/generate_groundobject_templates.py @@ -0,0 +1,49 @@ +import pickle +import typing + +from dcs.mission import Mission +from dcs.mapping import Point +from dcs.unit import * +from dcs.statics import warehouse_map, fortification_map + + +def load_templates(): + temp_mis = Mission() + temp_mis.load_file("resources/tools/groundobject_templates.miz") + + groups = {} # type: typing.Dict[str, typing.Dict[int, typing.Collection[Static]]] + + for static_group in temp_mis.country("USA").static_group: + for static in static_group.units: + static_name = str(static.name).split()[0] + tpl_name, tpl_idx = static_name[:-1], int(static_name[-1]) + + groups[tpl_name] = groups.get(tpl_name, {}) + groups[tpl_name][tpl_idx] = groups[tpl_name].get(tpl_idx, []) + groups[tpl_name][tpl_idx].append(static) + + tpls = {name: {idx: [] for idx in groups[name].keys()} for name in groups.keys()} + for category_name, category_groups in groups.items(): + for idx, static_groups in category_groups.items(): + dist = -1 + a, b = None, None + for aa in static_groups: + for bb in static_groups: + if aa.position.distance_to_point(bb.position) > dist: + dist = aa.position.distance_to_point(bb.position) + a = aa + b = bb + + center = a.position.point_from_heading(a.position.heading_between_point(b.position), dist / 2) + for static in static_groups: + tpls[category_name][idx].append({ + "type": static.type, + "offset": Point(center.x - static.position.x, center.y - static.position.y), + "heading": static.heading, + }) + + return tpls + + +with open("resources/groundobject_templates.p", "wb") as f: + pickle.dump(load_templates(), f) diff --git a/resources/tools/generate_groundobjectsmap.py b/resources/tools/generate_groundobjectsmap.py index 107ae981..a7962720 100644 --- a/resources/tools/generate_groundobjectsmap.py +++ b/resources/tools/generate_groundobjectsmap.py @@ -5,6 +5,7 @@ from dcs.mission import Mission from dcs.mapping import Point from dcs.terrain import * from dcs.unitgroup import VehicleGroup, StaticGroup +from dcs import vehicles from dcs.unit import * from dcs.statics import warehouse_map, fortification_map @@ -51,7 +52,7 @@ if __name__ == "__main__": theater_object.position = unit.position theater_object.heading = unit.heading - if isinstance(unit, Vehicle): + if isinstance(unit, Vehicle) and unit.type in vehicles.AirDefence.__dict__.values(): theater_object.dcs_identifier = "AA" else: theater_object.dcs_identifier = unit.type diff --git a/resources/tools/generate_landmap.py b/resources/tools/generate_landmap.py index 5e472aaa..e90293b1 100644 --- a/resources/tools/generate_landmap.py +++ b/resources/tools/generate_landmap.py @@ -1,14 +1,26 @@ import pickle from dcs.mission import Mission +from dcs.planes import A_10C for terrain in ["cau", "gulf"]: m = Mission() m.load_file("./{}_terrain.miz".format(terrain)) - landmap = [] + inclusion_zones = [] + exclusion_zones = [] for plane_group in m.country("USA").plane_group: - landmap.append([(x.position.x, x.position.y) for x in plane_group.points]) + zone = [(x.position.x, x.position.y) for x in plane_group.points] + + if terrain == "cau" and inclusion_zones: + # legacy + exclusion_zones.append(zone) + else: + if plane_group.units[0].type == "F-15C": + exclusion_zones.append(zone) + else: + inclusion_zones.append(zone) with open("../{}landmap.p".format(terrain), "wb") as f: - pickle.dump(landmap, f) + print(len(inclusion_zones), len(exclusion_zones)) + pickle.dump((inclusion_zones, exclusion_zones), f) diff --git a/resources/tools/groundobject_templates.miz b/resources/tools/groundobject_templates.miz new file mode 100644 index 00000000..486aa817 Binary files /dev/null and b/resources/tools/groundobject_templates.miz differ diff --git a/resources/tools/gulf_terrain.miz b/resources/tools/gulf_terrain.miz index a2ee9de2..01a262b0 100644 Binary files a/resources/tools/gulf_terrain.miz and b/resources/tools/gulf_terrain.miz differ diff --git a/theater/caucasus.py b/theater/caucasus.py index ed91c98c..ca5bb3f6 100644 --- a/theater/caucasus.py +++ b/theater/caucasus.py @@ -47,6 +47,9 @@ class CaucasusTheater(ConflictTheater): def __init__(self, load_ground_objects=True): super(CaucasusTheater, self).__init__() + self.soganlug.frontline_offset = 0.5 + self.soganlug.base.strength = 1 + self.add_controlpoint(self.soganlug, connected_to=[self.kutaisi, self.beslan]) self.add_controlpoint(self.beslan, connected_to=[self.soganlug, self.mozdok, self.nalchik]) self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye]) @@ -73,10 +76,6 @@ class CaucasusTheater(ConflictTheater): self.carrier_1.captured = True self.soganlug.captured = True - if load_ground_objects: - with open("resources/cau_groundobjects.p", "rb") as f: - self.set_groundobject(pickle.load(f)) - def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []): point.name = " ".join(re.split(r"[ -]", point.name)[:1]) diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py index 5f78c198..8d23956f 100644 --- a/theater/conflicttheater.py +++ b/theater/conflicttheater.py @@ -18,6 +18,8 @@ IMPORTANCE_LOW = 1 IMPORTANCE_MEDIUM = 1.2 IMPORTANCE_HIGH = 1.4 +GLOBAL_CP_CONFLICT_DISTANCE_MIN = 340000 + """ ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ] COAST_NS_E = [45, 90, 135, ] @@ -58,31 +60,28 @@ class ConflictTheater: def __init__(self): self.controlpoints = [] - def set_groundobject(self, dictionary: typing.Dict[int, typing.Collection[TheaterGroundObject]]): - for id, value in dictionary.items(): - for cp in self.controlpoints: - if cp.id == id: - cp.ground_objects = value - break - def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []): for connected_point in connected_to: point.connect(to=connected_point) self.controlpoints.append(point) + def is_in_sea(self, point: Point) -> bool: + if not self.landmap: + return False + + return poly_contains(point.x, point.y, self.landmap[0][0]) + def is_on_land(self, point: Point) -> bool: if not self.landmap: return True - # check first poly (main land poly) - if not poly_contains(point.x, point.y, self.landmap[0]): - return False + for inclusion_zone in self.landmap[0]: + if not poly_contains(point.x, point.y, inclusion_zone): + return False - # check others polys (exclusion zones from main) - for poly in self.landmap[1:]: - if poly_contains(point.x, point.y, poly): - # point is in one of the exclusion zones, meaning that it's in the lake or something + for exclusion_zone in self.landmap[1]: + if poly_contains(point.x, point.y, exclusion_zone): return False return True @@ -95,5 +94,9 @@ class ConflictTheater: for connected_point in [x for x in cp.connected_points if x.captured != from_player]: yield (cp, connected_point) + for global_cp in [x for x in self.controlpoints if x.is_global and x.captured == from_player]: + if global_cp.position.distance_to_point(connected_point.position) < GLOBAL_CP_CONFLICT_DISTANCE_MIN: + yield (global_cp, connected_point) + def enemy_points(self) -> typing.Collection[ControlPoint]: return [point for point in self.controlpoints if not point.captured] diff --git a/theater/controlpoint.py b/theater/controlpoint.py index 72e44017..e748aba1 100644 --- a/theater/controlpoint.py +++ b/theater/controlpoint.py @@ -9,15 +9,20 @@ from .theatergroundobject import TheaterGroundObject class ControlPoint: - connected_points = None # type: typing.List[ControlPoint] - ground_objects = None # type: typing.Collection[TheaterGroundObject] - position = None # type: Point - captured = False - has_frontline = True id = 0 + position = None # type: Point + name = None # type: str + full_name = None # type: str base = None # type: theater.base.Base at = None # type: db.StartPosition + connected_points = None # type: typing.List[ControlPoint] + ground_objects = None # type: typing.List[TheaterGroundObject] + + captured = False + has_frontline = True + frontline_offset = 0.0 + def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True): import theater.base diff --git a/theater/landmap.py b/theater/landmap.py index a52a00eb..c5384da7 100644 --- a/theater/landmap.py +++ b/theater/landmap.py @@ -1,7 +1,8 @@ import pickle import typing -Landmap = typing.Collection[typing.Collection[typing.Tuple[float, float]]] +Zone = typing.Collection[typing.Tuple[float, float]] +Landmap = typing.Tuple[typing.Collection[Zone], typing.Collection[Zone]] def load_landmap(filename: str) -> Landmap: diff --git a/theater/start_generator.py b/theater/start_generator.py index c7748888..e89a69b8 100644 --- a/theater/start_generator.py +++ b/theater/start_generator.py @@ -1,4 +1,7 @@ import math +import pickle +import random +import typing from theater.base import * from theater.conflicttheater import * @@ -15,7 +18,7 @@ COUNT_BY_TASK = { } -def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplier: float): +def generate_inital_units(theater: ConflictTheater, enemy: str, sams: bool, multiplier: float): for cp in theater.enemy_points(): if cp.captured: continue @@ -37,3 +40,49 @@ def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplie for unit_type in unittypes: logging.info("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type)) cp.base.commision_units({unit_type: count_per_type}) + + +def generate_groundobjects(theater: ConflictTheater): + with open("resources/groundobject_templates.p", "rb") as f: + tpls = pickle.load(f) + + def find_ground_location(near, theater, max, min) -> typing.Optional[Point]: + for _ in range(500): + p = near.random_point_within(max, min) + if theater.is_on_land(p): + return p + + return None + + group_id = 0 + for cp in theater.enemy_points(): + for _ in range(0, random.randrange(3, 6)): + point = find_ground_location(cp.position, theater, 120000, 5000) + if point is None: + print("Couldn't find point for {}".format(cp)) + continue + + dist = point.distance_to_point(cp.position) + for another_cp in theater.enemy_points(): + if another_cp.position.distance_to_point(point) < dist: + cp = another_cp + + tpl = random.choice(list(random.choice(list(tpls.values())).values())) + random_heading = random.randrange(0, 360) + + group_id += 1 + object_id = 0 + + for object in tpl: + object_id += 1 + + g = TheaterGroundObject() + g.group_id = group_id + g.object_id = object_id + g.cp_id = cp.id + + g.dcs_identifier = object["type"] + g.heading = object["heading"] + random_heading + g.position = Point(point.x + object["offset"].x, point.y + object["offset"].y) + + cp.ground_objects.append(g) diff --git a/theater/theatergroundobject.py b/theater/theatergroundobject.py index 96557887..6f9e17ca 100644 --- a/theater/theatergroundobject.py +++ b/theater/theatergroundobject.py @@ -1,6 +1,7 @@ import typing from dcs.mapping import Point +from dcs.statics import * NAME_BY_CATEGORY = { "power": "Power plant", @@ -9,6 +10,8 @@ NAME_BY_CATEGORY = { "aa": "AA Defense Site", "warehouse": "Warehouse", "farp": "FARP", + "fob": "FOB", + "factory": "Factory", "comms": "Comms. tower", "oil": "Oil platform" } @@ -20,6 +23,8 @@ ABBREV_NAME = { "aa": "AA", "warehouse": "WARE", "farp": "FARP", + "fob": "FOB", + "factory": "FACTORY", "comms": "COMMST", "oil": "OILP" } @@ -27,12 +32,14 @@ ABBREV_NAME = { CATEGORY_MAP = { "aa": ["AA"], - "power": ["Workshop A"], - "warehouse": ["Warehouse"], - "fuel": ["Tank"], - "ammo": [".Ammunition depot"], - "farp": ["FARP Tent"], - "comms": ["TV tower", "Comms tower"], + "power": ["Workshop A", "Electric power box", "Garage A"], + "warehouse": ["Warehouse", "Hangar A"], + "fuel": ["Tank", "Tank 2", "Fuel tank"], + "ammo": [".Ammunition depot", "Hangar B"], + "farp": ["FARP Tent", "FARP Ammo Dump Coating", "FARP Fuel Depot", "FARP Command Post", "FARP CP Blindage"], + "fob": ["Bunker 2", "Bunker 1", "Garage small B", ".Command Center", "Barracks 2"], + "factory": ["Tech combine", "Tech hangar A"], + "comms": ["TV tower", "Comms tower M"], "oil": ["Oil platform"], }