mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Support groups for SAM templates.
It's only possible to control emissions for the group as a whole, so Skynet needs PDs to be in separate groups from the main part of the SAM for PD to operate correctly. https://github.com/Khopa/dcs_liberation/issues/429 https://github.com/Khopa/dcs_liberation/issues/470
This commit is contained in:
parent
10debbc286
commit
85619b156d
@ -7,6 +7,7 @@ Saves from 2.3 are not compatible with 2.4.
|
|||||||
* **[Flight Planner]** Air-to-air and SEAD escorts will no longer be automatically planned for packages that are not in range of threats.
|
* **[Flight Planner]** Air-to-air and SEAD escorts will no longer be automatically planned for packages that are not in range of threats.
|
||||||
* **[Flight Planner]** Non-custom flight plans will now navigate around threat areas en route to the target area when practical.
|
* **[Flight Planner]** Non-custom flight plans will now navigate around threat areas en route to the target area when practical.
|
||||||
* **[Campaign AI]** Auto-purchase now prefers airfields that are not within range of the enemy.
|
* **[Campaign AI]** Auto-purchase now prefers airfields that are not within range of the enemy.
|
||||||
|
* **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior.
|
||||||
|
|
||||||
# 2.3.3
|
# 2.3.3
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
import pickle
|
import pickle
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@ -15,7 +14,6 @@ from dcs.vehicles import AirDefence
|
|||||||
from game import Game, db
|
from game import Game, db
|
||||||
from game.factions.faction import Faction
|
from game.factions.faction import Faction
|
||||||
from game.theater import Carrier, Lha, LocationType
|
from game.theater import Carrier, Lha, LocationType
|
||||||
from game.theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW
|
|
||||||
from game.theater.theatergroundobject import (
|
from game.theater.theatergroundobject import (
|
||||||
BuildingGroundObject,
|
BuildingGroundObject,
|
||||||
CarrierGroundObject,
|
CarrierGroundObject,
|
||||||
@ -479,11 +477,11 @@ class BaseDefenseGenerator:
|
|||||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
||||||
position, self.control_point, for_airbase=True)
|
position, self.control_point, for_airbase=True)
|
||||||
|
|
||||||
group = generate_anti_air_group(self.game, g, self.faction)
|
groups = generate_anti_air_group(self.game, g, self.faction)
|
||||||
if group is None:
|
if not groups:
|
||||||
logging.error(f"Could not generate SAM at {self.control_point}")
|
logging.error(f"Could not generate SAM at {self.control_point}")
|
||||||
return
|
return
|
||||||
g.groups.append(group)
|
g.groups = groups
|
||||||
self.control_point.base_defenses.append(g)
|
self.control_point.base_defenses.append(g)
|
||||||
|
|
||||||
def generate_shorad(self) -> None:
|
def generate_shorad(self) -> None:
|
||||||
@ -497,13 +495,13 @@ class BaseDefenseGenerator:
|
|||||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
||||||
position, self.control_point, for_airbase=True)
|
position, self.control_point, for_airbase=True)
|
||||||
|
|
||||||
group = generate_anti_air_group(self.game, g, self.faction,
|
groups = generate_anti_air_group(self.game, g, self.faction,
|
||||||
ranges=[{AirDefenseRange.Short}])
|
ranges=[{AirDefenseRange.Short}])
|
||||||
if group is None:
|
if not groups:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Could not generate SHORAD group at {self.control_point}")
|
f"Could not generate SHORAD group at {self.control_point}")
|
||||||
return
|
return
|
||||||
g.groups.append(group)
|
g.groups = groups
|
||||||
self.control_point.base_defenses.append(g)
|
self.control_point.base_defenses.append(g)
|
||||||
|
|
||||||
|
|
||||||
@ -642,12 +640,12 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
|
|
||||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
||||||
position, self.control_point, for_airbase=False)
|
position, self.control_point, for_airbase=False)
|
||||||
group = generate_anti_air_group(self.game, g, self.faction, ranges)
|
groups = generate_anti_air_group(self.game, g, self.faction, ranges)
|
||||||
if group is None:
|
if not groups:
|
||||||
logging.error("Could not generate air defense group for %s at %s",
|
logging.error("Could not generate air defense group for %s at %s",
|
||||||
g.name, self.control_point)
|
g.name, self.control_point)
|
||||||
return
|
return
|
||||||
g.groups = [group]
|
g.groups = groups
|
||||||
self.control_point.connected_objectives.append(g)
|
self.control_point.connected_objectives.append(g)
|
||||||
|
|
||||||
def generate_missile_sites(self) -> None:
|
def generate_missile_sites(self) -> None:
|
||||||
|
|||||||
@ -91,7 +91,6 @@ class TheaterGroundObject(MissionTarget):
|
|||||||
self.airbase_group = airbase_group
|
self.airbase_group = airbase_group
|
||||||
self.sea_object = sea_object
|
self.sea_object = sea_object
|
||||||
self.is_dead = False
|
self.is_dead = False
|
||||||
# TODO: There is never more than one group.
|
|
||||||
self.groups: List[Group] = []
|
self.groups: List[Group] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
|
import logging
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Iterator, List
|
||||||
|
|
||||||
|
from dcs.unitgroup import VehicleGroup
|
||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from gen.sam.group_generator import GroupGenerator
|
from gen.sam.group_generator import GroupGenerator
|
||||||
@ -21,6 +25,25 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
|
|||||||
ground_object.skynet_capable = True
|
ground_object.skynet_capable = True
|
||||||
super().__init__(game, ground_object)
|
super().__init__(game, ground_object)
|
||||||
|
|
||||||
|
self.auxiliary_groups: List[VehicleGroup] = []
|
||||||
|
|
||||||
|
def add_auxiliary_group(self, name_suffix: str) -> VehicleGroup:
|
||||||
|
group = VehicleGroup(self.game.next_group_id(),
|
||||||
|
"|".join([self.go.group_name, name_suffix]))
|
||||||
|
self.auxiliary_groups.append(group)
|
||||||
|
return group
|
||||||
|
|
||||||
|
def get_generated_group(self) -> VehicleGroup:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Deprecated call to AirDefenseGroupGenerator.get_generated_group "
|
||||||
|
"misses auxiliary groups. Use AirDefenseGroupGenerator.groups "
|
||||||
|
"instead.")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def groups(self) -> Iterator[VehicleGroup]:
|
||||||
|
yield self.vg
|
||||||
|
yield from self.auxiliary_groups
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from dcs import unitgroup
|
from dcs import unitgroup
|
||||||
|
from dcs.mapping import Point
|
||||||
from dcs.point import PointAction
|
from dcs.point import PointAction
|
||||||
from dcs.unit import Vehicle, Ship
|
from dcs.unit import Ship, Vehicle
|
||||||
from dcs.unittype import VehicleType
|
from dcs.unittype import VehicleType
|
||||||
|
|
||||||
from game.factions.faction import Faction
|
from game.factions.faction import Faction
|
||||||
@ -40,12 +42,17 @@ class GroupGenerator:
|
|||||||
|
|
||||||
def add_unit(self, unit_type: Type[VehicleType], name: str, pos_x: float,
|
def add_unit(self, unit_type: Type[VehicleType], name: str, pos_x: float,
|
||||||
pos_y: float, heading: int) -> Vehicle:
|
pos_y: float, heading: int) -> Vehicle:
|
||||||
|
return self.add_unit_to_group(self.vg, unit_type, name,
|
||||||
|
Point(pos_x, pos_y), heading)
|
||||||
|
|
||||||
|
def add_unit_to_group(self, group: unitgroup.VehicleGroup,
|
||||||
|
unit_type: Type[VehicleType], name: str,
|
||||||
|
position: Point, heading: int) -> Vehicle:
|
||||||
unit = Vehicle(self.game.next_unit_id(),
|
unit = Vehicle(self.game.next_unit_id(),
|
||||||
f"{self.go.group_name}|{name}", unit_type.id)
|
f"{group.name}|{name}", unit_type.id)
|
||||||
unit.position.x = pos_x
|
unit.position = position
|
||||||
unit.position.y = pos_y
|
|
||||||
unit.heading = heading
|
unit.heading = heading
|
||||||
self.vg.add_unit(unit)
|
group.add_unit(unit)
|
||||||
return unit
|
return unit
|
||||||
|
|
||||||
def get_circular_position(self, num_units, launcher_distance, coverage=90):
|
def get_circular_position(self, num_units, launcher_distance, coverage=90):
|
||||||
|
|||||||
@ -171,19 +171,19 @@ def get_faction_possible_ewrs_generator(faction: Faction) -> List[Type[GroupGene
|
|||||||
|
|
||||||
def _generate_anti_air_from(
|
def _generate_anti_air_from(
|
||||||
generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game,
|
generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game,
|
||||||
ground_object: SamGroundObject) -> Optional[VehicleGroup]:
|
ground_object: SamGroundObject) -> List[VehicleGroup]:
|
||||||
if not generators:
|
if not generators:
|
||||||
return None
|
return []
|
||||||
sam_generator_class = random.choice(generators)
|
sam_generator_class = random.choice(generators)
|
||||||
generator = sam_generator_class(game, ground_object)
|
generator = sam_generator_class(game, ground_object)
|
||||||
generator.generate()
|
generator.generate()
|
||||||
return generator.get_generated_group()
|
return list(generator.groups)
|
||||||
|
|
||||||
|
|
||||||
def generate_anti_air_group(
|
def generate_anti_air_group(
|
||||||
game: Game, ground_object: SamGroundObject, faction: Faction,
|
game: Game, ground_object: SamGroundObject, faction: Faction,
|
||||||
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None
|
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None
|
||||||
) -> Optional[VehicleGroup]:
|
) -> List[VehicleGroup]:
|
||||||
"""
|
"""
|
||||||
This generate a SAM group
|
This generate a SAM group
|
||||||
:param game: The Game.
|
:param game: The Game.
|
||||||
@ -212,11 +212,11 @@ def generate_anti_air_group(
|
|||||||
for range_options in ranges:
|
for range_options in ranges:
|
||||||
generators_for_range = [g for g in generators if
|
generators_for_range = [g for g in generators if
|
||||||
g.range() in range_options]
|
g.range() in range_options]
|
||||||
group = _generate_anti_air_from(generators_for_range, game,
|
groups = _generate_anti_air_from(generators_for_range, game,
|
||||||
ground_object)
|
ground_object)
|
||||||
if group is not None:
|
if groups:
|
||||||
return group
|
return groups
|
||||||
return None
|
return []
|
||||||
|
|
||||||
|
|
||||||
def generate_ewr_group(game: Game, ground_object: TheaterGroundObject,
|
def generate_ewr_group(game: Game, ground_object: TheaterGroundObject,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
from gen.sam.airdefensegroupgenerator import (
|
from gen.sam.airdefensegroupgenerator import (
|
||||||
@ -22,7 +23,9 @@ class HawkGenerator(AirDefenseGroupGenerator):
|
|||||||
self.add_unit(AirDefence.SAM_Hawk_TR_AN_MPQ_46, "TR", self.position.x + 40, self.position.y, self.heading)
|
self.add_unit(AirDefence.SAM_Hawk_TR_AN_MPQ_46, "TR", self.position.x + 40, self.position.y, self.heading)
|
||||||
|
|
||||||
# Triple A for close range defense
|
# Triple A for close range defense
|
||||||
self.add_unit(AirDefence.AAA_Vulcan_M163, "AAA", self.position.x + 20, self.position.y+30, self.heading)
|
aa_group = self.add_auxiliary_group("AA")
|
||||||
|
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163, "AAA",
|
||||||
|
self.position + Point(20, 30), self.heading)
|
||||||
|
|
||||||
num_launchers = random.randint(3, 6)
|
num_launchers = random.randint(3, 6)
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)
|
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
from gen.sam.airdefensegroupgenerator import (
|
from gen.sam.airdefensegroupgenerator import (
|
||||||
@ -21,8 +22,13 @@ class HQ7Generator(AirDefenseGroupGenerator):
|
|||||||
self.add_unit(AirDefence.HQ_7_Self_Propelled_LN, "LN", self.position.x + 20, self.position.y, self.heading)
|
self.add_unit(AirDefence.HQ_7_Self_Propelled_LN, "LN", self.position.x + 20, self.position.y, self.heading)
|
||||||
|
|
||||||
# Triple A for close range defense
|
# Triple A for close range defense
|
||||||
self.add_unit(AirDefence.AAA_ZU_23_on_Ural_375, "AAA1", self.position.x + 20, self.position.y+30, self.heading)
|
aa_group = self.add_auxiliary_group("AA")
|
||||||
self.add_unit(AirDefence.AAA_ZU_23_on_Ural_375, "AAA2", self.position.x - 20, self.position.y-30, self.heading)
|
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
|
||||||
|
"AAA1", self.position + Point(20, 30),
|
||||||
|
self.heading)
|
||||||
|
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
|
||||||
|
"AAA2", self.position.x - Point(20, 30),
|
||||||
|
self.heading)
|
||||||
|
|
||||||
num_launchers = random.randint(0, 3)
|
num_launchers = random.randint(0, 3)
|
||||||
if num_launchers > 0:
|
if num_launchers > 0:
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
from gen.sam.airdefensegroupgenerator import (
|
from gen.sam.airdefensegroupgenerator import (
|
||||||
@ -30,10 +31,12 @@ class PatriotGenerator(AirDefenseGroupGenerator):
|
|||||||
self.add_unit(AirDefence.SAM_Patriot_LN_M901, "LN#" + str(i), position[0], position[1], position[2])
|
self.add_unit(AirDefence.SAM_Patriot_LN_M901, "LN#" + str(i), position[0], position[1], position[2])
|
||||||
|
|
||||||
# Short range protection for high value site
|
# Short range protection for high value site
|
||||||
|
aa_group = self.add_auxiliary_group("AA")
|
||||||
num_launchers = random.randint(3, 4)
|
num_launchers = random.randint(3, 4)
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=200, coverage=360)
|
positions = self.get_circular_position(num_launchers, launcher_distance=200, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, (x, y, heading) in enumerate(positions):
|
||||||
self.add_unit(AirDefence.AAA_Vulcan_M163, "SPAAA#" + str(i), position[0], position[1], position[2])
|
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163,
|
||||||
|
f"SPAAA#{i}", Point(x, y), heading)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
from gen.sam.airdefensegroupgenerator import (
|
from gen.sam.airdefensegroupgenerator import (
|
||||||
@ -49,47 +50,46 @@ class SA10Generator(AirDefenseGroupGenerator):
|
|||||||
|
|
||||||
def generate_defensive_groups(self) -> None:
|
def generate_defensive_groups(self) -> None:
|
||||||
# AAA for defending against close targets.
|
# AAA for defending against close targets.
|
||||||
|
aa_group = self.add_auxiliary_group("AA")
|
||||||
num_launchers = random.randint(6, 8)
|
num_launchers = random.randint(6, 8)
|
||||||
positions = self.get_circular_position(
|
positions = self.get_circular_position(
|
||||||
num_launchers, launcher_distance=210, coverage=360)
|
num_launchers, launcher_distance=210, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, (x, y, heading) in enumerate(positions):
|
||||||
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "AA#" + str(i),
|
self.add_unit_to_group(aa_group, AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||||
position[0], position[1], position[2])
|
f"AA#{i}", Point(x, y), heading)
|
||||||
|
|
||||||
|
|
||||||
class Tier2SA10Generator(SA10Generator):
|
class Tier2SA10Generator(SA10Generator):
|
||||||
def generate_defensive_groups(self) -> None:
|
def generate_defensive_groups(self) -> None:
|
||||||
|
# Create AAA the way the main group does.
|
||||||
|
super().generate_defensive_groups()
|
||||||
|
|
||||||
# SA-15 for both shorter range targets and point defense.
|
# SA-15 for both shorter range targets and point defense.
|
||||||
|
pd_group = self.add_auxiliary_group("PD")
|
||||||
num_launchers = random.randint(2, 4)
|
num_launchers = random.randint(2, 4)
|
||||||
positions = self.get_circular_position(
|
positions = self.get_circular_position(
|
||||||
num_launchers, launcher_distance=140, coverage=360)
|
num_launchers, launcher_distance=140, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, (x, y, heading) in enumerate(positions):
|
||||||
self.add_unit(AirDefence.SAM_SA_15_Tor_9A331, "PD#" + str(i),
|
self.add_unit_to_group(pd_group, AirDefence.SAM_SA_15_Tor_9A331,
|
||||||
position[0], position[1], position[2])
|
f"PD#{i}", Point(x, y), heading)
|
||||||
|
|
||||||
# AAA for defending against close targets.
|
|
||||||
num_launchers = random.randint(6, 8)
|
|
||||||
positions = self.get_circular_position(
|
|
||||||
num_launchers, launcher_distance=210, coverage=360)
|
|
||||||
for i, position in enumerate(positions):
|
|
||||||
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "AA#" + str(i),
|
|
||||||
position[0], position[1], position[2])
|
|
||||||
|
|
||||||
|
|
||||||
class Tier3SA10Generator(SA10Generator):
|
class Tier3SA10Generator(SA10Generator):
|
||||||
def generate_defensive_groups(self) -> None:
|
def generate_defensive_groups(self) -> None:
|
||||||
# SA-15 for both shorter range targets and point defense.
|
|
||||||
num_launchers = random.randint(2, 4)
|
|
||||||
positions = self.get_circular_position(
|
|
||||||
num_launchers, launcher_distance=140, coverage=360)
|
|
||||||
for i, position in enumerate(positions):
|
|
||||||
self.add_unit(AirDefence.SAM_SA_15_Tor_9A331, "PD#" + str(i),
|
|
||||||
position[0], position[1], position[2])
|
|
||||||
|
|
||||||
# AAA for defending against close targets.
|
# AAA for defending against close targets.
|
||||||
|
aa_group = self.add_auxiliary_group("AA")
|
||||||
num_launchers = random.randint(6, 8)
|
num_launchers = random.randint(6, 8)
|
||||||
positions = self.get_circular_position(
|
positions = self.get_circular_position(
|
||||||
num_launchers, launcher_distance=210, coverage=360)
|
num_launchers, launcher_distance=210, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, (x, y, heading) in enumerate(positions):
|
||||||
self.add_unit(AirDefence.SAM_SA_19_Tunguska_2S6, "AA#" + str(i),
|
self.add_unit_to_group(aa_group, AirDefence.SAM_SA_19_Tunguska_2S6,
|
||||||
position[0], position[1], position[2])
|
f"AA#{i}", Point(x, y), heading)
|
||||||
|
|
||||||
|
# SA-15 for both shorter range targets and point defense.
|
||||||
|
pd_group = self.add_auxiliary_group("PD")
|
||||||
|
num_launchers = random.randint(2, 4)
|
||||||
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=140, coverage=360)
|
||||||
|
for i, (x, y, heading) in enumerate(positions):
|
||||||
|
self.add_unit_to_group(pd_group, AirDefence.SAM_SA_15_Tor_9A331,
|
||||||
|
f"PD#{i}", Point(x, y), heading)
|
||||||
|
|||||||
@ -355,8 +355,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
|
|||||||
# Generate SAM
|
# Generate SAM
|
||||||
generator = sam_generator(self.game, self.ground_object)
|
generator = sam_generator(self.game, self.ground_object)
|
||||||
generator.generate()
|
generator.generate()
|
||||||
generated_group = generator.get_generated_group()
|
self.ground_object.groups = generator.groups
|
||||||
self.ground_object.groups = [generated_group]
|
|
||||||
|
|
||||||
GameUpdateSignal.get_instance().updateBudget(self.game)
|
GameUpdateSignal.get_instance().updateBudget(self.game)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user