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();