SHARE
TWEET

Menu-Driven Item Giver

RiverFromSL Jun 29th, 2019 (edited) 156 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // MENU-DRIVEN ITEM GIVER
  2. // -------------------------------------------------------------------
  3. // Show a menu to allow selection (by numbered index) of the items
  4. // within this object's inventory, and deliver that item to the user.
  5. //
  6. // Features:
  7. // - Supports multiple pages of content, with next/back buttons.
  8. // - Remembers what page a user is on, to make it easy to select
  9. //   multiple items without extra navigation.
  10. // - More than one avi can use the menu at the same time. The script
  11. //   tracks the current menu page for each of them independently.
  12. // - Closes down the listener if there's been no activity for a
  13. //   defined period of time, to save sim resources.
  14. //
  15. // If distributing this code with mod permissions, please keep this
  16. // notice intact at the top of the script code.
  17. // Otherwise, you are free to use within your creations as you wish
  18. // without any further restrictions or conditions.
  19. //
  20. // River (riverborn.resident)  29 Jun 2019.
  21.  
  22. // -- Constants ------------------------------------------------------
  23.  
  24. // Menu items
  25. string  B_BACK   = "🡄  Back";
  26. string  B_NEXT   = "Next  🡆";
  27. string  B_CANCEL = "✖ Cancel";
  28. string  B_BLANK  = "-";
  29.  
  30. integer PER_PAGE   = 9;         // Items to show per menu page (9 max)
  31. float   MENU_TIME  = 300.0;     // Menu timeout in seconds
  32. float   DELIV_TIME = 1.0;       // Extra delay after delivery
  33.  
  34.  
  35. // -- Globals --------------------------------------------------------
  36.  
  37. list     allItems;          // Main list of items in the object
  38. integer  numItems;          // Count of items in the list
  39. integer  numPages;          // Number of menu pages
  40. list     userData;          // user/pagenumber data
  41.  
  42. integer  menuChannel;       // Channel number for menu
  43. integer  menuListener;      // Listener for menu responses
  44.  
  45.  
  46. // -- Item Functions -------------------------------------------------
  47.  
  48. // Populate the allItems list and numItems with the list of objects
  49. // in the inventory (ignoring this script, of course!)
  50. buildItemList()
  51. {
  52.     integer i;
  53.     integer m = llGetInventoryNumber(INVENTORY_ALL);
  54.     string this = llGetScriptName();
  55.     allItems = [];
  56.    
  57.     for (i = 0; i < m; ++i) {
  58.         string n = llGetInventoryName(INVENTORY_ALL, i);
  59.         if (n != this) allItems += [n];
  60.         }
  61.    
  62.     numItems = llGetListLength(allItems);
  63.     numPages = numItems / PER_PAGE;
  64.     if (numItems % PER_PAGE) ++numPages;
  65. }
  66.  
  67. // Just give a particular item to a particular user.
  68. // llGiveInventory() causes the script to sleep for 2 seconds, but
  69. // that's not always quite enough to accept the delivery before
  70. // the menu pops up over it again, so let's put a little extra
  71. // sleep in there, to give them time.
  72. giveItemToUser(key id, integer itm)
  73. {
  74.     string itmName = llList2String(allItems, itm-1);
  75.     llGiveInventory(id, itmName);
  76.     llSleep(DELIV_TIME);
  77. }
  78.  
  79.  
  80. // -- Menu Functions -------------------------------------------------
  81.  
  82. // Show a menu for a specified page of items from our master list
  83. showMenu(key id, integer page)
  84. {
  85.     // Bound check - we check the page number before calling the
  86.     // function, so this should never be an issue, but just in case...
  87.     if (page < 1 || page > numPages) return;
  88.    
  89.     // If listener is not active, turn it on so we can hear the
  90.     // response from the menu selection
  91.     if(menuListener == -1) {
  92.         menuListener = llListen(menuChannel, "", NULL_KEY, "");
  93.         }
  94.  
  95.     // (Re-)start the timeout for the menu tracking data
  96.     llSetTimerEvent(MENU_TIME);
  97.    
  98.     // Start forming the text for the menu, starting with the first line
  99.     string text = "Page " + (string)page + " of " + (string)numPages + "\n";
  100.    
  101.     // First, work out the start and end item indices for this page
  102.     integer startidx = (page-1) * PER_PAGE;
  103.     integer endidx = startidx + PER_PAGE;
  104.     if (endidx >= numItems) endidx = numItems;
  105.  
  106.     // Then, build a list (in order) of the buttons for those items
  107.     // and at the same time, add their name to the intro text
  108.     integer i;
  109.     list b = [];
  110.     for (i = startidx ; i < endidx ; ++i) {
  111.         b += [ (string)(i+1) ];
  112.         text += "\n" + (string)(i+1) + ": " + llList2String(allItems, i);
  113.         }
  114.  
  115.     // Double-check that the intro text is not too long (512 limit).
  116.     // If it's gone over, re-create the list using truncated titles
  117.     if (llStringLength(text) > 312) {
  118.         text = "Page " + (string)page + " of " + (string)numPages + "\n";
  119.         for (i = startidx ; i < endidx ; ++i) {
  120.             string title = llList2String(allItems, i);
  121.             if (llStringLength(title) > 45)
  122.                 title = llGetSubString(title, 0, 45) + "...";
  123.             text += "\n" + (string)(i+1) + ": " + title;
  124.             }
  125.         }    
  126.  
  127.     // Then, pad out the end of the button list with blanks,
  128.     // to make sure it's a multiple of three.
  129.     integer mod = ( llGetListLength(b) ) % 3;
  130.     if (mod == 2) b += [B_BLANK];
  131.     else if (mod == 1) b += [B_BLANK, B_BLANK];
  132.    
  133.     // And add the back/fwd controls (if there's more than one page)
  134.     if (numPages > 1) b += [ B_BACK, B_CANCEL, B_NEXT ];
  135.  
  136.     // Now, re-create the button list in the weird-ass order that SL uses
  137.     list buttons = [];
  138.     integer rows = llGetListLength(b) / 3;
  139.     while(rows-- > 0) buttons += llList2List(b, rows*3, rows*3+2);
  140.  
  141.     // And FINALLY show the menu
  142.     llDialog(id, text, buttons, menuChannel);
  143. }
  144.  
  145.  
  146. // -- Page number tracking -------------------------------------------
  147.  
  148. // These functions store the keys of users currently using the menu
  149. // system, and the menu page they last accessed. This way, we can
  150. // avoid the horrible "Someone else is using this menu" mesage, but
  151. // still keep track of the forward/back buttons for each of them.
  152. //  
  153. // The data is in a strided list, i.e:
  154. // [ userkey1, pagenumber1, userkey2, pagenumber2, ... ]
  155. //
  156. // We have functions to set a page number for a given user, query the
  157. // current page number, and delete a user from the tracking list.
  158. // Lots of list operations, which are typically best to avoid, but
  159. // we never expect this list to have more than a handful of items in
  160. // it, so they're not so bad in this context.
  161.  
  162. // Store a user's current page
  163. setUserPage(key id, integer page)
  164. {
  165.     // Is the user already in the list?
  166.     integer idx = llListFindList(userData, [id]);
  167.     if (idx == -1) {
  168.         // Not on list, so add new entry
  169.         userData += [ id, page ];
  170.         }
  171.     else {
  172.         // Already on list - so just change the current page
  173.         userData = llListReplaceList(userData, [page], idx+1, idx+1);
  174.         }
  175. }
  176.  
  177. // Get current page for a user
  178. integer getUserPage(key id)
  179. {
  180.     integer idx = llListFindList(userData, [id]);
  181.     if (idx == -1) {
  182.         // If not found, add to the list with the default of 1
  183.         userData += [ id, 1 ];
  184.         return 1;
  185.         }
  186.     else {
  187.         // If found, return the current page that's been stored
  188.         return llList2Integer(userData, idx+1);
  189.         }
  190. }
  191.  
  192. // Remove user's details from our page tracking
  193. delUserPage(key id)
  194. {
  195.     integer idx = llListFindList(userData, [id]);
  196.     if (idx != -1) {
  197.         // If found, remove this user id and page number from the list
  198.         userData = llDeleteSubList(userData, idx, idx+1);
  199.         }
  200. }
  201.  
  202.  
  203. // -- Event Handlers -------------------------------------------------
  204.  
  205. default
  206. {
  207.     // When the script starts or is reset...
  208.     state_entry()
  209.     {
  210.         // Initialise globals
  211.         menuListener = -1;
  212.         userData = [];
  213.  
  214.         // Get list of items
  215.         llOwnerSay("Loading item list...");
  216.         buildItemList();
  217.         llOwnerSay((string)numItems + " items found" +
  218.             " (" + (string)numPages + " menu pages)");
  219.  
  220.         // Get a unique channel number to use for the dialog
  221.         string hash = llSHA1String((string)llGetKey() + "hash");
  222.         menuChannel = -50000-((integer)("0x"+llGetSubString(hash, 0, 6)));
  223.  
  224.         // Now return control to the system and wait for something to happen
  225.         llOwnerSay("Ready.");    
  226.     }
  227.  
  228.  
  229.     // When someone touches the object...
  230.     touch_start(integer num)
  231.     {
  232.         // Look up their UUID, and show them the menu
  233.         // (at their current page if they have one, else page 1)
  234.         key avi = llDetectedKey(0);
  235.         showMenu(avi, getUserPage(avi));
  236.     }
  237.    
  238.    
  239.     // When a menu selection is made...
  240.     listen(integer ch, string name, key id, string m)
  241.     {
  242.         // Get the current page for this user..
  243.         integer page = getUserPage(id);
  244.        
  245.         if(m == B_NEXT) {
  246.             if(++page > numPages) page = 1;
  247.             setUserPage(id, page);
  248.             showMenu(id, page);
  249.             }
  250.        
  251.         else if (m == B_BACK) {
  252.             if(--page < 1) page = numPages;
  253.             setUserPage(id, page);
  254.             showMenu(id, page);
  255.             }
  256.        
  257.         else if (m == B_CANCEL) {
  258.             // Take user out of page tracking
  259.             delUserPage(id);
  260.             }
  261.            
  262.         else if (m == B_BLANK) {
  263.             // Blank button - redisplay same menu
  264.             showMenu(id, page);
  265.             }
  266.            
  267.         else {
  268.             // Must be an item number. Give the item, and
  269.             // re-display the menu in case they want to choose another
  270.             giveItemToUser(id, (integer)m);
  271.             showMenu(id, page);
  272.             }
  273.     }
  274.  
  275.    
  276.     // When the timer goes off...
  277.     timer()
  278.     {
  279.         // If we got here, it's been MENU_TIME since the last
  280.         // menu activity by ANYONE. So cancel timer...
  281.         llSetTimerEvent(0.0);
  282.            
  283.         // Clear the user data lists (we're not tracking pages any more)
  284.         userData = [];
  285.        
  286.         // And close the listener (we're not expecting any responses)
  287.         llListenRemove(menuListener);
  288.         menuListener = -1;
  289.     }
  290.  
  291.  
  292.     // When the contents of the object change...
  293.     changed(integer chg)
  294.     {
  295.         // Restart the script to re-read the content list if the
  296.         // contents of the object have been changed.
  297.         // This will dump all the page tracking for people using the
  298.         // menu, but that's OK- the pages will have changed anyway.
  299.         if(chg & CHANGED_INVENTORY) llResetScript();
  300.     }
  301. }
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top