diff --git a/Frigate/Frigate-set.xml b/Frigate/Frigate-set.xml
index dcbde43..5148d41 100644
--- a/Frigate/Frigate-set.xml
+++ b/Frigate/Frigate-set.xml
@@ -317,6 +317,9 @@
Aircraft/Frigate/Nasal/damage.nas
+
+ Aircraft/Frigate/Nasal/gci.nas
+
diff --git a/Frigate/Models/Armament/Weapons/MP_missile/smoke_trail.xml b/Frigate/Models/Armament/Weapons/MP_missile/smoke_trail.xml
index f8ad308..f7cf189 100644
--- a/Frigate/Models/Armament/Weapons/MP_missile/smoke_trail.xml
+++ b/Frigate/Models/Armament/Weapons/MP_missile/smoke_trail.xml
@@ -14,7 +14,7 @@
- 7
+ 0
0
0
@@ -50,30 +50,30 @@
- 0.4
- 0.5
- 0.5
- 0.5
+ 0.9
+ 1
+ 1
+ 0.75
- 1.0
+ 0.25
- 0.4
- 0.5
- 0.5
+ 0.9
+ 1
+ 1
0.1
- 6
+ 16
- 4
+ 16
0.5
diff --git a/Frigate/Nasal/damage.nas b/Frigate/Nasal/damage.nas
index 7e95537..16a85c6 100644
--- a/Frigate/Nasal/damage.nas
+++ b/Frigate/Nasal/damage.nas
@@ -81,6 +81,7 @@ var incoming_listener = func {
var last_vector = split(":", last);
var author = last_vector[0];
var callsign = getprop("sim/multiplay/callsign");
+ callsign = size(callsign) < 8 ? callsign : left(callsign,7);
if (size(last_vector) > 1 and author != callsign) {
# not myself
#print("not me");
@@ -197,6 +198,11 @@ var incoming_listener = func {
print("cannon");
if (size(last_vector) > 2 and last_vector[2] == " "~callsign) {
print("cannon hit us");
+ if (size(last_vector) < 4) {
+ # msg is either missing number of hits, or has no trailing dots from spam filter.
+ print('"'~last~'" is not a legal hit message, tell the shooter to upgrade his OPRF plane :)');
+ return;
+ }
var last3 = split(" ", last_vector[3]);
#print("last3[2]: " ~ last3[2]);
#print("last3[1]: " ~ last3[1]);
@@ -211,12 +217,12 @@ var incoming_listener = func {
var probability = cannon_types[last_vector[1]];
for (var i = 1; i <= hit_count; i = i + 1) {
var failed = fail_systems(probability);
- damaged_sys = damaged_sys + failed;
+ #damaged_sys = damaged_sys + failed;
}
# that someone is me!
#print("hitting me");
- printf("Took %.1f%% damage from cannon! %s systems was hit.", probability*hit_count*100, damaged_sys);
+ #printf("Took %.1f%% damage from cannon! %s systems was hit.", probability*hit_count*100, damaged_sys);
}
}
}
diff --git a/Frigate/Nasal/fdm.nas b/Frigate/Nasal/fdm.nas
index 1ab6e01..18d9863 100644
--- a/Frigate/Nasal/fdm.nas
+++ b/Frigate/Nasal/fdm.nas
@@ -36,11 +36,20 @@ setprop("/carrier/roll-deg",0);
setprop("/carrier/roll-offset",0);
setprop("/carrier/sunk",0);
+var arrived = 0;
+
var PositionUpdater = func () {
settimer( PositionUpdater, 1/frequency );
-
+
var position = geo.aircraft_position();
+
+ if ( arrived == 0 and getprop("/carrier/sunk") == 0 and getprop("/autopilot/route-manager/wp-last/dist") != nil and getprop("/autopilot/route-manager/wp-last/dist") < 1 ) {
+ setprop("/sim/multiplay/chat",getprop("sim/multiplay/callsign") ~ " has arrived safely!");
+ print("arrived");
+ arrived = 1;
+ #return;
+ }
var time_now = getprop("/sim/time/elapsed-sec");
var dt = (time_now - time_last) * sim_speed;
@@ -91,6 +100,8 @@ var PositionUpdater = func () {
setprop("/position/altitude-ft",getprop("/position/altitude-ft") - ( sink_rate ));
setprop("/carrier/pitch-offset",getprop("/carrier/pitch-offset") + ( pitch_rate ));
setprop("/carrier/roll-offset",getprop("/carrier/roll-offset") + ( roll_rate ));
+
+ print("sunk");
}
#set pitch
diff --git a/Frigate/Nasal/gci.nas b/Frigate/Nasal/gci.nas
new file mode 100644
index 0000000..b0f862a
--- /dev/null
+++ b/Frigate/Nasal/gci.nas
@@ -0,0 +1,499 @@
+# (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]
+};
+
+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.getNode() == 0 and 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()) {
+ 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() ) {
+ 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);
+
+ 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;
+}
+
+main_loop();