From fee08cca57480d0b5a6a0cf1b79f437020661443 Mon Sep 17 00:00:00 2001
From: Stuart Buchanan <stuart_d_buchanan@yahoo.co.uk>
Date: Fri, 18 Jun 2010 21:13:47 +0100
Subject: [PATCH] Initial check-in of MP groups function

---
 Nasal/multiplayer.nas | 98 +++++++++++++++++++++++++++++++++++++++----
 Nasal/string.nas      | 13 +++++-
 gui/menubar.xml       | 16 +++++--
 preferences.xml       | 29 ++++++++++++-
 4 files changed, 143 insertions(+), 13 deletions(-)

diff --git a/Nasal/multiplayer.nas b/Nasal/multiplayer.nas
index 9bf4d351f..c7ed0ce85 100644
--- a/Nasal/multiplayer.nas
+++ b/Nasal/multiplayer.nas
@@ -7,19 +7,24 @@
 # 2) Display a complete history of chat via dialog.
 #
 # 3) Allow chat messages to be written by the user.
-
+#
+# 4) Pilot list dialog, including ability to hide aircraft
 
 var is_active = func getprop("/sim/multiplay/txport") or getprop("/sim/multiplay/rxport");
 
-
+# Hashes of last message and aircraft visibility.
 var lastmsg = {};
-var ignore = {};
+var visibility = {};
+
+# Constants for the visibility hash
+var HIDDEN = 0;
+var VISIBLE = 1;
 
 var check_messages = func {
     foreach (var mp; values(model.callsign)) {
         var msg = mp.node.getNode("sim/multiplay/chat", 1).getValue();
         if (msg and msg != lastmsg[mp.callsign]) {
-            if (!contains(ignore, mp.callsign))
+            if (visibility[mp.callsign] == VISIBLE)
                 echo_message(mp.callsign, msg);
             lastmsg[mp.callsign] = msg;
         }
@@ -149,6 +154,80 @@ var handle_key = func(key)
   }
 }
 
+# Determine the visibility of an MP aircraft
+# based on previous ignore/show settings and the
+# class of the aircraft
+determineVisibility = func(callsign, ai_node) {
+
+    if (! contains(visibility, callsign)) {
+        # Not previously seen. Look at class
+
+        var class = ai_node.getNode("sim/multiplay/usage-class-hash", 1).getValue();
+
+        if ((class == nil) or (class == "")) {
+            # Default to "Default"
+            class = string.hash("Default");
+        }
+
+
+        foreach (var n; props.globals.getNode("/sim/multiplay/display").getChildren("usage-class")) {
+            var h = n.getNode("hash", 1);
+            var visible = n.getNode("visible", 1).getBoolValue();
+
+            if ((h == nil) or (h.getValue() == nil) or (h.getValue() == 0)) {
+                # No hash found, generate one.
+                var name = n.getNode("name", 1).getValue();
+                h.setValue(string.hash(name));
+            }
+
+            if (h.getValue() == class) {
+                # Class found. Determine whether visible or not
+                if (visible) {
+                    ai_node.getNode("controls/invisible", 1).setValue(0);
+                    visibility[callsign] = VISIBLE;
+                } else {
+                    ai_node.getNode("controls/invisible", 1).setValue(1);
+                    visibility[callsign] = HIDDEN;
+                }
+
+                found_usage = 1;
+                continue;
+            }
+        }
+
+        if (!found_usage) {
+            # If we don't recognize the class, don't display.
+            ai_node.getNode("controls/invisible", 1).setValue(1);
+            visibility[callsign] = HIDDEN;
+        }
+
+    } else if (visibility[callsign] == HIDDEN) {
+        # Previously seen and set to invisible.
+        ai_node.getNode("controls/invisible", 1).setValue(1);
+    } else if (visibility[callsign] == VISIBLE) {
+        # Previously seen and shown
+        ai_node.getNode("controls/invisible", 1).setValue(0);
+    }
+}
+
+# Check all aircraft visibility based on class. To be used when
+# the set of visible classes change.
+resetVisibility = func() {
+
+    visibility = {};
+
+    foreach (var n; props.globals.getNode("ai/models", 1).getChildren("multiplayer")) {
+        if (!n.getNode("valid").getValue())
+            continue;
+
+        if ((var callsign = n.getNode("callsign")) == nil or !(callsign = callsign.getValue()))
+            continue;
+        if (!(callsign = string.trim(callsign)))
+            continue;
+
+        determineVisibility(callsign, n);
+    }
+}
 
 
 # multiplayer.dialog.show() -- displays pilot list dialog
