oprf_assets/gci-radar/Nasal/gci.nas

512 lines
19 KiB
Plaintext

# (c) 2018 pinto
# license: gplv2+
# v0.1 - just a very basic proof of concept at this point.
# will only report the closest bad guy, does no threat assessment.
# todo:
# test
# toggle radar on/off
# all the other requests that a gci can do (only handles BRAA right now)
##########################################
### Variables
##########################################
# prop_watch is a mp[x] boolean property set by that aircraft designer. the plane sets this to 1 when it wants to receive BRAA
# if the plane is not on the enemy list, and is one of the first ~10 to
# request a BRAA, then it will receive it.
var damage_prop = props.globals.getNode("/carrier/sunk");
var prop_watch = {
"MiG-21bis": [0,1,2],
"MiG-21MF-75": [0,1,2],
"QF-4E": [0,1,2],
"F-16": [50,51,52],
};
var radar_stations = [
"gci",
"frigate",
];
var update_rate = 10; #how often the message should update in seconds
var hostile_radius = 300000; #max distance to check against, in meters
var true = 1;
var false = 0;
# request types
var NONE = 0;
var PICTURE = 1;
var BOGEYDOPE = 2;
var CUTOFF = 3;
var PENDING = 0;
var SENDING = 1;
var SENT = 2;
var output_prop = 0;
var radar_tx_output_prop = 11;
var enemy_node = props.globals.getNode("/enemies");
var player_node = props.globals.getNode("/ai/models");
var opfor_switch = props.globals.getNode("/enemies/opfor-switch"); # targets all non-opfor
var friend_switch = props.globals.getNode("/enemies/friend-switch"); # targets all opfor
##########################################
### Objects
##########################################
var gci_contact = {
new: func(c, class) {
var m = {parents:[gci_contact]};
m.time_from_last_message = 0;
m.node = c;
m.model = remove_suffix(remove_suffix(split(".", split("/", c.getNode("sim/model/path").getValue())[-1])[0], "-model"), "-anim");
m.valid = c.getNode("valid");
m.callsign = c.getNode("callsign").getValue();
if ( contains(prop_watch, m.model) ) {
m.picture_node = c.getNode("sim/multiplay/generic/bool["~prop_watch[m.model][0]~"]");
m.bogeydope_node = c.getNode("sim/multiplay/generic/bool["~prop_watch[m.model][1]~"]");
m.cutoff_node = c.getNode("sim/multiplay/generic/bool["~prop_watch[m.model][2]~"]");
} else {
m.picture_node = -1;
m.bogeydope_node = -1;
m.cutoff_node = -1;
}
m.is_radar_station = find_match(m.model,radar_stations);
m.contact = radar_logic.Contact.new(c,class);
m.foe = false;
m.match = false;
m.request = NONE;
m.last_request = NONE;
m.request_status = PENDING;
m.msg_queue = [];
if (m.is_radar_station) {
m.radar_station_process_send();
} else {
m.process_send();
}
return m;
},
getValid: func() {
if (me.valid.getValue() == 0 or me.callsign != me.node.getNode("callsign").getValue()) {
return 0;
} else {
return 1;
}
},
check_foe: func() {
me.foe = false;
if (opfor_switch.getValue() == true ) {
if (left(string.lc(me.callsign),5) != "opfor") {
me.foe = true;
}
} elsif (friend_switch.getValue() == true) {
if (left(string.lc(me.callsign),5) == "opfor") {
me.foe = true;
}
} else {
foreach (var cs; enemy_node.getChildren()) {
if (me.callsign == cs.getValue()) {
me.foe = true;
break;
}
}
}
return me.foe;
},
update_request: func() {
if (me.picture_node == -1) {
return;
}
#print("updating info");
if (me.picture_node.getValue() == true) {
me.request = PICTURE;
} elsif (me.bogeydope_node.getValue() == true) {
me.request = BOGEYDOPE;
} elsif (me.cutoff_node.getValue() == true) {
me.request = CUTOFF;
} else {
me.request = NONE;
}
},
check_node: func() {
#print('checking node');
if (me.picture_node == -1){
#print('watch node is -1');
return false;
} elsif (me.check_foe() == true) {
#print('check_foe is true');
return false;
} elsif (me.request == NONE) {
#print('node is false');
return false;
} elsif (systime() - me.time_from_last_message < update_rate) {
#print("update failed");
return false;
} elsif (me.request_status == SENT) {
return false;
} else {
me.time_from_last_message = systime();
return true;
}
},
process_send: func() {
# messages are first in, first out order
# updates every 1.5 seconds
#print('in process_send()');
if (damage_prop.getValue() == 0) {
me.update_request();
#print('rqst: ' ~ me.request);
#print('last: ' ~ me.last_request);
#print('stat: ' ~ me.request_status);
#print('queue size: ' ~ size(me.msg_queue));
if (me.request == NONE) {
#print('setting request to none');
me.request_status = PENDING;
} elsif (me.request != NONE and me.request_status == PENDING and me.request_status != SENT and size(me.msg_queue) > 0) {
#print('setting status to sending');
me.request_status = SENDING;
} elsif (me.request_status == SENDING and size(me.msg_queue) == 0) {
#print('setting status to sent');
me.request_status = SENT;
}
if (me.request == NONE or me.request_status == SENT) {
#print('emptying queue');
me.msg_queue = [];
}
if (me.request != me.last_request) {
#print('setting status to pending and emptying queue');
me.request_status = PENDING;
me.msg_queue = [];
}
me.last_request = me.request;
if (size(me.msg_queue) > 0 and me.request_status == SENDING) {
#print("msg_queue: " ~ debug.dump(me.msg_queue));
setprop("/sim/multiplay/generic/string["~output_prop~"]",me.msg_queue[0]);
output_prop = output_prop > 9 ? 0 : output_prop + 1;
me.msg_queue = purge_from_vector(me.msg_queue,0);
}
}
settimer(func(){me.process_send();},1.3);
},
radar_station_process_send: func() {
# messages are first in, first out order
# updates every 1.5 seconds
#print('in process_send()');
if (size(me.msg_queue) > 0 and damage_prop.getValue() == 0) {
#print("msg_queue: " ~ debug.dump(me.msg_queue));
setprop("/sim/multiplay/generic/string["~radar_tx_output_prop~"]",me.msg_queue[0]);
radar_tx_output_prop = radar_tx_output_prop >= 16 ? 11 : radar_tx_output_prop + 1;
me.msg_queue = purge_from_vector(me.msg_queue,0);
}
settimer(func(){me.radar_station_process_send();},1.3);
},
};
##########################################
### Functions
##########################################
# gather up all contacts, so we can iterate over them
var cx_master_list = [];
var gather_contacts = func() {
# first we need to clean the contact list
for (var i = 0; i < size(cx_master_list); i = i + 1) {
if (cx_master_list[i] == nil) { break; }
if (!cx_master_list[i].getValid()) {
print("purging contact: " ~ cx_master_list[i].contact.get_Callsign());
cx_master_list = purge_from_vector(cx_master_list, i);
}
}
var matching = false;
foreach(var mp; player_node.getChildren("multiplayer")) {
if (mp.getNode("valid").getValue() == 1) {
matching = false;
foreach(var cx; cx_master_list) {
if ( mp.getPath() == cx.contact.getNode().getPath() and mp.getNode("callsign").getValue() == cx.callsign) {
matching = true;
break;
}
}
if (matching == false) {
cx = gci_contact.new(mp,0);
print("adding contact: " ~ cx.contact.get_Callsign());
append(cx_master_list,cx);
}
}
}
}
var check_requests = func(){
foreach (var cx; cx_master_list) {
if (cx.check_foe() == true or cx.check_node() == false) { continue; }
if (size(cx.msg_queue) > 0) { continue; } # msg queue should be reset or emptied when request changes.
if (cx.request == NONE) {
#print('reqest is none in check_requests');
continue;
} elsif (cx.request == PICTURE) {
var blue_coords = [];
var opfor_coords = [];
var match = false;
append(blue_coords, cx.contact.get_Coord());
foreach (var check; cx_master_list) {
if (!check_visible(check)) { continue; }
match = false;
if (check.check_foe()) {
foreach (var coord; opfor_coords) {
if (coord.distance_to(check.contact.get_Coord()) < 3 * NM2M) {
match = true;
break;
}
}
if (!match) { append(opfor_coords,check.contact.get_Coord()); }
} else {
foreach (var coord; blue_coords) {
if (coord.distance_to(check.contact.get_Coord()) < 3 * NM2M) {
match = true;
break;
}
}
if (!match) { append(blue_coords,check.contact.get_Coord()); }
}
if (!match) {
#send message
var bearing = math.round(cx.contact.get_Coord().course_to(check.contact.get_Coord()),1);
var range = math.round(check.contact.get_Coord().distance_to(cx.contact.get_Coord()));
var altitude = math.round(check.contact.get_altitude(),1);
# requestor-callsign:unique-message-id:2:bearing:range:altitude:[BLUFOR=0|OPFOR=1]
append(cx.msg_queue, cx.callsign ~ ":" ~ get_random() ~ ":2:" ~ bearing ~ ":" ~ range ~ ":" ~ altitude ~ ":" ~ check.check_foe());
}
}
if (size(cx.msg_queue) == 0) {
append(cx.msg_queue, cx.callsign ~ ":" ~ get_random() ~ ":1:n:n:n:n");
} else {
append(cx.msg_queue, cx.callsign ~ ":" ~ get_random() ~ ":0:d:d:d:d");
}
} elsif (cx.request == BOGEYDOPE) {
min_dist = hostile_radius;
closest = nil;
#print('checking bogey dope');
foreach (var check; cx_master_list) {
if (!check.check_foe()) { continue; }
var dist = cx.contact.get_Coord().distance_to(check.contact.get_Coord());
if ( dist > min_dist) { continue; }
if (!check_visible(check)) { continue; }
min_dist = dist;
closest = check;
}
if (closest != nil) {
var bearing = math.round(cx.contact.get_Coord().course_to(closest.contact.get_Coord()),1);
var range = math.round(closest.contact.get_Coord().distance_to(cx.contact.get_Coord()));
var altitude = math.round(closest.contact.get_altitude(),1);
var aspect = math.round(math.periodic(-180,180,closest.contact.get_heading() - closest.contact.get_Coord().course_to(cx.contact.get_Coord())));
append(cx.msg_queue, cx.callsign ~ ":" ~ get_random() ~ ":3:" ~ bearing ~ ":" ~ range ~ ":" ~ altitude ~ ":" ~ aspect);
} else {
append(cx.msg_queue, cx.callsign ~ ":" ~ get_random() ~ ":1:n:n:n:n");
}
} elsif (cx.request == CUTOFF) {
min_dist = hostile_radius;
closest = nil;
foreach (var check; cx_master_list) {
if (!check.check_foe()) { continue; }
var dist = cx.contact.get_Coord().distance_to(check.contact.get_Coord());
if ( dist > min_dist) { continue; }
if (!check_visible(check)) { continue; }
min_dist = dist;
closest = check;
}
if (closest != nil) {
var bogey_speed = closest.contact.speed.getValue();
var cx_speed = cx.contact.speed.getValue();
var bogey_heading = closest.contact.heading.getValue();
var bearing = cx.contact.get_Coord().course_to(closest.contact.get_Coord());
var range = closest.contact.get_Coord().distance_to(cx.contact.get_Coord());
var altitude = math.round(closest.contact.get_altitude(),1);
var aspect = math.round(math.periodic(-180,180,closest.contact.get_heading() - closest.contact.get_Coord().course_to(cx.contact.get_Coord())));
# get_intercept(bearing, dist_m, runnerHeading, runnerSpeed, chaserSpeed)
var info = get_intercept(bearing, range, bogey_heading, bogey_speed * KT2MPS, cx_speed * KT2MPS);
if (info == nil) {
append(cx.msg_queue, cx.callsign ~ ":" ~ get_random() ~ ":1:n:n:n:n");
} else {
append(cx.msg_queue, cx.callsign ~ ":" ~ get_random() ~ ":4:" ~ int(info[1]) ~ ":" ~ int(info[0]) ~ ":" ~ altitude ~ ":" ~ aspect);
}
} else {
append(cx.msg_queue, cx.callsign ~ ":" ~ get_random() ~ ":1:n:n:n:n");
}
}
}
}
var cx_data_transmit = func(){
#print('transmitting');
foreach (var cx; cx_master_list) {
if (!cx.is_radar_station) { continue; }
if (size(cx.msg_queue) > 0) { continue; }
if (cx.check_foe()) { continue; }
foreach (var tx; cx_master_list) {
if (tx != cx) {
append(cx.msg_queue,cx.callsign ~ ":" ~ get_random() ~ ":" ~ tx.callsign);
}
}
}
}
var data_receive_callsigns = [];
var cx_data_receive = func() {
# clean out old data (15 seconds)
var time = systime();
var new_vec = [];
foreach (var rx; data_receive_callsigns){
if (time - rx[1] < 15) {
append(new_vec,rx);
}
}
data_receive_callsigns = new_vec;
foreach (var cx; cx_master_list) {
for (var i = 11; i <= 15; i = i + 1) {
var msg = getprop(cx.node.getPath() ~ "/sim/multiplay/generic/string["~i~"]");
if (msg == "") { continue; }
if (msg == nil) { continue; }
msg = split(":",msg);
if (msg[0] != getprop("/sim/multiplay/callsign")) { continue; };
if (find_match(msg[2], data_receive_callsigns)) { continue; }
#print("adding cs to received list");
append(data_receive_callsigns,[msg[2],systime()]);
}
}
}
var check_visible = func(check) {
foreach(var rx; data_receive_callsigns) {
if (rx[0] == check.callsign) {
return true;
}
}
if (radar_logic.isNotBehindTerrain(check.node) == false){ return false; }
var target_heading = check.contact.heading.getValue();
var relative_bearing = math.abs(geo.normdeg180(check.contact.get_Coord().course_to(geo.aircraft_position()) - target_heading));
var target_radial_airspeed = (-1 * ( ( relative_bearing / 90 ) - 1 ) ) * check.node.getNode("velocities/true-airspeed-kt").getValue();
if ( math.abs(target_radial_airspeed) < 20 ) { return false; } # i.e. notching, landed aircraft
return true;
}
var iter = 0;
var main_loop = func() {
#print("looping");
if (getprop("/carrier/sunk/") == 0) {
if (iter == 0) {
gather_contacts();
cx_data_transmit();
}
cx_data_receive();
check_requests();
}
iter = iter >= 7 ? 0 : iter + 1;
settimer(func(){main_loop();},1);
}
var get_intercept = func(bearing, dist_m, runnerHeading, runnerSpeed, chaserSpeed) {
# from Leto
# needs: bearing, dist_m, runnerHeading, runnerSpeed, chaserSpeed
# dist_m > 0 and chaserSpeed > 0
#var bearing = 184;var dist_m=31000;var runnerHeading=186;var runnerSpeed= 200;var chaserSpeed=250;
var trigAngle = 90-bearing;
var RunnerPosition = [dist_m*math.cos(trigAngle*D2R), dist_m*math.sin(trigAngle*D2R),0];
var ChaserPosition = [0,0,0];
var VectorFromRunner = vector.Math.minus(ChaserPosition, RunnerPosition);
var runner_heading = 90-runnerHeading;
var RunnerVelocity = [runnerSpeed*math.cos(runner_heading*D2R), runnerSpeed*math.sin(runner_heading*D2R),0];
var a = chaserSpeed * chaserSpeed - runnerSpeed * runnerSpeed;
var b = 2 * vector.Math.dotProduct(VectorFromRunner, RunnerVelocity);
var c = -dist_m * dist_m;
if ((b*b-4*a*c)<0) {
# intercept not possible
return nil;
}
var t1 = (-b+math.sqrt(b*b-4*a*c))/(2*a);
var t2 = (-b-math.sqrt(b*b-4*a*c))/(2*a);
if (t1 < 0 and t2 < 0) {
# intercept not possible
return nil;
}
var timeToIntercept = 0;
if (t1 > 0 and t2 > 0) {
timeToIntercept = math.min(t1, t2);
} else {
timeToIntercept = math.max(t1, t2);
}
var InterceptPosition = vector.Math.plus(RunnerPosition, vector.Math.product(timeToIntercept, RunnerVelocity));
var ChaserVelocity = vector.Math.product(1/timeToIntercept, vector.Math.minus(InterceptPosition, ChaserPosition));
var interceptAngle = vector.Math.angleBetweenVectors([0,1,0], ChaserVelocity);
var interceptHeading = geo.normdeg(ChaserVelocity[0]<0?-interceptAngle:interceptAngle);
#print("output:");
#print("time: " ~ timeToIntercept);
#print("heading: " ~ interceptHeading);
return [timeToIntercept, interceptHeading];
}
var purge_from_vector = func(vec, idx = nil, val = nil) {
if (idx == nil and val == nil) { return vec; }
var new_vec = [];
for (var i = 0; i < size(vec); i = i + 1) {
if ((idx != nil and i == idx) or (val != nil and val == vec[i])) { continue; }
append(new_vec, vec[i]);
}
return new_vec;
}
var get_random = func() {
return int(rand() * 9999)
}
var find_match = func(val,vec) {
if (size(vec) == 0) {
return 0;
}
foreach (var a; vec) {
#print(a);
if (a == val) { return 1; }
}
return 0;
}
var remove_suffix = func(s, x) {
var len = size(x);
if (substr(s, -len) == x)
return substr(s, 0, size(s) - len);
return s;
}
# visibility function
settimer( func{
setprop("/sim/multiplay/visibility-range-nm",1200);
print("set mp visibility to " ~ 1200);
}, 15);
main_loop();