Difference between revisions of "KellyFuel/Creator Instructions"

From
Jump to: navigation, search
(Fuel Taint)
(Programming Fuel Port Positions)
 
(30 intermediate revisions by the same user not shown)
Line 1: Line 1:
This section is intended for aircraft creators who wish to use KellyFuel in their build.  KellyFuel operates over a channel to send and receive commands to your helicopter.  You can configure a private channel to use for this purpose.  KellyFuel identifies the aircraft being refueled by checking the creator name and the object name against a database.  Since users often rename the aircraft, partial matching is used on the object name looking for specific key parts of the name.  As a creator, you can edit the object name matching criteria for your aircraft on the SA website.
+
This section is intended for aircraft creators who wish to use the KellyFuel pump with their aircraft.  You may choose to implement the full KellyFuel system, or simply set the position of fuel ports on your aircraft.
  
== Chat Commands ==
+
== KellyFuel Protocol ==
  
This section describes the basic commands sent by the pump and the expected replies from the aircraft.  All communication is on your configured channel.  Aircraft creators may optionally choose to open the chat listener for fuel only then the fuel cap on their aircraft is removed to limit the number of open channels.
+
KellyFuel operates over an agreed upon channel to send and receive commands to your helicopter.  You can configure a private channel to use for this purpose on the Shergood web page (coming soon).  KellyFuel identifies the aircraft being refueled by checking the creator name and the object name against a database.  Since users often rename the aircraft, partial matching is used on the object name looking for specific key parts of the name.  As a creator, you can edit the object name matching criteria for your aircraft on the SA website.
 +
 
 +
This section describes the basic commands sent by the pump and the expected replies from the aircraft.  All communication is on your configured channel.  Aircraft creators may optionally choose to open the chat listener for fuel only then the fuel cap on their aircraft is removed to limit the number of open channels.  The pump uses <tt>llRegionSayTo</tt> to send commands to a specific aircraft.  It is recommended that the aircraft also reply using <tt>llRegionSayTo</tt> to the object from which it received the request.
  
 
=== Aircraft Tank Query ===
 
=== Aircraft Tank Query ===
Line 31: Line 33:
 
Aircraft: <tt>@ack add <i>amount</i> <i>actual</i></tt>
 
Aircraft: <tt>@ack add <i>amount</i> <i>actual</i></tt>
  
This command incrementally adds fuel to a tank.  The query specifies the name of the tank, the number of gallons to add, and a fuel type.  Note the fuel type may be omitted by pumps prior to Version 3.0.  The fuel type can be one of:
+
This command incrementally adds fuel to a tank.  The pump will typically send multiple <tt>add</tt> commands over timed intervals to simulate the time needed to refuel.  The query specifies the name of the tank, the number of gallons to add, and a fuel type.  Note the fuel type may be omitted by pumps prior to Version 3.0.  The fuel type can be one of:
  
 
* 100ll - Aviation fuel used by piston aircraft
 
* 100ll - Aviation fuel used by piston aircraft
Line 37: Line 39:
 
* fueloil - Fuel used by certain vintage/steampunk aircraft
 
* fueloil - Fuel used by certain vintage/steampunk aircraft
  
The aircraft response should include the amount requested to be added, and the actual amount added to the tank.  If the amount requested to be added is more than the remaining capacity of the tank, the <i>actual</i> value should be the remaining capacity.  If the tank is full, 0 should be returned for <i>actual</i>.  It is important to return accurate values as detecting when <i>actual</i> is less than <i>requested</i> is how the pump detected when fueling is complete.
+
The aircraft response should include the amount requested to be added, and the actual amount added to the tank.  If the amount requested to be added is more than the remaining capacity of the tank, the <i>actual</i> value should be the remaining capacity.  If the tank is full, 0 should be returned for <i>actual</i>.  It is important to return accurate values as detecting when <i>actual</i> is less than <i>requested</i> is how the pump detects when fueling is complete.
 +
 
 +
<b>Example:</b> The left fuel tank (named "left") in your Cessna 172 holds a maximum of 21.5 gallons, and currently contains 18 gallons of fuel.  The following would be the expected exchange between the pumps and the aircraft:
 +
 
 +
