ab8aca15523eeaa2020e3ced63f5b64670400d56
[feisty_meow.git] / huffware / huffotronic_scripts / Vote_for_me_v4.2.txt
1 
2 // huffware script: vote for me, by fred huffhines.
3 //
4 // original author: ChuChu Ricardo January 14, 2010
5 // numerous mods: Fred Huffhines, March 2010  [including but not limited to refactoring
6 //    existing functions and fixing payment behavior]
7 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
8 //
9 //   this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
10 //   do not use it in objects without fully realizing you are implicitly accepting that license.
11 //
12
13 // global constants...
14
15 string LINDEN_MARK = "L$";  // the money symbol for linden dollars.
16 integer MINIMUM_FREE_MEMORY = 3400;  // the least memory we can get by with.
17
18 // a set of menu items we'll be displaying on the voter menu.
19 string ITEM_VOTEME = "Vote for Me";
20 string ITEM_STATUS = "Show Info";
21 string ITEM_NOT_NOW = "Not Now";
22 // items for the owner menu only.
23 string ITEM_RESET = "Reset";
24 string ITEM_GIVE_NOTE = "Give Note";
25 string ITEM_NO_NOTE = "No Notecard";
26 string ITEM_GIVE_OBJ = "Give Gift";
27 string ITEM_NO_OBJ = "No Gift";
28
29 integer ONE_MINUTE;  // number of seconds in a minute.  initialized later.
30 integer ONE_HOUR;  // number of seconds in an hour.  initialized later.
31 integer FULL_DAY;  // number of seconds in a day.  initialized later.
32
33 // these are the textures we'll show when it's time to register the average vote.
34 list TEXTURE_UUIDS = [
35     "8dfc709a-c37d-6103-2670-d287e871f0e7",  // zero average.
36     "14278369-1db4-4497-13ef-5dc75a1017f0",  // lowest rating (red, many bars).
37     "8a891c33-1070-6c6b-148b-919651323b5e",
38     "25f8618d-181d-9af3-720e-01a3a0fa1861",
39     "3f89b392-b70a-8a63-f09f-b51f6d91b437",
40     "a6d02c4e-7b8c-0fd9-47a5-08cb61159bfa",
41     "b7fb64db-5658-547d-560d-3ca86ff77e37",
42     "bc8057e8-61f2-1919-74f9-59ea5b731bc7",
43     "ec54863f-665c-9dc0-ab64-051fe96750af",
44     "2acaa661-db7a-1035-d4e4-0ed50304887c",
45     "53f1465d-7f39-d1cc-6ade-4610ec053646",
46     "c868135f-0ad1-20ea-a85b-0c48a780ea7f",
47     "78215a59-c52b-1886-0cae-bce11592e9b1",
48     "ffa2ed36-df97-4889-4903-06dca758336d",
49     "f1a625cb-4346-91b2-8e48-9290be22ec90",
50     "6301df4e-d3bf-2b41-9738-a6e4b7f10715",
51     "a3f566fa-0e2e-68dc-92ac-a60258717525",
52     "83088c7a-8397-404d-1285-54def3ff179e",
53     "cde91186-7365-02c1-fe79-740cc82bc01a",
54     "08d22575-0848-b7c4-1dfb-665216165831"  // highest rating (green, no bars)
55 ];
56
57 // the secret channel where the user can change the text shown on title.
58 integer COMMAND_CHANNEL = 9;
59
60 // global variables...
61
62 key last_owner;  // the last person who owned this object.
63 string persistent_message;  // additional text for the title bar.
64 integer voter_dialog_channel;  // where the voter (non-owner) menu will be heard.
65 integer owner_dialog_channel;  // where the owner's big menu will be heard.
66 integer vote_price = 10;  // amount of lindens paid per vote, with default value.
67 integer give_notecards = TRUE;  // should we hand out notecards for a vote?
68 integer give_objects = TRUE;  // should we hand out objects for a vote?
69
70 // these four variables need to reset daily...
71 list voter_ids;  // the identities of the voters.
72 list vote_magnitudes;  // the value (on the likes-me scale) of each vote cast.
73 float average_score;  // Average score computed so far.
74 integer last_reset_time;  // the last time we did a vote reset.
75
76 // perform initialization tasks.
77 setup_voteme()
78 {
79     // initialize our "constants" here, since LSL doesn't like generated constants.
80     ONE_MINUTE = 60;
81     ONE_HOUR = 60 * ONE_MINUTE;
82     FULL_DAY = 24 * ONE_HOUR;
83
84     last_owner = llGetOwner();  // remember so we can know when changing hands.
85     last_reset_time = llGetUnixTime();  // track when we cranked up.
86
87     // default add-in for titler is the avatar's name.
88     persistent_message = llKey2Name(llGetOwner());
89
90     llSetText("VoteME: "+ persistent_message, <1,1,1>, 1.0);
91
92     set_faces_to_texture((key)llList2String(TEXTURE_UUIDS, 0));
93
94     llRequestPermissions(llGetOwner(), PERMISSION_DEBIT);
95
96     // set up the menu dialog channel so we will hear what they picked.
97     owner_dialog_channel = -(integer)(llFrand(999999) + 250);
98     voter_dialog_channel = owner_dialog_channel + 1;
99     llListen(owner_dialog_channel, "", llGetOwner(), "");
100     llListen(voter_dialog_channel, "", NULL_KEY, "");
101
102     // listen on our special command channel.
103     llListen(COMMAND_CHANNEL, "", llGetOwner(), "");
104
105     llSetTimerEvent(ONE_MINUTE);
106 }
107
108 // looks for the voter to see if they've already voted today.
109 // true is returned if the "id" is found.
110 integer seek_voter(key id) { return llListFindList(voter_ids, [id]) >= 0; }
111
112 // adds a record for the voter with key "id", if they are not already present.
113 // true is returned if this is a new voter.
114 integer add_voter(key id)
115 {
116     if (llGetFreeMemory() < MINIMUM_FREE_MEMORY) {
117         llOwnerSay("omitting vote due to low memory.");
118         return FALSE;
119     }
120     // if the entry's not there already, add it.
121     if (seek_voter(id)) return FALSE;  // that one was there.
122     // if we got to here, then the voter was not there yet.
123     voter_ids += [ id ];
124     return TRUE;
125 }
126
127 // creates a string out of the time in "decode_time" which is expected to
128 // be compatible with llGetWallClock() values.
129 string time_string(integer decode_time)
130 {
131     integer hours = decode_time / ONE_HOUR;
132     decode_time -= hours * ONE_HOUR;
133     integer minutes = decode_time / ONE_MINUTE;
134     decode_time -= minutes * ONE_MINUTE;
135     integer seconds = decode_time;
136     return (string)hours + ":" + (string)minutes + ":" + (string)seconds;
137 }
138
139 // sets all the x/y faces of the object to the texture key specified.
140 // the top and bottom are unchanged.
141 set_faces_to_texture(key texture_id)
142 {
143     integer i;
144     for (i = 1; i <= 4; i++) {
145         llSetTexture(texture_id, i);
146     }
147 }
148
149 // pops up the menu for the owner's use.
150 show_owner_menu()
151 {
152     string msg0 = "You can choose the amount you want voters to pay,\nor configure giving out notecards and/or gifts to your voters.";
153     list menu0_btns = [
154         // first row at bottom.
155         ITEM_GIVE_NOTE, ITEM_NO_NOTE, ITEM_STATUS,
156         ITEM_GIVE_OBJ, ITEM_NO_OBJ, ITEM_RESET,
157         LINDEN_MARK + "10", LINDEN_MARK + "20", LINDEN_MARK + "50",
158         // last row at top.
159         LINDEN_MARK + "100"
160     ];
161
162     llDialog(llGetOwner(), msg0, menu0_btns, owner_dialog_channel);
163 }
164
165 // lets the avatar know we can't process their vote right now.
166 apologize_but_refuse_vote()
167 {
168     llSay(0, "Sorry, You have already voted for " + llKey2Name(llGetOwner()) + " today."); 
169 }
170
171 key last_toucher;  // the last person that clicked the voter object.
172 //move this!
173
174 // handles a particular avatar that has touched the voter.
175 process_toucher(integer which)
176 {
177     string voter_id = llDetectedKey(which);
178     string voter_name = llDetectedName(which);
179     // see if this was the owner touching the object.
180     if (voter_id == llGetOwner()) {
181         show_owner_menu();
182         return;  // done with owner touch handling.
183     }
184     // handle when someone other than the owner touches the voter.    
185     if (!seek_voter(voter_id)) {
186         // new voter.
187         if (last_toucher != voter_id) {
188             llSay(0, "Hello " + voter_name + "... please Vote for Me.");
189             last_toucher = voter_id;
190             if (give_notecards == TRUE) {
191                 string card_name = llGetInventoryName(INVENTORY_NOTECARD, 0);
192                 if (card_name != "") llGiveInventory(voter_id, card_name);
193             }
194         }
195         string msg1 = "Please Choose to Vote for me.\nYou can also choose to see my current voting status.";
196         list menu1_btns = [ ITEM_VOTEME, ITEM_STATUS, ITEM_NOT_NOW ];
197         llDialog(voter_id, msg1, menu1_btns, voter_dialog_channel);
198     } else {
199         // they had already voted, so we don't give them the whole menu.
200         string msg1 = "Thanks for your vote!  You can check my status, but you cannot vote again until tomorrow.";
201         list menu1_btns = [ ITEM_STATUS ];
202         llDialog(voter_id, msg1, menu1_btns, voter_dialog_channel);
203     }
204 }
205
206 // processes when an avatar touches the voter.
207 handle_touches(integer total_number)
208 {
209     // run through all the people that touched the voter this time.
210     integer i;
211     for (i = 0; i < total_number; i++) { process_toucher(i); }
212 }
213
214 // deals with people throwing money at us.
215 handle_monetary_gift(key giver, integer amount)
216 {
217     if (giver == llGetOwner()) {
218         // fine, genius, you gave yourself some money.  you don't get to vote.
219         return;
220     }
221     if (amount != vote_price) {
222         // refund the amount paid, since this was not the right vote cost.
223         llSay(0, "Oops!  That's not the right amount for the vote; SL is hosing up or something.  Here's a refund.");
224         llGiveMoney(giver, amount);
225         return;
226     }
227     if (seek_voter(giver)) {
228         // they've already voted so they're not allowed again.
229         apologize_but_refuse_vote();
230         llGiveMoney(giver, amount);
231         return;
232     }
233     add_voter(giver);
234     
235     llSay(0, "Thanks for the " + LINDEN_MARK + (string)amount + ", " + llKey2Name(giver)
236         + ".  Your voting menu will open shortly.");
237
238     string msg3 = "Voting Key:\n1-means you like me a little\n10-means you really adore me\nThank You!";
239     list menu3_btns = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ];
240     llDialog(giver, msg3, menu3_btns, voter_dialog_channel);
241 }
242
243 // announces how the owner is doing so far.
244 show_status()
245 {
246     integer count = llGetListLength(voter_ids);
247     llSay(0, "My name is " + llKey2Name(llGetOwner()) + " and I have received "
248         + (string)count + " votes.\n"
249         + "My average score per vote is " + (string)average_score + ".");
250
251 integer since_reset = llGetUnixTime() - last_reset_time;
252 llOwnerSay("last reset " + time_string(since_reset) + " H:M:S ago.");
253
254 }
255
256 // processes responses from menus and deals with spoken commands.
257 hear_voices(integer channel, string speaker_name, key id, string message)
258 {
259     // see if this is from the owner about what to show on the voter.
260     if ( (channel == COMMAND_CHANNEL) && (id == llGetOwner()) ) {
261 //hmmm: undocumented!
262         persistent_message = message;
263         llOwnerSay("Changed title bar to add \"" + persistent_message + "\".");
264         llSetText("VoteME: " + persistent_message, <1,1,1>, 1.0);
265         return;
266     }
267
268     // check if the message comes from the owner's dialog.
269     if (channel == owner_dialog_channel) {
270         if (message == ITEM_STATUS) {
271             show_status();
272             return;
273         } else if (message == ITEM_RESET) {
274 //hmmm: is this in the menu???
275             llOwnerSay("Resetting now...");
276             llResetScript();
277         } else if (llGetSubString(message, 0, 1) == LINDEN_MARK) {
278             vote_price = (integer)llGetSubString(message,2,-1);
279             llOwnerSay("Voting for you will now cost avatars "
280                 + LINDEN_MARK + (string)vote_price + ".");
281             llSetPayPrice(PAY_HIDE, [vote_price, PAY_HIDE, PAY_HIDE, PAY_HIDE]);
282         } else if (message == ITEM_GIVE_OBJ) {
283             llOwnerSay("Gift giving enabled.");
284             give_objects = TRUE;
285         } else if (message == ITEM_NO_OBJ) {
286             llOwnerSay("Gift giving disabled.");
287             give_objects = FALSE;
288         } else if (message == ITEM_GIVE_NOTE) {
289             llOwnerSay("Notecard giving enabled.");
290             give_notecards = TRUE;
291         } else if (message == ITEM_NO_NOTE) {
292             llOwnerSay("Notecard giving disabled.");
293             give_notecards = FALSE;
294         }
295         // assume for the choices that fall through that they want to see the menu again.
296         show_owner_menu();
297         return;  // done handling our own dialog.
298     }
299
300     // see if this is the dialog for the voting person.
301     if (channel == voter_dialog_channel) {
302         if (message == ITEM_VOTEME) {
303             string msg2 = "To show your confidence in me, pay me "
304                 + LINDEN_MARK + (string)vote_price + ".\n"
305                 + "Then rate me from 1 to 10 after you pay.\n"
306                 + "1 = You like me a little bit\n10 = You really adore me\n"
307                 + "Ready?  Right-click this object and pick \"Pay\".  Thanks very much!";
308             list menu2_btns = [ "Understood" ];
309             llDialog(id, msg2, menu2_btns, voter_dialog_channel);
310         } else if (message == ITEM_STATUS) {
311             show_status();
312         } else if (message == ITEM_NOT_NOW) {
313             llSay(0, "Thanks for the interest anyhow, " + llKey2Name(id) + ".");
314         } else if ((integer)message > 0 && (integer)message <= 10) {
315             if (give_notecards == TRUE) {
316                 string name = llGetInventoryName(INVENTORY_NOTECARD, 0); 
317                 if (name != "") llGiveInventory(id, name);
318             }
319             if (give_objects == TRUE) {
320                string name = llGetInventoryName(INVENTORY_OBJECT, 0); 
321                if (name != "") llGiveInventory(id, name);
322             }
323             register_vote((integer)message);
324         }
325     }
326 }
327
328 // deals with our timer clicks.
329 handle_timer()
330 {
331     integer wall_clock = (integer)llGetWallclock();
332
333 //llOwnerSay("tick: " +  time_string(wall_clock));
334
335     // see if it seems like time to reset.  we'll do this when the clock has just passed
336     // midnight (that is, we're in the first hour after midnight), or if the device had
337     // possibly been taken out of commission a long time ago (meaning more time has passed
338     // since the last reset than has elapsed today so far).
339     integer reset_it = FALSE;
340     integer time_since_reset = llGetUnixTime() - last_reset_time;
341     // is it the first hour of midnight, and we didn't just reset?
342     if ( (wall_clock < ONE_HOUR) && (time_since_reset > ONE_HOUR) ) reset_it = TRUE;
343     // or, is it a really long time since we did reset?  longer than when the hour turned?
344     if (time_since_reset > wall_clock) reset_it = TRUE;
345
346     if (reset_it) {
347         // aha, we've decided it's that time.
348         llOwnerSay("Resetting voters and average, since the clock rolled over.");
349         voter_ids = [];
350         vote_magnitudes = [];
351         average_score = 0;
352         last_reset_time = llGetUnixTime();
353         register_vote(0);  // just recalculate average and show texture.
354     }
355 }
356
357 // tracks that they've made a vote.
358 register_vote(integer this_vote_value)
359 {
360     // zero is a special value that just makes us recalculate the average.
361     if (this_vote_value != 0) vote_magnitudes += [ this_vote_value ];
362
363 //move this to an averager method.
364     integer sum = 0;
365     integer n = llGetListLength(vote_magnitudes);
366     integer i = 0;
367     for(i = 0;i<n;i++) {
368         sum = sum + (integer)llList2String(vote_magnitudes,i);
369     }
370     if (n != 0) average_score = (float)sum/(float)n;
371     else average_score = 0;
372         
373     // Score on scale of 0 to 19
374     float new_score = average_score * 2;
375     integer tex_num = (integer)new_score;  //Ean's Texture Number
376
377     if (tex_num >= 20) tex_num = 19;  //We dont have all green texture
378         
379     set_faces_to_texture((key)llList2String(TEXTURE_UUIDS, tex_num));
380 }
381
382 default {
383     state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
384     on_rez(integer parm) { state rerun; }
385 }
386 state rerun { state_entry() { state default; } }
387
388 // main state machine.
389 state real_default
390 {
391     state_entry() { setup_voteme(); }
392
393     on_rez(integer parm) {
394         if (last_owner != llGetOwner()) {
395             llOwnerSay("Now configuring for " + llKey2Name(llGetOwner()));
396             llResetScript();
397         }
398     }
399     
400     run_time_permissions(integer perm) {
401         if (perm & PERMISSION_DEBIT) {
402 //llOwnerSay("got permission for debits, will set price to " + (string)vote_price);
403             llSetPayPrice(PAY_HIDE, [vote_price, PAY_HIDE, PAY_HIDE, PAY_HIDE]);
404         } else {
405             llOwnerSay("We were denied debit permission and cannot function without it.  Use the menu and select \"" + ITEM_RESET + "\" to fix this.");
406         }
407     }
408
409     timer() { handle_timer(); }
410
411     touch_start(integer total_number) { handle_touches(total_number); }
412
413     money(key giver, integer amount) { handle_monetary_gift(giver, amount); }
414
415     listen(integer channel, string name, key id, string message)
416     { hear_voices(channel, name, id, message); }
417 }
418
419
420 //feature ideas:
421 // have the voter track the highest paying guy?
422 //   actually no, the owner sets that limit.  rats.
423 //   but what about the highest voting guy!?  the guy/gal who thought the most could be tracked.
424
425 //hmmm: need a helper notecard function for owner!
426