@@ -285,7 +364,7 @@ var dialog = {
                 "distance-to-km": distance / 1000.0,
                 "distance-to-nm": distance * M2NM,
                 "position/altitude-m": n.getNode("position/altitude-ft").getValue() * FT2M,
-                "controls/invisible": contains(ignore, mp.callsign),
+                "controls/invisible": (visibility[mp.callsign] == HIDDEN),
             });
         }
         if (PILOTSDLG_RUNNING)
@@ -314,10 +393,10 @@ var dialog = {
         }
     },
     toggle_ignore: func (callsign) {
-        if (contains(ignore, callsign)) {
-            delete(ignore, callsign);
+        if (visibility[callsign] == VISIBLE) {
+            visibility[callsign] = HIDDEN;
         } else {
-            ignore[callsign] = 1;
+            visibility[callsign] = VISIBLE;
         }
     },
     close: func {
@@ -424,6 +503,9 @@ var model = {
             var data = { node: n, callsign: callsign, model: model, root: root,
                     sort: string.lc(callsign), available: available };
 
+            # Control visibility
+            determineVisibility(callsign, n);
+
             me.data[root] = data;
             me.callsign[callsign] = data;
             append(available ? me.available : me.unavailable, data);
diff --git a/Nasal/string.nas b/Nasal/string.nas
index e5be2990a..3e6f29148 100644
--- a/Nasal/string.nas
+++ b/Nasal/string.nas
@@ -75,7 +75,18 @@ var uc = func(str) {
 var icmp = func(a, b) cmp(lc(a), lc(b));
 var imatch = func(a, b) match(lc(a), lc(b));
 
-
+##
+# Very simple hash function
+#
+var hash = func(str) {
+    var hash_val = 0;
+    if (str != nil) {
+        for (var i = 0; i < size(str); i += 1) {
+            hash_val += math.mod(str[i], 2*(i+1));
+        }
+    }
+	return int(hash_val);
+}
 
 
 ##
diff --git a/gui/menubar.xml b/gui/menubar.xml
index f6f8738ba..e684d6014 100644
--- a/gui/menubar.xml
+++ b/gui/menubar.xml
@@ -248,7 +248,7 @@
 				<script>setprop("/autopilot/route-manager/input", "@previous")</script>
 			</binding>
 		</item>
-		
+
         <item>
 			<label>Next Waypoint</label>
 			<binding>
@@ -267,7 +267,7 @@
 				</script>
 			</binding>
 		</item>
-		
+
 		<item>
 			<label>Map</label>
 			<binding>
@@ -335,7 +335,7 @@
 				<dialog-name>local_weather</dialog-name>
 			</binding>
 		</item>
-		
+
 		<item>
 			<label>Local Weather Tiles</label>
 			<binding>
@@ -442,6 +442,7 @@
 			</binding>
 		</item>
 
+
 		<item>
 			<label>Scenario</label>
 			<binding>
@@ -479,6 +480,15 @@
 			</binding>
 		</item>
 
+		<item>
+			<label>Usage Class</label>
+			<binding>
+				<command>dialog-show</command>
+				<dialog-name>mp-display</dialog-name>
+			</binding>
+		</item>
+
+
 		<item>
 			<label>MPCarrier selection</label>
 			<binding>
diff --git a/preferences.xml b/preferences.xml
index daa3e8506..757894058 100644
--- a/preferences.xml
+++ b/preferences.xml
@@ -595,6 +595,33 @@ Started September 2000 by David Megginson, david@megginson.com
    <chat-menu include="ATC/chat-menu-entries.xml"/>
    <write-message-log type="bool">false</write-message-log>
    <default-model type="string">Models/Geometry/glider.ac</default-model>
+   <usage-class type="string">Default</usage-class>
+   <display>
+    <usage-class>
+     <name type="string">Default</name>
+     <visible type="bool">true</visible>
+    </usage-class>
+    <usage-class>
+     <name type="string">Newbie</name>
+     <visible type="bool">true</visible>
+    </usage-class>
+    <usage-class>
+     <name type="string">Student</name>
+     <visible type="bool">true</visible>
+    </usage-class>
+    <usage-class>
+     <name type="string">FGCom</name>
+     <visible type="bool">true</visible>
+    </usage-class>
+    <usage-class>
+     <name type="string">Dogfight</name>
+     <visible type="bool">false</visible>
+    </usage-class>
+    <usage-class>
+     <name type="string">Ignore</name>
+     <visible type="bool">false</visible>
+    </usage-class>
+   </display>
   </multiplay>
 
   <user>
@@ -677,7 +704,7 @@ Started September 2000 by David Megginson, david@megginson.com
     <scenario>
       <name>Marginal VFR</name>
       <metar>XXXX 012345Z 23010KT 5000 SHRA SCT012 BKN018 OVC060 15/11 Q1010</metar>
-      <description>After the storm - limited visibility and some showers. 
+      <description>After the storm - limited visibility and some showers.
                    Go or No-Go?</description>
     </scenario>
     <scenario>