Pump: <tt>@refuel add left 10 100ll</tt><br>
 +
Aircraft: <tt>@ack add 10 3.5</tt>
 +
 
 +
The pump command requests to add 10 gallons of 100ll fuel to the left tank.  The reply from the aircraft acknowledges 10 gallons was requested, but only 3.5 gallons could be added to the tank (the remaining capacity of the tank).
  
 
==== Fuel Taint ====
 
==== Fuel Taint ====
  
The <i>fuel-type</i> parameter of the <tt>add</tt> query can be used to implement handling of misfueling in your aircraft.  If the
+
The <i>fuel-type</i> parameter of the <tt>add</tt> query can optionally be used to implement handling of misfueling in your aircraft.  If your aircraft has a piston (jet) engine and you receive an <tt>add</tt> query with a fuel type other 100ll (jeta), then you can mark an internal flag in your aircraft to indicate the fuel is tainted.  You are free to implement any symptoms of misfueling you choose including failure to start, or failure of engine after a time interval (in RL, engine failure from misfueling often occurs after takeoff once all the clean fuel in the fuel lines have been consumed).  You are also free to implement a method of clearing the taint flag (e.g., by draining the tank).
 +
 
 +
== Programming Fuel Port Positions ==
 +
 
 +
[[File:Fuel_Pump_Main_Menu.png|thumb|right|
 +
<figure id="fig:mainMenu"><caption>
 +
Fuel Pump Main Menu (Admin)</caption></figure>
 +
]]
 +
 
 +
[[File:Fuel Port Edit Menu.png|thumb|right|
 +
<figure id="fig:editMenu"><caption>
 +
Fuel Pump Edit Menu</caption></figure>
 +
]]
 +
 
 +
This section describes how to program the fuel pump for use with your aircraft.  The RP aspects of refueling can be greatly enhanced when the pump knows what fuel tanks your aircraft has, and the location of the filler ports.  The creator of an aircraft and their designated assistant has the ability to use the port configuration options of the KellyFuel pump.  These options allow you to Add, Edit or Remove a tank/port from your aircraft.
 +
 
 +
To program an aircraft, begin by rezing your aircraft near a KellyFuel 3.0 pump.  Click the pump and select the aircraft by object name (or by tail number if you are using the Shergood registration system).  If your aircraft is not detected, you can add it yourself on the Shergood web site at https://shergoodaviation.com/aircraft.php , or if you are a new creator contact Kelly Shergood to be added as a creator on the site. 
 +
 
 +
After connecting the aircraft, click the pump to bring up the main menu.  If you have admin privileges for the aircraft, in addition to the currently configured tank names, you should see a button labeled "*Edit" (see <xr id='fig:mainMenu'/>).  This option is used for editing the aircraft type information on the servers.  The prompt part of the menu includes information about the selected aircraft, its type, and the fuel system designated for that type.  An example of the edit menu is shown in <xr id='fig:editMenu'/>.  However, the "*SetPortPos" and "*DelPort" options will appear only if a specific port has first been selected from the main menu.
 +
 
 +
Before performing edit operations, you should acquire the edit tools from the pump.  Use the option "*Get Setup" from the edit menu.  This will give you a folder with the following tools:
 +
 
 +
* <tt>sa-kf-ground-setup</tt> - Indicates the position of the ground wire clip
 +
* <tt>sa-kf-nozzle-setup</tt> - Indicates the position of a standard gravity fed nozzle
 +
* <tt>sa-kf-pressNozzle-setup</tt> - Indicates the position of a pressure refueling nozzle
 +
 
 +
The following sections will describe how to use these tools.
 +
 
 +
=== Adding A Port ===
 +
 
 +
To add a port/tank to an aircraft use the "AddPort" option from the Edit menu.  This will bring up a text box into which you can type the name of a tank/port on your aircraft.  If the aircraft has only one tank, it is recommended that you use the name "main" for the tank.  Once the port has been added, follow the directions in the next section to set the position of the nozzle for that port.
 +
 
 +
=== Setting Nozzle Position for a Port ===
 +
 
 +
