Merge pull request #649 from Khopa/develop_2_3_x

Release 2.3.3
This commit is contained in:
C. Perreau 2020-12-31 01:26:19 +01:00 committed by GitHub
commit 64c424b9a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
171 changed files with 2066 additions and 681 deletions

76
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at khopa.studio@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

26
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,26 @@
First, note that we have a code of conduct, please follow it in all your interactions with the project.
## Contributing as a non-developer
* Report bugs by opening issues here on Github.
* Help others users on Discord by answering their questions.
* Raise awareness about the project, by making a video and/or a tutorial.
Should you report a bug, please use the search bar at the top of the page to see if it has already been reported.
Note that you may need to remove the filter for open bugs if it's something we've recently fixed.
## Making content for Liberation
You can create new campaigns : See [campaign creation wiki](https://github.com/Khopa/dcs_liberation/wiki/Custom-Campaigns).
You can also improve existing campaigns.
You can then submit new campaigns on the "campaigns" channel on Discord, or by making a pull request if you are comfortable with git.
## Develop new features
If you want to develop a new feature, we recommend you first open an issue describing the new feature and discuss it with us on Discord before starting development.
However, feel free to work on any existing issue.
## Pull requests
Please submit your pull requests on the **develop** branch. We expect a description of its content, and when applicable, a reference to the issue(s) it is resolving.

View File

@ -1,3 +1,37 @@
# 2.3.3
## Features/Improvements
* **[Campaigns]** Reworked Golan Heights campaign on Syria, (Added FOB and preset locations for SAMS)
* **[Campaigns]** Added a lite version of the Golan Heights campaign
* **[Campaigns]** Reworked Syrian Civil War campaign (Added FOB and preset locations for SAMS)
* **[Campaigns]** Reworked Emirates campaign
* **[Campaigns]** AA units added to frontlines and updated all factions to include some frontline AA units.
* **[Mission Generator]** Infantry will only be generated for APC and IFV groups
* **[Mission Generator]** Infantry squads size is not randomized anymore
* **[Mission Generator]** Infantry squads can have a mortar.
* **[Mission Generator]** SCUD missiles sites will now fire on enemy controls points in range when possible
* **[Factions]** Updated Nato Desert Storm to include F-14A
* **[Factions]** Updated Iraq 1991 factions to include Zsu-57 and Mig-29A
* **[Factions]** Germany 1944, added Stug III and Stug IV
* **[Factions]** Added factions Insurgents (Hard) with better and more weapons
* **[Plugins]** [The EWRS plugin](https://github.com/Bob7heBuilder/EWRS) is now included.
* **[UI]** Added enemy intelligence summary and details window.
## Fixes:
* **[Factions]** AI would never buy artillery units for the frontline - fixed
* **[Factions]** Removed the F-111 unit from the NATO desert storm faction. (Recruiting it would cause crashes in DCS, since it is not a valid unit)
* **[Campaign]** Automatic redeployment of ground units would sometimes fail - fixed
* **[Mission Generator]** Artillery groups would retreat in the wrong direction - fixed
* **[Units]** Fixed SPG_Stryker_M1128_MGS not being in db
* **[UI]** Fixed and added many missing ground units icons
* **[UI]** Ship groups could be replaced by SAM sites in the UI, which would lead to broken mission being generated - fixed
* **[New Game Wizard]** Removed the "mid game" campaign generator option which is currently broken
* **[Mission Generator]** Empty navy groups will no longer be generated
* **[Mission Generator]** Fixed BAI, SEAD, and DEAD flights ocassionally being assigned the wrong targets.
* **[Flight Planner]** Fixed not being able to plan packages against opfor carriers
* **[UI]** Repaired SAMs no longer show as dead.
* **[UI]** Fixed not being able to manage a disbanded site after disbanding and closing the base menu.
# 2.3.2
## Features/Improvements

View File

@ -385,6 +385,7 @@ PRICES = {
Armor.ATGM_M1045_HMMWV_TOW: 8,
Armor.IFV_M2A2_Bradley: 12,
Armor.APC_M1126_Stryker_ICV: 10,
Armor.SPG_M1128_Stryker_MGS: 14,
Armor.ATGM_M1134_Stryker: 12,
Armor.MBT_M60A3_Patton: 16,
Armor.MBT_M1A2_Abrams: 25,
@ -408,6 +409,7 @@ PRICES = {
Artillery.MLRS_BM_21_Grad: 15,
Artillery.MLRS_9K57_Uragan_BM_27: 50,
Artillery.MLRS_9A52_Smerch: 40,
Artillery._2B11_mortar: 4,
Unarmed.Transport_UAZ_469: 3,
Unarmed.Transport_Ural_375: 3,
@ -436,6 +438,7 @@ PRICES = {
Armor.LAC_M8_Greyhound: 8,
Armor.TD_M10_GMC: 14,
Armor.StuG_III_Ausf__G: 12,
Armor.StuG_IV: 14,
Artillery.M12_GMC: 10,
Artillery.Sturmpanzer_IV_Brummbär: 10,
Armor.Daimler_Armoured_Car: 8,
@ -755,6 +758,7 @@ UNIT_BY_TASK = {
Armor.APC_M1126_Stryker_ICV,
Armor.APC_M1126_Stryker_ICV,
Armor.APC_M1126_Stryker_ICV,
Armor.SPG_M1128_Stryker_MGS,
Armor.IFV_MCV_80,
Armor.IFV_MCV_80,
Armor.IFV_MCV_80,
@ -819,6 +823,7 @@ UNIT_BY_TASK = {
Armor.TD_M10_GMC,
Armor.TD_M10_GMC,
Armor.StuG_III_Ausf__G,
Armor.StuG_IV,
Artillery.M12_GMC,
Artillery.Sturmpanzer_IV_Brummbär,
Armor.Daimler_Armoured_Car,
@ -837,6 +842,30 @@ UNIT_BY_TASK = {
Artillery.M12_GMC,
Artillery.Sturmpanzer_IV_Brummbär,
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
AirDefence.AAA_ZSU_57_2,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.SAM_SA_15_Tor_9A331,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SPAAA_Gepard,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Linebacker_M6,
AirDefence.SAM_Chaparral_M48,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Roland_ADS,
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.AAA_8_8cm_Flak_18,
AirDefence.AAA_8_8cm_Flak_36,
AirDefence.AAA_8_8cm_Flak_37,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_Bofors_40mm,
AirDefence.AAA_M1_37mm,
AirDefence.AA_gun_QF_3_7,
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__TOYOTA_GREEN,
@ -862,23 +891,6 @@ UNIT_BY_TASK = {
],
AirDefence: [
# those are listed multiple times here to balance prioritization more into lower tier AAs
AirDefence.AAA_Vulcan_M163,
AirDefence.AAA_Vulcan_M163,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Linebacker_M6,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.AAA_ZU_23_Closed,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_SA_2_LN_SM_90,
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, ],
@ -1262,6 +1274,7 @@ INFANTRY: List[VehicleType] = [
Infantry.Soldier_RPG,
Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4,
Infantry.Soldier_M249,
Artillery._2B11_mortar,
Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK,
Infantry.Paratrooper_RPG_16,
Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4,

View File

@ -152,12 +152,10 @@ class Event:
loss.group.units.remove(loss.unit)
loss.group.units_losts.append(loss.unit)
if not loss.ground_object.alive_unit_count:
loss.ground_object.is_dead = True
def commit_building_losses(self, debriefing: Debriefing) -> None:
for loss in debriefing.building_losses:
loss.ground_object.is_dead = True
loss.ground_object.kill()
self.game.informations.append(Information(
"Building destroyed",
f"{loss.ground_object.dcs_identifier} has been destroyed at "

View File

@ -12,7 +12,6 @@ from dcs.task import CAP, CAS, PinpointStrike
from dcs.vehicles import AirDefence
from game import db
from game.db import PLAYER_BUDGET_BASE, REWARDS
from game.inventory import GlobalAircraftInventory
from game.models.game_stats import GameStats
from game.plugins import LuaPluginManager
@ -27,6 +26,7 @@ from .debriefing import Debriefing
from .event.event import Event, UnitsDeliveryEvent
from .event.frontlineattack import FrontlineAttackEvent
from .factions.faction import Faction
from .income import Income
from .infos.information import Information
from .procurement import ProcurementAi
from .settings import Settings
@ -167,30 +167,14 @@ class Game:
front_line.control_point_a,
front_line.control_point_b)
@property
def budget_reward_amount(self) -> int:
reward = PLAYER_BUDGET_BASE * len(self.theater.player_points())
for cp in self.theater.player_points():
for g in cp.ground_objects:
if g.category in REWARDS.keys() and not g.is_dead:
reward += REWARDS[g.category]
return int(reward * self.settings.player_income_multiplier)
def process_player_income(self):
self.budget += self.budget_reward_amount
self.budget += Income(self, player=True).total
def process_enemy_income(self):
# TODO: Clean up save compat.
if not hasattr(self, "enemy_budget"):
self.enemy_budget = 0
production = 0.0
for enemy_point in self.theater.enemy_points():
for g in enemy_point.ground_objects:
if g.category in REWARDS.keys() and not g.is_dead:
production = production + REWARDS[g.category]
self.enemy_budget += production * self.settings.enemy_income_multiplier
self.enemy_budget += Income(self, player=False).total
def units_delivery_event(self, to_cp: ControlPoint) -> UnitsDeliveryEvent:
event = UnitsDeliveryEvent(attacker_name=self.player_name,

64
game/income.py Normal file
View File

@ -0,0 +1,64 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from game.db import PLAYER_BUDGET_BASE, REWARDS
from game.theater import ControlPoint
if TYPE_CHECKING:
from game import Game
@dataclass(frozen=True)
class BuildingIncome:
name: str
category: str
number: int
income_per_building: int
@property
def income(self) -> int:
return self.number * self.income_per_building
@dataclass(frozen=True)
class ControlPointIncome:
control_point: ControlPoint
income: int
class Income:
def __init__(self, game: Game, player: bool) -> None:
if player:
self.multiplier = game.settings.player_income_multiplier
else:
self.multiplier = game.settings.enemy_income_multiplier
self.control_points = []
self.buildings = []
self.income_per_base = PLAYER_BUDGET_BASE if player else 0
names = set()
for cp in game.theater.control_points_for(player):
self.control_points.append(
ControlPointIncome(cp, self.income_per_base))
for tgo in cp.ground_objects:
names.add(tgo.obj_name)
for name in names:
count = 0
tgos = game.theater.find_ground_objects_by_obj_name(name)
category = tgos[0].category
if category not in REWARDS:
continue
for tgo in tgos:
if not tgo.is_dead:
count += 1
self.buildings.append(BuildingIncome(name, category, count,
REWARDS[category]))
self.from_bases = sum(cp.income for cp in self.control_points)
self.total_buildings = sum(b.income for b in self.buildings)
self.total = ((self.total_buildings + self.from_bases) *
self.multiplier)

View File

@ -1,16 +1,16 @@
from __future__ import annotations
from dataclasses import dataclass
import math
import random
from dataclasses import dataclass
from typing import Iterator, List, Optional, TYPE_CHECKING, Type
from dcs.task import CAP, CAS
from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.unittype import FlyingType, VehicleType
from game import db
from game.factions.faction import Faction
from game.theater import ControlPoint, MissionTarget
from game.theater import ControlPoint, MissionTarget, TYPE_SHORAD
from gen.flights.ai_flight_planner_db import (
capable_aircraft_for_task,
preferred_aircraft_for_task,
@ -74,15 +74,25 @@ class ProcurementAi:
return budget
def random_affordable_ground_unit(
self, budget: int) -> Optional[Type[VehicleType]]:
affordable_units = [u for u in self.faction.frontline_units if
self, budget: int, cp: ControlPoint) -> Optional[Type[VehicleType]]:
affordable_units = [u for u in self.faction.frontline_units + self.faction.artillery_units if
db.PRICES[u] <= budget]
total_number_aa = cp.base.total_frontline_aa + cp.pending_frontline_aa_deliveries_count
total_non_aa = cp.base.total_armor + cp.pending_deliveries_count - total_number_aa
max_aa = math.ceil(total_non_aa/8)
# Limit the number of AA units the AI will buy
if not total_number_aa < max_aa:
for unit in [u for u in affordable_units if u in TYPE_SHORAD]:
affordable_units.remove(unit)
if not affordable_units:
return None
return random.choice(affordable_units)
def reinforce_front_line(self, budget: int) -> int:
if not self.faction.frontline_units:
if not self.faction.frontline_units and not self.faction.artillery_units:
return budget
while budget > 0:
@ -91,7 +101,7 @@ class ProcurementAi:
break
cp = random.choice(candidates)
unit = self.random_affordable_ground_unit(budget)
unit = self.random_affordable_ground_unit(budget, cp)
if unit is None:
# Can't afford any more units.
break

View File

@ -9,6 +9,7 @@ from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.vehicles import AirDefence, Armor
from game import db
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
STRENGTH_AA_ASSEMBLE_MIN = 0.2
PLANES_SCRAMBLE_MIN_BASE = 2
@ -36,6 +37,10 @@ class Base:
def total_armor(self) -> int:
return sum(self.armor.values())
@property
def total_frontline_aa(self) -> int:
return sum([v for k, v in self.armor.items() if k in TYPE_SHORAD])
@property
def total_aa(self) -> int:
return sum(self.aa.values())
@ -98,11 +103,11 @@ class Base:
self.armor = {k: v for k, v in self.armor.items() if k in applicable_units}
def commision_units(self, units: typing.Dict[typing.Any, int]):
for value in units.values():
assert value > 0
assert value == math.floor(value)
for unit_type, unit_count in units.items():
if unit_count <= 0:
continue
for_task = db.unit_task(unit_type)
target_dict = None

View File

@ -503,8 +503,13 @@ class ConflictTheater:
)
return new_point
def control_points_for(self, player: bool) -> Iterator[ControlPoint]:
for point in self.controlpoints:
if point.captured == player:
yield point
def player_points(self) -> List[ControlPoint]:
return [point for point in self.controlpoints if point.captured]
return list(self.control_points_for(player=True))
def conflicts(self, from_player=True) -> Iterator[FrontLine]:
for cp in [x for x in self.controlpoints if x.captured == from_player]:
@ -512,7 +517,7 @@ class ConflictTheater:
yield FrontLine(cp, connected_point, self)
def enemy_points(self) -> List[ControlPoint]:
return [point for point in self.controlpoints if not point.captured]
return list(self.control_points_for(player=False))
def closest_control_point(self, point: Point) -> ControlPoint:
closest = self.controlpoints[0]

View File

@ -20,6 +20,7 @@ from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.unittype import FlyingType
from game import db
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
from gen.runways import RunwayAssigner, RunwayData
from gen.ground_forces.combat_stance import CombatStance
from .base import Base
@ -457,6 +458,26 @@ class ControlPoint(MissionTarget, ABC):
u.position.x = u.position.x + delta.x
u.position.y = u.position.y + delta.y
@property
def pending_frontline_aa_deliveries_count(self):
"""
Get number of pending frontline aa units
"""
if self.pending_unit_deliveries:
return sum([v for k,v in self.pending_unit_deliveries.units.items() if k in TYPE_SHORAD])
else:
return 0
@property
def pending_deliveries_count(self):
"""
Get number of pending units
"""
if self.pending_unit_deliveries:
return sum([v for k, v in self.pending_unit_deliveries.units.items()])
else:
return 0
class Airfield(ControlPoint):
@ -529,7 +550,6 @@ class NavalControlPoint(ControlPoint, ABC):
return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
yield from super().mission_types(for_player)
from gen.flights.flight import FlightType
if self.is_friendly(for_player):
yield from [
@ -540,6 +560,7 @@ class NavalControlPoint(ControlPoint, ABC):
]
else:
yield FlightType.ANTISHIP
yield from super().mission_types(for_player)
@property
def heading(self) -> int:

View File

@ -85,10 +85,13 @@ class TheaterGroundObject(MissionTarget):
self.dcs_identifier = dcs_identifier
self.airbase_group = airbase_group
self.sea_object = sea_object
self.is_dead = False
# TODO: There is never more than one group.
self.groups: List[Group] = []
@property
def is_dead(self) -> bool:
return self.alive_unit_count == 0
@property
def units(self) -> List[Unit]:
"""
@ -161,6 +164,9 @@ class BuildingGroundObject(TheaterGroundObject):
sea_object=False
)
self.object_id = object_id
# Other TGOs track deadness based on the number of alive units, but
# buildings don't have groups assigned to the TGO.
self._dead = False
@property
def group_name(self) -> str:
@ -171,6 +177,15 @@ class BuildingGroundObject(TheaterGroundObject):
def waypoint_name(self) -> str:
return f"{super().waypoint_name} #{self.object_id}"
@property
def is_dead(self) -> bool:
if not hasattr(self, "_dead"):
self._dead = False
return self._dead
def kill(self) -> None:
self._dead = True
class NavalGroundObject(TheaterGroundObject):
def mission_types(self, for_player: bool) -> Iterator[FlightType]:

View File

@ -2,7 +2,7 @@ from pathlib import Path
def _build_version_string() -> str:
components = ["2.3.2"]
components = ["2.3.3"]
build_number_path = Path("resources/buildnumber")
if build_number_path.exists():
with build_number_path.open("r") as build_number_file:

View File

@ -1474,10 +1474,7 @@ class BaiIngressBuilder(PydcsWaypointBuilder):
target_group = self.package.target
if isinstance(target_group, TheaterGroundObject):
# Match search is used due to TheaterGroundObject.name not matching
# the Mission group name because of SkyNet prefixes.
tgroup = self.mission.find_group(target_group.group_name,
search="match")
tgroup = self.mission.find_group(target_group.group_name)
if tgroup is not None:
task = AttackGroup(tgroup.id, weapon_type=WeaponType.Auto)
task.params["attackQtyLimit"] = False
@ -1527,10 +1524,7 @@ class DeadIngressBuilder(PydcsWaypointBuilder):
target_group = self.package.target
if isinstance(target_group, TheaterGroundObject):
# Match search is used due to TheaterGroundObject.name not matching
# the Mission group name because of SkyNet prefixes.
tgroup = self.mission.find_group(target_group.group_name,
search="match")
tgroup = self.mission.find_group(target_group.group_name)
if tgroup is not None:
task = AttackGroup(tgroup.id, weapon_type=WeaponType.Guided)
task.params["expend"] = "All"
@ -1593,10 +1587,7 @@ class SeadIngressBuilder(PydcsWaypointBuilder):
target_group = self.package.target
if isinstance(target_group, TheaterGroundObject):
# Match search is used due to TheaterGroundObject.name not matching
# the Mission group name because of SkyNet prefixes.
tgroup = self.mission.find_group(target_group.group_name,
search="match")
tgroup = self.mission.find_group(target_group.group_name)
if tgroup is not None:
waypoint.add_task(EngageTargetsInZone(
position=tgroup.position,

View File

@ -50,6 +50,8 @@ FIGHT_DISTANCE = 3500
RANDOM_OFFSET_ATTACK = 250
INFANTRY_GROUP_SIZE = 5
@dataclass(frozen=True)
class JtacInfo:
@ -226,7 +228,7 @@ class GroundConflictGenerator:
heading=forward_heading,
move_formation=PointAction.OffRoad)
for i in range(random.randint(3, 10)):
for i in range(INFANTRY_GROUP_SIZE):
u = random.choice(possible_infantry_units)
position = infantry_position.random_point_within(55, 5)
self.mission.vehicle_group(
@ -281,7 +283,7 @@ class GroundConflictGenerator:
# Hold position
dcs_group.points[1].tasks.append(Hold())
retreat = self.find_retreat_point(dcs_group, heading_sum(forward_heading, 180), (int)(RETREAT_DISTANCE/3))
retreat = self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE/3))
dcs_group.add_waypoint(dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad)
dcs_group.points[2].tasks.append(Hold())
dcs_group.add_waypoint(retreat, PointAction.OffRoad)
@ -675,12 +677,14 @@ class GroundConflictGenerator:
else:
g.set_skill(self.game.settings.enemy_vehicle_skill)
positioned_groups.append((g, group))
self.gen_infantry_group_for_group(
g,
is_player,
self.mission.country(country),
opposite_heading(spawn_heading)
)
if group.role in [CombatGroupRole.APC, CombatGroupRole.IFV]:
self.gen_infantry_group_for_group(
g,
is_player,
self.mission.country(country),
opposite_heading(spawn_heading)
)
else:
logging.warning(f"Unable to get valid position for {group}")

View File

@ -32,6 +32,9 @@ class ChineseNavyGroupGenerator(ShipGroupGenerator):
else:
include_cc = False
if not any([include_frigate, include_dd, include_cc]):
include_frigate = True
if include_frigate:
self.add_unit(Type_054A_Frigate, "FF1", self.position.x + 1200, self.position.y + 900, self.heading)
self.add_unit(Type_054A_Frigate, "FF2", self.position.x + 1200, self.position.y - 900, self.heading)

View File

@ -35,6 +35,9 @@ class RussianNavyGroupGenerator(ShipGroupGenerator):
else:
include_cc = False
if not any([include_frigate, include_dd, include_cc]):
include_frigate = True
if include_frigate:
frigate_type = random.choice([FFL_1124_4_Grisha, FSG_1241_1MP_Molniya])
self.add_unit(frigate_type, "FF1", self.position.x + 1200, self.position.y + 900, self.heading)

View File

@ -3,180 +3,14 @@ from enum import Enum
from typing import Dict, List
from dcs.unittype import VehicleType
from dcs.vehicles import Armor, Artillery, Infantry, Unarmed
import pydcs_extensions.frenchpack.frenchpack as frenchpack
from game.theater import ControlPoint
from gen.ground_forces.ai_ground_planner_db import *
from gen.ground_forces.combat_stance import CombatStance
TYPE_TANKS = [
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_72B3,
Armor.MBT_T_80U,
Armor.MBT_T_90,
Armor.MBT_Leopard_2,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leclerc,
Armor.MBT_Challenger_II,
Armor.MBT_M1A2_Abrams,
Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_Mk__4,
Armor.ZTZ_96B,
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.StuG_IV,
Armor.CT_Centaur_IV,
Armor.CT_Cromwell_IV,
Armor.HIT_Churchill_VII,
Armor.LT_Mk_VII_Tetrarch,
# Mods
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__KAMIKAZE,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
]
TYPE_ATGM = [
Armor.ATGM_M1045_HMMWV_TOW,
Armor.ATGM_M1134_Stryker,
Armor.IFV_BMP_2,
# WW2 (Tank Destroyers)
Armor.M30_Cargo_Carrier,
Armor.TD_Jagdpanzer_IV,
Armor.TD_Jagdpanther_G1,
Armor.TD_M10_GMC,
# Mods
frenchpack.VBAE_CRAB_MMP,
frenchpack.VAB_MEPHISTO,
frenchpack.TRM_2000_PAMELA,
]
TYPE_IFV = [
Armor.IFV_BMP_3,
Armor.IFV_BMP_2,
Armor.IFV_BMP_1,
Armor.IFV_Marder,
Armor.IFV_MCV_80,
Armor.IFV_LAV_25,
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.IFV_M2A2_Bradley,
Armor.IFV_BMD_1,
Armor.ZBD_04A,
# WW2
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.LAC_M8_Greyhound,
Armor.Daimler_Armoured_Car,
# Mods
frenchpack.ERC_90,
frenchpack.VBAE_CRAB,
frenchpack.VAB_T20_13
]
TYPE_APC = [
Armor.APC_M1043_HMMWV_Armament,
Armor.APC_M1126_Stryker_ICV,
Armor.APC_M113,
Armor.APC_BTR_80,
Armor.APC_BTR_82A,
Armor.APC_MTLB,
Armor.APC_M2A1,
Armor.APC_Cobra,
Armor.APC_Sd_Kfz_251,
Armor.APC_AAV_7,
Armor.TPz_Fuchs,
Armor.ARV_BRDM_2,
Armor.ARV_BTR_RD,
Armor.FDDM_Grad,
# WW2
Armor.APC_M2A1,
Armor.APC_Sd_Kfz_251,
# Mods
frenchpack.VAB__50,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
]
TYPE_ARTILLERY = [
Artillery.MLRS_9A52_Smerch,
Artillery.SPH_2S1_Gvozdika,
Artillery.SPH_2S3_Akatsia,
Artillery.MLRS_BM_21_Grad,
Artillery.MLRS_9K57_Uragan_BM_27,
Artillery.SPH_M109_Paladin,
Artillery.MLRS_M270,
Artillery.SPH_2S9_Nona,
Artillery.SpGH_Dana,
Artillery.SPH_2S19_Msta,
Artillery.MLRS_FDDM,
# WW2
Artillery.Sturmpanzer_IV_Brummbär,
Artillery.M12_GMC
]
TYPE_LOGI = [
Unarmed.Transport_M818,
Unarmed.Transport_KAMAZ_43101,
Unarmed.Transport_Ural_375,
Unarmed.Transport_GAZ_66,
Unarmed.Transport_GAZ_3307,
Unarmed.Transport_GAZ_3308,
Unarmed.Transport_Ural_4320_31_Armored,
Unarmed.Transport_Ural_4320T,
Unarmed.Blitz_3_6_6700A,
Unarmed.Kübelwagen_82,
Unarmed.Sd_Kfz_7,
Unarmed.Sd_Kfz_2,
Unarmed.Willys_MB,
Unarmed.Land_Rover_109_S3,
Unarmed.Land_Rover_101_FC,
# Mods
frenchpack.VBL,
frenchpack.VAB,
]
TYPE_INFANTRY = [
Infantry.Infantry_Soldier_Insurgents,
Infantry.Soldier_AK,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_Mauser_98,
Infantry.Infantry_SMLE_No_4_Mk_1,
Infantry.Georgian_soldier_with_M4,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_RPG_16,
Infantry.Soldier_M249,
Infantry.Infantry_M4,
Infantry.Soldier_RPG,
]
MAX_COMBAT_GROUP_PER_CP = 10
class CombatGroupRole(Enum):
TANK = 1
APC = 2
@ -224,6 +58,7 @@ class CombatGroup:
s += "UNITS " + self.units[0].name + " * " + str(len(self.units))
return s
class GroundPlanner:
def __init__(self, cp:ControlPoint, game):
@ -243,7 +78,6 @@ class GroundPlanner:
self.units_per_cp[cp.id] = []
self.reserve: List[CombatGroup] = []
def plan_groundwar(self):
if hasattr(self.cp, 'stance'):
@ -275,6 +109,9 @@ class GroundPlanner:
elif key in TYPE_ATGM:
collection = self.atgm_group
role = CombatGroupRole.ATGM
elif key in TYPE_SHORAD:
collection = self.shorad_groups
role = CombatGroupRole.SHORAD
else:
print("Warning unit type not handled by ground generator")
print(key)
@ -282,12 +119,16 @@ class GroundPlanner:
available = self.cp.base.armor[key]
while available > 0:
n = random.choice(group_size_choice)
if n > available:
if available >= 2:
n = 2
else:
n = 1
if role == CombatGroupRole.SHORAD:
n = 1
else:
n = random.choice(group_size_choice)
if n > available:
if available >= 2:
n = 2
else:
n = 1
available -= n
group = CombatGroup(role)

View File

@ -0,0 +1,199 @@
from dcs.vehicles import AirDefence, Infantry, Unarmed, Artillery, Armor
from pydcs_extensions.frenchpack import frenchpack
TYPE_TANKS = [
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_72B3,
Armor.MBT_T_80U,
Armor.MBT_T_90,
Armor.MBT_Leopard_2,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leclerc,
Armor.MBT_Challenger_II,
Armor.MBT_M1A2_Abrams,
Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_Mk__4,
Armor.ZTZ_96B,
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.StuG_IV,
Armor.CT_Centaur_IV,
Armor.CT_Cromwell_IV,
Armor.HIT_Churchill_VII,
Armor.LT_Mk_VII_Tetrarch,
# Mods
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__KAMIKAZE,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
]
TYPE_ATGM = [
Armor.ATGM_M1045_HMMWV_TOW,
Armor.ATGM_M1134_Stryker,
Armor.IFV_BMP_2,
# WW2 (Tank Destroyers)
Armor.M30_Cargo_Carrier,
Armor.TD_Jagdpanzer_IV,
Armor.TD_Jagdpanther_G1,
Armor.TD_M10_GMC,
# Mods
frenchpack.VBAE_CRAB_MMP,
frenchpack.VAB_MEPHISTO,
frenchpack.TRM_2000_PAMELA,
]
TYPE_IFV = [
Armor.IFV_BMP_3,
Armor.IFV_BMP_2,
Armor.IFV_BMP_1,
Armor.IFV_Marder,
Armor.IFV_MCV_80,
Armor.IFV_LAV_25,
Armor.SPG_M1128_Stryker_MGS,
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.IFV_M2A2_Bradley,
Armor.IFV_BMD_1,
Armor.ZBD_04A,
# WW2
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.LAC_M8_Greyhound,
Armor.Daimler_Armoured_Car,
# Mods
frenchpack.ERC_90,
frenchpack.VBAE_CRAB,
frenchpack.VAB_T20_13
]
TYPE_APC = [
Armor.APC_M1043_HMMWV_Armament,
Armor.APC_M1126_Stryker_ICV,
Armor.APC_M113,
Armor.APC_BTR_80,
Armor.APC_BTR_82A,
Armor.APC_MTLB,
Armor.APC_M2A1,
Armor.APC_Cobra,
Armor.APC_Sd_Kfz_251,
Armor.APC_AAV_7,
Armor.TPz_Fuchs,
Armor.ARV_BRDM_2,
Armor.ARV_BTR_RD,
Armor.FDDM_Grad,
# WW2
Armor.APC_M2A1,
Armor.APC_Sd_Kfz_251,
# Mods
frenchpack.VAB__50,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
]
TYPE_ARTILLERY = [
Artillery.MLRS_9A52_Smerch,
Artillery.SPH_2S1_Gvozdika,
Artillery.SPH_2S3_Akatsia,
Artillery.MLRS_BM_21_Grad,
Artillery.MLRS_9K57_Uragan_BM_27,
Artillery.SPH_M109_Paladin,
Artillery.MLRS_M270,
Artillery.SPH_2S9_Nona,
Artillery.SpGH_Dana,
Artillery.SPH_2S19_Msta,
Artillery.MLRS_FDDM,
# WW2
Artillery.Sturmpanzer_IV_Brummbär,
Artillery.M12_GMC
]
TYPE_LOGI = [
Unarmed.Transport_M818,
Unarmed.Transport_KAMAZ_43101,
Unarmed.Transport_Ural_375,
Unarmed.Transport_GAZ_66,
Unarmed.Transport_GAZ_3307,
Unarmed.Transport_GAZ_3308,
Unarmed.Transport_Ural_4320_31_Armored,
Unarmed.Transport_Ural_4320T,
Unarmed.Blitz_3_6_6700A,
Unarmed.Kübelwagen_82,
Unarmed.Sd_Kfz_7,
Unarmed.Sd_Kfz_2,
Unarmed.Willys_MB,
Unarmed.Land_Rover_109_S3,
Unarmed.Land_Rover_101_FC,
# Mods
frenchpack.VBL,
frenchpack.VAB,
]
TYPE_INFANTRY = [
Infantry.Infantry_Soldier_Insurgents,
Infantry.Soldier_AK,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_Mauser_98,
Infantry.Infantry_SMLE_No_4_Mk_1,
Infantry.Georgian_soldier_with_M4,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_RPG_16,
Infantry.Soldier_M249,
Infantry.Infantry_M4,
Infantry.Soldier_RPG,
]
TYPE_SHORAD = [
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
AirDefence.AAA_ZSU_57_2,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.SAM_SA_15_Tor_9A331,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SPAAA_Gepard,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Linebacker_M6,
AirDefence.SAM_Chaparral_M48,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Roland_ADS,
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.AAA_8_8cm_Flak_18,
AirDefence.AAA_8_8cm_Flak_36,
AirDefence.AAA_8_8cm_Flak_37,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_Bofors_40mm,
AirDefence.AAA_M1_37mm,
AirDefence.AA_gun_QF_3_7,
]

View File

@ -9,20 +9,21 @@ from __future__ import annotations
import logging
import random
from typing import Dict, Iterator, Optional, TYPE_CHECKING, Type
from typing import Dict, Iterator, Optional, TYPE_CHECKING, Type, List
from dcs import Mission
from dcs import Mission, Point
from dcs.country import Country
from dcs.statics import fortification_map, warehouse_map
from dcs.task import (
ActivateBeaconCommand,
ActivateICLSCommand,
EPLRS,
OptAlarmState,
OptAlarmState, FireAtPoint,
)
from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup
from dcs.unittype import StaticType, UnitType
from dcs.vehicles import vehicle_map
from game import db
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
@ -31,7 +32,7 @@ from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import (
BuildingGroundObject, CarrierGroundObject,
GenericCarrierGroundObject,
LhaGroundObject, ShipGroundObject,
LhaGroundObject, ShipGroundObject, MissileSiteGroundObject,
)
from game.unitmap import UnitMap
from game.utils import knots_to_kph, kph_to_mps, mps_to_kph
@ -50,7 +51,7 @@ AA_CP_MIN_DISTANCE = 40000
class GenericGroundObjectGenerator:
"""An unspecialized ground object generator.
Currently used only for SAM and missile (V1/V2) sites.
Currently used only for SAM
"""
def __init__(self, ground_object: TheaterGroundObject, country: Country,
game: Game, mission: Mission, unit_map: UnitMap) -> None:
@ -111,6 +112,55 @@ class GenericGroundObjectGenerator:
persistence_group, miz_group)
class MissileSiteGenerator(GenericGroundObjectGenerator):
def generate(self) -> None:
super(MissileSiteGenerator, self).generate()
# Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now)
# TODO : Should be pre-planned ?
# TODO : Add delay to task to spread fire task over mission duration ?
for group in self.ground_object.groups:
vg = self.m.find_group(group.name)
targets = self.possible_missile_targets(vg)
if vg is not None and targets:
target = random.choice(targets)
real_target = target.point_from_heading(random.randint(0, 360), random.randint(0, 2500))
vg.points[0].add_task(FireAtPoint(real_target))
logging.info("Set up fire task for missile group.")
else:
logging.info("Couldn't setup missile site to fire, group was not generated.")
def possible_missile_targets(self, vg: Group) -> List[Point]:
"""
Find enemy control points in range
:param vg: Vehicle group we are searching a target for (There is always only oe group right now)
:return: List of possible missile targets
"""
targets: List[Point] = []
for cp in self.game.theater.controlpoints:
if cp.captured != self.ground_object.control_point.captured:
distance = cp.position.distance_to_point(vg.position)
if distance < self.missile_site_range:
targets.append(cp.position)
return targets
@property
def missile_site_range(self) -> int:
"""
Get the missile site range
:return: Missile site range
"""
site_range = 0
for group in self.ground_object.groups:
vg = self.m.find_group(group.name)
if vg is not None:
for u in vg.units:
if u.type in vehicle_map:
if vehicle_map[u.type].threat_range > site_range:
site_range = vehicle_map[u.type].threat_range
return site_range
class BuildingSiteGenerator(GenericGroundObjectGenerator):
"""Generator for building sites.
@ -421,8 +471,11 @@ class GroundObjectsGenerator:
generator = ShipObjectGenerator(
ground_object, country, self.game, self.m,
self.unit_map)
elif isinstance(ground_object, MissileSiteGroundObject):
generator = MissileSiteGenerator(
ground_object, country, self.game, self.m,
self.unit_map)
else:
generator = GenericGroundObjectGenerator(
ground_object, country, self.game, self.m,
self.unit_map)

2
pydcs

@ -1 +1 @@
Subproject commit edc87fab1d65d4e4153c84006f537e6ae6b0671a
Subproject commit 059c88c91b5be4b5b6406249a52527c3ccea3db9

View File

@ -1,6 +1,7 @@
from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QPushButton
import qt_ui.uiconstants as CONST
from game.income import Income
from qt_ui.windows.finances.QFinancesMenu import QFinancesMenu
@ -41,7 +42,7 @@ class QBudgetBox(QGroupBox):
return
self.game = game
self.setBudget(self.game.budget, self.game.budget_reward_amount)
self.setBudget(self.game.budget, Income(self.game, player=True).total)
self.finances.setEnabled(True)
def openFinances(self):

107
qt_ui/widgets/QIntelBox.py Normal file
View File

@ -0,0 +1,107 @@
from typing import Optional
from PySide2.QtWidgets import (
QGridLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QPushButton,
)
from game import Game
from game.income import Income
from qt_ui.windows.intel import IntelWindow
class QIntelBox(QGroupBox):
def __init__(self, game: Game) -> None:
super().__init__("Intel")
self.setProperty("style", "IntelSummary")
self.game = game
columns = QHBoxLayout()
self.setLayout(columns)
summary = QGridLayout()
columns.addLayout(summary)
summary.addWidget(QLabel("Air superiority:"), 0, 0)
self.air_strength = QLabel()
summary.addWidget(self.air_strength, 0, 1)
summary.addWidget(QLabel("Front line:"), 1, 0)
self.ground_strength = QLabel()
summary.addWidget(self.ground_strength, 1, 1)
summary.addWidget(QLabel("Economic strength:"), 2, 0)
self.economic_strength = QLabel()
summary.addWidget(self.economic_strength, 2, 1)
details = QPushButton("Details")
columns.addWidget(details)
details.clicked.connect(self.open_details_window)
self.update_summary()
self.details_window: Optional[IntelWindow] = None
def set_game(self, game: Optional[Game]) -> None:
self.game = game
self.update_summary()
@staticmethod
def forces_strength_text(own: int, enemy: int) -> str:
if not enemy:
return "enemy eliminated"
ratio = own / enemy
if ratio < 0.6:
return "outnumbered"
if ratio < 0.8:
return "slightly outnumbered"
if ratio < 1.2:
return "evenly matched"
if ratio < 1.4:
return "slight advantage"
return "strong advantage"
def economic_strength_text(self) -> str:
assert self.game is not None
own = Income(self.game, player=True).total
enemy = Income(self.game, player=False).total
if not enemy:
return "enemy economy ruined"
ratio = own / enemy
if ratio < 0.6:
return "strong disadvantage"
if ratio < 0.8:
return "slight disadvantage"
if ratio < 1.2:
return "evenly matched"
if ratio < 1.4:
return "slight advantage"
return "strong advantage"
def update_summary(self) -> None:
if self.game is None:
self.air_strength.setText("no data")
self.ground_strength.setText("no data")
self.economic_strength.setText("no data")
return
data = self.game.game_stats.data_per_turn[-1]
self.air_strength.setText(self.forces_strength_text(
data.allied_units.aircraft_count,
data.enemy_units.aircraft_count))
self.ground_strength.setText(self.forces_strength_text(
data.allied_units.vehicles_count,
data.enemy_units.vehicles_count))
self.economic_strength.setText(self.economic_strength_text())
def open_details_window(self) -> None:
self.details_window = IntelWindow(self.game)
self.details_window.show()

View File

@ -16,6 +16,7 @@ from gen.flights.traveltime import TotEstimator
from qt_ui.models import GameModel
from qt_ui.widgets.QBudgetBox import QBudgetBox
from qt_ui.widgets.QFactionsInfos import QFactionsInfos
from qt_ui.widgets.QIntelBox import QIntelBox
from qt_ui.widgets.clientslots import MaxPlayerCount
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.QWaitingForMissionResultWindow import \
@ -71,6 +72,8 @@ class QTopPanel(QFrame):
self.statistics.setProperty("style", "btn-primary")
self.statistics.clicked.connect(self.openStatisticsWindow)
self.intel_box = QIntelBox(self.game)
self.buttonBox = QGroupBox("Misc")
self.buttonBoxLayout = QHBoxLayout()
self.buttonBoxLayout.addWidget(self.settings)
@ -90,6 +93,7 @@ class QTopPanel(QFrame):
self.layout.addWidget(self.factionsInfos)
self.layout.addWidget(self.conditionsWidget)
self.layout.addWidget(self.budgetBox)
self.layout.addWidget(self.intel_box)
self.layout.addWidget(self.buttonBox)
self.layout.addStretch(1)
self.layout.addWidget(self.proceedBox)
@ -106,6 +110,7 @@ class QTopPanel(QFrame):
self.statistics.setEnabled(True)
self.conditionsWidget.setCurrentTurn(game.turn, game.conditions)
self.intel_box.set_game(game)
self.budgetBox.setGame(game)
self.factionsInfos.setGame(game)

View File

@ -4,7 +4,7 @@ from PySide2.QtGui import QColor, QPainter
from PySide2.QtWidgets import QAction, QMenu
import qt_ui.uiconstants as const
from game.theater import ControlPoint
from game.theater import ControlPoint, NavalControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
from .QMapObject import QMapObject
@ -108,7 +108,8 @@ class QMapControlPoint(QMapObject):
def open_new_package_dialog(self) -> None:
"""Extends the default packagedialog to redirect to base menu for red air base."""
if not self.control_point.captured:
self.on_click()
else:
super(QMapControlPoint, self).open_new_package_dialog()
is_navy = isinstance(self.control_point, NavalControlPoint)
if self.control_point.captured or is_navy:
super().open_new_package_dialog()
return
self.on_click()

View File

@ -47,7 +47,7 @@ class QMapObject(QGraphicsRectItem):
object_details_action.triggered.connect(self.on_click)
menu.addAction(object_details_action)
# Not all locations have valid objetives. Off-map spawns, for example,
# Not all locations have valid objectives. Off-map spawns, for example,
# have no mission types.
if list(self.mission_target.mission_types(for_player=True)):
new_package_action = QAction(f"New package")

View File

@ -7,7 +7,8 @@ from PySide2.QtWidgets import (
QWidget,
)
from game.theater import Airport, ControlPoint
from game.theater import Airport, ControlPoint, Fob
from game.theater.theatergroundobject import BuildingGroundObject
from qt_ui.windows.basemenu.base_defenses.QBaseDefenseGroupInfo import \
QBaseDefenseGroupInfo
@ -30,9 +31,18 @@ class QBaseInformation(QFrame):
scroll_content.setLayout(task_box_layout)
for g in self.cp.ground_objects:
if g.airbase_group and len(g.groups) > 0:
group_info = QBaseDefenseGroupInfo(self.cp, g, self.game)
task_box_layout.addWidget(group_info)
# Airbase groups are the objects that are hidden on the map because
# they're shown in the base menu.
if not g.airbase_group:
continue
# Of these, we need to ignore the FOB structure itself since that's
# not supposed to be targetable.
if isinstance(self.cp, Fob) and isinstance(g, BuildingGroundObject):
continue
group_info = QBaseDefenseGroupInfo(self.cp, g, self.game)
task_box_layout.addWidget(group_info)
scroll_content.setLayout(task_box_layout)
scroll = QScrollArea()

View File

@ -1,19 +1,69 @@
from PySide2.QtWidgets import QDialog, QGridLayout, QLabel, QFrame, QSizePolicy
from PySide2.QtWidgets import (
QDialog,
QFrame,
QGridLayout,
QLabel,
QSizePolicy,
)
import qt_ui.uiconstants as CONST
from game.db import REWARDS, PLAYER_BUDGET_BASE
from game.game import Game
from game.income import Income
class QHorizontalSeparationLine(QFrame):
def __init__(self):
super().__init__()
self.setMinimumWidth(1)
self.setFixedHeight(20)
self.setFrameShape(QFrame.HLine)
self.setFrameShadow(QFrame.Sunken)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
def __init__(self):
super().__init__()
self.setMinimumWidth(1)
self.setFixedHeight(20)
self.setFrameShape(QFrame.HLine)
self.setFrameShadow(QFrame.Sunken)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
class FinancesLayout(QGridLayout):
def __init__(self, game: Game, player: bool) -> None:
super().__init__()
income = Income(game, player)
self.addWidget(QLabel("<b>Control Points</b>"), 0, 0)
self.addWidget(QLabel(
f"{len(income.control_points)} bases x {income.income_per_base}M"),
0, 1)
self.addWidget(QLabel(f"{income.from_bases}M"), 0, 2)
self.addWidget(QHorizontalSeparationLine(), 1, 0, 1, 3)
buildings = reversed(sorted(income.buildings, key=lambda b: b.income))
row = 2
for row, building in enumerate(buildings, row):
self.addWidget(
QLabel(f"<b>{building.category.upper()} [{building.name}]</b>"),
row, 0)
self.addWidget(QLabel(
f"{building.number} buildings x {building.income_per_building}M"),
row, 1)
rlabel = QLabel(f"{building.income}M")
rlabel.setProperty("style", "green")
self.addWidget(rlabel, row, 2)
self.addWidget(QHorizontalSeparationLine(), row + 1, 0, 1, 3)
self.addWidget(QLabel(
f"Income multiplier: {income.multiplier:.1f}"),
row + 2, 1
)
self.addWidget(QLabel(f"<b>{income.total}M</b>"), row + 2, 2)
if player:
budget = game.budget
else:
budget = game.enemy_budget
self.addWidget(QLabel(f"Balance"), row + 3, 1)
self.addWidget(QLabel(f"<b>{budget}M</b>"), row + 3, 2)
self.setRowStretch(row + 4, 1)
class QFinancesMenu(QDialog):
@ -26,49 +76,4 @@ class QFinancesMenu(QDialog):
self.setWindowIcon(CONST.ICONS["Money"])
self.setMinimumSize(450, 200)
reward = PLAYER_BUDGET_BASE * len(self.game.theater.player_points())
layout = QGridLayout()
layout.addWidget(QLabel("<b>Control Points</b>"), 0, 0)
layout.addWidget(QLabel(str(len(self.game.theater.player_points())) + " bases x " + str(PLAYER_BUDGET_BASE) + "M"), 0, 1)
layout.addWidget(QLabel(str(reward) + "M"), 0, 2)
layout.addWidget(QHorizontalSeparationLine(), 1, 0, 1, 3)
i = 2
for cp in self.game.theater.player_points():
obj_names = []
[obj_names.append(ground_object.obj_name) for ground_object in cp.ground_objects if ground_object.obj_name not in obj_names]
for obj_name in obj_names:
reward = 0
g = None
cat = None
number = 0
for ground_object in cp.ground_objects:
if ground_object.obj_name != obj_name or ground_object.is_dead:
continue
else:
if g is None:
g = ground_object
cat = g.category
if cat in REWARDS.keys():
number = number + 1
reward += REWARDS[cat]
if g is not None and cat in REWARDS.keys():
layout.addWidget(QLabel("<b>" + g.category.upper() + " [" + obj_name + "]</b>"), i, 0)
layout.addWidget(QLabel(str(number) + " buildings x " + str(REWARDS[cat]) + "M"), i, 1)
rlabel = QLabel(str(reward) + "M")
rlabel.setProperty("style", "green")
layout.addWidget(rlabel, i, 2)
i = i + 1
self.setLayout(layout)
layout.addWidget(QHorizontalSeparationLine(), i + 1, 0, 1, 3)
layout.addWidget(QLabel(
f"Income multiplier: {game.settings.player_income_multiplier:.1f}"),
i + 2, 1
)
layout.addWidget(
QLabel("<b>" + str(self.game.budget_reward_amount) + "M </b>"),
i + 2, 2)
self.setLayout(FinancesLayout(game, player=True))

View File

@ -21,6 +21,7 @@ from game import Game, db
from game.data.building_data import FORTIFICATION_BUILDINGS
from game.db import PRICES, PinpointStrike, REWARDS, unit_type_of
from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import NavalGroundObject
from gen.defenses.armor_group_generator import \
generate_armor_group_of_type_and_size
from gen.sam.sam_group_generator import get_faction_possible_sams_generator
@ -81,9 +82,10 @@ class QGroundObjectMenu(QDialog):
self.buy_replace.clicked.connect(self.buy_group)
self.buy_replace.setProperty("style", "btn-success")
if self.total_value > 0:
self.actionLayout.addWidget(self.sell_all_button)
self.actionLayout.addWidget(self.buy_replace)
if not isinstance(self.ground_object, NavalGroundObject):
if self.total_value > 0:
self.actionLayout.addWidget(self.sell_all_button)
self.actionLayout.addWidget(self.buy_replace)
if self.cp.captured and self.ground_object.dcs_identifier == "AA":
self.mainLayout.addLayout(self.actionLayout)

147
qt_ui/windows/intel.py Normal file
View File

@ -0,0 +1,147 @@
import itertools
from PySide2.QtWidgets import (
QDialog,
QFrame,
QGridLayout,
QLabel,
QLayout,
QScrollArea,
QSizePolicy,
QSpacerItem,
QTabWidget,
QVBoxLayout,
QWidget,
)
from game.game import Game
from qt_ui.uiconstants import ICONS
from qt_ui.windows.finances.QFinancesMenu import FinancesLayout
class ScrollingFrame(QFrame):
def __init__(self) -> None:
super().__init__()
widget = QWidget()
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(widget)
self.scrolling_layout = QVBoxLayout()
widget.setLayout(self.scrolling_layout)
self.setLayout(QVBoxLayout())
self.layout().addWidget(scroll_area)
def addWidget(self, widget: QWidget, *args, **kwargs) -> None:
self.scrolling_layout.addWidget(widget, *args, **kwargs)
def addLayout(self, layout: QLayout, *args, **kwargs) -> None:
self.scrolling_layout.addLayout(layout, *args, **kwargs)
class EconomyIntelTab(ScrollingFrame):
def __init__(self, game: Game) -> None:
super().__init__()
self.addLayout(FinancesLayout(game, player=False))
class IntelTableLayout(QGridLayout):
def __init__(self) -> None:
super().__init__()
self.row = itertools.count(0)
def add_header(self, text: str) -> None:
self.addWidget(QLabel(f"<b>{text}</b>"), next(self.row), 0)
def add_spacer(self) -> None:
self.addItem(
QSpacerItem(0, 0, QSizePolicy.Preferred, QSizePolicy.Expanding),
next(self.row), 0)
def add_row(self, text: str, count: int) -> None:
row = next(self.row)
self.addWidget(QLabel(text), row, 0)
self.addWidget(QLabel(str(count)), row, 1)
class AircraftIntelLayout(IntelTableLayout):
def __init__(self, game: Game, player: bool) -> None:
super().__init__()
total = 0
for control_point in game.theater.control_points_for(player):
base = control_point.base
total += base.total_aircraft
if not base.total_aircraft:
continue
self.add_header(control_point.name)
for airframe, count in base.aircraft.items():
if not count:
continue
self.add_row(airframe.id, count)
self.add_spacer()
self.add_row("<b>Total</b>", total)
class AircraftIntelTab(ScrollingFrame):
def __init__(self, game: Game) -> None:
super().__init__()
self.addLayout(AircraftIntelLayout(game, player=False))
class ArmyIntelLayout(IntelTableLayout):
def __init__(self, game: Game, player: bool) -> None:
super().__init__()
total = 0
for control_point in game.theater.control_points_for(player):
base = control_point.base
total += base.total_armor
if not base.total_armor:
continue
self.add_header(control_point.name)
for vehicle, count in base.armor.items():
if not count:
continue
self.add_row(vehicle.id, count)
self.add_spacer()
self.add_row("<b>Total</b>", total)
class ArmyIntelTab(ScrollingFrame):
def __init__(self, game: Game) -> None:
super().__init__()
self.addLayout(ArmyIntelLayout(game, player=False))
class IntelTabs(QTabWidget):
def __init__(self, game: Game):
super().__init__()
self.addTab(EconomyIntelTab(game), "Economy")
self.addTab(AircraftIntelTab(game), "Air forces")
self.addTab(ArmyIntelTab(game), "Ground forces")
class IntelWindow(QDialog):
def __init__(self, game: Game):
super().__init__()
self.game = game
self.setModal(True)
self.setWindowTitle("Intelligence")
self.setWindowIcon(ICONS["Statistics"])
self.setMinimumSize(600, 500)
layout = QVBoxLayout()
self.setLayout(layout)
layout.addWidget(IntelTabs(game), stretch=1)

View File

@ -81,7 +81,7 @@ class NewGameWizard(QtWidgets.QWizard):
enemy_budget=int(self.field("enemy_starting_money")),
# QSlider forces integers, so we use 1 to 50 and divide by 10 to
# give 0.1 to 5.0.
midgame=self.field("midGame"),
midgame=False,
inverted=self.field("invertMap"),
no_carrier=self.field("no_carrier"),
no_lha=self.field("no_lha"),
@ -271,10 +271,10 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
mapSettingsLayout.addWidget(QtWidgets.QLabel("Invert Map"), 0, 0)
mapSettingsLayout.addWidget(invertMap, 0, 1)
mapSettingsLayout.addWidget(QtWidgets.QLabel("Start at mid game"), 1, 0)
midgame = QtWidgets.QCheckBox()
self.registerField('midGame', midgame)
mapSettingsLayout.addWidget(midgame, 1, 1)
#mapSettingsLayout.addWidget(QtWidgets.QLabel("Start at mid game"), 1, 0)
#midgame = QtWidgets.QCheckBox()
#self.registerField('midGame', midgame)
#mapSettingsLayout.addWidget(midgame, 1, 1)
mapSettingsGroup.setLayout(mapSettingsLayout)
# Time Period

View File

@ -3,106 +3,5 @@
"theater": "Persian Gulf",
"authors": "Khopa",
"description": "<p>In this scenario, you can play an invasion of the Emirates and Oman, where your forces starts in Fujairah.</p><p><strong>Note:</strong> Fujairah airfield has very few slots for aircrafts, so it recommended to operate from carriers at the start of the campaign. Thus, a carrier-capable faction is recommended.</p>",
"player_points": [
{
"type": "airbase",
"id": "Fujairah Intl",
"radials": [
180,
225,
270,
315,
0
],
"size": 1000,
"importance": 1,
"captured_invert": true
},
{
"type": "lha",
"id": 1002,
"x": -79770,
"y": 49430,
"captured_invert": true
},
{
"type": "carrier",
"id": 1001,
"x": -61770,
"y": 69039,
"captured_invert": true
}
],
"enemy_points": [
{
"type": "airbase",
"id": "Al Dhafra AB",
"size": 2000,
"importance": 1.2
},
{
"type": "airbase",
"id": "Al Ain International Airport",
"size": 2000,
"importance": 1
},
{
"type": "airbase",
"id": "Al Maktoum Intl",
"size": 2000,
"importance": 1
},
{
"type": "airbase",
"id": "Al Minhad AB",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Sharjah Intl",
"size": 2000,
"importance": 1
},
{
"type": "airbase",
"id": "Ras Al Khaimah",
"size": 1000,
"importance": 1
}
],
"links": [
[
"Al Ain International Airport",
"Al Dhafra AB"
],
[
"Al Dhafra AB",
"Al Maktoum Intl"
],
[
"Al Ain International Airport",
"Fujairah Intl"
],
[
"Al Ain International Airport",
"Al Maktoum Intl"
],
[
"Al Maktoum Intl",
"Al Minhad AB"
],
[
"Al Minhad AB",
"Sharjah Intl"
],
[
"Ras Al Khaimah",
"Sharjah Intl"
],
[
"Fujairah Intl",
"Sharjah Intl"
]
]
"miz": "emirates.miz"
}

Binary file not shown.

View File

@ -0,0 +1,7 @@
{
"name": "Syria - Battle for Golan Heights",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.<br/><br/>You can use the inverted configuration to start on the Syrian side.<br/><br/>If this scenario is too heavy, try the lite version.</p>",
"miz": "golan_heights.miz"
}

Binary file not shown.

View File

@ -1,83 +0,0 @@
{
"name": "Syria - Golan heights battle",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.</p>",
"player_points": [
{
"type": "airbase",
"id": "Ramat David",
"size": 1000,
"importance": 1.4
},
{
"type": "carrier",
"id": 1001,
"x": -280000,
"y": -238000,
"captured_invert": true
},
{
"type": "lha",
"id": 1002,
"x": -237000,
"y": -89800,
"captured_invert": true
}
],
"enemy_points": [
{
"type": "airbase",
"id": "Khalkhalah",
"size": 1000,
"importance": 1.2
},
{
"type": "airbase",
"id": "King Hussein Air College",
"size": 1000,
"importance": 1.4
},
{
"type": "airbase",
"id": "Marj Ruhayyil",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Mezzeh",
"size": 1000,
"importance": 1.2
},
{
"type": "airbase",
"id": "Al-Dumayr",
"size": 1000,
"importance": 1.2,
"captured_invert": true
}
],
"links": [
[
"Khalkhalah",
"Ramat David"
],
[
"Khalkhalah",
"King Hussein Air College"
],
[
"Khalkhalah",
"Marj Ruhayyil"
],
[
"Marj Ruhayyil",
"Mezzeh"
],
[
"Al-Dumayr",
"Marj Ruhayyil"
]
]
}

View File

@ -0,0 +1,7 @@
{
"name": "Syria - Battle for Golan Heights - Lite",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.<br/><br/>This scenario is designed to be performance friendly.</p>",
"miz": "golan_heights_lite.miz"
}

Binary file not shown.

View File

@ -2,92 +2,6 @@
"name": "Syria - Syrian Civil War",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>This scenario can be used to simulate parts of the Syrian Civil War.</p>",
"player_points": [
{
"type": "airbase",
"id": "Bassel Al-Assad",
"size": 1000,
"importance": 1.4
},
{
"type": "airbase",
"id": "Marj Ruhayyil",
"size": 1000,
"importance": 1
},
{
"type": "carrier",
"id": 1001,
"x": 18537,
"y": -52000,
"captured_invert": true
},
{
"type": "lha",
"id": 1002,
"x": 116000,
"y": -30000,
"captured_invert": true
}
],
"enemy_points": [
{
"type": "airbase",
"id": "Hama",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Aleppo",
"size": 1000,
"importance": 1.2,
"captured_invert": true
},
{
"type": "airbase",
"id": "Al Qusayr",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Palmyra",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Al-Dumayr",
"size": 1000,
"importance": 1.2
}
],
"links": [
[
"Bassel Al-Assad",
"Hama"
],
[
"Al-Dumayr",
"Marj Ruhayyil"
],
[
"Aleppo",
"Hama"
],
[
"Al Qusayr",
"Hama"
],
[
"Al Qusayr",
"Al-Dumayr"
],
[
"Al Qusayr",
"Palmyra"
]
]
"description": "<p>This scenario can be used to simulate parts of the Syrian Civil War.<br/><br/>You start on the coast with an airbase in Latakia, and ground forces in Tartus.<br/><br/>This scenario can also be used to simulate a western invasion of Syria.<br/><br/>In inverted configuration you start in Aleppo.</p>",
"miz": "syrian_civil_war.miz"
}

Binary file not shown.

View File

@ -5,7 +5,8 @@
"description": "<p>A faction to recreate the actual unit lineup during Desert Storm as closely as possible</p>",
"aircrafts": [
"F_15C",
"F_14A",
"F_14A_135_GR",
"F_14B",
"F_15E",
"F_16C_50",
"FA_18C_hornet",
@ -16,7 +17,6 @@
"B_52H",
"B_1B",
"Tornado_IDS",
"F_111F",
"F_4E",
"F_117A",
"M_2000C",
@ -32,7 +32,7 @@
],
"tankers": [
"KC_135",
"KC135MPRS"
"KC130"
],
"frontline_units": [
"MBT_M1A2_Abrams",
@ -46,7 +46,8 @@
"IFV_MCV_80",
"MBT_Challenger_II",
"MBT_M60A3_Patton",
"SPG_M1128_Stryker_MGS"
"SPG_M1128_Stryker_MGS",
"SAM_Avenger_M1097"
],
"artillery_units": [
"MLRS_M270",

View File

@ -25,7 +25,9 @@
"LAC_M8_Greyhound",
"TD_M10_GMC",
"Daimler_Armoured_Car",
"LT_Mk_VII_Tetrarch"
"LT_Mk_VII_Tetrarch",
"AA_gun_QF_3_7",
"AAA_Bofors_40mm"
],
"artillery_units": [
"M12_GMC"

View File

@ -15,7 +15,8 @@
],
"frontline_units": [
"MT_M4_Sherman",
"APC_M2A1"
"APC_M2A1",
"AAA_Bofors_40mm"
],
"artillery_units": [
],

View File

@ -25,7 +25,8 @@
],
"frontline_units": [
"MBT_M60A3_Patton",
"APC_M113"
"APC_M113",
"SAM_Chaparral_M48"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@ -26,7 +26,8 @@
],
"frontline_units": [
"MBT_M60A3_Patton",
"APC_M113"
"APC_M113",
"SAM_Chaparral_M48"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@ -27,7 +27,8 @@
],
"frontline_units": [
"MBT_M60A3_Patton",
"APC_M113"
"APC_M113",
"SAM_Chaparral_M48"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@ -43,7 +43,9 @@
"IFV_Marder",
"IFV_LAV_25",
"APC_M1043_HMMWV_Armament",
"ATGM_M1045_HMMWV_TOW"
"ATGM_M1045_HMMWV_TOW",
"SAM_Linebacker_M6",
"SAM_Avenger_M1097"
],
"artillery_units": [
"MLRS_M270",

View File

@ -20,7 +20,8 @@
"MBT_Leopard_2",
"IFV_LAV_25",
"APC_M113",
"IFV_MCV_80"
"IFV_MCV_80",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@ -22,7 +22,8 @@
"ZTZ_96B",
"MBT_T_55",
"ZBD_04A",
"IFV_BMP_1"
"IFV_BMP_1",
"HQ_7_Self_Propelled_LN"
],
"artillery_units": [
"MLRS_9A52_Smerch",

View File

@ -24,7 +24,8 @@
"ATGM_M1134_Stryker",
"IFV_LAV_25",
"APC_M1043_HMMWV_Armament",
"ATGM_M1045_HMMWV_TOW"
"ATGM_M1045_HMMWV_TOW",
"SAM_Roland_ADS"
],
"artillery_units": [
"MLRS_M270",
@ -36,7 +37,8 @@
"infantry_units": [
"Infantry_M4",
"Soldier_M249",
"Stinger_MANPADS"
"Stinger_MANPADS",
"_2B11_mortar"
],
"air_defenses": [
"RolandGenerator",

View File

@ -31,7 +31,8 @@
"VBAE_CRAB",
"VBAE_CRAB_MMP",
"AMX_30B2",
"Leclerc_Serie_XXI"
"Leclerc_Serie_XXI",
"SAM_Roland_ADS"
],
"artillery_units": [
"MLRS_M270",

View File

@ -34,7 +34,8 @@
"VBAE_CRAB",
"VBAE_CRAB_MMP",
"AMX_30B2",
"Leclerc_Serie_XXI"
"Leclerc_Serie_XXI",
"SAM_Roland_ADS"
],
"artillery_units": [
"MLRS_M270",

View File

@ -17,7 +17,8 @@
"IFV_BMP_1",
"IFV_BMP_2",
"MBT_T_72B",
"MBT_T_55"
"MBT_T_55",
"SAM_SA_13_Strela_10M3_9A35M3"
],
"artillery_units": [
"MLRS_BM_21_Grad",

View File

@ -13,7 +13,8 @@
"MT_Pz_Kpfw_IV_Ausf_H",
"APC_Sd_Kfz_251",
"AC_Sd_Kfz_234_2_Puma",
"TD_Jagdpanzer_IV"
"TD_Jagdpanzer_IV",
"AAA_8_8cm_Flak_18"
],
"artillery_units": [
"Sturmpanzer_IV_Brummbär"

View File

@ -18,7 +18,11 @@
"AC_Sd_Kfz_234_2_Puma",
"Sd_Kfz_184_Elefant",
"TD_Jagdpanther_G1",
"TD_Jagdpanzer_IV"
"TD_Jagdpanzer_IV",
"StuG_III_Ausf__G",
"StuG_IV",
"AAA_8_8cm_Flak_18",
"AAA_8_8cm_Flak_41"
],
"artillery_units": [
"Sturmpanzer_IV_Brummbär"

View File

@ -10,7 +10,8 @@
],
"frontline_units": [
"MT_Pz_Kpfw_IV_Ausf_H",
"APC_Sd_Kfz_251"
"APC_Sd_Kfz_251",
"AAA_8_8cm_Flak_18"
],
"artillery_units": [
],

View File

@ -22,7 +22,8 @@
"TPz_Fuchs",
"MBT_Leopard_1A3",
"MBT_Leopard_2",
"IFV_Marder"
"IFV_Marder",
"SPAAA_Gepard"
],
"artillery_units": [
],
@ -32,6 +33,7 @@
"infantry_units": [
"Infantry_M4",
"Soldier_M249",
"_2B11_mortar",
"Stinger_MANPADS"
],
"air_defenses": [

View File

@ -22,7 +22,8 @@
"frontline_units": [
"MBT_T_90",
"MBT_T_72B",
"IFV_BMP_2"
"IFV_BMP_2",
"SAM_SA_19_Tunguska_2S6"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",

View File

@ -22,6 +22,7 @@
"infantry_units": [
"Infantry_Soldier_Insurgents",
"Soldier_RPG",
"_2B11_mortar",
"SAM_SA_18_Igla_MANPADS"
],
"air_defenses": [

View File

@ -0,0 +1,39 @@
{
"country": "Insurgents",
"name": "Insurgents (Hard)",
"authors": "Khopa",
"description": "<p>Insurgents faction.</p>",
"aircrafts": [
],
"frontline_units": [
"ATGM_M1045_HMMWV_TOW",
"APC_M1043_HMMWV_Armament",
"ARV_BRDM_2",
"APC_BTR_80",
"ARV_BTR_RD",
"IFV_BMP_1",
"MBT_T_55",
"AAA_ZU_23_Insurgent_on_Ural_375",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad",
"SPH_2S19_Msta"
],
"logistics_units": [
"Transport_Ural_375",
"Transport_UAZ_469"
],
"infantry_units": [
"Infantry_Soldier_Insurgents",
"Soldier_RPG",
"_2B11_mortar",
"SAM_SA_18_Igla_MANPADS"
],
"air_defenses": [
"SA9Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralInsurgentGenerator"
]
}

View File

@ -27,7 +27,9 @@
"APC_BTR_80",
"MBT_M60A3_Patton",
"IFV_BMP_1",
"MBT_T_72B"
"MBT_T_72B",
"SPAAA_ZSU_23_4_Shilka",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad",

View File

@ -16,7 +16,8 @@
"Tu_22M3",
"L_39C",
"L_39ZA",
"Mi_24V"
"Mi_24V",
"MiG_29A"
],
"awacs": [
"A_50"
@ -31,7 +32,9 @@
"MBT_T_72B",
"APC_BTR_80",
"ARV_BRDM_2",
"SPH_2S1_Gvozdika"
"SPH_2S1_Gvozdika",
"AAA_ZSU_57_2",
"SPAAA_ZSU_23_4_Shilka"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -15,7 +15,8 @@
"MT_M4A4_Sherman_Firefly",
"APC_M2A1",
"MT_M4_Sherman",
"LAC_M8_Greyhound"
"LAC_M8_Greyhound",
"AAA_Bofors_40mm"
],
"artillery_units": [
],

View File

@ -19,7 +19,8 @@
"MT_M4_Sherman",
"APC_M2A1",
"MBT_M60A3_Patton",
"APC_M113"
"APC_M113",
"SAM_Chaparral_M48"
],
"artillery_units": [
],

View File

@ -22,7 +22,8 @@
"frontline_units": [
"APC_M113",
"MBT_M60A3_Patton",
"MBT_Merkava_Mk__4"
"MBT_Merkava_Mk__4",
"AAA_Vulcan_M163"
],
"artillery_units": [
],

View File

@ -23,7 +23,8 @@
"APC_M113",
"APC_M1043_HMMWV_Armament",
"ATGM_M1045_HMMWV_TOW",
"MBT_Merkava_Mk__4"
"MBT_Merkava_Mk__4",
"AAA_Vulcan_M163"
],
"artillery_units": [
"SPH_M109_Paladin",

View File

@ -18,7 +18,8 @@
],
"frontline_units": [
"MBT_Leopard_1A3",
"APC_M113"
"APC_M113",
"SAM_Avenger_M1097"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@ -19,7 +19,8 @@
],
"frontline_units": [
"MBT_Leopard_1A3",
"APC_M113"
"APC_M113",
"SAM_Avenger_M1097"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@ -23,7 +23,8 @@
"IFV_Marder",
"TPz_Fuchs",
"IFV_LAV_25",
"APC_M1043_HMMWV_Armament"
"APC_M1043_HMMWV_Armament",
"SPAAA_Gepard"
],
"artillery_units": [
"SPH_M109_Paladin",

View File

@ -20,7 +20,9 @@
"IFV_BMP_1",
"ARV_BRDM_2",
"MBT_T_72B",
"MBT_T_55"
"MBT_T_55",
"SPAAA_ZSU_23_4_Shilka",
"SAM_SA_8_Osa_9A33"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -17,7 +17,8 @@
],
"frontline_units": [
"APC_M113",
"MBT_Leopard_1A3"
"MBT_Leopard_1A3",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@ -24,7 +24,9 @@
"IFV_BMP_1",
"MBT_T_55",
"MBT_T_72B",
"MBT_T_80U"
"MBT_T_80U",
"AAA_ZSU_57_2",
"SAM_SA_9_Strela_1_9P31"
],
"artillery_units": [
"MLRS_BM_21_Grad",

View File

@ -23,7 +23,8 @@
"MBT_T_55",
"ZBD_04A",
"APC_BTR_80",
"APC_M113"
"APC_M113",
"HQ_7_Self_Propelled_LN"
],
"artillery_units": [
"MLRS_9A52_Smerch",

View File

@ -13,7 +13,8 @@
"frontline_units": [
"APC_Cobra",
"APC_BTR_80",
"ARV_BRDM_2"
"ARV_BRDM_2",
"SAM_SA_13_Strela_10M3_9A35M3"
],
"artillery_units": [
"SPH_2S19_Msta"

View File

@ -11,7 +11,9 @@
],
"frontline_units": [
"APC_M1043_HMMWV_Armament",
"IFV_MCV_80"
"IFV_MCV_80",
"IFV_LAV_25",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@ -12,7 +12,9 @@
],
"frontline_units": [
"APC_M1043_HMMWV_Armament",
"IFV_MCV_80"
"IFV_MCV_80",
"IFV_LAV_25",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@ -17,7 +17,8 @@
"FDDM_Grad",
"APC_MTLB",
"MBT_T_55",
"AAA_ZU_23_on_Ural_375"
"AAA_ZU_23_on_Ural_375",
"AAA_8_8cm_Flak_18"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -21,7 +21,9 @@
"ARV_BTR_RD",
"IFV_BMD_1",
"IFV_BMP_1",
"MBT_T_55"
"MBT_T_55",
"AAA_ZU_23_on_Ural_375",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -14,7 +14,8 @@
"APC_BTR_80",
"IFV_BMD_1",
"IFV_BMP_1",
"MBT_T_55"
"MBT_T_55",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad",

View File

@ -25,7 +25,8 @@
"APC_BTR_80",
"IFV_BMD_1",
"IFV_BMP_1",
"MBT_T_55"
"MBT_T_55",
"SAM_SA_8_Osa_9A33"
],
"artillery_units": [
"MLRS_BM_21_Grad",
@ -38,7 +39,8 @@
],
"infantry_units": [
"Infantry_Soldier_Rus",
"Soldier_RPG"
"Soldier_RPG",
"_2B11_mortar"
],
"air_defenses": [
"ColdWarFlakGenerator",

View File

@ -29,7 +29,8 @@
"IFV_BMP_1",
"IFV_BMP_2",
"MBT_T_72B",
"MBT_T_80U"
"MBT_T_80U",
"SAM_SA_13_Strela_10M3_9A35M3"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",
@ -43,6 +44,7 @@
"Paratrooper_AKS",
"Infantry_Soldier_Rus",
"Paratrooper_RPG_16",
"_2B11_mortar",
"SAM_SA_18_Igla_S_MANPADS"
],
"air_defenses": [

View File

@ -34,7 +34,8 @@
"APC_BTR_82A",
"MBT_T_90",
"MBT_T_80U",
"MBT_T_72B3"
"MBT_T_72B3",
"SAM_SA_19_Tunguska_2S6"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",
@ -48,6 +49,7 @@
"Paratrooper_AKS",
"Infantry_Soldier_Rus",
"Paratrooper_RPG_16",
"_2B11_mortar",
"SAM_SA_18_Igla_MANPADS"
],
"air_defenses": [

View File

@ -32,7 +32,8 @@
"IFV_BMP_3",
"MBT_T_90",
"MBT_T_80U",
"MBT_T_72B"
"MBT_T_72B",
"SAM_SA_19_Tunguska_2S6"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",

View File

@ -11,7 +11,8 @@
"MT_M4_Sherman",
"APC_M2A1",
"Daimler_Armoured_Car",
"LT_Mk_VII_Tetrarch"
"LT_Mk_VII_Tetrarch",
"AAA_Bofors_40mm"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -20,7 +20,8 @@
"frontline_units": [
"MBT_M60A3_Patton",
"MBT_Leopard_2",
"APC_M113"
"APC_M113",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@ -18,7 +18,8 @@
"frontline_units": [
"IFV_MCV_80",
"MBT_Leopard_2",
"APC_M1126_Stryker_ICV"
"APC_M1126_Stryker_ICV",
"SAM_Chaparral_M48"
],
"artillery_units": [
],
@ -32,7 +33,6 @@
"air_defenses": [
"ChaparralGenerator",
"EarlyColdWarFlakGenerator",
"AvengerGenerator",
"HawkGenerator",
"VulcanGenerator"
],

View File

@ -17,7 +17,8 @@
"frontline_units": [
"IFV_MCV_80",
"MBT_Leopard_2",
"APC_M1126_Stryker_ICV"
"APC_M1126_Stryker_ICV",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@ -11,7 +11,8 @@
"AC_Sd_Kfz_234_2_Puma",
"APC_Sd_Kfz_251",
"MT_Pz_Kpfw_IV_Ausf_H",
"MT_M4_Sherman"
"MT_M4_Sherman",
"AAA_Bofors_40mm"
],
"artillery_units": [
],

View File

@ -19,7 +19,9 @@
"frontline_units": [
"ARV_BRDM_2",
"MT_Pz_Kpfw_IV_Ausf_H",
"MBT_T_55"
"MBT_T_55",
"AAA_ZU_23_on_Ural_375",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -22,7 +22,8 @@
"MBT_T_55",
"MT_Pz_Kpfw_IV_Ausf_H",
"StuG_III_Ausf__G",
"TD_Jagdpanzer_IV"
"TD_Jagdpanzer_IV",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -19,7 +19,9 @@
"frontline_units": [
"IFV_BMP_1",
"APC_MTLB",
"MBT_T_55"
"MBT_T_55",
"AAA_ZU_23_on_Ural_375",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -21,7 +21,9 @@
"IFV_BMP_1",
"APC_MTLB",
"MBT_T_55",
"MBT_T_72B"
"MBT_T_72B",
"AAA_ZU_23_on_Ural_375",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@ -31,7 +31,8 @@
"APC_Cobra",
"MBT_T_55",
"MBT_T_72B",
"MBT_T_90"
"MBT_T_90",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",

View File

@ -21,7 +21,8 @@
"MBT_Leopard_1A3",
"MBT_M60A3_Patton",
"APC_Cobra",
"APC_BTR_80"
"APC_BTR_80",
"SAM_Avenger_M1097"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@ -22,7 +22,8 @@
"CT_Centaur_IV",
"HIT_Churchill_VII",
"Daimler_Armoured_Car",
"LT_Mk_VII_Tetrarch"
"LT_Mk_VII_Tetrarch",
"AAA_Bofors_40mm"
],
"artillery_units": [
],

View File

@ -21,7 +21,8 @@
"MBT_Challenger_II",
"IFV_MCV_80",
"APC_M1043_HMMWV_Armament",
"ATGM_M1045_HMMWV_TOW"
"ATGM_M1045_HMMWV_TOW",
"SAM_Avenger_M1097"
],
"artillery_units": [
"MLRS_M270",
@ -33,6 +34,7 @@
"infantry_units": [
"Infantry_M4",
"Soldier_M249",
"_2B11_mortar",
"Stinger_MANPADS"
],
"air_defenses": [

View File

@ -25,7 +25,8 @@
"IFV_BMP_2",
"APC_BTR_80",
"MBT_T_80U",
"MBT_T_72B"
"MBT_T_72B",
"SAM_SA_13_Strela_10M3_9A35M3"
],
"artillery_units": [
],

View File

@ -33,7 +33,8 @@
"ATGM_M1134_Stryker",
"IFV_M2A2_Bradley",
"IFV_LAV_25",
"APC_M1043_HMMWV_Armament"
"APC_M1043_HMMWV_Armament",
"SAM_Avenger_M1097"
],
"artillery_units": [
"MLRS_M270",

View File

@ -19,7 +19,8 @@
"APC_M2A1",
"M30_Cargo_Carrier",
"LAC_M8_Greyhound",
"TD_M10_GMC"
"TD_M10_GMC",
"AA_gun_QF_3_7"
],
"artillery_units": [
"M12_GMC"

View File

@ -12,7 +12,8 @@
"frontline_units": [
"MT_M4_Sherman",
"MBT_M60A3_Patton",
"APC_M2A1"
"APC_M2A1",
"AAA_Bofors_40mm"
],
"artillery_units": [
"M12_GMC"

Some files were not shown because too many files have changed in this diff Show More