mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
commit
64c424b9a6
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal 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
26
CONTRIBUTING.md
Normal 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.
|
||||
34
changelog.md
34
changelog.md
@ -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
|
||||
|
||||
47
game/db.py
47
game/db.py
@ -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,
|
||||
|
||||
@ -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 "
|
||||
|
||||
22
game/game.py
22
game/game.py
@ -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
64
game/income.py
Normal 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)
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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]:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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,
|
||||
|
||||
20
gen/armor.py
20
gen/armor.py
@ -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}")
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
199
gen/ground_forces/ai_ground_planner_db.py
Normal file
199
gen/ground_forces/ai_ground_planner_db.py
Normal 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,
|
||||
|
||||
]
|
||||
@ -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
2
pydcs
@ -1 +1 @@
|
||||
Subproject commit edc87fab1d65d4e4153c84006f537e6ae6b0671a
|
||||
Subproject commit 059c88c91b5be4b5b6406249a52527c3ccea3db9
|
||||
@ -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
107
qt_ui/widgets/QIntelBox.py
Normal 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()
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
147
qt_ui/windows/intel.py
Normal 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)
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
}
|
||||
BIN
resources/campaigns/emirates.miz
Normal file
BIN
resources/campaigns/emirates.miz
Normal file
Binary file not shown.
7
resources/campaigns/golan_heights.json
Normal file
7
resources/campaigns/golan_heights.json
Normal 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"
|
||||
}
|
||||
BIN
resources/campaigns/golan_heights.miz
Normal file
BIN
resources/campaigns/golan_heights.miz
Normal file
Binary file not shown.
@ -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"
|
||||
]
|
||||
]
|
||||
}
|
||||
7
resources/campaigns/golan_heights_lite.json
Normal file
7
resources/campaigns/golan_heights_lite.json
Normal 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"
|
||||
}
|
||||
BIN
resources/campaigns/golan_heights_lite.miz
Normal file
BIN
resources/campaigns/golan_heights_lite.miz
Normal file
Binary file not shown.
@ -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"
|
||||
}
|
||||
BIN
resources/campaigns/syrian_civil_war.miz
Normal file
BIN
resources/campaigns/syrian_civil_war.miz
Normal file
Binary file not shown.
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"MT_M4_Sherman",
|
||||
"APC_M2A1"
|
||||
"APC_M2A1",
|
||||
"AAA_Bofors_40mm"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -25,7 +25,8 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"MBT_M60A3_Patton",
|
||||
"APC_M113"
|
||||
"APC_M113",
|
||||
"SAM_Chaparral_M48"
|
||||
],
|
||||
"artillery_units": [
|
||||
"SPH_M109_Paladin"
|
||||
|
||||
@ -26,7 +26,8 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"MBT_M60A3_Patton",
|
||||
"APC_M113"
|
||||
"APC_M113",
|
||||
"SAM_Chaparral_M48"
|
||||
],
|
||||
"artillery_units": [
|
||||
"SPH_M109_Paladin"
|
||||
|
||||
@ -27,7 +27,8 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"MBT_M60A3_Patton",
|
||||
"APC_M113"
|
||||
"APC_M113",
|
||||
"SAM_Chaparral_M48"
|
||||
],
|
||||
"artillery_units": [
|
||||
"SPH_M109_Paladin"
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -20,7 +20,8 @@
|
||||
"MBT_Leopard_2",
|
||||
"IFV_LAV_25",
|
||||
"APC_M113",
|
||||
"IFV_MCV_80"
|
||||
"IFV_MCV_80",
|
||||
"SAM_Avenger_M1097"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -31,7 +31,8 @@
|
||||
"VBAE_CRAB",
|
||||
"VBAE_CRAB_MMP",
|
||||
"AMX_30B2",
|
||||
"Leclerc_Serie_XXI"
|
||||
"Leclerc_Serie_XXI",
|
||||
"SAM_Roland_ADS"
|
||||
],
|
||||
"artillery_units": [
|
||||
"MLRS_M270",
|
||||
|
||||
@ -34,7 +34,8 @@
|
||||
"VBAE_CRAB",
|
||||
"VBAE_CRAB_MMP",
|
||||
"AMX_30B2",
|
||||
"Leclerc_Serie_XXI"
|
||||
"Leclerc_Serie_XXI",
|
||||
"SAM_Roland_ADS"
|
||||
],
|
||||
"artillery_units": [
|
||||
"MLRS_M270",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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": [
|
||||
],
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"infantry_units": [
|
||||
"Infantry_Soldier_Insurgents",
|
||||
"Soldier_RPG",
|
||||
"_2B11_mortar",
|
||||
"SAM_SA_18_Igla_MANPADS"
|
||||
],
|
||||
"air_defenses": [
|
||||
|
||||
39
resources/factions/insurgents_hard.json
Normal file
39
resources/factions/insurgents_hard.json
Normal 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"
|
||||
]
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
"MT_M4A4_Sherman_Firefly",
|
||||
"APC_M2A1",
|
||||
"MT_M4_Sherman",
|
||||
"LAC_M8_Greyhound"
|
||||
"LAC_M8_Greyhound",
|
||||
"AAA_Bofors_40mm"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
"MT_M4_Sherman",
|
||||
"APC_M2A1",
|
||||
"MBT_M60A3_Patton",
|
||||
"APC_M113"
|
||||
"APC_M113",
|
||||
"SAM_Chaparral_M48"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -22,7 +22,8 @@
|
||||
"frontline_units": [
|
||||
"APC_M113",
|
||||
"MBT_M60A3_Patton",
|
||||
"MBT_Merkava_Mk__4"
|
||||
"MBT_Merkava_Mk__4",
|
||||
"AAA_Vulcan_M163"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"MBT_Leopard_1A3",
|
||||
"APC_M113"
|
||||
"APC_M113",
|
||||
"SAM_Avenger_M1097"
|
||||
],
|
||||
"artillery_units": [
|
||||
"SPH_M109_Paladin"
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"MBT_Leopard_1A3",
|
||||
"APC_M113"
|
||||
"APC_M113",
|
||||
"SAM_Avenger_M1097"
|
||||
],
|
||||
"artillery_units": [
|
||||
"SPH_M109_Paladin"
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -17,7 +17,8 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"APC_M113",
|
||||
"MBT_Leopard_1A3"
|
||||
"MBT_Leopard_1A3",
|
||||
"SAM_Avenger_M1097"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -11,7 +11,9 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"APC_M1043_HMMWV_Armament",
|
||||
"IFV_MCV_80"
|
||||
"IFV_MCV_80",
|
||||
"IFV_LAV_25",
|
||||
"SAM_Avenger_M1097"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -12,7 +12,9 @@
|
||||
],
|
||||
"frontline_units": [
|
||||
"APC_M1043_HMMWV_Armament",
|
||||
"IFV_MCV_80"
|
||||
"IFV_MCV_80",
|
||||
"IFV_LAV_25",
|
||||
"SAM_Avenger_M1097"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -20,7 +20,8 @@
|
||||
"frontline_units": [
|
||||
"MBT_M60A3_Patton",
|
||||
"MBT_Leopard_2",
|
||||
"APC_M113"
|
||||
"APC_M113",
|
||||
"SAM_Avenger_M1097"
|
||||
],
|
||||
"artillery_units": [
|
||||
],
|
||||
|
||||
@ -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"
|
||||
],
|
||||
|
||||
@ -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": [
|
||||
],
|
||||
|
||||
@ -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": [
|
||||
],
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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": [
|
||||
],
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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": [
|
||||
],
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user