To set the position of a port, first rez the appropriate nozzle tool you received from the "*Get Setup" option.  There are two nozzle types.  One type is the <tt>sa-kf-nozzle-setup</tt> nozzle which is used for gravity-fed fuel system (similar to a car fuel pump nozzle).  The second type is the <tt>sa-kf-pressNozzle-setup</tt> nozzle which is used for high-speed pressure-fed fuel systems (normally only used on large aircraft with high-capacity tanks).  To register the port position:
 +
 
 +
# Edit the tool to the position on your aircraft where your fuel port is located.
 +
# Make sure you have selected the port for which you want register a position from the main menu.
 +
# Select "*SetPortPos" from the *Edit menu.  This will record the position of the fuel tool (relative to the aircraft) on the server.
 +
 
 +
=== Deleting a Port ===
 +
 
 +
To delete a port, select it from the main menu, then choose "*DelPort" from the Edit menu.
 +
 
 +
=== Renaming a Port ===
 +
 
 +
To rename a port, simply delete the port and add it with the new name.  You will need to set its position again if you have already set it.
 +
 
 +
=== Setting Ground Wire Clip Position ===
 +
 
 +
To set the ground wire position:
 +
 
 +
# Rez the <tt>sa-kf-ground-setup</tt> tool near your aircraft
 +
# Edit the tool to the position where you want the ground wire to attach
 +
# Select "*SetGrndPos" from the Edit menu.
 +
 
 +
== Sample Fuel Script ==
 +
 
 +
<pre>
 +
//
 +
// This is a sample kelly-fuel protocol script.  Adapt as necessary for your aircraft.
 +
//
 +
 
 +
integer FUEL_CHAN = 123;                        // Port number to use for fuel system (as registered on web site)
 +
string FUEL_TYPE = "jeta";                      // Fuel type of this aircraft.  Should be "jeta", "100ll".
 +
list TANK_NAMES = ["tank1","tank2"];            // Names of the tanks used by this aircraft (if only one tank, it is suggested it be called "main")
 +
list FUEL_MAX = [30,60];                        // Maximum capacities in gallons of the tanks
 +
 
 +
list fuel_levels = [0,0];                      // Current fuel levels of each tank
 +
integer fuel_taint = 0;                        // Bit mask of fuel taint status in each tank
 +
 
 +
//
 +
// Return 1 if tank #index is tainted with the incorrect fuel type.
 +
//
 +
integer is_tainted(integer index)
 +
{
 +
    return (fuel_taint&(1<<index)) != 0;
 +
}
 +
 
 +
//
 +
// Show fuel levels of all tanks in hover text
 +
//
 +
showLevels()
 +
{
 +
    integer n = llGetListLength(TANK_NAMES);
 +
    integer i;
 +
   
 +
    list lines;
 +
    for (i = 0;i < n;i++) {
 +
        string text = llList2String(TANK_NAMES,i)+": "+llList2String(fuel_levels,i)+" / "+llList2String(FUEL_MAX,i);
 +
        if (is_tainted(i))
 +
            text += " *tainted*";
 +
        lines += [text];
 +
    }
 +
    llSetText(llDumpList2String(lines,"\n"),<1,1,1>,1);
 +
}
 +
 
 +
