mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Add customString and customInteger to Unit data model
Introduced customString and customInteger fields to the Unit class in both backend (C++) and frontend (TypeScript/React). Updated data indexes, interfaces, and API handling to support setting and retrieving these custom fields. Also added UI elements in the unit control menu to display and handle these new properties.
This commit is contained in:
parent
3eef91fb24
commit
a257afca4b
@ -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);
|
||||
|
||||
@ -344,6 +344,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