Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
130
mods/ra/maps/fall-of-greece-2-evacuation/evacuation-AI.lua
Normal file
130
mods/ra/maps/fall-of-greece-2-evacuation/evacuation-AI.lua
Normal file
@@ -0,0 +1,130 @@
|
||||
--[[
|
||||
Copyright (c) The OpenRA Developers and Contributors
|
||||
This file is part of OpenRA, which is free software. It is made
|
||||
available to you under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version. For more
|
||||
information, see COPYING.
|
||||
]]
|
||||
AttackGroup = { }
|
||||
SovietInfantry = { "e1", "e2" }
|
||||
SovietVehicles = { "3tnk", "3tnk", "v2rl" }
|
||||
SovietAircraftType = { "yak" }
|
||||
Yaks = { }
|
||||
AttackPaths = { AttackRight, AttackLeft }
|
||||
|
||||
AttackGroupSizes =
|
||||
{
|
||||
easy = 8,
|
||||
normal = 9,
|
||||
hard = 10
|
||||
}
|
||||
|
||||
ProductionInterval =
|
||||
{
|
||||
easy = DateTime.Seconds(20),
|
||||
normal = DateTime.Seconds(10),
|
||||
hard = DateTime.Seconds(5)
|
||||
}
|
||||
|
||||
SendAttackGroup = function()
|
||||
if #AttackGroup < AttackGroupSize then
|
||||
return
|
||||
end
|
||||
|
||||
local path = Utils.Random(AttackPaths)
|
||||
Utils.Do(AttackGroup, function(unit)
|
||||
if not unit.IsDead then
|
||||
unit.AttackMove(path.Location)
|
||||
Trigger.OnIdle(unit, unit.Hunt)
|
||||
end
|
||||
end)
|
||||
|
||||
AttackGroup = { }
|
||||
end
|
||||
|
||||
ProduceInfantry = function()
|
||||
if USSRRax.IsDead or USSRRax.Owner ~= USSR then
|
||||
return
|
||||
end
|
||||
|
||||
USSR.Build({ Utils.Random(SovietInfantry) }, function(units)
|
||||
table.insert(AttackGroup, units[1])
|
||||
SendAttackGroup()
|
||||
Trigger.AfterDelay(ProductionInterval[Difficulty], ProduceInfantry)
|
||||
end)
|
||||
end
|
||||
|
||||
ProduceVehicles = function()
|
||||
if USSRWarFactory.IsDead or USSRWarFactory.Owner ~= USSR then
|
||||
return
|
||||
end
|
||||
|
||||
USSR.Build({ Utils.Random(SovietVehicles) }, function(units)
|
||||
table.insert(AttackGroup, units[1])
|
||||
SendAttackGroup()
|
||||
Trigger.AfterDelay(ProductionInterval[Difficulty], ProduceVehicles)
|
||||
end)
|
||||
end
|
||||
|
||||
ProduceAircraft = function()
|
||||
if Airfield.IsDead or Airfield.Owner ~= USSR then
|
||||
return
|
||||
end
|
||||
|
||||
USSR.Build(SovietAircraftType, function(units)
|
||||
local yak = units[1]
|
||||
Yaks[#Yaks + 1] = yak
|
||||
|
||||
Trigger.OnKilled(yak, ProduceAircraft)
|
||||
|
||||
local alive = Utils.Where(Yaks, function(y) return not y.IsDead end)
|
||||
if #alive < 2 then
|
||||
Trigger.AfterDelay(DateTime.Seconds(ProductionInterval[Difficulty] / 2), ProduceAircraft)
|
||||
end
|
||||
|
||||
InitializeAttackAircraft(yak, Allies)
|
||||
end)
|
||||
end
|
||||
|
||||
ParadropDelays =
|
||||
{
|
||||
easy = { DateTime.Minutes(1), DateTime.Minutes(2) },
|
||||
normal = { DateTime.Seconds(45), DateTime.Seconds(105) },
|
||||
hard = { DateTime.Seconds(30), DateTime.Seconds(90) }
|
||||
}
|
||||
|
||||
ParadropLZs = { ParaLZ1.CenterPosition, ParaLZ2.CenterPosition, ParaLZ3.CenterPosition, ParaLZ4.CenterPosition }
|
||||
|
||||
Paradrop = function()
|
||||
if Airfield.IsDead or Airfield.Owner ~= USSR then
|
||||
return
|
||||
end
|
||||
|
||||
local aircraft = StandardDrop.TargetParatroopers(Utils.Random(ParadropLZs))
|
||||
Utils.Do(aircraft, function(a)
|
||||
Trigger.OnPassengerExited(a, function(t, p)
|
||||
IdleHunt(p)
|
||||
end)
|
||||
end)
|
||||
|
||||
Trigger.AfterDelay(Utils.RandomInteger(ParadropDelay[1], ParadropDelay[2]), Paradrop)
|
||||
end
|
||||
|
||||
ActivateAI = function()
|
||||
local buildings = Utils.Where(Map.ActorsInWorld, function(self) return self.Owner == USSR and self.HasProperty("StartBuildingRepairs") end)
|
||||
Utils.Do(buildings, function(actor)
|
||||
Trigger.OnDamaged(actor, function(building)
|
||||
if building.Owner == USSR and building.Health < building.MaxHealth * 3/4 then
|
||||
building.StartBuildingRepairs()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
ParadropDelay = ParadropDelays[Difficulty]
|
||||
AttackGroupSize = AttackGroupSizes[Difficulty]
|
||||
Trigger.AfterDelay(DateTime.Seconds(30), ProduceInfantry)
|
||||
Trigger.AfterDelay(DateTime.Minutes(3), ProduceVehicles)
|
||||
Trigger.AfterDelay(DateTime.Minutes(4), Paradrop)
|
||||
Trigger.AfterDelay(DateTime.Minutes(5), ProduceAircraft)
|
||||
end
|
||||
266
mods/ra/maps/fall-of-greece-2-evacuation/evacuation.lua
Normal file
266
mods/ra/maps/fall-of-greece-2-evacuation/evacuation.lua
Normal file
@@ -0,0 +1,266 @@
|
||||
--[[
|
||||
Copyright (c) The OpenRA Developers and Contributors
|
||||
This file is part of OpenRA, which is free software. It is made
|
||||
available to you under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version. For more
|
||||
information, see COPYING.
|
||||
]]
|
||||
AlliedReinforcementsA = { "jeep", "jeep" }
|
||||
AlliedReinforcementsB = { "e1", "e1", "e1", "e3", "e3" }
|
||||
AlliedReinforcementsC = { "jeep", "mcv" }
|
||||
IslandPatrol = { SubPatrol1.Location, SubPatrol2.Location, SubPatrol3.Location, SubPatrol4.Location }
|
||||
Submarines = { Sub1, Sub2, Sub3, Sub4, Sub5, Sub6, Sub7, Sub8 }
|
||||
RifleSquad = { Rifle1, Rifle2, Rifle3 }
|
||||
NWVillageTrigger = { CPos.New(31, 64), CPos.New(32, 64), CPos.New(34, 51), CPos.New(35, 51), CPos.New(36, 51) }
|
||||
SWVillageTrigger = { CPos.New(44, 97), CPos.New(45, 97), CPos.New(46, 97), CPos.New(47, 97) }
|
||||
MiddleVillageTrigger = { CPos.New(52, 70), CPos.New(53, 70), CPos.New(54, 70) }
|
||||
EastVillageTrigger = { CPos.New(78, 61), CPos.New(79, 61), CPos.New(80, 61), CPos.New(81, 61), CPos.New(82, 61) }
|
||||
CivilianTypes = { "c2", "c3", "c4", "c5", "c6", "c8", "c9", "c10", "c11" }
|
||||
NWVillage = { NWChurch, NWHouse1, NWHouse2, NWHouse3 }
|
||||
SWVillage = { SWChurch, SWHouse1, SWHouse2, SWHouse3, SWHouse4 }
|
||||
MiddleVillage = { MiddleChurch, MiddleHouse1, MiddleHouse2, MiddleHouse3, MiddleHouse4 }
|
||||
EastVillage = { EastChurch, EastHouse1, EastHouse2, EastHouse3 }
|
||||
|
||||
MissionStart = function()
|
||||
Reinforcements.Reinforce(Allies, AlliedReinforcementsA, { AlliedSpawn.Location, AlliedBase.Location }, 5)
|
||||
Trigger.AfterDelay(DateTime.Seconds(2), function()
|
||||
AlliesArrived = true
|
||||
Reinforcements.Reinforce(Allies, AlliedReinforcementsB, { AlliedSpawn.Location, AlliedBase.Location }, 2)
|
||||
Utils.Do(RifleSquad, function(actor)
|
||||
if not actor.IsDead then
|
||||
actor.AttackMove(AlliedBase.Location)
|
||||
actor.Hunt()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
Sub1.Patrol(IslandPatrol, true, 1)
|
||||
|
||||
Trigger.AfterDelay(DateTime.Seconds(5), function()
|
||||
Reinforcements.Reinforce(Allies, AlliedReinforcementsC, { AlliedSpawn.Location, AlliedBase.Location }, 5)
|
||||
end)
|
||||
|
||||
Trigger.OnKilled(NWChurch, function()
|
||||
if not NWChurchEmpty then
|
||||
CiviliansKilled = CiviliansKilled + 5
|
||||
end
|
||||
end)
|
||||
Trigger.OnKilled(EastChurch, function()
|
||||
if not EastChurchEmpty then
|
||||
CiviliansKilled = CiviliansKilled + 5
|
||||
end
|
||||
end)
|
||||
Trigger.OnKilled(MiddleChurch, function()
|
||||
if not MiddleChurchEmpty then
|
||||
CiviliansKilled = CiviliansKilled + 5
|
||||
end
|
||||
end)
|
||||
Trigger.OnKilled(SWChurch, function()
|
||||
if not SWChurchEmpty then
|
||||
CiviliansKilled = CiviliansKilled + 5
|
||||
end
|
||||
end)
|
||||
|
||||
Trigger.OnAllKilled(Submarines, function()
|
||||
Allies.MarkCompletedObjective(ClearWaterway)
|
||||
end)
|
||||
end
|
||||
|
||||
VillageSetup = function()
|
||||
local foot1Triggered
|
||||
Trigger.OnEnteredFootprint(NWVillageTrigger, function(actor, id)
|
||||
if actor.Owner == Allies and not foot1Triggered then
|
||||
Trigger.RemoveFootprintTrigger(id)
|
||||
foot1Triggered = true
|
||||
|
||||
Utils.Do(NWVillage, function(building)
|
||||
building.Owner = Allies
|
||||
end)
|
||||
|
||||
Civs1 = Reinforcements.Reinforce(Allies, Utils.Take(5, Utils.Shuffle(CivilianTypes)), { ChurchNorthwest.Location, VillageNorthwest.Location }, 0)
|
||||
if not NWChurch.IsDead then
|
||||
Utils.Do(Civs1, function(civ)
|
||||
Trigger.OnKilled(civ, function()
|
||||
CiviliansKilled = CiviliansKilled + 1
|
||||
end)
|
||||
end)
|
||||
NWChurchEmpty = true
|
||||
end
|
||||
end
|
||||
|
||||
Trigger.AfterDelay(DateTime.Seconds(30), function()
|
||||
local nwAttackers = Reinforcements.Reinforce(USSR, { "3tnk", "3tnk", "3tnk" }, { NWVillageAttack.Location, VillageNorthwest.Location }, 20)
|
||||
Utils.Do(nwAttackers, IdleHunt)
|
||||
end)
|
||||
end)
|
||||
|
||||
local foot2Triggered
|
||||
Trigger.OnEnteredFootprint(EastVillageTrigger, function(actor, id)
|
||||
if actor.Owner == Allies and not foot2Triggered then
|
||||
Trigger.RemoveFootprintTrigger(id)
|
||||
foot2Triggered = true
|
||||
|
||||
Utils.Do(EastVillage, function(building)
|
||||
building.Owner = Allies
|
||||
end)
|
||||
|
||||
Civs2 = Reinforcements.Reinforce(Allies, Utils.Take(5, Utils.Shuffle(CivilianTypes)), { ChurchEast.Location, VillageEast.Location }, 0)
|
||||
if not EastChurch.IsDead then
|
||||
Utils.Do(Civs2, function(civ)
|
||||
Trigger.OnKilled(civ, function()
|
||||
CiviliansKilled = CiviliansKilled + 1
|
||||
end)
|
||||
end)
|
||||
EastChurchEmpty = true
|
||||
end
|
||||
|
||||
local villageDrop = FlamerDrop.TargetParatroopers(VillageEast.CenterPosition, Angle.North)
|
||||
Utils.Do(villageDrop, function(a)
|
||||
Trigger.OnPassengerExited(a, function(t, p)
|
||||
IdleHunt(p)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
local foot3Triggered
|
||||
Trigger.OnEnteredFootprint(MiddleVillageTrigger, function(actor, id)
|
||||
if actor.Owner == Allies and not foot3Triggered then
|
||||
Trigger.RemoveFootprintTrigger(id)
|
||||
foot3Triggered = true
|
||||
|
||||
Utils.Do(MiddleVillage, function(building)
|
||||
building.Owner = Allies
|
||||
end)
|
||||
|
||||
Civs3 = Reinforcements.Reinforce(Allies, Utils.Take(5, Utils.Shuffle(CivilianTypes)), { ChurchMiddle.Location, VillageMiddle.Location }, 0)
|
||||
if not MiddleChurch.IsDead then
|
||||
Utils.Do(Civs3, function(civ)
|
||||
Trigger.OnKilled(civ, function()
|
||||
CiviliansKilled = CiviliansKilled + 1
|
||||
end)
|
||||
end)
|
||||
MiddleChurchEmpty = true
|
||||
end
|
||||
|
||||
local proxy = Actor.Create("powerproxy.parabombs", false, { Owner = USSR })
|
||||
proxy.TargetAirstrike(ChurchMiddle.CenterPosition, Angle.NorthWest)
|
||||
end
|
||||
end)
|
||||
|
||||
local foot4Triggered
|
||||
Trigger.OnEnteredFootprint(SWVillageTrigger, function(actor, id)
|
||||
if actor.Owner == Allies and not foot4Triggered then
|
||||
Trigger.RemoveFootprintTrigger(id)
|
||||
foot4Triggered = true
|
||||
|
||||
Utils.Do(SWVillage, function(building)
|
||||
building.Owner = Allies
|
||||
end)
|
||||
|
||||
Civs4 = Reinforcements.Reinforce(Allies, Utils.Take(5, Utils.Shuffle(CivilianTypes)), { ChurchSouthwest.Location, VillageSouthwest.Location }, 0)
|
||||
if not SWChurch.IsDead then
|
||||
Utils.Do(Civs4, function(civ)
|
||||
Trigger.OnKilled(civ, function()
|
||||
CiviliansKilled = CiviliansKilled + 1
|
||||
end)
|
||||
end)
|
||||
SWChurchEmpty = true
|
||||
end
|
||||
end
|
||||
|
||||
Trigger.AfterDelay(DateTime.Seconds(30), function()
|
||||
local swAttackers = Reinforcements.Reinforce(USSR, { "3tnk", "3tnk", "3tnk", "ttnk", "e4", "e4", "e4" }, { SWVillageAttack.Location, VillageSouthwest.Location }, 20)
|
||||
Utils.Do(swAttackers, IdleHunt)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
SetCivilianEvacuatedText = function()
|
||||
local civiliansEvacuated = UserInterface.GetFluentMessage("civilians-evacuated",
|
||||
{ ["evacuated"] = CiviliansEvacuated, ["threshold"] = CiviliansEvacuatedThreshold })
|
||||
UserInterface.SetMissionText(civiliansEvacuated, TextColor)
|
||||
end
|
||||
|
||||
CiviliansEvacuatedThreshold =
|
||||
{
|
||||
hard = 20,
|
||||
normal = 15,
|
||||
easy = 10
|
||||
}
|
||||
CiviliansKilledThreshold =
|
||||
{
|
||||
hard = 1,
|
||||
normal = 6,
|
||||
easy = 11
|
||||
}
|
||||
CiviliansEvacuated = 0
|
||||
CiviliansKilled = 0
|
||||
EvacuateCivilians = function()
|
||||
Trigger.OnInfiltrated(SafeHouse, function()
|
||||
CiviliansEvacuated = CiviliansEvacuated + 1
|
||||
SetCivilianEvacuatedText()
|
||||
end)
|
||||
|
||||
Trigger.OnKilled(SafeHouse, function()
|
||||
USSR.MarkCompletedObjective(SovietObj)
|
||||
end)
|
||||
|
||||
local enemyBase = Utils.Where(USSR.GetActors(), function(actor)
|
||||
return
|
||||
actor.HasProperty("Sell") and
|
||||
actor.Type ~= "brik"
|
||||
end)
|
||||
|
||||
Trigger.OnAllKilled(enemyBase, function()
|
||||
Media.PlaySoundNotification(Allies, "AlertBleep")
|
||||
Media.DisplayMessage(UserInterface.GetFluentMessage("chinook-assist-evacuation"), UserInterface.GetFluentMessage("chinook-pilot"))
|
||||
Reinforcements.Reinforce(Allies, { "tran" }, { ChinookEntry.Location, ChinookLZ.Location })
|
||||
end)
|
||||
end
|
||||
|
||||
Tick = function()
|
||||
USSR.Cash = 10000
|
||||
if CiviliansEvacuated >= CiviliansEvacuatedThreshold then
|
||||
Allies.MarkCompletedObjective(RescueCivilians)
|
||||
end
|
||||
|
||||
if CiviliansKilled >= CiviliansKilledThreshold then
|
||||
Allies.MarkFailedObjective(RescueCivilians)
|
||||
end
|
||||
|
||||
if AlliesArrived and Allies.HasNoRequiredUnits() then
|
||||
USSR.MarkCompletedObjective(SovietObj)
|
||||
end
|
||||
end
|
||||
|
||||
WorldLoaded = function()
|
||||
Allies = Player.GetPlayer("Allies")
|
||||
USSR = Player.GetPlayer("USSR")
|
||||
|
||||
InitObjectives(Allies)
|
||||
|
||||
if Difficulty == "easy" then
|
||||
RescueCivilians = AddPrimaryObjective(Allies, "rescue-civilians-island-shelter-easy")
|
||||
elseif Difficulty == "normal" then
|
||||
RescueCivilians = AddPrimaryObjective(Allies, "rescue-civilians-island-shelter-normal")
|
||||
else
|
||||
RescueCivilians = AddPrimaryObjective(Allies, "rescue-civilians-island-shelter-hard")
|
||||
end
|
||||
|
||||
ClearWaterway = AddSecondaryObjective(Allies, "clear-enemy-submarines")
|
||||
SovietObj = AddPrimaryObjective(USSR, "")
|
||||
|
||||
CiviliansEvacuatedThreshold = CiviliansEvacuatedThreshold[Difficulty]
|
||||
CiviliansKilledThreshold = CiviliansKilledThreshold[Difficulty]
|
||||
TextColor = Allies.Color
|
||||
SetCivilianEvacuatedText()
|
||||
StandardDrop = Actor.Create("paradrop", false, { Owner = USSR })
|
||||
FlamerDrop = Actor.Create("flamerdrop", false, { Owner = USSR })
|
||||
Camera.Position = DefaultCameraPosition.CenterPosition
|
||||
MissionStart()
|
||||
VillageSetup()
|
||||
EvacuateCivilians()
|
||||
ActivateAI()
|
||||
end
|
||||
BIN
mods/ra/maps/fall-of-greece-2-evacuation/map.bin
Normal file
BIN
mods/ra/maps/fall-of-greece-2-evacuation/map.bin
Normal file
Binary file not shown.
7
mods/ra/maps/fall-of-greece-2-evacuation/map.ftl
Normal file
7
mods/ra/maps/fall-of-greece-2-evacuation/map.ftl
Normal file
@@ -0,0 +1,7 @@
|
||||
## rules.yaml
|
||||
briefing =
|
||||
Soviet forces are advancing into rural Athens, threatening the civilian population. Evacuate civilians from each of the four towns to the island in the northeast.
|
||||
|
||||
The civilians are hiding in the town churches, and will only come out once when you enter the town. Get in, get them out, and get them to the island.
|
||||
|
||||
The civilians are your primary objective - removing the Soviet threat is optional.
|
||||
BIN
mods/ra/maps/fall-of-greece-2-evacuation/map.png
Normal file
BIN
mods/ra/maps/fall-of-greece-2-evacuation/map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
1085
mods/ra/maps/fall-of-greece-2-evacuation/map.yaml
Normal file
1085
mods/ra/maps/fall-of-greece-2-evacuation/map.yaml
Normal file
File diff suppressed because it is too large
Load Diff
106
mods/ra/maps/fall-of-greece-2-evacuation/rules.yaml
Normal file
106
mods/ra/maps/fall-of-greece-2-evacuation/rules.yaml
Normal file
@@ -0,0 +1,106 @@
|
||||
Player:
|
||||
PlayerResources:
|
||||
DefaultCash: 3500
|
||||
|
||||
World:
|
||||
LuaScript:
|
||||
Scripts: campaign.lua, utils.lua, evacuation.lua, evacuation-AI.lua
|
||||
MissionData:
|
||||
WinVideo: allymorf.vqa
|
||||
LossVideo: snstrafe.vqa
|
||||
Briefing: briefing
|
||||
ScriptLobbyDropdown@difficulty:
|
||||
ID: difficulty
|
||||
Label: dropdown-difficulty.label
|
||||
Description: dropdown-difficulty.description
|
||||
Values:
|
||||
easy: options-difficulty.easy
|
||||
normal: options-difficulty.normal
|
||||
hard: options-difficulty.hard
|
||||
Default: normal
|
||||
|
||||
PARADROP:
|
||||
ParatroopersPower:
|
||||
DropItems: E1, E1, E1, E2, E2
|
||||
|
||||
FLAMERDROP:
|
||||
ParatroopersPower:
|
||||
DropItems: E2, E2, E4, E4, E4
|
||||
|
||||
^CivInfantry:
|
||||
Infiltrates:
|
||||
Types: CivilianEntry
|
||||
ValidRelationships: Ally
|
||||
|
||||
TECN:
|
||||
-Infiltrates:
|
||||
|
||||
TECN2:
|
||||
-Infiltrates:
|
||||
|
||||
GNRL:
|
||||
-Infiltrates:
|
||||
|
||||
MISS:
|
||||
Targetable:
|
||||
TargetTypes: GroundActor, C4, DetonateAttack, Structure, CivilianEntry
|
||||
|
||||
QTNK:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
ATEK:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
STEK:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
E7:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
E7.noautotarget:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
PDOX:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
MSLO:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
FTRK:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
MCV:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
MRJ:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
HPAD:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
TTNK:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
CA:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
GAP:
|
||||
Buildable:
|
||||
Prerequisites: ~disabled
|
||||
|
||||
3TNK:
|
||||
Buildable:
|
||||
Prerequisites: ~vehicles.soviet
|
||||
Reference in New Issue
Block a user