default
 +
{
 +
    state_entry()
 +
    {
 +
        //
 +
        // Listen on the fuel channel (in aircraft, you can open this channel only while fuel cap is removed to reduce open channels)
 +
        //
 +
        llListen(FUEL_CHAN,"",NULL_KEY,"");
 +
        showLevels();
 +
    }
 +
   
 +
    touch_start(integer n) {
 +
        //
 +
        // Clear fuel tanks and any fuel taint
 +
        //
 +
        integer n = llGetListLength(TANK_NAMES);
 +
        integer i;
 +
        fuel_levels = [];
 +
        fuel_taint = 0;
 +
        for (i = 0;i < n;i++) {
 +
            fuel_levels += [0];
 +
        }
 +
        showLevels();
 +
    }
 +
 
 +
    listen(integer chan,string name,key id,string data) {
 +
        //
 +
        // Show received command for debuging
 +
        //
 +
        llOwnerSay("KF: "+data);
 +
       
 +
        //
 +
        // Parse line into words.  If the first word is not "@refuel" it is not a kellyfuel command (so ignore it)
 +
        //
 +
        list decoded_line = llParseString2List(data,[" "],[]);
 +
        string prefix = llList2String(decoded_line,0);
 +
        if (prefix != "@refuel") return;
 +
        string command = llList2String(decoded_line,1);
 +
       
 +
        if (command == "check") {
 +
            //
 +
            // Check the levels of all tanks
 +
            //
 +
            integer n = llGetListLength(TANK_NAMES);
 +
            integer i;
 +
            for (i = 0;i < n;i++) {
 +
                llRegionSayTo(id,FUEL_CHAN,"@ack 2 check "+llList2String(fuel_levels,i)+" "+llList2String(FUEL_MAX,i)+" "
 +
                                    +llList2String(TANK_NAMES,i)+" "+(string)is_tainted(i));
 +
            }
 +
        } else if (command == "look") {
 +
            //
 +
            // Check the level of a specific tank
 +
            //
 +
            string tank = llList2String(decoded_line,2);
 +
            integer tank_index = llListFindList(TANK_NAMES,[tank]);
 +
            if (tank_index >= 0) {
 +
                llRegionSayTo(id,FUEL_CHAN,"@ack look "+llList2String(fuel_levels,tank_index)+" "+llList2String(FUEL_MAX,tank_index)+" "
 +
                                    +llList2String(TANK_NAMES,tank_index)+" "+(string)is_tainted(tank_index));
 +
            } else {
 +
                llRegionSayTo(id,FUEL_CHAN,"@error Unknown fuel tank");
 +
            }
 +
        } else if (command == "full") {
 +
            //
 +
            // Set all tanks to full
 +
            //
 +
            integer n = llGetListLength(TANK_NAMES);
 +
            integer i;
 +
            fuel_levels = FUEL_MAX;
 +
            llRegionSayTo(id,FUEL_CHAN,"@ack full");
 +
            showLevels();
 +
        } else if (command == "add") {
 +
            //
 +
            // Add fuel to a tank
 +
            //
 +
            string tank = llList2String(decoded_line,2);                // Get name of tank
 +
            integer tank_index = llListFindList(TANK_NAMES,[tank]);    // Get index of tank
 +
            if (tank_index >= 0) {
 +
                float amount = (float)llList2String(decoded_line,3);    // Get amount of fuel to add
 +
                string type = llList2String(decoded_line,4);            // Get the fuel type
 +
               
 +
                if (type != "" && type != FUEL_TYPE)                    // If a fuel type is given, but incorrect for this
 +
                    fuel_taint = fuel_taint | (1<<tank_index);          //    aircraft, mark that tank as tainted.
 +
 
 +
                float current = llList2Float(fuel_levels,tank_index);  // Get current fuel level in selected tank
 +
                float new_level = current + amount;                    // Calculate new fuel level in selected tank
 +
                float max = llList2Float(FUEL_MAX,tank_index);          // Get max fuel level in selected tank
 +
                float added = amount;                                  // Set the amount of fuel added
 +
 
 +
                if (new_level > max) {                                  // If the new fuel level is greater than the
 +
                    added = max-current;                                //    maxmium tank capacity, adjust the amount
 +
                    new_level = max;                                    //    added and the new tank level.
 +
                }
 +
               
 +
                //
 +
                // Acknowledge to pump the actual fuel added
 +
                //
 +
                llRegionSayTo(id,FUEL_CHAN,"@ack add "+(string)amount+" "+(string)added);
 +
               
 +
                fuel_levels = llListReplaceList(fuel_levels,[new_level],tank_index,tank_index);
 +
                showLevels();
 +
            } else {
 +
                llRegionSayTo(id,FUEL_CHAN,"@error Unknown fuel tank");
 +
            }
 +
        }
 +
    }
 +
}
 +
</pre>

Latest revision as of 14:57, 7 September 2022

This section is intended for aircraft creators who wish to use the KellyFuel pump with their aircraft. You may choose to implement the full KellyFuel system, or simply set the position of fuel ports on your aircraft.

1 KellyFuel Protocol

