diff --git a/game/db.py b/game/db.py index 2b2a3d60..d05f3f09 100644 --- a/game/db.py +++ b/game/db.py @@ -139,12 +139,12 @@ PRICES = { AirDefence.AAA_ZU_23_Closed: 2, AirDefence.SPAAA_ZSU_23_4_Shilka: 4, - AirDefence.SAM_SA_9_Strela_1_9P31: 13, + AirDefence.SAM_SA_9_Strela_1_9P31: 8, AirDefence.SAM_SA_19_Tunguska_2S6: 15, - AirDefence.SAM_SA_6_Kub_LN_2P25: 12, - AirDefence.SAM_SA_8_Osa_9A33: 6, - AirDefence.SAM_SA_3_S_125_LN_5P73: 10, - AirDefence.SAM_SA_11_Buk_LN_9A310M1: 20, + AirDefence.SAM_SA_6_Kub_LN_2P25: 22, + AirDefence.SAM_SA_8_Osa_9A33: 12, + AirDefence.SAM_SA_3_S_125_LN_5P73: 35, + AirDefence.SAM_SA_11_Buk_LN_9A310M1: 25, # ship CV_1143_5_Admiral_Kuznetsov: 100, @@ -210,7 +210,6 @@ UNIT_BY_TASK = { AH_64D, OH_58D ], - Transport: [ IL_76MD, An_26B, @@ -219,15 +218,12 @@ UNIT_BY_TASK = { C_130, ], - Refueling: [ IL_78M, KC_135, S_3B_Tanker, ], - AWACS: [E_3A, A_50, ], - PinpointStrike: [ Armor.APC_MTLB, Armor.APC_MTLB, @@ -299,11 +295,9 @@ UNIT_BY_TASK = { AirDefence.SAM_SA_3_S_125_LN_5P73, #AirDefence.SAM_SA_11_Buk_LN_9A310M1, ], - Reconnaissance: [Unarmed.Transport_M818, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469], Nothing: [Infantry.Infantry_M4, Infantry.Soldier_AK, ], Embarking: [UH_1H, Mi_8MT, ], - Carriage: [CVN_74_John_C__Stennis, LHA_1_Tarawa, CV_1143_5_Admiral_Kuznetsov, ], CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev, Tanker_Elnya_160, Armed_speedboat, ], } @@ -323,6 +317,28 @@ SAM_BAN = [ AirDefence.SAM_SA_11_Buk_LN_9A310M1, ] +""" +Used to convert SAM site parts to the corresponding site +""" +SAM_CONVERT = { + AirDefence.SAM_SR_P_19: AirDefence.SAM_SA_3_S_125_LN_5P73, + AirDefence.SAM_SA_3_S_125_TR_SNR: AirDefence.SAM_SA_3_S_125_LN_5P73, + AirDefence.SAM_SA_3_S_125_LN_5P73: AirDefence.SAM_SA_3_S_125_LN_5P73, + AirDefence.SAM_SA_6_Kub_LN_2P25: AirDefence.SAM_SA_6_Kub_LN_2P25, + AirDefence.SAM_SA_6_Kub_STR_9S91: AirDefence.SAM_SA_6_Kub_LN_2P25, + AirDefence.SAM_SA_10_S_300PS_LN_5P85C: AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + AirDefence.SAM_SA_10_S_300PS_SR_5N66M: AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + AirDefence.SAM_SA_10_S_300PS_TR_30N6: AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + AirDefence.SAM_SA_10_S_300PS_CP_54K6: AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + AirDefence.SAM_SA_10_S_300PS_SR_64H6E: AirDefence.SAM_SA_10_S_300PS_CP_54K6, + 'except': { + # this radar is shared between the two S300's. if we attempt to find a SAM site at a base and can't find one + # model, we can safely assume the other was deployed + # well, perhaps not safely, but we'll make the assumption anyway :p + AirDefence.SAM_SA_10_S_300PS_TR_30N6: AirDefence.SAM_SA_10_S_300PS_CP_54K6, + } +} + """ Units that will always be spawned in the air """ @@ -924,7 +940,8 @@ def unit_task(unit: UnitType) -> Task: for task, units in UNIT_BY_TASK.items(): if unit in units: return task - + elif unit in SAM_CONVERT: + return SAM_CONVERT[unit] assert False diff --git a/gen/aaa.py b/gen/aaa.py index 070230ec..67d9633c 100644 --- a/gen/aaa.py +++ b/gen/aaa.py @@ -11,6 +11,33 @@ EXTRA_AA_MAX_DISTANCE = 150000 EXTRA_AA_POSITION_FROM_CP = 550 +def num_sam_dead(sam_type, destroyed_count): + """ + Given a type and count of SAM units, determine if enough units were destroyed to warrant the + loss of a site + :param sam_type: + inidivudal unit name in SAM site which was destroyed + :param destroyed_count: + count of that unit type which was destroyed *in the sortie* + :return: + INT: number of sites lost + """ + sam_threshold = { + AirDefence.SAM_SR_P_19: 1, + AirDefence.SAM_SA_3_S_125_TR_SNR: 1, + AirDefence.SAM_SA_6_Kub_STR_9S91: 1, + AirDefence.SAM_SA_10_S_300PS_SR_5N66M: 1, + AirDefence.SAM_SA_10_S_300PS_TR_30N6: 1, + AirDefence.SAM_SA_10_S_300PS_CP_54K6: 1, + AirDefence.SAM_SA_10_S_300PS_SR_64H6E: 1, + AirDefence.SAM_SA_3_S_125_LN_5P73: 4, + AirDefence.SAM_SA_6_Kub_LN_2P25: 6, + AirDefence.SAM_SA_10_S_300PS_LN_5P85C: 8, + } + + return int(destroyed_count / sam_threshold[sam_type]) + + def determine_positions(position, heading, num_units, launcher_distance, coverage=90): """ Given a position on the map, array a group of units in a circle a uniform distance from the unit @@ -52,7 +79,7 @@ def determine_positions(position, heading, num_units, launcher_distance, coverag def aaa_vehicle_group(self, country, name, _type: unittype.VehicleType, position: mapping.Point, - heading=0, + heading=0, group_size=1, formation=unitgroup.VehicleGroup.Formation.Line, move_formation: PointAction=PointAction.OffRoad): """ @@ -62,208 +89,208 @@ def aaa_vehicle_group(self, country, name, _type: unittype.VehicleType, position """ vg = unitgroup.VehicleGroup(self.next_group_id(), self.string(name)) - heading = randint(0, 359) - if _type == AirDefence.SAM_SA_3_S_125_LN_5P73: - # 4 launchers (180 degrees all facing the same direction), 1 SR, 1 TR - num_launchers = 4 - # search radar - v = self.vehicle( - name + " Unit #{nr}".format(nr=1), - AirDefence.SAM_SR_P_19, - ) - v.position.x = position.x - v.position.y = position.y - v.heading = heading - vg.add_unit(v) - # track radar - v = self.vehicle( - name + " Unit #{nr}".format(nr=2), - AirDefence.SAM_SA_3_S_125_TR_SNR, - ) - - center_x = position.x + randint(20, 40) - center_y = position.y - - v.position.x = center_x - v.position.y = center_y - v.heading = heading - vg.add_unit(v) - plop_positions = determine_positions( - position, - heading, - num_launchers, - launcher_distance=100, - coverage=180, - ) - for x in range(0, num_launchers): + for i in range(1, group_size + 1): + heading = randint(0, 359) + if _type == AirDefence.SAM_SA_3_S_125_LN_5P73: + # 4 launchers (180 degrees all facing the same direction), 1 SR, 1 TR + num_launchers = 4 + # search radar v = self.vehicle( - name + " Unit #{nr}".format(nr=3+x), - AirDefence.SAM_SA_3_S_125_LN_5P73, + name + " Unit #{nr}-sr".format(nr=i), + AirDefence.SAM_SR_P_19, + ) + v.position.x = position.x + v.position.y = position.y + (i - 1) * 20 + v.heading = heading + vg.add_unit(v) + # track radar + v = self.vehicle( + name + " Unit #{nr}-tr".format(nr=i), + AirDefence.SAM_SA_3_S_125_TR_SNR, ) - v.position.x = plop_positions[x][0] - v.position.y = plop_positions[x][1] - v.heading = plop_positions[x][2] + center_x = position.x + randint(20, 40) + center_y = position.y + (i - 1) * 20 + + v.position.x = center_x + v.position.y = center_y + v.heading = heading + vg.add_unit(v) + plop_positions = determine_positions( + position, + heading, + num_launchers, + launcher_distance=100, + coverage=180, + ) + for x in range(0, num_launchers): + v = self.vehicle( + name + " Unit #{nr}-{x}".format(nr=i, x=x), + AirDefence.SAM_SA_3_S_125_LN_5P73, + ) + + v.position.x = plop_positions[x][0] + v.position.y = plop_positions[x][1] + v.heading = plop_positions[x][2] + vg.add_unit(v) + + elif _type == AirDefence.SAM_SA_6_Kub_LN_2P25: + # 6 launchers (360 degree coverage) + # 1 S/TR + # search/track radar + num_launchers = 6 + v = self.vehicle( + name + " Unit #{nr}-str".format(nr=i), + AirDefence.SAM_SA_6_Kub_STR_9S91, + ) + v.position.x = position.x + v.position.y = position.y + (i - 1) * 20 + v.heading = heading vg.add_unit(v) - elif _type == AirDefence.SAM_SA_6_Kub_LN_2P25: - # 6 launchers (360 degree coverage) - # 1 S/TR - # search/track radar - num_launchers = 6 - v = self.vehicle( - name + " Unit #{nr}".format(nr=1), - AirDefence.SAM_SA_6_Kub_STR_9S91, - ) - v.position.x = position.x - v.position.y = position.y - v.heading = heading - vg.add_unit(v) + plop_positions = determine_positions( + position, + heading, + num_launchers, + launcher_distance=100, + coverage=360, + ) + for x in range(0, num_launchers): + v = self.vehicle( + name + " Unit #{nr}-{x}".format(nr=i, x=x), + AirDefence.SAM_SA_6_Kub_LN_2P25, + ) - plop_positions = determine_positions( - position, - heading, - num_launchers, - launcher_distance=100, - coverage=360, - ) - for x in range(0, num_launchers): + v.position.x = plop_positions[x][0] + v.position.y = plop_positions[x][1] + v.heading = plop_positions[x][2] + vg.add_unit(v) + elif _type == AirDefence.SAM_SA_10_S_300PS_LN_5P85C: + # 8 launchers - 4 directions, two in each direction + # 1 SR (offset) + # 1 TR (center) + # search radar + num_launchers = 8 v = self.vehicle( - name + " Unit #{nr}".format(nr=1+x), - AirDefence.SAM_SA_6_Kub_LN_2P25, + name + " Unit #{nr}-sr".format(nr=i), + AirDefence.SAM_SA_10_S_300PS_SR_5N66M, + ) + v.position.x = position.x + v.position.y = position.y + (i - 1) * 20 + v.heading = heading + vg.add_unit(v) + # track radar + v = self.vehicle( + name + " Unit #{nr}-tr".format(nr=i), + AirDefence.SAM_SA_10_S_300PS_TR_30N6, ) - v.position.x = plop_positions[x][0] - v.position.y = plop_positions[x][1] - v.heading = plop_positions[x][2] + center_x = position.x + randint(20, 40) + center_y = position.y + (i - 1) * 20 + + v.position.x = center_x + v.position.y = center_y + v.heading = heading vg.add_unit(v) - elif _type == AirDefence.SAM_SA_10_S_300PS_LN_5P85C: - # 8 launchers - 4 directions, two in each direction - # 1 SR (offset) - # 1 TR (center) - # search radar - num_launchers = 8 - v = self.vehicle( - name + " Unit #{nr}".format(nr=1), - AirDefence.SAM_SA_10_S_300PS_SR_5N66M, - ) - v.position.x = position.x - v.position.y = position.y - v.heading = heading - vg.add_unit(v) - # track radar - v = self.vehicle( - name + " Unit #{nr}".format(nr=2), - AirDefence.SAM_SA_10_S_300PS_TR_30N6, - ) - - center_x = position.x + randint(20, 40) - center_y = position.y - - v.position.x = center_x - v.position.y = center_y - v.heading = heading - vg.add_unit(v) - # command center - v = self.vehicle( - name + " Unit #{nr}".format(nr=3), - AirDefence.SAM_SA_10_S_300PS_CP_54K6, - ) - - center_x = position.x + randint(40, 60) - center_y = position.y - - v.position.x = center_x - v.position.y = center_y - v.heading = heading - vg.add_unit(v) - - plop_positions = determine_positions( - position, - heading, - num_launchers, - launcher_distance=150, - coverage=360, - ) - for x in range(0, num_launchers): + # command center v = self.vehicle( - name + " Unit #{nr}".format(nr=3+x), - AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + name + " Unit #{nr}-c".format(nr=i), + AirDefence.SAM_SA_10_S_300PS_CP_54K6, ) - v.position.x = plop_positions[x][0] - v.position.y = plop_positions[x][1] - v.heading = plop_positions[x][2] + center_x = position.x + randint(40, 60) + center_y = position.y + (i - 1) * 20 + + v.position.x = center_x + v.position.y = center_y + v.heading = heading vg.add_unit(v) - elif _type == AirDefence.SAM_SA_10_S_300PS_CP_54K6: - # 8 launchers - 4 directions, two in each direction - # 1 SR (offset) - # 1 TR (center) - # search radar - num_launchers = 8 - v = self.vehicle( - name + " Unit #{nr}".format(nr=1), - AirDefence.SAM_SA_10_S_300PS_SR_64H6E, - ) - v.position.x = position.x - v.position.y = position.y - v.heading = heading - vg.add_unit(v) - # track radar - v = self.vehicle( - name + " Unit #{nr}".format(nr=2), - AirDefence.SAM_SA_10_S_300PS_TR_30N6, - ) + plop_positions = determine_positions( + position, + heading, + num_launchers, + launcher_distance=150, + coverage=360, + ) + for x in range(0, num_launchers): + v = self.vehicle( + name + " Unit #{nr}-{x}".format(nr=i, x=x), + AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + ) - center_x = position.x + randint(20, 40) - center_y = position.y + v.position.x = plop_positions[x][0] + v.position.y = plop_positions[x][1] + v.heading = plop_positions[x][2] + vg.add_unit(v) - v.position.x = center_x - v.position.y = center_y - v.heading = heading - vg.add_unit(v) - # command center - v = self.vehicle( - name + " Unit #{nr}".format(nr=3), - AirDefence.SAM_SA_10_S_300PS_CP_54K6, - ) - - center_x = position.x + randint(40, 60) - center_y = position.y - - v.position.x = center_x - v.position.y = center_y - v.heading = heading - vg.add_unit(v) - - plop_positions = determine_positions( - position, - heading, - num_units=num_launchers, - launcher_distance=150, - coverage=360, - ) - for x in range(0, num_launchers): + elif _type == AirDefence.SAM_SA_10_S_300PS_CP_54K6: + # 8 launchers - 4 directions, two in each direction + # 1 SR (offset) + # 1 TR (center) + # search radar + num_launchers = 8 v = self.vehicle( - name + " Unit #{nr}".format(nr=3+x), - AirDefence.SAM_SA_10_S_300PS_LN_5P85D, + name + " Unit #{nr}-sr".format(nr=i), + AirDefence.SAM_SA_10_S_300PS_SR_64H6E, + ) + v.position.x = position.x + v.position.y = position.y + (i - 1) * 20 + v.heading = heading + vg.add_unit(v) + # track radar + v = self.vehicle( + name + " Unit #{nr}-tr".format(nr=i), + AirDefence.SAM_SA_10_S_300PS_TR_30N6, ) - v.position.x = plop_positions[x][0] - v.position.y = plop_positions[x][1] - v.heading = plop_positions[x][2] + center_x = position.x + randint(20, 40) + center_y = position.y + (i - 1) * 20 + + v.position.x = center_x + v.position.y = center_y + v.heading = heading + vg.add_unit(v) + # command center + v = self.vehicle( + name + " Unit #{nr}-c".format(nr=i), + AirDefence.SAM_SA_10_S_300PS_CP_54K6, + ) + + center_x = position.x + randint(40, 60) + center_y = position.y + (i - 1) * 20 + + v.position.x = center_x + v.position.y = center_y + v.heading = heading + vg.add_unit(v) + + plop_positions = determine_positions( + position, + heading, + num_units=num_launchers, + launcher_distance=150, + coverage=360, + ) + for x in range(0, num_launchers): + v = self.vehicle( + name + " Unit #{nr}-{x}".format(nr=i, x=x), + AirDefence.SAM_SA_10_S_300PS_LN_5P85D, + ) + + v.position.x = plop_positions[x][0] + v.position.y = plop_positions[x][1] + v.heading = plop_positions[x][2] + vg.add_unit(v) + else: + v = self.vehicle(name + " Unit #{nr}-sam".format(nr=i), _type) + v.position.x = position.x + v.position.y = position.y + (i - 1) * 20 + v.heading = heading vg.add_unit(v) - else: - v = self.vehicle(name + " Unit #{nr}".format(nr=1), _type) - v.position.x = position.x - v.position.y = position.y # + (i - 1) * 20 - v.heading = heading - vg.add_unit(v) wp = vg.add_waypoint(vg.units[0].position, move_formation, 0) wp.ETA_locked = True - if _type.eplrs: wp.tasks.append(task.EPLRS(self.next_eplrs("vehicle"))) @@ -296,7 +323,8 @@ class AAConflictGenerator: country=self.conflict.defenders_country, name=namegen.next_unit_name(self.conflict.defenders_country, type), _type=type, - position=p) + position=p, + group_size=1) class ExtraAAConflictGenerator: diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index 4cefee90..8c159487 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -116,12 +116,7 @@ def farp_aa(mission_obj, country, name, position: mapping.Point): units = [ AirDefence.SPAAA_ZSU_23_4_Shilka, AirDefence.AAA_ZU_23_Closed, - AirDefence.AAA_ZU_23_Emplacement, - AirDefence.AAA_ZU_23_on_Ural_375, - AirDefence.AAA_ZU_23_Insurgent_Closed, - AirDefence.AAA_ZU_23_Insurgent_on_Ural_375, Armor.MBT_T_55, - Armor.IFV_BMP_3, ] v = mission_obj.vehicle(name + "_AAA", random.choice(units)) diff --git a/gen/triggergen.py b/gen/triggergen.py index 6d529475..942a1157 100644 --- a/gen/triggergen.py +++ b/gen/triggergen.py @@ -23,7 +23,8 @@ REGROUP_ALT = 5000 TRIGGER_WAYPOINT_OFFSET = 2 TRIGGER_MIN_DISTANCE_FROM_START = 10000 -TRIGGER_RADIUS_MINIMUM = 20000 +# modified since we now have advanced SAM units +TRIGGER_RADIUS_MINIMUM = 3000000 TRIGGER_RADIUS_SMALL = 50000 TRIGGER_RADIUS_MEDIUM = 100000 @@ -62,8 +63,12 @@ class TriggersGenerator: if coalition_name == enemy_coalition: for plane_group in country.plane_group + country.helicopter_group: plane_group.late_activation = True - #activate_by_trigger.append(plane_group) - self.delayed_trigger(plane_group, flag_id) + self.delayed_trigger( + plane_group, + flag_id, + flag_lower_limit=180, + flag_upper_limit=2400, + ) flag_id += 1 for vehicle_group in country.vehicle_group: @@ -148,21 +153,38 @@ class TriggersGenerator: for vehicle_group in country.vehicle_group: vehicle_group.set_skill(Skill(skill_level[1])) - def delayed_trigger(self, group, flag_id): + def delayed_trigger(self, group, flag_id, flag_lower_limit, flag_upper_limit): + """ + Create an activation trigger a randomized amount after the main activation occurs + :param group: + group to activate + :param flag_id: + ID of the flag to use + :param flag_lower_limit: + lower limit of what the random time can be (shouldn't be negative) + :param flag_upper_limit: + uopper limit of what the random time can be + :return: + N/A + """ trigger_one = TriggerOnce(Event.NoEvent, "Activation trigger") - trigger_one.add_action(SetFlagValue(flag_id, random.randint(0, 1200))) + trigger_one.add_condition(FlagEquals(1, 1)) + trigger_one.add_action(SetFlagValue(flag_id, 1)) trigger_two = TriggerCondition() - trigger_two.add_condition(TimeSinceFlag(flag_id, seconds=1)) - trigger_two.add_action(DecreaseFlag(flag_id, 1)) - - trigger_three = TriggerOnce() - trigger_three.add_condition(FlagEquals(flag_id, 1)) - trigger_three.add_action(ActivateGroup(group.id)) + trigger_two.add_condition( + TimeSinceFlag( + flag_id, + seconds=random.randint( + flag_lower_limit, + flag_upper_limit + ) + ) + ) + trigger_two.add_action(ActivateGroup(group.id)) self.mission.triggerrules.triggers.append(trigger_one) self.mission.triggerrules.triggers.append(trigger_two) - self.mission.triggerrules.triggers.append(trigger_three) def generate(self, player_cp: ControlPoint, is_quick: bool, activation_trigger_radius: int, awacs_enabled: bool): player_coalition = self.game.player_country == "USA" and "blue" or "red" diff --git a/theater/base.py b/theater/base.py index e77057df..65e8b473 100644 --- a/theater/base.py +++ b/theater/base.py @@ -8,6 +8,7 @@ from dcs.vehicles import * from dcs.task import * from game import db +from gen import aaa STRENGTH_AA_ASSEMBLE_MIN = 0.2 PLANES_SCRAMBLE_MIN_BASE = 2 @@ -119,12 +120,33 @@ class Base: target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count def commit_losses(self, units_lost: typing.Dict[typing.Any, int]): + # advanced SAM sites have multiple units - this code was not at all set up to handle that + # to avoid having to restructure a bunch of upstream code, we track total destroyed units and + # use that to determine if a site was destroyed + # this can be thought of as the enemy re-distributing parts of SAM sites to keep as many + # operational as possible (pulling specific units from ...storage... to bring them back online + # if non-letal damage was done) + # in the future, I may add more depth to this (e.g. a base having a certain number of spares and tracking + # the number of pieces of each site), but for now this is what we get + sams_destroyed = {} + # we count complex SAM sites at the end - don't double count + aa_skip = [ + AirDefence.SAM_SA_6_Kub_LN_2P25, + AirDefence.SAM_SA_3_S_125_LN_5P73, + AirDefence.SAM_SA_11_Buk_LN_9A310M1 + ] for unit_type, count in units_lost.items(): + if unit_type in db.SAM_CONVERT or unit_type in db.SAM_CONVERT['except']: + # unit is part of an advanced SAM site, which means it will fail the below check + try: + sams_destroyed[unit_type] += 1 + except KeyError: + sams_destroyed[unit_type] = 1 if unit_type in self.aircraft: target_array = self.aircraft elif unit_type in self.armor: target_array = self.armor - elif unit_type in self.aa: + elif unit_type in self.aa and unit_type not in aa_skip: target_array = self.aa else: print("Base didn't find event type {}".format(unit_type)) @@ -138,6 +160,21 @@ class Base: if target_array[unit_type] == 0: del target_array[unit_type] + # now that we have a complete picture of the SAM sites destroyed, determine if any were destroyed + for sam_site, count in sams_destroyed.items(): + dead_count = aaa.num_sam_dead(sam_site, count) + try: + modified_sam_site = db.SAM_CONVERT[sam_site] + except KeyError: + modified_sam_site = db.SAM_CONVERT[sam_site]['except'] + + self.aa[modified_sam_site] = max( + self.aa[modified_sam_site] - dead_count, + 0 + ) + if self.aa[modified_sam_site] == 0: + del self.aa[modified_sam_site] + def affect_strength(self, amount): self.strength += amount if self.strength > BASE_MAX_STRENGTH: