mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge branch 'python-api' into release-candidate
This commit is contained in:
commit
def15f5565
@ -72,6 +72,8 @@ namespace DataIndex {
|
||||
airborne,
|
||||
cargoWeight,
|
||||
drawArguments,
|
||||
customString,
|
||||
customInteger,
|
||||
lastIndex,
|
||||
endOfData = 255
|
||||
};
|
||||
|
||||
@ -132,6 +132,8 @@ public:
|
||||
virtual void setAirborne(bool newValue) { updateValue(airborne, newValue, DataIndex::airborne); }
|
||||
virtual void setCargoWeight(double newValue) { updateValue(cargoWeight, newValue, DataIndex::cargoWeight); }
|
||||
virtual void setDrawArguments(vector<DataTypes::DrawArgument> newValue);
|
||||
virtual void setCustomString(string newValue) { updateValue(customString, newValue, DataIndex::customString); }
|
||||
virtual void setCustomInteger(unsigned long newValue) { updateValue(customInteger, newValue, DataIndex::customInteger); }
|
||||
|
||||
/********** Getters **********/
|
||||
virtual string getCategory() { return category; }
|
||||
@ -201,6 +203,8 @@ public:
|
||||
virtual bool getAirborne() { return airborne; }
|
||||
virtual double getCargoWeight() { return cargoWeight; }
|
||||
virtual vector<DataTypes::DrawArgument> getDrawArguments() { return drawArguments; }
|
||||
virtual string getCustomString() { return customString; }
|
||||
virtual unsigned long getCustomInteger() { return customInteger; }
|
||||
|
||||
protected:
|
||||
unsigned int ID;
|
||||
@ -273,6 +277,9 @@ protected:
|
||||
bool airborne = false;
|
||||
double cargoWeight = 0;
|
||||
vector<DataTypes::DrawArgument> drawArguments;
|
||||
|
||||
string customString = "";
|
||||
unsigned long customInteger = 0;
|
||||
|
||||
/********** Other **********/
|
||||
unsigned int taskCheckCounter = 0;
|
||||
|
||||
@ -189,7 +189,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
string color = to_string(value[L"color"]);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
|
||||
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
command = dynamic_cast<Command*>(new Smoke(color, loc));
|
||||
log(username + " added a " + color + " smoke at (" + to_string(lat) + ", " + to_string(lng) + ")", true);
|
||||
@ -223,8 +223,8 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
string liveryID = to_string(unit[L"liveryID"]);
|
||||
string skill = to_string(unit[L"skill"]);
|
||||
|
||||
spawnOptions.push_back({unitType, location, loadout, skill, liveryID, heading});
|
||||
log(username + " spawned a " + coalition + " " + unitType , true);
|
||||
spawnOptions.push_back({ unitType, location, loadout, skill, liveryID, heading });
|
||||
log(username + " spawned a " + coalition + " " + unitType, true);
|
||||
}
|
||||
|
||||
if (key.compare("spawnAircrafts") == 0)
|
||||
@ -257,8 +257,8 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
Coords location; location.lat = lat; location.lng = lng;
|
||||
string liveryID = to_string(unit[L"liveryID"]);
|
||||
string skill = to_string(unit[L"skill"]);
|
||||
|
||||
spawnOptions.push_back({ unitType, location, "", skill, liveryID, heading});
|
||||
|
||||
spawnOptions.push_back({ unitType, location, "", skill, liveryID, heading });
|
||||
log(username + " spawned a " + coalition + " " + unitType, true);
|
||||
}
|
||||
|
||||
@ -404,7 +404,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unsigned int ID = unit[L"ID"].as_integer();
|
||||
double lat = unit[L"location"][L"lat"].as_double();
|
||||
double lng = unit[L"location"][L"lng"].as_double();
|
||||
|
||||
|
||||
Coords location; location.lat = lat; location.lng = lng;
|
||||
cloneOptions.push_back({ ID, location });
|
||||
log(username + " cloning unit with ID " + to_string(ID), true);
|
||||
@ -433,7 +433,8 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unsigned char alarmState = value[L"alarmState"].as_number().to_uint32();
|
||||
unit->setAlarmState(alarmState);
|
||||
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") alarm state to " + to_string(alarmState), true);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
log("Error while setting setAlarmState. Unit does not exist.");
|
||||
}
|
||||
}
|
||||
@ -562,7 +563,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unit->setTargetingRange(value[L"targetingRange"].as_number().to_double());
|
||||
unit->setAimMethodRange(value[L"aimMethodRange"].as_number().to_double());
|
||||
unit->setAcquisitionRange(value[L"acquisitionRange"].as_number().to_double());
|
||||
|
||||
|
||||
log(username + " updated unit " + unit->getUnitName() + "(" + unit->getName() + ") engagementProperties", true);
|
||||
}
|
||||
}
|
||||
@ -587,7 +588,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr) {
|
||||
unit->setOnOff(onOff);
|
||||
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") onOff to: " + (onOff? "true": "false"), true);
|
||||
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") onOff to: " + (onOff ? "true" : "false"), true);
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
@ -711,7 +712,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unitsManager->acquireControl(ID);
|
||||
unsigned char operateAs = value[L"operateAs"].as_number().to_uint32();
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
if (unit != nullptr)
|
||||
unit->setOperateAs(operateAs);
|
||||
}
|
||||
/************************/
|
||||
@ -817,7 +818,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
command = dynamic_cast<Command*>(new DeleteSpot(spotID));
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("setCommandModeOptions") == 0)
|
||||
else if (key.compare("setCommandModeOptions") == 0)
|
||||
{
|
||||
setCommandModeOptions(value);
|
||||
log(username + " updated the Command Mode Options", true);
|
||||
@ -827,7 +828,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unitsManager->loadDatabases();
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("setCargoWeight") == 0)
|
||||
else if (key.compare("setCargoWeight") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
@ -852,6 +853,28 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("setCustomString") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr) {
|
||||
string customString = to_string(value[L"customString"]);
|
||||
unit->setCustomString(customString);
|
||||
log(username + " set custom string to unit " + unit->getUnitName() + "(" + unit->getName() + "), " + customString, true);
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("setCustomInteger") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr) {
|
||||
double customNumber = value[L"customInteger"].as_double();
|
||||
unit->setCustomInteger(customNumber);
|
||||
log(username + " set custom number to unit " + unit->getUnitName() + "(" + unit->getName() + "), " + to_string(customNumber), true);
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
else
|
||||
{
|
||||
log("Unknown command: " + key);
|
||||
|
||||
@ -346,6 +346,8 @@ void Unit::getData(stringstream& ss, unsigned long long time)
|
||||
case DataIndex::airborne: appendNumeric(ss, datumIndex, airborne); break;
|
||||
case DataIndex::cargoWeight: appendNumeric(ss, datumIndex, cargoWeight); break;
|
||||
case DataIndex::drawArguments: appendVector(ss, datumIndex, drawArguments); break;
|
||||
case DataIndex::customString: appendString(ss, datumIndex, customString); break;
|
||||
case DataIndex::customInteger: appendNumeric(ss, datumIndex, customInteger); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -549,6 +549,8 @@ export enum DataIndexes {
|
||||
airborne,
|
||||
cargoWeight,
|
||||
drawingArguments,
|
||||
customString,
|
||||
customInteger,
|
||||
endOfData = 255,
|
||||
}
|
||||
|
||||
|
||||
@ -293,6 +293,8 @@ export interface UnitData {
|
||||
airborne: boolean;
|
||||
cargoWeight: number;
|
||||
drawingArguments: DrawingArgument[];
|
||||
customString: string;
|
||||
customInteger: number;
|
||||
}
|
||||
|
||||
export interface LoadoutItemBlueprint {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -161,6 +161,8 @@ export abstract class Unit extends CustomMarker {
|
||||
#airborne: boolean = false;
|
||||
#cargoWeight: number = 0;
|
||||
#drawingArguments: DrawingArgument[] = [];
|
||||
#customString: string = "";
|
||||
#customInteger: number = 0;
|
||||
|
||||
/* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */
|
||||
#blueprint: UnitBlueprint | null = null;
|
||||
@ -414,6 +416,12 @@ export abstract class Unit extends CustomMarker {
|
||||
getDrawingArguments() {
|
||||
return this.#drawingArguments;
|
||||
}
|
||||
getCustomString() {
|
||||
return this.#customString;
|
||||
}
|
||||
getCustomInteger() {
|
||||
return this.#customInteger;
|
||||
}
|
||||
|
||||
static getConstructor(type: string) {
|
||||
if (type === "GroundUnit") return GroundUnit;
|
||||
@ -811,6 +819,12 @@ export abstract class Unit extends CustomMarker {
|
||||
case DataIndexes.drawingArguments:
|
||||
this.#drawingArguments = dataExtractor.extractDrawingArguments();
|
||||
break;
|
||||
case DataIndexes.customString:
|
||||
this.#customString = dataExtractor.extractString();
|
||||
break;
|
||||
case DataIndexes.customInteger:
|
||||
this.#customInteger = dataExtractor.extractUInt32();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -936,6 +950,8 @@ export abstract class Unit extends CustomMarker {
|
||||
airborne: this.#airborne,
|
||||
cargoWeight: this.#cargoWeight,
|
||||
drawingArguments: this.#drawingArguments,
|
||||
customString: this.#customString,
|
||||
customInteger: this.#customInteger
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
8
scripts/python/API/.vscode/launch.json
vendored
8
scripts/python/API/.vscode/launch.json
vendored
@ -51,6 +51,14 @@
|
||||
"program": "example_precise_movement.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
},
|
||||
{
|
||||
"name": "Infantry boarding",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "infantry_boarding.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -69,4 +69,6 @@ class DataIndexes(Enum):
|
||||
AIRBORNE = 65
|
||||
CARGO_WEIGHT = 66
|
||||
DRAW_ARGUMENTS = 67
|
||||
CUSTOM_STRING = 68
|
||||
CUSTOM_INTEGER = 69
|
||||
END_OF_DATA = 255
|
||||
625
scripts/python/API/infantry_boarding.py
Normal file
625
scripts/python/API/infantry_boarding.py
Normal file
@ -0,0 +1,625 @@
|
||||
import asyncio
|
||||
from asyncio import Semaphore
|
||||
import json
|
||||
from random import randrange
|
||||
from api import API, Unit, UnitSpawnTable
|
||||
from math import pi
|
||||
import logging
|
||||
|
||||
#Set some globals up
|
||||
alternate_time = 300
|
||||
before_can_re_embark_time = 300
|
||||
####Transport types#####
|
||||
transport_ground = {
|
||||
"M-113": {
|
||||
"max_capacity": 4,
|
||||
"max_embark_range": 50,
|
||||
"doors": 1,
|
||||
"door_positions": [(3.35,pi),(0,0)],
|
||||
"board_positions": [(15,pi),(0,0)],
|
||||
"door_argument_nos": None,
|
||||
"door_open_thresholds": None,
|
||||
"is_rear_loader": True,
|
||||
"boarding_distance": 5
|
||||
}
|
||||
}
|
||||
|
||||
transport_helicopters = {
|
||||
"UH-1H":{
|
||||
"max_capacity": 8,
|
||||
"max_embark_range": 100,
|
||||
"doors": 2,
|
||||
"door_positions": [(2.5,-pi/2),(0.8,0),(2.5,pi/2),(0.8,0)], #two values here offset and heading offset in radians and second distance offset and heading offset in radians
|
||||
"board_positions": [(15,-pi/2),(0,0),(15,pi/2),(0,0)],
|
||||
"door_argument_nos": [43,44], #draw argument numbers for the doors
|
||||
"door_open_thresholds": [0.8,0.8], #value above which the door is considered open
|
||||
"is_rear_loader": False,
|
||||
"boarding_distance": 5
|
||||
}
|
||||
}
|
||||
|
||||
transport_types = set(transport_helicopters.keys()).union(transport_ground.keys())
|
||||
|
||||
#Infantry transport
|
||||
embarker_inf_red = {}
|
||||
embarker_inf_blue = {"Soldier M4 GRG","soldier_wwii_us"}
|
||||
embarker_types = embarker_inf_blue.union(embarker_inf_red)
|
||||
|
||||
#Time it takes after loading or unloading to swap back to the other
|
||||
|
||||
# Setup a logger for the module
|
||||
logger = logging.getLogger("infantry_transport")
|
||||
logger.setLevel(logging.INFO)
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('[%(asctime)s] %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
class Transporter(Unit):
|
||||
def __init__(self, Unit):
|
||||
self.unit = Unit
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"is_transport": self.unit.is_transport,
|
||||
"max_capacity": self.unit.max_capacity,
|
||||
"current_capacity": self.unit.current_capacity,
|
||||
"max_embark_range": self.unit.max_embark_range,
|
||||
"boarding_distance": self.unit.boarding_distance,
|
||||
"current_cargo_weight": self.unit.current_cargo_weight,
|
||||
"unit_array": [unit.ID for unit in self.unit.unit_array],
|
||||
"en_boarding_queue": [unit.ID for unit in self.unit.en_boarding_queue],
|
||||
"doors": self.unit.doors,
|
||||
"door_positions": self.unit.door_positions,
|
||||
"board_positions": self.unit.board_positions,
|
||||
"door_argument_nos": self.unit.door_argument_nos,
|
||||
"door_open_thresholds": self.unit.door_open_thresholds,
|
||||
"is_rear_loader": self.unit.is_rear_loader,
|
||||
"will_disembark": self.unit.will_disembark
|
||||
}
|
||||
|
||||
def set_as_transport(self):
|
||||
self.unit.is_transport = True
|
||||
if self.unit.name in transport_helicopters:
|
||||
if self.unit.name == "UH-1H":
|
||||
self.unit.max_capacity = transport_helicopters["UH-1H"]["max_capacity"]
|
||||
self.unit.max_embark_range = transport_helicopters["UH-1H"]["max_embark_range"]
|
||||
self.unit.boarding_distance = transport_helicopters["UH-1H"]["boarding_distance"]
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.doors = transport_helicopters["UH-1H"]["doors"]
|
||||
self.unit.door_positions = transport_helicopters["UH-1H"]["door_positions"]
|
||||
self.unit.board_positions = transport_helicopters["UH-1H"]["board_positions"]
|
||||
|
||||
self.unit.door_argument_nos = transport_helicopters["UH-1H"]["door_argument_nos"]
|
||||
self.unit.will_disembark = False
|
||||
self.unit.register_draw_argument(43) #Register draw argument 43 for UH-1H
|
||||
self.unit.register_draw_argument(44)
|
||||
self.unit.door_open_thresholds = transport_helicopters["UH-1H"]["door_open_thresholds"]
|
||||
self.unit.is_rear_loader = transport_helicopters["UH-1H"]["is_rear_loader"]
|
||||
else:
|
||||
self.unit.max_capacity = 8
|
||||
self.unit.max_embark_range = 100
|
||||
self.unit.boarding_distance = 5
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.doors = 1
|
||||
self.unit.door_positions = [(5,pi),(0,0)]
|
||||
self.unit.board_positions = [(15,pi),(0,0)]
|
||||
self.unit.door_argument_nos = None
|
||||
self.unit.door_open_thresholds = None
|
||||
self.unit.will_disembark = False
|
||||
self.unit.is_rear_loader = True
|
||||
|
||||
elif self.unit.name in transport_ground:
|
||||
if self.unit.name == "M-113":
|
||||
self.unit.max_capacity = transport_ground["M-113"]["max_capacity"]
|
||||
self.unit.max_embark_range = transport_ground["M-113"]["max_embark_range"]
|
||||
self.unit.boarding_distance = transport_ground["M-113"]["boarding_distance"]
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.doors = transport_ground["M-113"]["doors"]
|
||||
self.unit.door_positions = transport_ground["M-113"]["door_positions"]
|
||||
self.unit.board_positions = transport_ground["M-113"]["board_positions"]
|
||||
self.unit.door_argument_nos = transport_ground["M-113"]["door_argument_nos"]
|
||||
self.unit.door_open_thresholds = transport_ground["M-113"]["door_open_thresholds"]
|
||||
self.unit.will_disembark = False
|
||||
self.unit.is_rear_loader = transport_ground["M-113"]["is_rear_loader"]
|
||||
else:
|
||||
self.unit.max_capacity = 4
|
||||
self.unit.max_embark_range = 50
|
||||
self.unit.boarding_distance = 5
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.doors = 1
|
||||
self.unit.door_positions = [(5,pi),(0,0)]
|
||||
self.unit.board_positions = [(15,pi),(0,0)]
|
||||
self.unit.door_argument_nos = None
|
||||
self.unit.door_open_thresholds = None
|
||||
self.unit.will_disembark = False
|
||||
self.unit.is_rear_loader = True
|
||||
|
||||
logger.info(f"Set unit '{self.unit.name}' as transport, with {self.unit.current_capacity} / {self.unit.max_capacity}.")
|
||||
|
||||
class DisembarkedInfantry(Unit):
|
||||
def __str__(self):
|
||||
return f"DisembarkedInfrantry(unit_id={self.unit_id}, group_id={self.group_id}, position={self.position}, heading={self.heading})"
|
||||
|
||||
def __init__(self, Unit):
|
||||
self.unit = Unit
|
||||
|
||||
def disembark_from_transport(self):
|
||||
destination = self.position.project_with_bearing_and_distance(30, self.heading)
|
||||
# Set the destination for the unit
|
||||
self.set_roe(4) #set to hold fire to avoid stopping to shoot
|
||||
self.is_loadable = False
|
||||
self.set_path([destination])
|
||||
if self.check_for_enemy_in_range():
|
||||
self.set_speed(10)
|
||||
else:
|
||||
self.set_speed(3)
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=15.0,
|
||||
timeout=30.0 # Timeout after 30 seconds if the destination is not reached
|
||||
)
|
||||
|
||||
def check_for_enemy_in_range(self):
|
||||
units = api.get_units()
|
||||
for unit in units.values():
|
||||
if unit.alive and unit.coalition != self.coalition:
|
||||
distance_to_enemy = self.position.distance_to(unit.position)
|
||||
if distance_to_enemy < 2000: #if an enemy is within 100m
|
||||
return True
|
||||
return False
|
||||
|
||||
async def on_destination_reached(self, _, reached: bool):
|
||||
if not reached:
|
||||
# logger.info(f"Unit {self} did not reach its destination.")
|
||||
self.set_roe(1)
|
||||
new_patrol = self.position.project_with_bearing_and_distance(1000, self.transport_spawn_heading)
|
||||
await asyncio.sleep(self.time_delay) #wait a bit before trying again
|
||||
self.set_path([new_patrol])
|
||||
if self.check_for_enemy_in_range():
|
||||
self.set_speed(10)
|
||||
else:
|
||||
self.set_speed(1.3)
|
||||
await asyncio.sleep(before_can_re_embark_time) #wait before setting to be boardable
|
||||
self.is_loadable = True
|
||||
logger.info(f"Unit {self} is now boardable again.")
|
||||
else:
|
||||
self.set_roe(1)
|
||||
logger.info(f"Unit {self} has reached its destination.")
|
||||
new_patrol = self.position.project_with_bearing_and_distance(1000, self.transport_spawn_heading)
|
||||
await asyncio.sleep(self.time_delay) #wait a bit before trying again
|
||||
self.set_path([new_patrol])
|
||||
if self.check_for_enemy_in_range():
|
||||
self.set_speed(10)
|
||||
else:
|
||||
self.set_speed(1.3)
|
||||
await asyncio.sleep(before_can_re_embark_time) #wait before setting to be boardable
|
||||
self.is_loadable = True
|
||||
logger.info(f"Unit {self} is now boardable again.")
|
||||
|
||||
|
||||
class Embarker(Unit):
|
||||
def __str__(self):
|
||||
return f"DisembarkedInfrantry(unit_id={self.unit_id}, group_id={self.group_id}, position={self.position}, heading={self.heading})"
|
||||
|
||||
def __init__(self, Unit):
|
||||
self.unit = Unit
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"is_embarker": self.unit.is_embarker,
|
||||
"is_moving": self.unit.is_moving,
|
||||
"is_loadable": self.unit.is_loadable,
|
||||
"in_embark_queue": self.unit.in_embark_queue if hasattr(self.unit, 'in_embark_queue') else False,
|
||||
"transport_unit": self.unit.transport_unit.ID if hasattr(self.unit, 'transport_unit') and self.unit.transport_unit else None
|
||||
}
|
||||
|
||||
def set_as_embarker(self):
|
||||
self.unit.is_embarker = True
|
||||
self.unit.is_moving = False
|
||||
self.unit.is_loadable = True
|
||||
logger.info(f"Set unit '{self.unit.name}' as embarker.")
|
||||
self.unit.set_custom_string("I am an embarker.")
|
||||
|
||||
def can_board(self):
|
||||
transport = self.transport_unit
|
||||
if transport.current_capacity < transport.max_capacity:
|
||||
transport.unit_array.append(self.name)
|
||||
transport.current_capacity += 1
|
||||
self.delete_unit()
|
||||
else:
|
||||
pass
|
||||
|
||||
def board_transport(self):
|
||||
door, num_doors_open = self.get_closest_door()
|
||||
if num_doors_open > 1: door_bypass = True
|
||||
else: door_bypass = False
|
||||
|
||||
if door is None:
|
||||
pass
|
||||
elif door is not None:
|
||||
if self.is_moving:
|
||||
pass
|
||||
elif not self.is_moving:
|
||||
distance_to_door = self.position.distance_to(door)
|
||||
distance_to_centre = self.position.distance_to(self.transport_unit.position)
|
||||
if distance_to_door < distance_to_centre:
|
||||
bearing = self.position.bearing_to(door)
|
||||
if hasattr(self,'nudge'):
|
||||
nudge_factor = self.nudge
|
||||
else:
|
||||
nudge_factor = 0
|
||||
destination = self.position.project_with_bearing_and_distance(distance_to_door+nudge_factor, bearing)
|
||||
destination.threshold = 2
|
||||
# Set the destination for the unit
|
||||
self.set_path([destination])
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=2.0,
|
||||
timeout=10.0 # Timeout after 30 seconds if the destination is not reached
|
||||
)
|
||||
self.is_moving = True
|
||||
else:# distance_to_door >= distance_to_centre:
|
||||
if self.transport_unit.is_rear_loader:
|
||||
in_front_of_transport = self.transport_unit.position.project_with_bearing_and_distance(15, self.transport_unit.heading-pi)
|
||||
else:
|
||||
in_front_of_transport = self.transport_unit.position.project_with_bearing_and_distance(15, self.transport_unit.heading)
|
||||
bearing = self.position.bearing_to(in_front_of_transport)
|
||||
destination = self.position.project_with_bearing_and_distance(distance_to_door, bearing)
|
||||
destination.threshold = 2
|
||||
self.set_path([destination])
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=2.0,
|
||||
timeout=10.0
|
||||
)
|
||||
self.is_moving = True
|
||||
|
||||
def get_closest_door(self):
|
||||
return check_closest_open_door(self.transport_unit, self)
|
||||
|
||||
async def on_destination_reached(self, _, reached: bool):
|
||||
if not reached:
|
||||
logger.info(f"Unit {self} did not reach its destination.")
|
||||
self.is_moving = False
|
||||
else:
|
||||
logger.info(f"Unit {self} has reached its destination.")
|
||||
self.is_moving = False
|
||||
|
||||
await asyncio.sleep(10)
|
||||
self.board_transport() # Attempt to board again
|
||||
|
||||
def check_closest_open_door(transport, embarker):
|
||||
if transport.name in transport_helicopters:
|
||||
if transport.door_argument_nos is None and transport.doors > 0:
|
||||
return transport.position.project_with_bearing_and_distance(5,transport.heading + pi), transport.heading + pi
|
||||
elif transport.door_argument_nos is not None and transport.doors > 0:
|
||||
closest_door = None
|
||||
doors_open = 0
|
||||
distance_to_closest_door = float('inf')
|
||||
for i in range(transport.doors):
|
||||
if transport.draw_arguments[i].value >= transport.door_open_thresholds[i]:
|
||||
doors_open += 1
|
||||
distance = embarker.position.distance_to(transport.position.project_with_bearing_and_distance(transport.door_positions[i*2][0], transport.heading + transport.door_positions[i*2][1]).project_with_bearing_and_distance(transport.door_positions[i*2+1][0], transport.heading + transport.door_positions[i*2+1][1]))
|
||||
if distance < distance_to_closest_door:
|
||||
distance_to_closest_door = distance
|
||||
closest_door = transport.position.project_with_bearing_and_distance(transport.door_positions[i*2][0], transport.heading + transport.door_positions[i*2][1]).project_with_bearing_and_distance(transport.door_positions[i*2+1][0], transport.heading + transport.door_positions[i*2+1][1])
|
||||
return closest_door, doors_open
|
||||
else:
|
||||
return None, 0
|
||||
elif transport.name in transport_ground:
|
||||
if transport.door_argument_nos is None and transport.doors > 0:
|
||||
return transport.position.project_with_bearing_and_distance(2,transport.heading + pi), transport.heading + pi
|
||||
elif transport.door_argument_nos is not None and transport.doors > 0:
|
||||
closest_door = None
|
||||
doors_open = 0
|
||||
distance_to_closest_door = float('inf')
|
||||
for i in range(transport.doors):
|
||||
if transport.draw_arguments[i].value >= transport.door_open_thresholds[i]:
|
||||
doors_open += 1
|
||||
distance = embarker.position.distance_to(transport.position.project_with_bearing_and_distance(transport.door_positions[i*2][0], transport.heading + transport.door_positions[i*2][1]).project_with_bearing_and_distance(transport.door_positions[i*2+1][0], transport.heading + transport.door_positions[i*2+1][1]))
|
||||
if distance < distance_to_closest_door:
|
||||
distance_to_closest_door = distance
|
||||
closest_door = transport.position.project_with_bearing_and_distance(transport.door_positions[i*2][0], transport.heading + transport.door_positions[i*2][1]).project_with_bearing_and_distance(transport.door_positions[i*2+1][0], transport.heading + transport.door_positions[i*2+1][1])
|
||||
return closest_door, doors_open
|
||||
else:
|
||||
return None, 0
|
||||
|
||||
def check_for_door_status(transporter):
|
||||
if transporter.name in transport_helicopters:
|
||||
if transporter.door_argument_nos is None and transporter.doors > 0:
|
||||
return True
|
||||
elif transporter.door_argument_nos is not None and transporter.doors > 0:
|
||||
a_door_is_open = False
|
||||
for i in range(transporter.doors):
|
||||
if transporter.draw_arguments[i].value >= transporter.door_open_thresholds[i]:
|
||||
a_door_is_open = True
|
||||
return a_door_is_open
|
||||
else:
|
||||
return False
|
||||
elif transporter.name in transport_ground:
|
||||
if transporter.door_argument_nos is None and transporter.doors > 0:
|
||||
return True
|
||||
elif transporter.door_argument_nos is not None and transporter.doors > 0:
|
||||
a_door_is_open = False
|
||||
for i in range(transporter.doors):
|
||||
if transporter.draw_arguments[i].value >= transporter.door_open_thresholds[i]:
|
||||
a_door_is_open = True
|
||||
return a_door_is_open
|
||||
else:
|
||||
return False
|
||||
|
||||
async def load_loadable_units():
|
||||
units = api.get_units()
|
||||
for embarker in units.values():
|
||||
if embarker.alive and hasattr(embarker, 'is_embarker'):
|
||||
if hasattr(embarker, 'in_embark_queue') and hasattr(embarker, 'transport_unit') and hasattr(embarker, 'is_moving'):
|
||||
if embarker.transport_unit.name in transport_types:
|
||||
#check the speed and distance, slow down if close
|
||||
distance_to_transport = embarker.position.distance_to(embarker.transport_unit.position)
|
||||
if distance_to_transport > 10 and embarker.speed < 1.4:
|
||||
embarker.set_speed(10)
|
||||
elif distance_to_transport < 10 and embarker.speed >= 3:
|
||||
embarker.set_speed(2)
|
||||
elif distance_to_transport < 5 and embarker.speed >= 1.3:
|
||||
embarker.set_speed(1.3)
|
||||
if embarker.roe != "hold":
|
||||
embarker.set_roe(4) #set to hold fire to avoid stopping to shoot
|
||||
#check the doors are open
|
||||
if check_for_door_status(embarker.transport_unit):
|
||||
closest_door, num_doors_open = check_closest_open_door(embarker.transport_unit, embarker)
|
||||
if closest_door is not None:
|
||||
#print(f"A door is open on {embarker.transport_unit.name}, closest door is {closest_door}, {num_doors_open} doors open")
|
||||
embarker.__class__ = Embarker
|
||||
#check if close enough to board
|
||||
closest_door, _ = embarker.get_closest_door()
|
||||
door_distance = embarker.position.distance_to(closest_door)
|
||||
if door_distance < embarker.transport_unit.boarding_distance:
|
||||
transport = embarker.transport_unit
|
||||
embarker_units = [
|
||||
(embarker, embarker.position.distance_to(transport.position))
|
||||
for embarker in units.values()
|
||||
if embarker.alive
|
||||
and hasattr(embarker, 'is_embarker')
|
||||
and embarker.position.distance_to(transport.position) < transport.boarding_distance
|
||||
]
|
||||
|
||||
embarkers_sorted = sorted(embarker_units, key=lambda x: x[1])
|
||||
if not embarkers_sorted:
|
||||
pass
|
||||
else:
|
||||
if embarker.ID == embarkers_sorted[0][0].ID:
|
||||
transport.current_capacity += 1
|
||||
transport.unit_array.append(embarker)
|
||||
transport.set_cargo_weight(transport.current_cargo_weight + 100) #assume 100kg per infantry with kit
|
||||
transport.current_cargo_weight += 100
|
||||
embarker.delete_unit()
|
||||
asyncio.create_task(set_as_disembarking(transport))
|
||||
break
|
||||
#else run it closer
|
||||
if embarker.is_moving:
|
||||
if hasattr(embarker, 'last_pos'):
|
||||
if embarker.position == embarker.last_pos:
|
||||
embarker.is_moving = False
|
||||
embarker.set_speed(1.3)
|
||||
if hasattr(embarker, 'nudge'):
|
||||
embarker.nudge = embarker.nudge + 2
|
||||
else:
|
||||
embarker.nudge = 2
|
||||
embarker.last_pos = embarker.position
|
||||
pass
|
||||
elif not embarker.is_moving:
|
||||
embarker.board_transport()
|
||||
else:
|
||||
#no doors so do nothing
|
||||
pass
|
||||
|
||||
def generate_transport_units():
|
||||
units = api.get_units()
|
||||
for unit in units.values():
|
||||
if unit.alive and unit.name in transport_types and not hasattr(unit, 'is_transport'):
|
||||
new_transport = Transporter(unit)
|
||||
new_transport.set_as_transport()
|
||||
|
||||
elif unit.alive and unit.name in embarker_types and not hasattr(unit, 'is_embarker'):
|
||||
new_emabarquee = Embarker(unit)
|
||||
new_emabarquee.set_as_embarker()
|
||||
|
||||
async def set_as_disembarking(transport):
|
||||
await asyncio.sleep(alternate_time)
|
||||
transport.will_disembark = True
|
||||
|
||||
async def set_as_not_disembarking(transport):
|
||||
await asyncio.sleep(alternate_time)
|
||||
transport.will_disembark = False
|
||||
|
||||
unload_semaphore = Semaphore(1)
|
||||
|
||||
async def check_for_unloadable_units():
|
||||
# Use the semaphore to ensure only one instance runs at a time
|
||||
async with unload_semaphore:
|
||||
units = api.get_units()
|
||||
try:
|
||||
for transporter in units.values():
|
||||
if transporter.alive and hasattr(transporter, 'is_transport') and transporter.will_disembark:
|
||||
# Check if the transporter is in a position to disembark units
|
||||
if transporter.speed < 2 and check_for_door_status(transporter) and not transporter.airborne: # check speed is less than 2 m/s and doors are open
|
||||
first_two_spawns = True # Track if we are handling the first two spawns
|
||||
to_remove = [] #sets up variable to hold units to remove from queue
|
||||
for disembarker in transporter.unit_array:
|
||||
# Get the open doors
|
||||
open_doors = []
|
||||
open_doors_headings = []
|
||||
for i in range(transporter.doors):
|
||||
if transporter.draw_arguments[i].value >= transporter.door_open_thresholds[i]:
|
||||
door_position = transporter.position.project_with_bearing_and_distance(
|
||||
transporter.door_positions[i * 2][0],
|
||||
transporter.heading + transporter.door_positions[i * 2][1]
|
||||
).project_with_bearing_and_distance(
|
||||
transporter.door_positions[i * 2 + 1][0],
|
||||
transporter.heading + transporter.door_positions[i * 2 + 1][1]
|
||||
)
|
||||
door_heading = transporter.heading + transporter.door_positions[i * 2][1]
|
||||
open_doors.append(door_position)
|
||||
open_doors_headings.append(door_heading)
|
||||
|
||||
# Round-robin spawn mechanism
|
||||
if not hasattr(transporter, 'last_door_index'):
|
||||
transporter.last_door_index = 0 # Initialize the last used door index
|
||||
|
||||
# Get the next door in the round-robin sequence
|
||||
door_index = transporter.last_door_index % len(open_doors)
|
||||
transporter.last_door_index += 1 # Increment the door index for the next spawn
|
||||
|
||||
# Spawn the unit at the selected door
|
||||
door_position = open_doors[door_index]
|
||||
door_heading = open_doors_headings[door_index]
|
||||
|
||||
spawn_table: UnitSpawnTable = UnitSpawnTable(
|
||||
unit_type=disembarker.name,
|
||||
location=door_position,
|
||||
heading=door_heading,
|
||||
skill="High",
|
||||
livery_id=""
|
||||
)
|
||||
|
||||
async def execution_callback(new_group_ID: int):
|
||||
logger.info(f"New units spawned, groupID: {new_group_ID}")
|
||||
units = api.get_units()
|
||||
for new_unit in units.values():
|
||||
if new_unit.group_id == new_group_ID:
|
||||
logger.info(f"New unit spawned: {new_unit}")
|
||||
new_unit.__class__ = DisembarkedInfantry
|
||||
new_unit.transport_spawn_heading = transporter.heading
|
||||
new_unit.disembark_from_transport()
|
||||
new_unit.original_position = new_unit.position
|
||||
#the delay is a function of how many units are left to disembark and how long it takes to get to the disembark spot
|
||||
new_unit.time_delay = transporter.max_capacity*2 - transporter.current_capacity # Random delay between 10 and 30 seconds
|
||||
|
||||
api.spawn_ground_units([spawn_table], transporter.coalition, "", True, 0, execution_callback)
|
||||
to_remove.append(disembarker)
|
||||
transporter.en_boarding_queue = []
|
||||
transporter.current_capacity -= 1
|
||||
transporter.set_cargo_weight(transporter.current_cargo_weight - 100) # Assume 100kg per infantry with kit
|
||||
transporter.current_cargo_weight -= 100
|
||||
|
||||
# Add a delay between spawns
|
||||
if len(open_doors) > 1 and first_two_spawns:
|
||||
# Shorter delay for the first two spawns if both doors are open
|
||||
await asyncio.sleep(0.5)
|
||||
first_two_spawns = False
|
||||
else:
|
||||
# Normal delay for subsequent spawns or single-door spawns
|
||||
await asyncio.sleep(2.5)
|
||||
for disembarker in to_remove:
|
||||
transporter.unit_array.remove(disembarker)
|
||||
if transporter.current_capacity == 0:
|
||||
await set_as_not_disembarking(transporter)
|
||||
|
||||
logger.info(f"Spawned unit '{disembarker.name}' from open door of transport '{transporter.name}'.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error in check_for_unloadable_units: {e}")
|
||||
|
||||
def check_for_loadable_units():
|
||||
units = api.get_units()
|
||||
for transporter in units.values():
|
||||
if transporter.alive and hasattr(transporter, 'is_transport') and not transporter.will_disembark:
|
||||
if len(transporter.unit_array) < transporter.max_capacity:
|
||||
if transporter.speed < 2 and check_for_door_status(transporter): #check speed is less than 2 m/s and doors are open
|
||||
# print("Speed is okay")
|
||||
embarker_units = [
|
||||
(embarker, embarker.position.distance_to(transporter.position))
|
||||
for embarker in units.values()
|
||||
if embarker.alive
|
||||
and hasattr(embarker, 'is_embarker')
|
||||
and getattr(embarker, 'is_loadable', True) # Check if is_loadable is True
|
||||
and embarker.position.distance_to(transporter.position) < transporter.max_embark_range
|
||||
]
|
||||
if embarker_units is None or len(embarker_units) == 0:
|
||||
continue
|
||||
else:
|
||||
for embarker in embarker_units:
|
||||
if hasattr(embarker, 'in_embark_queue') and embarker.in_embark_queue:
|
||||
if embarker.in_embark_queue:
|
||||
embarker_units.remove(embarker)
|
||||
|
||||
embarkers_sorted = sorted(embarker_units, key=lambda x: x[1])
|
||||
closest_embarkers = embarkers_sorted[:transporter.max_capacity-len(transporter.en_boarding_queue)]
|
||||
|
||||
for embarker, distance in closest_embarkers:
|
||||
if embarker not in transporter.en_boarding_queue and distance < transporter.max_embark_range:
|
||||
transporter.en_boarding_queue.append(embarker)
|
||||
embarker.in_embark_queue = True
|
||||
embarker.transport_unit = transporter
|
||||
logger.info(f"Added embarker '{embarker.name}' to '{transporter.name}' s boarding queue.")
|
||||
elif embarker in transporter.en_boarding_queue:
|
||||
pass
|
||||
else:
|
||||
pass #we pass as the transport is full
|
||||
|
||||
|
||||
#############
|
||||
#API SECTION#
|
||||
#############
|
||||
def on_api_startup(api: API):
|
||||
global units_to_delete
|
||||
logger.info("API started")
|
||||
|
||||
# Get all the units from the API. Force an update to get the latest units.
|
||||
units = api.update_units()
|
||||
|
||||
# Initialize the list to hold units to delete
|
||||
units_to_delete = []
|
||||
|
||||
def on_unit_alive_change(unit: Unit, value: bool):
|
||||
global units_to_delete
|
||||
|
||||
if units_to_delete is None:
|
||||
logger.error("units_to_delete is not initialized.")
|
||||
return
|
||||
|
||||
# Check if the unit has been deleted
|
||||
if value is False:
|
||||
if unit in units_to_delete:
|
||||
units_to_delete.remove(unit)
|
||||
else:
|
||||
pass
|
||||
|
||||
async def update_data():
|
||||
units = api.get_units()
|
||||
for unit in units.values():
|
||||
if unit.alive and hasattr(unit, 'is_transport'):
|
||||
stringified_json = json.dumps(Transporter(unit).to_json())
|
||||
unit.set_custom_string(stringified_json)
|
||||
elif unit.alive and hasattr(unit, 'is_embarker'):
|
||||
stringified_json = json.dumps(Embarker(unit).to_json())
|
||||
unit.set_custom_string(stringified_json)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def on_api_update(api: API):
|
||||
generate_transport_units()
|
||||
check_for_loadable_units()
|
||||
asyncio.create_task(load_loadable_units())
|
||||
asyncio.create_task(check_for_unloadable_units())
|
||||
asyncio.create_task(update_data())
|
||||
|
||||
if __name__ == "__main__":
|
||||
api = API()
|
||||
api.register_on_update_callback(on_api_update)
|
||||
api.register_on_startup_callback(on_api_startup)
|
||||
api.run()
|
||||
@ -4,7 +4,7 @@
|
||||
"port": 4512
|
||||
},
|
||||
"authentication": {
|
||||
"gameMasterPassword": "a00a5973aacb17e4659125fbe10f4160d096dd84b2f586d2d75669462a30106d",
|
||||
"gameMasterPassword": "a474219e5e9503c84d59500bb1bda3d9ade81e52d9fa1c234278770892a6dd74",
|
||||
"blueCommanderPassword": "7d2e1ef898b21db7411f725a945b76ec8dcad340ed705eaf801bc82be6fe8a4a",
|
||||
"redCommanderPassword": "abc5de7abdb8ed98f6d11d22c9d17593e339fde9cf4b9e170541b4f41af937e3"
|
||||
},
|
||||
|
||||
@ -83,6 +83,8 @@ class Unit:
|
||||
self.acquisition_range = 0.0
|
||||
self.cargo_weight = 0.0
|
||||
self.draw_arguments: List[DrawArgument] = []
|
||||
self.custom_string = ""
|
||||
self.custom_integer = 0
|
||||
|
||||
self.previous_total_ammo = 0
|
||||
self.total_ammo = 0
|
||||
@ -670,6 +672,20 @@ class Unit:
|
||||
# Trigger callbacks for property change
|
||||
if "draw_arguments" in self.on_property_change_callbacks:
|
||||
self._trigger_callback("draw_arguments", self.draw_arguments)
|
||||
elif datum_index == DataIndexes.CUSTOM_STRING.value:
|
||||
custom_string = data_extractor.extract_string()
|
||||
if custom_string != self.custom_string:
|
||||
self.custom_string = custom_string
|
||||
# Trigger callbacks for property change
|
||||
if "custom_string" in self.on_property_change_callbacks:
|
||||
self._trigger_callback("custom_string", self.custom_string)
|
||||
elif datum_index == DataIndexes.CUSTOM_INTEGER.value:
|
||||
custom_integer = data_extractor.extract_uint32()
|
||||
if custom_integer != self.custom_integer:
|
||||
self.custom_integer = custom_integer
|
||||
# Trigger callbacks for property change
|
||||
if "custom_integer" in self.on_property_change_callbacks:
|
||||
self._trigger_callback("custom_integer", self.custom_integer)
|
||||
|
||||
# --- API functions requiring ID ---
|
||||
def set_path(self, path: List[LatLng]):
|
||||
@ -778,4 +794,10 @@ class Unit:
|
||||
return self.api.send_command({"setCargoWeight": {"ID": self.ID, "weight": cargo_weight}})
|
||||
|
||||
def register_draw_argument(self, argument: int, active: bool = True):
|
||||
return self.api.send_command({"registerDrawArgument": {"ID": self.ID, "argument": argument, "active": active}})
|
||||
return self.api.send_command({"registerDrawArgument": {"ID": self.ID, "argument": argument, "active": active}})
|
||||
|
||||
def set_custom_string(self, custom_string: str):
|
||||
return self.api.send_command({"setCustomString": {"ID": self.ID, "customString": custom_string}})
|
||||
|
||||
def set_custom_integer(self, custom_integer: int):
|
||||
return self.api.send_command({"setCustomInteger": {"ID": self.ID, "customInteger": custom_integer}})
|
||||
Loading…
x
Reference in New Issue
Block a user