KellyFuel operates over an agreed upon channel to send and receive commands to your helicopter. You can configure a private channel to use for this purpose on the Shergood web page (coming soon). KellyFuel identifies the aircraft being refueled by checking the creator name and the object name against a database. Since users often rename the aircraft, partial matching is used on the object name looking for specific key parts of the name. As a creator, you can edit the object name matching criteria for your aircraft on the SA website.

This section describes the basic commands sent by the pump and the expected replies from the aircraft. All communication is on your configured channel. Aircraft creators may optionally choose to open the chat listener for fuel only then the fuel cap on their aircraft is removed to limit the number of open channels. The pump uses llRegionSayTo to send commands to a specific aircraft. It is recommended that the aircraft also reply using llRegionSayTo to the object from which it received the request.

1.1 Aircraft Tank Query

Pump: @refuel check
Aircraft: @ack 2 check cur-level max-level tank-name

This command is used to query which tanks on an aircraft are ready for fueling. In the aircraft response, "2" is the protocol-number, cur-level is the current number of gallons in a tank, max-level is the maximum number of gallons in the tank, and tank-name is the name of the tank. If the aircraft has only one tank, the name "main" should be used for the tank. If the aircraft has multiple tanks, an "@ack" command should be sent for each tank.

1.2 Fuel Level Query

Pump: @refuel look name
Aircraft: @ack look cur-level max-level tank-name taint-status

This command is used to "look" into a tank. It is similar to the "check" command except that it includes a specific tank name in the query. The response elements are the same as the "check" command but includes and additional taint-status value. The taint status should be 0 if fuel in that tank is clean, and 1 if it is tainted. See the discussion on the "add" command for information on fuel tainting.

1.3 Fill Tank to Full

Pump: @refuel full
Aircraft: @ack full

This command requests that all tanks be completely filled. This command is sent when a user chooses "Auto Fill" from the menu bypassing the normal RP elements.

1.4 Add Fuel to Tank

Pump: @refuel add tank-name amount type
Aircraft: @ack add amount actual

This command incrementally adds fuel to a tank. The pump will typically send multiple add commands over timed intervals to simulate the time needed to refuel. The query specifies the name of the tank, the number of gallons to add, and a fuel type. Note the fuel type may be omitted by pumps prior to Version 3.0. The fuel type can be one of:

  • 100ll - Aviation fuel used by piston aircraft
  • jeta - Fuel for most jet and turbo-prop aircraft
  • fueloil - Fuel used by certain vintage/steampunk aircraft

The aircraft response should include the amount requested to be added, and the actual amount added to the tank. If the amount requested to be added is more than the remaining capacity of the tank, the actual value should be the remaining capacity. If the tank is full, 0 should be returned for actual. It is important to return accurate values as detecting when actual is less than requested is how the pump detects when fueling is complete.

Example: The left fuel tank (named "left") in your Cessna 172 holds a maximum of 21.5 gallons, and currently contains 18 gallons of fuel. The following would be the expected exchange between the pumps and the aircraft:

Pump: @refuel add left 10 100ll
Aircraft: @ack add 10 3.5

The pump command requests to add 10 gallons of 100ll fuel to the left tank. The reply from the aircraft acknowledges 10 gallons was requested, but only 3.5 gallons could be added to the tank (the remaining capacity of the tank).

1.4.1 Fuel Taint

The fuel-type parameter of the add query can optionally be used to implement handling of misfueling in your aircraft. If your aircraft has a piston (jet) engine and you receive an add query with a fuel type other 100ll (jeta), then you can mark an internal flag in your aircraft to indicate the fuel is tainted. You are free to implement any symptoms of misfueling you choose including failure to start, or failure of engine after a time interval (in RL, engine failure from misfueling often occurs after takeoff once all the clean fuel in the fuel lines have been consumed). You are also free to implement a method of clearing the taint flag (e.g., by draining the tank).

2 Programming Fuel Port Positions

Figure 1: Fuel Pump Main Menu (Admin)
Figure 2: Fuel Pump Edit Menu

This section describes how to program the fuel pump for use with your aircraft. The RP aspects of refueling can be greatly enhanced when the pump knows what fuel tanks your aircraft has, and the location of the filler ports. The creator of an aircraft and their designated assistant has the ability to use the port configuration options of the KellyFuel pump. These options allow you to Add, Edit or Remove a tank/port from your aircraft.

To program an aircraft, begin by rezing your aircraft near a KellyFuel 3.0 pump. Click the pump and select the aircraft by object name (or by tail number if you are using the Shergood registration system). If your aircraft is not detected, you can add it yourself on the Shergood web site at https://shergoodaviation.com/aircraft.php , or if you are a new creator contact Kelly Shergood to be added as a creator on the site.

After connecting the aircraft, click the pump to bring up the main menu. If you have admin privileges for the aircraft, in addition to the currently configured tank names, you should see a button labeled "*Edit" (see Figure 1). This option is used for editing the aircraft type information on the servers. The prompt part of the menu includes information about the selected aircraft, its type, and the fuel system designated for that type. An example of the edit menu is shown in Figure 2. However, the "*SetPortPos" and "*DelPort" options will appear only if a specific port has first been selected from the main menu.

Before performing edit operations, you should acquire the edit tools from the pump. Use the option "*Get Setup" from the edit menu. This will give you a folder with the following tools:

  • sa-kf-ground-setup - Indicates the position of the ground wire clip
  • sa-kf-nozzle-setup - Indicates the position of a standard gravity fed nozzle
  • sa-kf-pressNozzle-setup - Indicates the position of a pressure refueling nozzle

The following sections will describe how to use these tools.

2.1 Adding A Port

To add a port/tank to an aircraft use the "AddPort" option from the Edit menu. This will bring up a text box into which you can type the name of a tank/port on your aircraft. If the aircraft has only one tank, it is recommended that you use the name "main" for the tank. Once the port has been added, follow the directions in the next section to set the position of the nozzle for that port.

2.2 Setting Nozzle Position for a Port

To set the position of a port, first rez the appropriate nozzle tool you received from the "*Get Setup" option. There are two nozzle types. One type is the sa-kf-nozzle-setup nozzle which is used for gravity-fed fuel system (similar to a car fuel pump nozzle). The second type is the sa-kf-pressNozzle-setup nozzle which is used for high-speed pressure-fed fuel systems (normally only used on large aircraft with high-capacity tanks). To register the port position:

  1. Edit the tool to the position on your aircraft where your fuel port is located.
  2. Make sure you have selected the port for which you want register a position from the main menu.
  3. Select "*SetPortPos" from the *Edit menu. This will record the position of the fuel tool (relative to the aircraft) on the server.

2.3 Deleting a Port

To delete a port, select it from the main menu, then choose "*DelPort" from the Edit menu.

2.4 Renaming a Port

To rename a port, simply delete the port and add it with the new name. You will need to set its position again if you have already set it.

2.5 Setting Ground Wire Clip Position

To set the ground wire position:

  1. Rez the sa-kf-ground-setup tool near your aircraft
  2. Edit the tool to the position where you want the ground wire to attach
  3. Select "*SetGrndPos" from the Edit menu.

3 Sample Fuel Script

//
// This is a sample kelly-fuel protocol script.  Adapt as necessary for your aircraft.
//

integer FUEL_CHAN = 123;                        // Port number to use for fuel system (as registered on web site)
string FUEL_TYPE = "jeta";                      // Fuel type of this aircraft.  Should be "jeta", "100ll".
list TANK_NAMES = ["tank1","tank2"];            // Names of the tanks used by this aircraft (if only one tank, it is suggested it be called "main")
list FUEL_MAX = [30,60];                        // Maximum capacities in gallons of the tanks

list fuel_levels = [0,0];                       // Current fuel levels of each tank
integer fuel_taint = 0;                         // Bit mask of fuel taint status in each tank

//
// Return 1 if tank #index is tainted with the incorrect fuel type.
//
integer is_tainted(integer index)
{
    return (fuel_taint&(1<<index)) != 0;
}

//
// Show fuel levels of all tanks in hover text
//
showLevels()
{
    integer n = llGetListLength(TANK_NAMES);
    integer i;
    
    list lines;
    for (i = 0;i < n;i++) {
        string text = llList2String(TANK_NAMES,i)+": "+llList2String(fuel_levels,i)+" / "+llList2String(FUEL_MAX,i);
        if (is_tainted(i))
            text += " *tainted*";
        lines += [text];
    }
    llSetText(llDumpList2String(lines,"\n"),<1,1,1>,1);
}

default
{
    state_entry()
    {
        //
        // Listen on the fuel channel (in aircraft, you can open this channel only while fuel cap is removed to reduce open channels)
        //
        llListen(FUEL_CHAN,"",NULL_KEY,"");
        showLevels();
    }
    
    touch_start(integer n) {
        //
        // Clear fuel tanks and any fuel taint
        //
        integer n = llGetListLength(TANK_NAMES);
        integer i;
        fuel_levels = [];
        fuel_taint = 0;
        for (i = 0;i < n;i++) {
            fuel_levels += [0];
        }
        showLevels();
    }

    listen(integer chan,string name,key id,string data) {
        //
        // Show received command for debuging
        //
        llOwnerSay("KF: "+data);
        
        //
        // Parse line into words.  If the first word is not "@refuel" it is not a kellyfuel command (so ignore it)
        //
        list decoded_line = llParseString2List(data,[" "],[]);
        string prefix = llList2String(decoded_line,0);
        if (prefix != "@refuel") return;
        string command = llList2String(decoded_line,1);
        
        if (command == "check") {
            //
            // Check the levels of all tanks
            //
            integer n = llGetListLength(TANK_NAMES);
            integer i;
            for (i = 0;i < n;i++) {
                llRegionSayTo(id,FUEL_CHAN,"@ack 2 check "+llList2String(fuel_levels,i)+" "+llList2String(FUEL_MAX,i)+" "
                                    +llList2String(TANK_NAMES,i)+" "+(string)is_tainted(i));
            }
        } else if (command == "look") {
            //
            // Check the level of a specific tank
            //
            string tank = llList2String(decoded_line,2);
            integer tank_index = llListFindList(TANK_NAMES,[tank]);
            if (tank_index >= 0) {
                llRegionSayTo(id,FUEL_CHAN,"@ack look "+llList2String(fuel_levels,tank_index)+" "+llList2String(FUEL_MAX,tank_index)+" "
                                    +llList2String(TANK_NAMES,tank_index)+" "+(string)is_tainted(tank_index));
            } else {
                llRegionSayTo(id,FUEL_CHAN,"@error Unknown fuel tank");
            }
        } else if (command == "full") {
            //
            // Set all tanks to full
            //
            integer n = llGetListLength(TANK_NAMES);
            integer i;
            fuel_levels = FUEL_MAX;
            llRegionSayTo(id,FUEL_CHAN,"@ack full");
            showLevels();
        } else if (command == "add") {
            //
            // Add fuel to a tank
            //
            string tank = llList2String(decoded_line,2);                // Get name of tank
            integer tank_index = llListFindList(TANK_NAMES,[tank]);     // Get index of tank
            if (tank_index >= 0) {
                float amount = (float)llList2String(decoded_line,3);    // Get amount of fuel to add
                string type = llList2String(decoded_line,4);            // Get the fuel type
                
                if (type != "" && type != FUEL_TYPE)                    // If a fuel type is given, but incorrect for this
                    fuel_taint = fuel_taint | (1<<tank_index);          //    aircraft, mark that tank as tainted.

                float current = llList2Float(fuel_levels,tank_index);   // Get current fuel level in selected tank
                float new_level = current + amount;                     // Calculate new fuel level in selected tank
                float max = llList2Float(FUEL_MAX,tank_index);          // Get max fuel level in selected tank
                float added = amount;                                   // Set the amount of fuel added

                if (new_level > max) {                                  // If the new fuel level is greater than the
                    added = max-current;                                //    maxmium tank capacity, adjust the amount
                    new_level = max;                                    //    added and the new tank level.
                }
                
                //
                // Acknowledge to pump the actual fuel added
                //
                llRegionSayTo(id,FUEL_CHAN,"@ack add "+(string)amount+" "+(string)added);
                
                fuel_levels = llListReplaceList(fuel_levels,[new_level],tank_index,tank_index);
                showLevels();
            } else {
                llRegionSayTo(id,FUEL_CHAN,"@error Unknown fuel tank");
            }
        }
    }
}