NPC Rev 3.9.lsl 111 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200
  1. // :SHOW:1
  2. // :CATEGORY:NPC
  3. // :NAME:Hypergrid Story Three
  4. // :AUTHOR:Ferd Frederix
  5. // :KEYWORDS:NPC, Puppeteer
  6. // :CREATED:2013-09-08 18:27:47
  7. // :EDITED:2015-11-24 19:36:34
  8. // :ID:1090
  9. // :NUM:1861
  10. // :REV:4.0
  11. // :WORLD:OpenSim
  12. // :DESCRIPTION:
  13. // All in one NPC recorder player.
  14. // Supports both absolute and relative paths and many new commands
  15. // Add animations named "Fly, Walk, Stand and Run"
  16. // Click Prim to use.
  17. // Should be worn as a HUD to record.
  18. // Put it on the ground and click Sensor or Start NPC when done.
  19. // :CODE:
  20. // This is Rev 3.9 08/23/2015
  21. // Revision History
  22. // Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor
  23. // Rev 1.2 10-14-2014 @ sit had wrong type.
  24. // Rev 1.3 relative movement fixed for @fly
  25. // Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop.
  26. // Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim
  27. // Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency
  28. // Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference)
  29. // Rev 1.8 tuned up Kill NPC, added more flexible upgrader
  30. // Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC.
  31. // Rev 2.1 No laggy sensor used exept to sit on stuff
  32. // Rev 2.2 Various sensor fixes
  33. // Rev 2.3 Sets No Sensor in menu, must be started by hand
  34. // Rev 2.4 - reserved for patches to 2.3 if needed
  35. // Rev 3.0 Refactor out into subs, not states to make command injection easier
  36. // New command: @appearance=Notecardname so you can switch to a new notecard on the fly
  37. // New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1)
  38. // Rev 3.1 Commands are not interruptible by Link Message
  39. // Rev 3.2 Sensor patches for consistency in removing the NPC
  40. // Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002
  41. // Added Menu 3 for notecard and appearance commands
  42. // Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked
  43. // solves the NPC starting up when no sensor is set.
  44. // Rev 3.5 fixes saving to !Path notecard
  45. // Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted
  46. // Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC
  47. // Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message
  48. // Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working
  49. //*******************************************************************//
  50. // Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/
  51. // This is an OpenSim-only script.
  52. // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
  53. ////////////////////////////////////////////////////////////////////////////////////////////
  54. // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
  55. ///////////////////////////////////////////////////////////////////////////////////////////
  56. // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
  57. // rights of fair usage, the disclaimer and warranty conditions. //
  58. ///////////////////////////////////////////////////////////////////////////////////////////
  59. // The original NPC controller was from http://was.fm/opensim:npc
  60. // Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com
  61. // llSensor had two params swapped
  62. // @Wander would wander where it had rezzed, not where it was.
  63. // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
  64. // The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed.
  65. // New code:
  66. // Merged with new Route recorder and notecard writer
  67. // If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this
  68. // Exposed many tunable variables and ported the code to LSLEditor.
  69. // Added floating point to times in notecard.
  70. // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
  71. //
  72. // notecards integers are not floats for better control
  73. //
  74. // Link Messages may be used to perform external control by injecting @commands into the stream of actions
  75. // Example:
  76. // To chat something, such as with a chat robot
  77. // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
  78. // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
  79. // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
  80. //
  81. // ; Turn on OSSL
  82. // AllowOSFunctions = true
  83. // OSFunctionThreatLevel = Severe
  84. //[NPC]
  85. // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
  86. // Enabled = true
  87. //
  88. // and then the server has to be restarted.
  89. // Commands: All commands begin with an @ sign. All other lines are ignored
  90. // @commands may have optional parameters. The syntax is always:
  91. // @cmd=parm1|parm2
  92. // NaN in the table below meand Not a Number. This means there is no parameter
  93. //Command First Parameter Second Parameter Description
  94. //@spawn name location (vector) Rezzes an NPC with name at a location.
  95. //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
  96. //@walk destination (vector) NaN Makes the NPC walk to destination.
  97. //@fly destination (vector) NaN Makes the NPC fly to destination.
  98. //@land destination (vector) NaN Makes the NPC land at destination.
  99. //@say string NaN Makes the NPC speak a phrase.
  100. //@whisper string NaN Makes the NPC whisper a phrase.
  101. //@shout string NaN Makes the NPC shout a phrase.
  102. //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
  103. //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
  104. //@delete NaN NaN Removes the NPC. Requires a link message to continue
  105. //@goto label (string) NaN Jump to the label label in the script.
  106. //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
  107. //@sound sound_name NaN plays a sound from inventory
  108. //@randsound NaN NaN Plays a random sound from inventory
  109. //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
  110. //@sit primitive name NaN Sit on a primitive with a given name.
  111. //@touch primitive name NaN Touch on a primitive with a given name.
  112. //@stand NaN NaN If sitting on a primitive, stand up.
  113. //@cmd channel (integer) string Says string on channel, for controlling external gadgets
  114. //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
  115. //@go NaN NaN Continues on next notecard line, for use in link messages
  116. //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
  117. //@notecard notename (string) NaN load a new Path notecard
  118. //@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point
  119. // Constant attachmentPoint Comment
  120. // ATTACH_CHEST 1 chest/sternum
  121. // ATTACH_HEAD 2 head
  122. // ATTACH_LSHOULDER 3 left shoulder
  123. // ATTACH_RSHOULDER 4 right shoulder
  124. // ATTACH_LHAND 5 left hand
  125. // ATTACH_RHAND 6 right hand
  126. // ATTACH_LFOOT 7 left foot
  127. // ATTACH_RFOOT 8 right foot
  128. // ATTACH_BACK 9 back
  129. // ATTACH_PELVIS 10 pelvis
  130. // ATTACH_MOUTH 11 mouth
  131. // ATTACH_CHIN 12 chin
  132. // ATTACH_LEAR 13 left ear
  133. // ATTACH_REAR 14 right ear
  134. // ATTACH_LEYE 15 left eye
  135. // ATTACH_REYE 16 right eye
  136. // ATTACH_NOSE 17 nose
  137. // ATTACH_RUARM 18 right upper arm
  138. // ATTACH_RLARM 19 right lower arm
  139. // ATTACH_LUARM 20 left upper arm
  140. // ATTACH_LLARM 21 left lower arm
  141. // ATTACH_RHIP 22 right hip
  142. // ATTACH_RULEG 23 right upper leg
  143. // ATTACH_RLLEG 24 right lower leg
  144. // ATTACH_LHIP 25 left hip
  145. // ATTACH_LULEG 26 left upper leg
  146. // ATTACH_LLLEG 27 left lower leg
  147. // ATTACH_BELLY 28 belly/stomach/tummy
  148. // ATTACH_LEFT_PEC 29 left pectoral
  149. // ATTACH_RIGHT_PEC 30 right pectoral
  150. // ATTACH_HUD_CENTER_2 31 HUD Center 2
  151. // ATTACH_HUD_TOP_RIGHT 32 HUD Top Right
  152. // ATTACH_HUD_TOP_CENTER33 HUD Top
  153. // ATTACH_HUD_TOP_LEFT 34 HUD Top Left
  154. // ATTACH_HUD_CENTER_1 35 HUD Center
  155. // ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left
  156. // ATTACH_HUD_BOTTOM 37 HUD Bottom
  157. // ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right
  158. // ATTACH_NECK 39 neck
  159. // ATTACH_AVATAR_CENTER 40 avatar center/root
  160. //////////////////////////////////////////////////////////
  161. // DEBUG //
  162. //////////////////////////////////////////////////////////
  163. integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions
  164. integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
  165. // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
  166. integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller
  167. //////////////////////////////////////////////////////////
  168. // TUNABLE CONFIGURATION //
  169. //////////////////////////////////////////////////////////
  170. float TIMER = 2; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds
  171. float QUICK = 1; // when we need to move to the next state, we use a QUICK timer
  172. string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
  173. string Notecard = "!Path"; // The name of the recorded routes
  174. integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE
  175. float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target
  176. integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
  177. float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
  178. integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time.
  179. float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
  180. float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process
  181. string STAND = "Stand"; // the name of the default Stand animation
  182. string WALK = "Walk"; // the name of the default Walk animation
  183. string FLY = "Fly"; // the name of the default Fly animation
  184. string RUN = "Run"; // the name of the default Run animation
  185. string LAND = "Land"; // the name of the default land animation ( for birds only)
  186. float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
  187. float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up.
  188. integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
  189. // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
  190. // These vars are stored by saving them with KeyValueSet
  191. // "pr" is a 0 if it is set for Owner Only, 1 for Group control
  192. // "se" is "on" if Started
  193. // "co" = "R" or "A" for relative or absolute addressing mode
  194. // "key" = NPC key
  195. // These Globals used to be stored in description. Moved to RAM in V1.6
  196. float RAMPause; // @pause param
  197. float RAMwd ; // @wander distance
  198. integer RAMwc; // @wander count
  199. float RAMrot; // @rotate
  200. string RAMsit; // @sit primname
  201. string RAMtouch; // @touch primname
  202. string RAManimationName; // @animate animation (string) time (float)
  203. float RAManimationTime;
  204. // other globals section
  205. integer iChannel; // a listen channel, randomly assigned
  206. integer iHandle; // the handle to it
  207. // NPC controls
  208. vector newDest ; // tmp storage for the walks
  209. integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
  210. string sNPCName; // the name of the NPC that may be in world. So we can remove it.
  211. integer bNPC_STOP = FALSE; // boolean to reuse a listener
  212. integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them
  213. float fTimerVal ; // how long we wait when wandering (calculated)
  214. float NPCEnabled; // true if the NPC is suppodes to be running
  215. // OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC.
  216. // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
  217. integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
  218. integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0
  219. integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
  220. // OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given.
  221. // OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped
  222. //OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect.
  223. // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
  224. // menus
  225. string mSensor="Sense is Off"; // Sensor or "No Sensor"
  226. list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"];
  227. list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"];
  228. list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ];
  229. string sCommand; // place to store a command for two-prompted ones
  230. string sParam2; // place to store a prompt for two-prompted ones
  231. string priPub = "Owner Only"; // Private or Group
  232. key kUserKey; // the person who is controlling the avatar, not the Owner
  233. // the command lists
  234. list lCommands; // commands are stored here
  235. list lNPCScript; // Storage for the NPC script.
  236. string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
  237. string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
  238. // misc vars
  239. string sNotecard; // commands are stored here temporarily for dumping
  240. vector vWanderPos; // a place to wander to
  241. string lastANIM ; // last animation run
  242. // Sensor
  243. integer avatarPresent; // Sensor sets this flag when people are within Range.
  244. // Coordinate control
  245. vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
  246. vector vDestPos = ZERO_VECTOR; // Storage for destination position.
  247. string relAbs = "Relative"; // absolute vs relative positioning
  248. vector lastKnownPos; // last known NPC position when we deleted it
  249. // STATES
  250. integer MENU ; // processing a dialog box state, may be concurrent with STATE
  251. integer STATE; // state storage
  252. integer MakeNotecard = 1; // displaying a text box for NPC name
  253. integer RecordPath = 2; // displaying a path notecard menu
  254. integer NobodyHome = 3; // looking for an avatar
  255. integer Spawning = 4; // spawning an avatar
  256. integer Animate = 5; // animation timer needed
  257. integer Walking = 6; // Hey! I am walking here!
  258. integer Wander = 7; // Wandering around neeeds a timer, too
  259. integer WanderHold = 8; // We reached a wander point
  260. integer DoProcess = 9; // Set this to make it process a new command
  261. integer Touch = 10; // Timer is busy sensing something to touch
  262. integer Sit = 11; // Timer is busy sensing something to sit on
  263. integer Paused = 12; // Timer is busy pausing
  264. key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
  265. list Stack ; // a command stack from link message input
  266. integer SensorFunc = 0; // define which function shall be triggered inside the sensor function
  267. // 0 means none, 1 sit, 2 touch
  268. ///////////////////////////////////////////////////////////////////////////
  269. // FUNCTIONS //
  270. ///////////////////////////////////////////////////////////////////////////
  271. SetStop(integer what)
  272. {
  273. DEBUG("Stopped set to " + (string ) what);
  274. Stopped = what;
  275. }
  276. // Do* functions are much like states from the old V2 scripts.
  277. // Save a Path notecard
  278. DoSave()
  279. {
  280. STATE = MakeNotecard;
  281. makeText("Stand where you want the NPC to appear, and enter the NPC Name");
  282. }
  283. // This function is used to record the path for the NPC
  284. // Each command can take 0, 1, or 2 params
  285. DoMenuForCommands() {
  286. makeMenu(lAtButtons);
  287. }
  288. // No one is here when sensors were on, so we kill off the NPC
  289. DoNobodyHome()
  290. {
  291. DEBUG("Nobody Home");
  292. STATE = NobodyHome;
  293. if (NPCKey() != NULL_KEY) {
  294. osNpcRemove(NPCKey());
  295. SaveKey(NULL_KEY);
  296. }
  297. TimerEvent(5); // keep ticking to sense avatars
  298. }
  299. // Create a NPC
  300. StateSpawn() {
  301. DEBUG("state spawn");
  302. STATE = Spawning;
  303. NPCEnabled = TRUE; // in world
  304. // see if there is already one out there.
  305. if (NPCKey() != NULL_KEY) {
  306. DEBUG("Already living");
  307. return;
  308. }
  309. list name = llParseString2List(sNPCName, [" "], []);
  310. if (relAbs == "Relative"){
  311. vInitialPos += llGetPos();
  312. }
  313. DEBUG("Rezzing the NPC:" + (string) vInitialPos);
  314. key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions);
  315. SaveKey(aKey ); // save in desceription and global, too
  316. osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
  317. TimerEvent(REZTIME);
  318. NPCAnimate(STAND);
  319. }
  320. DoRotate() {
  321. DEBUG("@rotate=" + (string) RAMrot);
  322. osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
  323. }
  324. StateSit() {
  325. DEBUG ("state sit - looking for " + RAMsit);
  326. STATE=Sit;
  327. SensorFunc = 1; //triggers osNpcSit
  328. llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
  329. }
  330. StateTouch() {
  331. DEBUG ("state touch - looking for " + RAMtouch);
  332. STATE = Touch;
  333. SensorFunc = 2; //triggers osNpcTouch
  334. llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
  335. }
  336. DoStand() {
  337. DEBUG("state stand");
  338. osNpcStand(NPCKey());
  339. }
  340. StateAnimate() {
  341. DEBUG("state animate");
  342. STATE = Animate;
  343. NPCAnimate(RAManimationName);
  344. if (RAManimationTime <=0 ) // V 3.4 tweak
  345. RAManimationTime = 1;
  346. TimerEvent(RAManimationTime);
  347. }
  348. StateWalk() {
  349. DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
  350. STATE = Walking;
  351. // walk, fly, run, land
  352. if (walkstate == 1) {
  353. NPCAnimate(WALK);
  354. } else if (walkstate == 2) {
  355. llShout(FLIGHT,"flying");
  356. NPCAnimate(FLY);
  357. } else if (walkstate == 3) {
  358. NPCAnimate(RUN);
  359. } else if (walkstate == 4) {
  360. NPCAnimate(LAND);
  361. }
  362. newDest = vDestPos ;
  363. newDest.z += OffsetZ;
  364. // notecard is stored as offsets from this box with relative addressing. Convert to absolute
  365. if (relAbs == "Relative"){
  366. newDest += llGetPos();
  367. }
  368. DEBUG("Moveto:" + (string) newDest);
  369. osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
  370. iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
  371. TimerEvent(TIMER);
  372. }
  373. StateWander(){
  374. DEBUG("state wander");
  375. STATE = Wander;
  376. vector point = CirclePoint(RAMwd);
  377. DEBUG("CirclePoint:" + (string) point);
  378. vWanderPos = vDestPos + point;
  379. DEBUG("vWanderPos:" + (string) vWanderPos);
  380. fTimerVal = WANDERTIME; // default time to pause after each wander
  381. if (WANDERRAND)
  382. fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times
  383. NPCAnimate(WALK);
  384. DEBUG("Wander to:" + (string) vWanderPos);
  385. osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
  386. iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
  387. TimerEvent(TIMER);
  388. }
  389. StateWanderhold() {
  390. DEBUG("Wander Hold");
  391. STATE = WanderHold;
  392. // now that we have reached a wander spot, slow the timer down to the desired value
  393. TimerEvent(fTimerVal);
  394. }
  395. // @pause=10 will do nothing for 10 seconds
  396. DoPause() {
  397. STATE =Paused;
  398. if (RAMPause < 0.1)
  399. RAMPause = 0.1;
  400. DEBUG("@pause=" + (string)RAMPause);
  401. TimerEvent(RAMPause);
  402. }
  403. // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
  404. DoStop() {
  405. DEBUG("NPC is Stopped");
  406. STATE = 0; // accept commands
  407. SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
  408. TimerEvent(0);
  409. Stack = []; // v3.8
  410. }
  411. // @delete removes the NPC forever. Next command starts it up again at the beginning
  412. DoDelete() {
  413. DEBUG("state delete");
  414. STATE = 0; // accept commands
  415. osNpcRemove(NPCKey());
  416. SaveKey(NULL_KEY);
  417. TimerEvent(0);
  418. Stack = []; // v3.8
  419. }
  420. // change the appearance of the NPC
  421. DoAppearance(string notecard) {
  422. DEBUG("state appearance");
  423. if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){
  424. DEBUG("Load appearance " + notecard);
  425. osNpcLoadAppearance(NPCKey(),notecard);
  426. }
  427. }
  428. // Change the avatar speed
  429. DoSpeed(string speed) {
  430. float newspeed = (float) speed;
  431. if (newspeed > 0.1 && newspeed < 5.0) {// sanity check
  432. osSetSpeed(NPCKey(),newspeed);
  433. }
  434. }
  435. DoNewNote (string card) {
  436. DEBUG("Load Notecard " + card);
  437. NPCReadNoteCard(card);
  438. SetStop(FALSE);
  439. }
  440. DoAttach(string params) {
  441. list Data = llParseString2List(params, ["|"], []);
  442. string itemName = llList2String(Data, 0);
  443. integer attachmentPoint = (integer) llList2String(Data, 1);
  444. if (attachmentPoint > 0
  445. && attachmentPoint < 40
  446. && llGetInventoryType(itemName) == INVENTORY_OBJECT
  447. )
  448. {
  449. osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint);
  450. }
  451. }
  452. // This loops over the notecard, processing each command
  453. DoProcessNPCLine() {
  454. DEBUG("ProcessNPCLine, stopped = " + (string) Stopped);
  455. STATE = DoProcess;
  456. // auto load a notecard
  457. if (! llGetListLength(lNPCScript)) {
  458. DEBUG("Read Notecard");
  459. NPCReadNoteCard(Notecard);
  460. }
  461. // look for link messages on the stack
  462. string next = llList2String(Stack,0); // lets see if there is anithing from a link message
  463. if (llStringLength(next))
  464. {
  465. Stack = llDeleteSubList(Stack,0,0);
  466. ProcessCmd(next); //lets do this command instead.
  467. return;
  468. }
  469. // @stop issued?
  470. if (Stopped) {
  471. TimerEvent(0);
  472. DEBUG("Stopped, waiting for input");
  473. STATE = 0;
  474. return;
  475. }
  476. // No, we have an @go for liftoff
  477. next = llList2String(lNPCScript, 0); // get the next command
  478. DEBUG("Execute:" + next);
  479. lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it
  480. if (llGetListLength(lNPCScript) == 0) {
  481. DEBUG("EOF");
  482. }
  483. ProcessCmd(next);
  484. }
  485. ProcessCmd(string cmd) {
  486. DEBUG("ProcessCmd:" + cmd);
  487. if (llGetSubString(cmd, 0, 0) != "@") {
  488. DEBUG("ignoring");
  489. STATE = DoProcess;
  490. TimerEvent(QUICK); // this is so we do not recurse the stack
  491. STATE = 0;
  492. return;
  493. }
  494. list data = llParseString2List(cmd, ["="], []);
  495. npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
  496. DEBUG("Action:" + npcAction);
  497. npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
  498. @commands;
  499. ProcessSensor();
  500. if(npcAction == "@spawn") {
  501. DEBUG("@spawn");
  502. list spawnData = llParseString2List(npcParams, ["|"], []);
  503. sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
  504. list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []);
  505. vInitialPos.x = llList2Float(spawnDest, 0);
  506. vInitialPos.y = llList2Float(spawnDest, 1);
  507. vInitialPos.z = llList2Float(spawnDest, 2);
  508. DEBUG("Coords for NPC at " + (string) vInitialPos);
  509. StateSpawn();
  510. return;
  511. }
  512. if (! avatarPresent){
  513. DoNobodyHome();
  514. DEBUG("No avatar nearby");
  515. STATE = 0;
  516. return;
  517. } else {
  518. if ( NPCKey() == NULL_KEY) {
  519. StateSpawn();
  520. }
  521. }
  522. if(npcAction == "@stop") {
  523. DoStop();
  524. STATE = 0;
  525. return;
  526. }
  527. else if(npcAction == "@goto") {
  528. DEBUG("goto");
  529. integer lastIdx = llGetListLength(lNPCScript)-1;
  530. lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx);
  531. // Wind commands till goto label.
  532. @wind;
  533. string next1 = llList2String(lNPCScript, 0);
  534. lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
  535. lNPCScript += next1;
  536. if(next1 != npcParams) jump wind;
  537. // Wind the label too.
  538. next1 = llList2String(lNPCScript, 0);
  539. lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
  540. lNPCScript += next1;
  541. // Get next command.
  542. list data1 = llParseString2List(next1, ["="], []);
  543. npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
  544. npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
  545. // Reschedule.
  546. jump commands;
  547. }
  548. else if(npcAction == "@sound") {
  549. DEBUG("sound");
  550. llTriggerSound(npcParams,1.0);
  551. }
  552. else if(npcAction == "@randsound") {
  553. DEBUG("@randsound");
  554. integer N = llGetInventoryNumber(INVENTORY_SOUND);
  555. integer rand = llCeil(llFrand(N)) -1; // pick a random sound
  556. string toPlay = llGetInventoryName(INVENTORY_SOUND,rand);
  557. llTriggerSound(toPlay,1.0);
  558. }
  559. else if(npcAction == "@walk") {
  560. DEBUG("@walk");
  561. GetDest(npcParams);
  562. walkstate = 1;// walking
  563. NPCWalkOption = OS_NPC_NO_FLY ;
  564. StateWalk();
  565. return;
  566. }
  567. else if(npcAction == "@fly") {
  568. GetDest(npcParams);
  569. walkstate = 2;// flying
  570. NPCWalkOption = OS_NPC_FLY ;
  571. StateWalk();
  572. return;
  573. }
  574. else if(npcAction == "@run") {
  575. DEBUG("@run");
  576. GetDest(npcParams);
  577. walkstate = 3;// running
  578. NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
  579. StateWalk();
  580. return;
  581. }
  582. else if(npcAction == "@land") {
  583. DEBUG("@land");
  584. GetDest(npcParams);
  585. walkstate = 4;// landing
  586. NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
  587. StateWalk();
  588. return;
  589. }
  590. else if(npcAction == "@say") {
  591. DEBUG("@say " + npcParams);
  592. osNpcSay(NPCKey(), 0, npcParams);
  593. }
  594. else if(npcAction == "@shout") {
  595. DEBUG("@shout");
  596. osNpcShout(NPCKey(),0, npcParams);
  597. }
  598. else if(npcAction == "@whisper") {
  599. DEBUG("@whisper " + npcParams);
  600. osNpcWhisper(NPCKey(),0, npcParams);
  601. }
  602. // speak a command on a channel, so you can open doors and control stuff.
  603. else if(npcAction == "@cmd") {
  604. DEBUG("@cmd");
  605. list dataToSpeak = llParseString2List(npcParams, ["|"], []);
  606. string channel = llList2String(dataToSpeak,0);
  607. DEBUG("Channel:"+(string) channel);
  608. integer iChannel = (integer) channel;
  609. string stringToSpeak = llList2String(dataToSpeak,1);
  610. llSay(iChannel, stringToSpeak);
  611. }
  612. // stop everything
  613. else if(npcAction == "@pause") {
  614. RAMPause = (float) npcParams;
  615. DoPause();
  616. return;
  617. }
  618. else if(npcAction == "@wander") {
  619. list wanderData = llParseString2List(npcParams, ["|"], []);
  620. RAMwd = (float) llList2String(wanderData, 0);
  621. RAMwc = (integer) llList2String(wanderData, 1);
  622. vDestPos = osNpcGetPos(NPCKey()); // set the wander start
  623. DEBUG("Starting at " + (string) vDestPos);
  624. StateWander();
  625. return;
  626. }
  627. else if(npcAction == "@rotate") {
  628. RAMrot = (float) npcParams;
  629. DoRotate();
  630. }
  631. else if(npcAction == "@sit") {
  632. RAMsit= npcParams;
  633. StateSit();
  634. return;
  635. }
  636. else if(npcAction == "@touch") {
  637. RAMtouch= npcParams;
  638. StateTouch();
  639. return;
  640. }
  641. else if(npcAction == "@stand") {
  642. DoStand();
  643. }
  644. else if(npcAction == "@delete") {
  645. DoDelete();
  646. SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
  647. return;
  648. }
  649. else if(npcAction == "@animate") {
  650. list animateData = llParseString2List(npcParams, ["|"], []);
  651. RAManimationName = llList2String(animateData, 0);
  652. RAManimationTime = (float) llList2String(animateData, 1);
  653. StateAnimate();
  654. return;
  655. }
  656. else if(npcAction == "@appearance" ){
  657. DoAppearance(npcParams);
  658. }
  659. else if (npcAction =="@speed") {
  660. DoSpeed(npcParams);
  661. }
  662. else if (npcAction =="@notecard") {
  663. DoNewNote(npcParams);
  664. Notecard = npcParams;
  665. }
  666. else if (npcAction == "@attach")
  667. {
  668. DoAttach(npcParams);
  669. }
  670. STATE = 0;
  671. TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack
  672. }
  673. /////////////////// UTILITY Functions, not state-like //////////////////
  674. // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
  675. DEBUG(string str) {
  676. if (debug)
  677. llOwnerSay( str); // Send the owner debug info so you can chase NPCS
  678. if (iTitleText) {
  679. llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
  680. }
  681. }
  682. GetDest(string npcParams) {
  683. list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
  684. vDestPos.x = llList2Float(dest, 0);
  685. vDestPos.y = llList2Float(dest, 1);
  686. vDestPos.z = llList2Float(dest, 2);
  687. }
  688. NPCReadNoteCard(string Note) {
  689. DEBUG("NPCReadNoteCard");
  690. lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []);
  691. }
  692. integer SenseAvatar()
  693. {
  694. //Returns a strided list of the UUID, position, and name of each avatar in the region
  695. list avatars = llGetAgentList(AGENT_LIST_REGION ,[]);
  696. integer numOfAvatars = llGetListLength(avatars);
  697. if (numOfAvatars == 0)
  698. {
  699. DEBUG("No people");
  700. return 0;
  701. }
  702. //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's");
  703. integer nAvatars;
  704. integer i;
  705. for( i = 0;i < numOfAvatars; i++) {
  706. key aviKey = llList2Key(avatars,i);
  707. if (!osIsNpc(aviKey)) {
  708. list detail = llGetObjectDetails(aviKey,[OBJECT_POS]);
  709. vector pos = llList2Vector(detail,0);
  710. float dist = llVecDist(pos, llGetPos());
  711. if (dist < RANGE)
  712. {
  713. nAvatars++;
  714. DEBUG("In range:" + llKey2Name(aviKey));
  715. }
  716. }
  717. }
  718. //DEBUG("Located " + (string) nAvatars + " avatars");
  719. return nAvatars;
  720. }
  721. // return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set.
  722. integer checkPerms() {
  723. integer group = (integer) KeyValueGet("pr");
  724. if (! group)
  725. priPub = "Owner Only";
  726. else
  727. priPub = "Group";
  728. if (llDetectedKey(0) == llGetOwner()){
  729. kUserKey = llDetectedKey(0);
  730. return TRUE;
  731. }
  732. if ( group && llDetectedGroup(0)) {
  733. kUserKey = llDetectedKey(0);
  734. return TRUE;
  735. }
  736. kUserKey = llDetectedKey(0);
  737. return FALSE;
  738. }
  739. NPCAnimate(string anim)
  740. {
  741. DEBUG("Start Anim: " + anim);
  742. if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) {
  743. if (lastANIM != anim) {
  744. if(llStringLength(lastANIM)) {
  745. osNpcStopAnimation(NPCKey(), lastANIM);
  746. }
  747. osNpcPlayAnimation(NPCKey(), anim);
  748. lastANIM = anim;
  749. }
  750. } else {
  751. llSay(DEBUG_CHANNEL, "No animation named " + anim);
  752. }
  753. }
  754. TimerEvent(float timesent)
  755. {
  756. DEBUG("Setting timer: " + (string) timesent);
  757. llSetTimerEvent(timesent);
  758. }
  759. // Kill a NPC by Name
  760. Kill(string param)
  761. {
  762. integer count;
  763. list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\
  764. integer i;
  765. integer j = llGetListLength(avatars);
  766. for (i=0 ; i <= j; i+=3){
  767. string desired = llList2String(avatars,i+2);
  768. desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
  769. if (desired == param){
  770. vector v = llList2Vector(avatars,i+1);
  771. key target = llList2Key(avatars,i); // get the UUID of the avatar
  772. osNpcRemove(target);
  773. SaveKey(NULL_KEY );
  774. llOwnerSay("Removed " + param+ " at location " + (string) v);
  775. count++;
  776. }
  777. }
  778. NPCEnabled = FALSE; // not in world
  779. if (count)
  780. llOwnerSay("Removed " + (string) count + " NPC's");
  781. else
  782. llOwnerSay("Could not locate " + param);
  783. }
  784. // return a String for the position we are at. Strings used as the caller wants strings
  785. string Pos()
  786. {
  787. vector where = llGetPos(); // find the box position
  788. where.z += OffsetZ; // use the ground position + an offset
  789. if (Editor)
  790. where = <128,128,31 + llFrand(1)>; // center of sim for editing
  791. // if attached the height will be too high by 1/2 the agent size
  792. if (llGetAttached()) {
  793. vector size = llGetAgentSize(llGetOwner());
  794. float Z = size.z;
  795. where.z -= Z/2;
  796. }
  797. // DEBUG("Pos= " + (string) where);
  798. return (string) where;
  799. }
  800. // setup a menu with a timer for timeouts, called by all make*()
  801. menu()
  802. {
  803. llListenRemove(iHandle);
  804. iChannel = llCeil(llFrand(100000) + 20000);
  805. iHandle = llListen(iChannel,"","","");
  806. TimerEvent(30.0);
  807. MENU = TRUE;
  808. }
  809. // make a text box
  810. makeText(string Param)
  811. {
  812. menu();
  813. llTextBox(kUserKey, Param, iChannel);
  814. }
  815. // top level menu
  816. makeMainMenu()
  817. {
  818. menu();
  819. list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"];
  820. llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel);
  821. }
  822. // Rev 1.4
  823. // top level menu for non group/ non owners
  824. makeUserMenu()
  825. {
  826. if (!allowUsers) return;
  827. menu();
  828. list buttons = ["Start NPC","Stop NPC"];
  829. llDialog(kUserKey,"Choose",buttons,iChannel);
  830. }
  831. // programmable menu for @commands
  832. makeMenu(list buttons)
  833. {
  834. menu();
  835. llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel);
  836. }
  837. // make one or two text boxes with prompts
  838. Text(string cmd, string p1, string p2)
  839. {
  840. sCommand = cmd;
  841. sParam2 = "";
  842. if (llStringLength(p2))
  843. sParam2 = p2;
  844. makeText(p1);
  845. }
  846. // Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present.
  847. ProcessSensor()
  848. {
  849. integer SensorOn;
  850. if ("on" == KeyValueGet("se"))
  851. {
  852. SensorOn = TRUE; // we need to scan for avatars
  853. } else {
  854. SensorOn = FALSE; // we need to scan for avatars
  855. }
  856. DEBUG("Sensor:" + (string) SensorOn);
  857. integer n = SenseAvatar();
  858. DEBUG("Avatars:" + (string) n);
  859. if (SensorOn && n)
  860. avatarPresent = TRUE; // someone is here and we need to tell the system to run
  861. else if (SensorOn && !n)
  862. avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
  863. else { // sensor is off, lete see if there is a NPC. If so, we are ON
  864. DEBUG("NPCEnabled:" + (string) NPCEnabled);
  865. if (NPCEnabled)
  866. avatarPresent = TRUE;
  867. else
  868. avatarPresent = FALSE;
  869. }
  870. // start up from when when no one is near
  871. if (avatarPresent && STATE == NobodyHome)
  872. STATE = 0;
  873. //DEBUG("Avatar Present: " + (string) avatarPresent);
  874. }
  875. vector CirclePoint(float radius) {
  876. float x = llFrand(radius *2) - radius; // +/- radius, randomized
  877. float y = llFrand(radius *2) - radius; // +/- radius, randomized
  878. return <x, y, 0>; // so this should always happen
  879. }
  880. string KeyValueGet(string var) {
  881. list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
  882. do {
  883. list data = llParseString2List(llList2String(dVars, 0), ["="], []);
  884. string k = llList2String(data, 0);
  885. if(k != var) jump continue;
  886. //DEBUG("got " + var + " = " + llList2String(data, 1));
  887. return llList2String(data, 1);
  888. @continue;
  889. dVars = llDeleteSubList(dVars, 0, 0);
  890. } while(llGetListLength(dVars));
  891. return "";
  892. }
  893. KeyValueSet(string var, string val) {
  894. //DEBUG("set " + var + " = " + val);
  895. list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
  896. if(llGetListLength(dVars) == 0)
  897. {
  898. llSetObjectDesc(var + "=" + val);
  899. return;
  900. }
  901. list result = [];
  902. do {
  903. list data = llParseString2List(llList2String(dVars, 0), ["="], []);
  904. string k = llList2String(data, 0);
  905. if(k == "") jump continue;
  906. if(k == var && val == "") jump continue;
  907. if(k == var) {
  908. result += k + "=" + val;
  909. val = "";
  910. jump continue;
  911. }
  912. string v = llList2String(data, 1);
  913. if(v == "") jump continue;
  914. result += k + "=" + v;
  915. @continue;
  916. dVars = llDeleteSubList(dVars, 0, 0);
  917. } while(llGetListLength(dVars));
  918. if(val != "") result += var + "=" + val;
  919. llSetObjectDesc(llDumpList2String(result, "&"));
  920. }
  921. // clear RAM
  922. Clr() {
  923. lCommands = [];
  924. llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
  925. makeMainMenu();
  926. }
  927. integer checkNoteCards()
  928. {
  929. // Check that they have saved an Appeaance and Path notecard
  930. integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
  931. integer i;
  932. integer count;
  933. for (; i < num; i++){
  934. if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard)
  935. count++;
  936. if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance)
  937. count++;
  938. }
  939. DEBUG("Checked " + (string) count + " Notecards");
  940. // if we have both, run the NPC
  941. return count;
  942. }
  943. Update(string SName) {
  944. // delete all NPC*scripts except myself
  945. integer i;
  946. integer j = llGetInventoryNumber(INVENTORY_SCRIPT);
  947. for (i = 0; i < j; i++) {
  948. string name = llGetInventoryName(INVENTORY_SCRIPT,i);
  949. string match = llGetSubString(name,0,2);
  950. if (match == SName && llGetScriptName() != name)
  951. {
  952. llRemoveInventory(name);
  953. llOwnerSay("Upgraded");
  954. }
  955. }
  956. }
  957. // Get all default saved params from the Description
  958. GetSwitches()
  959. {
  960. string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
  961. if (rA == "A")
  962. relAbs = "Absolute";
  963. else if (rA == "R")
  964. relAbs = "Relative";
  965. else
  966. relAbs = "Absolute";
  967. // reenable NPC if sensor is on.
  968. if ("on" == KeyValueGet("se"))
  969. {
  970. NPCEnabled = TRUE;
  971. mSensor = "Sense is On";
  972. ProcessSensor(); // fake 1 avatar to get it rezzed
  973. } else {
  974. mSensor = "Sense is Off";
  975. }
  976. }
  977. SaveKey(key akey)
  978. {
  979. DEBUG("Saving Key of " + (string) akey);
  980. KeyValueSet("key", akey);
  981. if (akey != (key) KeyValueGet("key") )
  982. {
  983. DEBUG("Fatal error, cannot save key");
  984. }
  985. gNpcKey = akey;
  986. }
  987. key NPCKey()
  988. {
  989. key akey = gNpcKey; // from cached copy
  990. // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description
  991. if (gNpcKey == NULL_KEY)
  992. {
  993. //DEBUG("Get DKey");
  994. akey = KeyValueGet("key"); // from Description of the prim
  995. }
  996. // DEBUG("NPC KEY:" + (string) akey);
  997. return akey;
  998. }
  999. /////////////////// CODE BEGINS //////////////////
  1000. default
  1001. {
  1002. changed(integer change) {
  1003. if(change & CHANGED_REGION_START) {
  1004. llResetScript();
  1005. }
  1006. }
  1007. on_rez(integer start_param)
  1008. {
  1009. llResetScript();
  1010. }
  1011. state_entry() {
  1012. llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
  1013. DoDelete(); // kill any NPC that is out running
  1014. Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it.
  1015. GetSwitches(); // Get all default saved params from the Description
  1016. llSetTimerEvent(TIMER);
  1017. }
  1018. touch_start(integer n)
  1019. { // if touched, make a menu
  1020. if (checkPerms()) {
  1021. if (RecordPath == STATE) {
  1022. makeMenu(lAtButtons);
  1023. } else {
  1024. makeMainMenu();
  1025. }
  1026. } else {
  1027. makeUserMenu();
  1028. }
  1029. }
  1030. // menu listener
  1031. listen(integer iChannel, string name, key id, string message) {
  1032. if (MENU) {
  1033. llListenRemove(iHandle);
  1034. MENU = 0; // menu is off
  1035. iHandle = 0;
  1036. }
  1037. if (message == "Stop NPC")
  1038. {
  1039. lNPCScript = []; // force reload of notecard
  1040. NPCEnabled = FALSE;
  1041. if (NPCKey() != NULL_KEY){
  1042. Kill(sNPCName);
  1043. sNPCName = "";
  1044. } else {
  1045. bNPC_STOP = TRUE;
  1046. makeText("Enter name of an NPC to stop");
  1047. }
  1048. }
  1049. else if (message == "Menu" ) {
  1050. makeMainMenu();
  1051. }
  1052. else if (message == "Erase RAM"){
  1053. Clr();
  1054. }
  1055. else if (message == "Relative"){
  1056. relAbs = "Absolute";
  1057. KeyValueSet("co","A"); // remember coordinates = A
  1058. Clr();
  1059. }
  1060. else if (message == "Absolute"){
  1061. relAbs = "Relative";
  1062. KeyValueSet("co","R"); // remember coordinates = R
  1063. Clr();
  1064. }
  1065. else if (message == "Recording"){
  1066. DoMenuForCommands(); // show them the recording menu
  1067. }
  1068. else if (message == "Owner Only") {
  1069. priPub = "Group";
  1070. KeyValueSet("pr","1");
  1071. llOwnerSay("Group members have control");
  1072. makeMainMenu();
  1073. }
  1074. else if (message == "Group") {
  1075. priPub = "Owner Only";
  1076. KeyValueSet("pr","0");
  1077. llOwnerSay("Only you have control");
  1078. makeMainMenu();
  1079. }
  1080. else if (message == "Sense is On") {
  1081. mSensor ="Sense is Off";
  1082. KeyValueSet("se", "off");
  1083. llOwnerSay(mSensor);
  1084. makeMainMenu();
  1085. }
  1086. else if (message == "Sense is Off") {
  1087. mSensor ="Sense is On";
  1088. llOwnerSay(mSensor);
  1089. KeyValueSet("se", "on");
  1090. NPCEnabled = FALSE;
  1091. integer count = checkNoteCards();
  1092. if (count >= 2) {
  1093. DEBUG("Notecards approved , calling DoProcessNPCLine");
  1094. DoProcessNPCLine();
  1095. return;
  1096. }
  1097. if (Editor) {
  1098. DoProcessNPCLine();
  1099. return;
  1100. }
  1101. llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
  1102. makeMainMenu();
  1103. }
  1104. else if (message == "Appearance") {
  1105. llRemoveInventory(Appearance); // delete the notecard
  1106. osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard
  1107. llOwnerSay("Your outfit has been saved");
  1108. makeMainMenu();
  1109. }
  1110. else if (message == "Save") {
  1111. if (llGetListLength(lCommands) == 0) {
  1112. llOwnerSay("Nothing recorded, you need to make a recording first");
  1113. makeMainMenu();
  1114. return;
  1115. }
  1116. DoSave();
  1117. }
  1118. else if (message == "Help"){
  1119. llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/");
  1120. makeMainMenu();
  1121. }
  1122. else if (message == "Start NPC") {
  1123. integer count = checkNoteCards();
  1124. NPCEnabled = TRUE;
  1125. if (Editor) {
  1126. DoProcessNPCLine();
  1127. return;
  1128. }
  1129. if (count >= 2) {
  1130. DEBUG("Notecards approved , calling DoProcessNPCLine");
  1131. SetStop(FALSE); // Let's run the notecard
  1132. DoProcessNPCLine();
  1133. return;
  1134. }
  1135. llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC");
  1136. }
  1137. else if (bNPC_STOP){
  1138. bNPC_STOP = FALSE;
  1139. Kill(message);
  1140. }
  1141. else if (message == ">>"){
  1142. makeMenu(lMenu2);
  1143. }
  1144. else if (message == ">>>"){
  1145. makeMenu(lMenu3);
  1146. }
  1147. else if (message == "<<") {
  1148. makeMenu(lAtButtons);
  1149. }
  1150. else if (message == "<<<") {
  1151. makeMenu(lMenu2);
  1152. }
  1153. else if (message == "@comment"){
  1154. Text("# ","Enter a comment","");
  1155. }
  1156. else if (message == "@stop"){
  1157. lCommands += "@stop"+ "\n";
  1158. makeMenu(lAtButtons);
  1159. }
  1160. else if (message == "@run"){
  1161. lCommands += "@run=" + Pos() + "\n";
  1162. llOwnerSay("Recorded position: " + Pos());
  1163. makeMenu(lAtButtons);
  1164. }
  1165. else if (message == "@fly"){
  1166. lCommands += "@fly=" + Pos() + "\n";
  1167. llOwnerSay("Recorded position: " + Pos());
  1168. makeMenu(lAtButtons);
  1169. }
  1170. else if (message == "@land"){
  1171. lCommands += "@land=" + Pos() + "\n";
  1172. llOwnerSay("Recorded position: " + Pos());
  1173. makeMenu(lAtButtons);
  1174. }
  1175. else if (message == "@walk") {
  1176. lCommands += "@walk=" + Pos() + "\n";
  1177. llOwnerSay("Recorded position: " + Pos());
  1178. makeMenu(lAtButtons);
  1179. }
  1180. else if (message == "@stop"){
  1181. lCommands += "@stop"+ "\n";
  1182. makeMenu(lAtButtons);
  1183. }
  1184. else if (message == "@sound"){
  1185. Text("@sound=","Enter a sound name or UUID to trigger","");
  1186. }
  1187. else if (message == "@randsound"){
  1188. lCommands += "@randsound"+ "\n";
  1189. makeMenu(lAtButtons);
  1190. }
  1191. else if (message == "@say") {
  1192. Text("@say=","Enter what the NPC will say","");
  1193. }
  1194. else if (message == "@whisper"){
  1195. Text("@whisper=","Enter what the NPC will whisper","");
  1196. }
  1197. else if (message == "@shout"){
  1198. Text("@shout=","Enter what the NPC will shout","");
  1199. }
  1200. else if (message == "@wander") {
  1201. Text("@wander=","Enter radius to wander","Enter number of wanders");
  1202. }
  1203. else if (message == "@pause") {
  1204. Text("@pause=","Enter time to pause","");
  1205. }
  1206. else if (message == "@rotate") {
  1207. Text("@rotate=","Enter degrees to rotate","");
  1208. }
  1209. else if (message == "@sit"){
  1210. Text("@sit=","Enter name of object to sit on","");
  1211. }
  1212. else if (message == "@touch"){
  1213. Text("@touch=","Enter name of object to touch","");
  1214. }
  1215. else if (message == "@cmd"){
  1216. Text("@cmd=","Enter cjhannel to speak on","Enter text to speak");
  1217. }
  1218. else if (message == "@stand"){
  1219. lCommands += "@stand\n";
  1220. llOwnerSay("Stand Recorded");
  1221. makeMenu(lAtButtons);
  1222. }
  1223. else if (message == "@animate"){
  1224. Text("@animate=","Enter animation name to play","Enter time to play the animation");
  1225. }
  1226. else if (message == "@attach"){
  1227. Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)");
  1228. }
  1229. else if (message == "@speed"){
  1230. Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed","");
  1231. }
  1232. // Save NPC name
  1233. else if (MakeNotecard == STATE) {
  1234. sNPCName = message; // in case we need to kill it.
  1235. vector vDest = (vector) Pos();
  1236. if (relAbs == "Relative")
  1237. {
  1238. vDest -= llGetPos(); // just an offset for relative
  1239. }
  1240. sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
  1241. integer i;
  1242. integer j = llGetListLength(lCommands);
  1243. for (; i < j; i++){
  1244. // get the command to save to the notecard
  1245. string line = llList2String(lCommands,i);
  1246. if (relAbs == "Absolute") {
  1247. sNotecard += line; // add the un-modified string to the notecard
  1248. } else {
  1249. // since we have to record absolute coords since we do not know where the box goes until they press Save,
  1250. // we process the absolute to relative conversion for walks here
  1251. list parts = llParseString2List(line,["="],[]); //get the @command
  1252. if (llList2String(parts,0) == "@walk") {
  1253. vector vec = (vector) llList2String(parts,1) - llGetPos();
  1254. sNotecard += "@walk=" + (string) vec + "\n";
  1255. }
  1256. else if (llList2String(parts,0) == "@fly") {
  1257. vector vec = (vector) llList2String(parts,1) - llGetPos();
  1258. sNotecard += "@fly=" + (string) vec + "\n";
  1259. }
  1260. else if (llList2String(parts,0) == "@run") {
  1261. vector vec = (vector) llList2String(parts,1) - llGetPos();
  1262. sNotecard += "@run=" + (string) vec + "\n";
  1263. }
  1264. else if (llList2String(parts,0) == "@land") {
  1265. vector vec = (vector) llList2String(parts,1) - llGetPos();
  1266. sNotecard += "@land=" + (string) vec + "\n";
  1267. }
  1268. else {
  1269. sNotecard += line; // add the un-modified string to the notecard
  1270. }
  1271. }
  1272. }
  1273. llRemoveInventory(Notecard); // delete the old notecard
  1274. osMakeNotecard(Notecard,sNotecard); // Makes the notecard.
  1275. llSay(0,sNotecard);
  1276. llOwnerSay("Commands notecard has been written");
  1277. STATE = 0;
  1278. } // MakeNotecard
  1279. else if (! llStringLength(sParam2)) {
  1280. lCommands += sCommand + message + "\n";
  1281. llOwnerSay("Recorded");
  1282. makeMenu(lAtButtons);
  1283. }
  1284. else if (llStringLength(sParam2)){
  1285. sCommand = sCommand + message + "|";
  1286. llOwnerSay("Recorded");
  1287. makeText(sParam2);
  1288. sParam2 = "";
  1289. }
  1290. }
  1291. timer(){
  1292. // DEBUG("tick");
  1293. // if llDialog is up, kill the listener for the dialog box.
  1294. if (iHandle) {
  1295. llOwnerSay("Menu timed out");
  1296. llListenRemove(iHandle);
  1297. iHandle = 0;
  1298. return; // ^^^^^^^^^^^^^^^^^^^^^^^
  1299. }
  1300. // if NoBodyHome, we are sensing for an avatar
  1301. else if (NobodyHome == STATE) {
  1302. ProcessSensor();
  1303. return;
  1304. }
  1305. // if we are spawning, we need time to rez the NPC, then start processing NPC Commands.
  1306. else if (Spawning == STATE) {
  1307. STATE = 0;
  1308. TimerEvent(TIMER);
  1309. }
  1310. // We end aniamtions with a timer
  1311. else if (Animate == STATE){
  1312. NPCAnimate(STAND);
  1313. TimerEvent(TIMER);
  1314. }
  1315. else if (Walking == STATE) {
  1316. if (--iWaitCounter) {
  1317. if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) {
  1318. return;
  1319. }
  1320. }
  1321. DEBUG("At Destination: " + (string) newDest);
  1322. // walk, fly, run, land
  1323. if (walkstate == 1) {
  1324. NPCAnimate(STAND);
  1325. NPCWalkOption = OS_NPC_NO_FLY;
  1326. } else if (walkstate == 2) {
  1327. // nothing
  1328. } else if (walkstate == 3) {
  1329. NPCAnimate(STAND);
  1330. NPCWalkOption = OS_NPC_NO_FLY;
  1331. } else if (walkstate == 4) {
  1332. llShout(FLIGHT,"landing");
  1333. NPCAnimate(STAND);
  1334. NPCWalkOption = OS_NPC_NO_FLY;
  1335. }
  1336. }
  1337. // Wandering timer
  1338. else if (Wander == STATE) {
  1339. if (--iWaitCounter) { // wait 60 seconds to get to a destination.
  1340. if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST)
  1341. return;
  1342. }
  1343. // see if wander counter == 0, if so, stop walking, go to stand and process next line
  1344. if(RAMwc == 0) {
  1345. NPCAnimate(STAND);
  1346. DEBUG("Wander ended, calling DoProcessNPCLine");
  1347. STATE = 0;
  1348. DoProcessNPCLine();
  1349. return;
  1350. }
  1351. // one less time to wander around
  1352. RAMwc--;
  1353. NPCAnimate(STAND);
  1354. TimerEvent(TIMER);
  1355. StateWanderhold();
  1356. return;
  1357. }
  1358. // Wandering requires us to re-wander when we reach a destination
  1359. else if (WanderHold == STATE) {
  1360. StateWander();
  1361. TimerEvent(TIMER);
  1362. return;
  1363. }
  1364. else if (DoProcess == STATE) {
  1365. TimerEvent(QUICK);
  1366. }
  1367. STATE = 0;
  1368. // We always process a NPC line at end of timer.
  1369. DEBUG("Tick end, calling DoProcessNPCLine");
  1370. DoProcessNPCLine();
  1371. }
  1372. // sensors are used for sitting on prims
  1373. // Neo Cortex: added different SensorFunc states to trigger sit or touch
  1374. sensor(integer num) {
  1375. if (SensorFunc == 1) {
  1376. osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW);
  1377. DEBUG("Seated, calling DoProcessNPCLine");
  1378. SensorFunc = 0;
  1379. } else if (SensorFunc == 2) {
  1380. osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS);
  1381. DEBUG("Touched, calling DoProcessNPCLine");
  1382. SensorFunc = 0;
  1383. }
  1384. DoProcessNPCLine();
  1385. }
  1386. no_sensor(){
  1387. DEBUG ("no target prim located, calling DoProcessNPCLine");
  1388. SensorFunc = 0;
  1389. DoProcessNPCLine();
  1390. }
  1391. link_message(integer sender, integer num, string str, key id){
  1392. DEBUG("Command In:" + str);
  1393. if (str=="@go") {
  1394. SetStop(FALSE); // Let's run the notecard
  1395. DEBUG("@go running");
  1396. DoProcessNPCLine();
  1397. } else {
  1398. Stack += [str]; // take anything, the controller will filter away non @ stuff
  1399. if (STATE == 0) {
  1400. DEBUG("calling DoNPC");
  1401. DoProcessNPCLine();
  1402. } else{
  1403. DEBUG("Queued");
  1404. }
  1405. }
  1406. }
  1407. }
  1408. // This is Rev 3.7 08/11/2015 which added @attach
  1409. // Revision History
  1410. // Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor
  1411. // Rev 1.2 10-14-2014 @ sit had wrong type.
  1412. // Rev 1.3 relative movement fixed for @fly
  1413. // Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop.
  1414. // Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim
  1415. // Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency
  1416. // Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference)
  1417. // Rev 1.8 tuned up Kill NPC, added more flexible upgrader
  1418. // Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC.
  1419. // Rev 2.1 No laggy sensor used exept to sit on stuff
  1420. // Rev 2.2 Various sensor fixes
  1421. // Rev 2.3 Sets No Sensor in menu, must be started by hand
  1422. // Rev 2.4 - reserved for patches to 2.3 if needed
  1423. // Rev 3.0 Refactor out into subs, not states to make command injection easier
  1424. // New command: @appearance=Notecardname so you can switch to a new notecard on the fly
  1425. // New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1)
  1426. // Rev 3.1 Commands are not interruptible by Link Message
  1427. // Rev 3.2 Sensor patches for consistency in removing the NPC
  1428. // Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002
  1429. // Added Menu 3 for notecard and appearance commands
  1430. // Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked
  1431. // solves the NPC starting up when no sensor is set.
  1432. // Rev 3.5 fixes saving to !Path notecard
  1433. // Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted
  1434. // Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC
  1435. // Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message
  1436. //*******************************************************************//
  1437. // Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/
  1438. // This is an OpenSim-only script.
  1439. // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
  1440. ////////////////////////////////////////////////////////////////////////////////////////////
  1441. // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
  1442. ///////////////////////////////////////////////////////////////////////////////////////////
  1443. // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
  1444. // rights of fair usage, the disclaimer and warranty conditions. //
  1445. ///////////////////////////////////////////////////////////////////////////////////////////
  1446. // The original NPC controller was from http://was.fm/opensim:npc
  1447. // Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com
  1448. // llSensor had two params swapped
  1449. // @Wander would wander where it had rezzed, not where it was.
  1450. // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
  1451. // The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed.
  1452. // New code:
  1453. // Merged with new Route recorder and notecard writer
  1454. // If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this
  1455. // Exposed many tunable variables and ported the code to LSLEditor.
  1456. // Added floating point to times in notecard.
  1457. // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
  1458. //
  1459. // notecards integers are not floats for better control
  1460. //
  1461. // Link Messages may be used to perform external control by injecting @commands into the stream of actions
  1462. // Example:
  1463. // To chat something, such as with a chat robot
  1464. // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
  1465. // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
  1466. // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
  1467. //
  1468. // ; Turn on OSSL
  1469. // AllowOSFunctions = true
  1470. // OSFunctionThreatLevel = Severe
  1471. //[NPC]
  1472. // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
  1473. // Enabled = true
  1474. //
  1475. // and then the server has to be restarted.
  1476. // Commands: All commands begin with an @ sign. All other lines are ignored
  1477. // @commands may have optional parameters. The syntax is always:
  1478. // @cmd=parm1|parm2
  1479. // NaN in the table below meand Not a Number. This means there is no parameter
  1480. //Command First Parameter Second Parameter Description
  1481. //@spawn name location (vector) Rezzes an NPC with name at a location.
  1482. //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
  1483. //@walk destination (vector) NaN Makes the NPC walk to destination.
  1484. //@fly destination (vector) NaN Makes the NPC fly to destination.
  1485. //@land destination (vector) NaN Makes the NPC land at destination.
  1486. //@say string NaN Makes the NPC speak a phrase.
  1487. //@whisper string NaN Makes the NPC whisper a phrase.
  1488. //@shout string NaN Makes the NPC shout a phrase.
  1489. //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
  1490. //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
  1491. //@delete NaN NaN Removes the NPC. Requires a link message to continue
  1492. //@goto label (string) NaN Jump to the label label in the script.
  1493. //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
  1494. //@sound sound_name NaN plays a sound from inventory
  1495. //@randsound NaN NaN Plays a random sound from inventory
  1496. //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
  1497. //@sit primitive name NaN Sit on a primitive with a given name.
  1498. //@touch primitive name NaN Touch on a primitive with a given name.
  1499. //@stand NaN NaN If sitting on a primitive, stand up.
  1500. //@cmd channel (integer) string Says string on channel, for controlling external gadgets
  1501. //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
  1502. //@go NaN NaN Continues on next notecard line, for use in link messages
  1503. //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
  1504. //@notecard notename (string) NaN load a new Path notecard
  1505. //@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point
  1506. // Constant attachmentPoint Comment
  1507. // ATTACH_CHEST 1 chest/sternum
  1508. // ATTACH_HEAD 2 head
  1509. // ATTACH_LSHOULDER 3 left shoulder
  1510. // ATTACH_RSHOULDER 4 right shoulder
  1511. // ATTACH_LHAND 5 left hand
  1512. // ATTACH_RHAND 6 right hand
  1513. // ATTACH_LFOOT 7 left foot
  1514. // ATTACH_RFOOT 8 right foot
  1515. // ATTACH_BACK 9 back
  1516. // ATTACH_PELVIS 10 pelvis
  1517. // ATTACH_MOUTH 11 mouth
  1518. // ATTACH_CHIN 12 chin
  1519. // ATTACH_LEAR 13 left ear
  1520. // ATTACH_REAR 14 right ear
  1521. // ATTACH_LEYE 15 left eye
  1522. // ATTACH_REYE 16 right eye
  1523. // ATTACH_NOSE 17 nose
  1524. // ATTACH_RUARM 18 right upper arm
  1525. // ATTACH_RLARM 19 right lower arm
  1526. // ATTACH_LUARM 20 left upper arm
  1527. // ATTACH_LLARM 21 left lower arm
  1528. // ATTACH_RHIP 22 right hip
  1529. // ATTACH_RULEG 23 right upper leg
  1530. // ATTACH_RLLEG 24 right lower leg
  1531. // ATTACH_LHIP 25 left hip
  1532. // ATTACH_LULEG 26 left upper leg
  1533. // ATTACH_LLLEG 27 left lower leg
  1534. // ATTACH_BELLY 28 belly/stomach/tummy
  1535. // ATTACH_LEFT_PEC 29 left pectoral
  1536. // ATTACH_RIGHT_PEC 30 right pectoral
  1537. // ATTACH_HUD_CENTER_2 31 HUD Center 2
  1538. // ATTACH_HUD_TOP_RIGHT 32 HUD Top Right
  1539. // ATTACH_HUD_TOP_CENTER33 HUD Top
  1540. // ATTACH_HUD_TOP_LEFT 34 HUD Top Left
  1541. // ATTACH_HUD_CENTER_1 35 HUD Center
  1542. // ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left
  1543. // ATTACH_HUD_BOTTOM 37 HUD Bottom
  1544. // ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right
  1545. // ATTACH_NECK 39 neck
  1546. // ATTACH_AVATAR_CENTER 40 avatar center/root
  1547. //////////////////////////////////////////////////////////
  1548. // DEBUG //
  1549. //////////////////////////////////////////////////////////
  1550. integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions
  1551. integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
  1552. // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
  1553. integer iTitleText = TRUE; // set to TRUE to see debug info in text above the controller
  1554. //////////////////////////////////////////////////////////
  1555. // TUNABLE CONFIGURATION //
  1556. //////////////////////////////////////////////////////////
  1557. float TIMER = 2; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds
  1558. float QUICK = 1; // when we need to move to the next state, we use a QUICK timer
  1559. string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
  1560. string Notecard = "!Path"; // The name of the recorded routes
  1561. integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE
  1562. float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target
  1563. integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
  1564. float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
  1565. integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time.
  1566. float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
  1567. float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process
  1568. string STAND = "Stand"; // the name of the default Stand animation
  1569. string WALK = "Walk"; // the name of the default Walk animation
  1570. string FLY = "Fly"; // the name of the default Fly animation
  1571. string RUN = "Run"; // the name of the default Run animation
  1572. string LAND = "Land"; // the name of the default land animation ( for birds only)
  1573. float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
  1574. float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up.
  1575. integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
  1576. // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
  1577. // These vars are stored by saving them with KeyValueSet
  1578. // "pr" is a 0 if it is set for Owner Only, 1 for Group control
  1579. // "se" is "on" if Started
  1580. // "co" = "R" or "A" for relative or absolute addressing mode
  1581. // "key" = NPC key
  1582. // These Globals used to be stored in description. Moved to RAM in V1.6
  1583. float RAMPause; // @pause param
  1584. float RAMwd ; // @wander distance
  1585. integer RAMwc; // @wander count
  1586. float RAMrot; // @rotate
  1587. string RAMsit; // @sit primname
  1588. string RAMtouch; // @touch primname
  1589. string RAManimationName; // @animate animation (string) time (float)
  1590. float RAManimationTime;
  1591. // other globals section
  1592. integer iChannel; // a listen channel, randomly assigned
  1593. integer iHandle; // the handle to it
  1594. // NPC controls
  1595. vector newDest ; // tmp storage for the walks
  1596. integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
  1597. string sNPCName; // the name of the NPC that may be in world. So we can remove it.
  1598. integer bNPC_STOP = FALSE; // boolean to reuse a listener
  1599. integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them
  1600. float fTimerVal ; // how long we wait when wandering (calculated)
  1601. float NPCEnabled; // true if the NPC is suppodes to be running
  1602. // OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC.
  1603. // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
  1604. integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
  1605. integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0
  1606. integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
  1607. // OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given.
  1608. // OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped
  1609. //OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect.
  1610. // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
  1611. // menus
  1612. string mSensor="Sense is Off"; // Sensor or "No Sensor"
  1613. list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"];
  1614. list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"];
  1615. list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ];
  1616. string sCommand; // place to store a command for two-prompted ones
  1617. string sParam2; // place to store a prompt for two-prompted ones
  1618. string priPub = "Owner Only"; // Private or Group
  1619. key kUserKey; // the person who is controlling the avatar, not the Owner
  1620. // the command lists
  1621. list lCommands; // commands are stored here
  1622. list lNPCScript; // Storage for the NPC script.
  1623. string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
  1624. string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
  1625. // misc vars
  1626. string sNotecard; // commands are stored here temporarily for dumping
  1627. vector vWanderPos; // a place to wander to
  1628. string lastANIM ; // last animation run
  1629. // Sensor
  1630. integer avatarPresent; // Sensor sets this flag when people are within Range.
  1631. // Coordinate control
  1632. vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
  1633. vector vDestPos = ZERO_VECTOR; // Storage for destination position.
  1634. string relAbs = "Relative"; // absolute vs relative positioning
  1635. vector lastKnownPos; // last known NPC position when we deleted it
  1636. // STATES
  1637. integer MENU ; // processing a dialog box state, may be concurrent with STATE
  1638. integer STATE; // state storage
  1639. integer MakeNotecard = 1; // displaying a text box for NPC name
  1640. integer RecordPath = 2; // displaying a path notecard menu
  1641. integer NobodyHome = 3; // looking for an avatar
  1642. integer Spawning = 4; // spawning an avatar
  1643. integer Animate = 5; // animation timer needed
  1644. integer Walking = 6; // Hey! I am walking here!
  1645. integer Wander = 7; // Wandering around neeeds a timer, too
  1646. integer WanderHold = 8; // We reached a wander point
  1647. integer DoProcess = 9; // Set this to make it process a new command
  1648. key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
  1649. list Stack ; // a command stack from link message input
  1650. integer SensorFunc = 0; // define which function shall be triggered inside the sensor function
  1651. // 0 means none, 1 sit, 2 touch
  1652. ///////////////////////////////////////////////////////////////////////////
  1653. // FUNCTIONS //
  1654. ///////////////////////////////////////////////////////////////////////////
  1655. // Do* functions are much like states from the old V2 scripts.
  1656. // Save a Path notecard
  1657. DoSave()
  1658. {
  1659. STATE = MakeNotecard;
  1660. makeText("Stand where you want the NPC to appear, and enter the NPC Name");
  1661. }
  1662. // This function is used to record the path for the NPC
  1663. // Each command can take 0, 1, or 2 params
  1664. DoMenuForCommands() {
  1665. makeMenu(lAtButtons);
  1666. }
  1667. // No one is here when sensors were on, so we kill off the NPC
  1668. DoNobodyHome()
  1669. {
  1670. DEBUG("Nobody Home");
  1671. STATE = NobodyHome;
  1672. if (NPCKey() != NULL_KEY) {
  1673. osNpcRemove(NPCKey());
  1674. SaveKey(NULL_KEY);
  1675. }
  1676. TimerEvent(5); // keep ticking to sense avatars
  1677. }
  1678. // Create a NPC
  1679. DoSpawn() {
  1680. DEBUG("state spawn");
  1681. NPCEnabled = TRUE; // in world
  1682. // see if there is already one out there.
  1683. if (NPCKey() != NULL_KEY) {
  1684. DEBUG("Already living");
  1685. return;
  1686. }
  1687. STATE = Spawning;
  1688. list name = llParseString2List(sNPCName, [" "], []);
  1689. if (relAbs == "Relative"){
  1690. vInitialPos += llGetPos();
  1691. }
  1692. DEBUG("Rezzing the NPC:" + (string) vInitialPos);
  1693. key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions);
  1694. SaveKey(aKey ); // save in desceription and global, too
  1695. osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
  1696. TimerEvent(REZTIME);
  1697. NPCAnimate(STAND);
  1698. }
  1699. DoRotate() {
  1700. DEBUG("@rotate=" + (string) RAMrot);
  1701. osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
  1702. }
  1703. DoSit() {
  1704. DEBUG ("state sit - looking for " + RAMsit);
  1705. SensorFunc = 1; //triggers osNpcSit
  1706. llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
  1707. }
  1708. DoTouch() {
  1709. DEBUG ("state touch - looking for " + RAMtouch);
  1710. SensorFunc = 2; //triggers osNpcTouch
  1711. llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
  1712. }
  1713. DoStand() {
  1714. DEBUG("state stand");
  1715. osNpcStand(NPCKey());
  1716. }
  1717. DoAnimate() {
  1718. DEBUG("state animate");
  1719. STATE = Animate;
  1720. NPCAnimate(RAManimationName);
  1721. if (RAManimationTime <=0 ) // V 3.4 tweak
  1722. RAManimationTime = 1;
  1723. TimerEvent(RAManimationTime);
  1724. }
  1725. DoWalk() {
  1726. DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
  1727. STATE = Walking;
  1728. // walk, fly, run, land
  1729. if (walkstate == 1) {
  1730. NPCAnimate(WALK);
  1731. } else if (walkstate == 2) {
  1732. llShout(FLIGHT,"flying");
  1733. NPCAnimate(FLY);
  1734. } else if (walkstate == 3) {
  1735. NPCAnimate(RUN);
  1736. } else if (walkstate == 4) {
  1737. NPCAnimate(LAND);
  1738. }
  1739. newDest = vDestPos ;
  1740. newDest.z += OffsetZ;
  1741. // notecard is stored as offsets from this box with relative addressing. Convert to absolute
  1742. if (relAbs == "Relative"){
  1743. newDest += llGetPos();
  1744. }
  1745. DEBUG("Moveto:" + (string) newDest);
  1746. osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
  1747. iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
  1748. TimerEvent(TIMER);
  1749. }
  1750. DoWander(){
  1751. DEBUG("state wander");
  1752. STATE = Wander;
  1753. vector point = CirclePoint(RAMwd);
  1754. DEBUG("CirclePoint:" + (string) point);
  1755. vWanderPos = vDestPos + point;
  1756. DEBUG("vWanderPos:" + (string) vWanderPos);
  1757. fTimerVal = WANDERTIME; // default time to pause after each wander
  1758. if (WANDERRAND)
  1759. fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times
  1760. NPCAnimate(WALK);
  1761. DEBUG("Wander to:" + (string) vWanderPos);
  1762. osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
  1763. iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
  1764. TimerEvent(TIMER);
  1765. }
  1766. DoWanderhold() {
  1767. DEBUG("Wander Hold");
  1768. STATE = WanderHold;
  1769. // now that we have reached a wander spot, slow the timer down to the desired value
  1770. TimerEvent(fTimerVal);
  1771. }
  1772. // @pause=10 will do nothing for 10 seconds
  1773. DoPause() {
  1774. if (RAMPause < 0.1)
  1775. RAMPause = 0.1;
  1776. DEBUG("@pause=" + (string)RAMPause);
  1777. TimerEvent(RAMPause);
  1778. }
  1779. // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
  1780. DoStop() {
  1781. DEBUG("NPC is Stopped");
  1782. Stopped = TRUE; // Link controlled - we mnust have a @go to continue with notecards
  1783. TimerEvent(0);
  1784. Stack = []; // v3.8
  1785. }
  1786. // @delete removes the NPC forever. Next command starts it up again at the beginning
  1787. DoDelete() {
  1788. DEBUG("state delete");
  1789. osNpcRemove(NPCKey());
  1790. SaveKey(NULL_KEY);
  1791. Stopped = TRUE; // Link controlled - we mnust have a @go to continue with notecards
  1792. TimerEvent(0);
  1793. Stack = []; // v3.8
  1794. }
  1795. // change the appearance of the NPC
  1796. DoAppearance(string notecard) {
  1797. DEBUG("state appearance");
  1798. if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){
  1799. DEBUG("Load appearance " + notecard);
  1800. osNpcLoadAppearance(NPCKey(),notecard);
  1801. }
  1802. }
  1803. // Change the avatar speed
  1804. DoSpeed(string speed) {
  1805. float newspeed = (float) speed;
  1806. if (newspeed > 0.1 && newspeed < 5.0) // sanity check
  1807. osSetSpeed(NPCKey(),newspeed);
  1808. }
  1809. DoNewNote (string card) {
  1810. DEBUG("Load Notecard " + card);
  1811. NPCReadNoteCard(card);
  1812. Stopped = FALSE;
  1813. }
  1814. DoAttach(string params) {
  1815. list Data = llParseString2List(params, ["|"], []);
  1816. string itemName = llList2String(Data, 0);
  1817. integer attachmentPoint = (integer) llList2String(Data, 1);
  1818. if (attachmentPoint > 0
  1819. && attachmentPoint < 40
  1820. && llGetInventoryType(itemName) == INVENTORY_OBJECT
  1821. )
  1822. {
  1823. osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint);
  1824. }
  1825. }
  1826. // This loops over the notecard, processing each command
  1827. DoProcessNPCLine() {
  1828. DEBUG("ProcessNPCLine");
  1829. STATE = 0;
  1830. // auto load a notecard
  1831. if (! llGetListLength(lNPCScript)) {
  1832. DEBUG("Read Notecard");
  1833. NPCReadNoteCard(Notecard);
  1834. Stopped = FALSE;
  1835. }
  1836. // look for link messages on the stack
  1837. string next = llList2String(Stack,0); // lets see if there is anithing from a link message
  1838. if (llStringLength(next))
  1839. {
  1840. Stack = llDeleteSubList(Stack,0,0);
  1841. ProcessCmd(next); //lets do this command instead.
  1842. return;
  1843. }
  1844. // @stop issued?
  1845. if (Stopped) {
  1846. TimerEvent(0);
  1847. DEBUG("Waiting for input");
  1848. return;
  1849. }
  1850. // No, we have an @go for liftoff
  1851. next = llList2String(lNPCScript, 0); // get the next command
  1852. DEBUG("Execute:" + next);
  1853. lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it
  1854. if (llGetListLength(lNPCScript) == 0) {
  1855. DEBUG("EOF");
  1856. }
  1857. ProcessCmd(next);
  1858. }
  1859. ProcessCmd(string cmd) {
  1860. DEBUG("ProcessCmd:" + cmd);
  1861. if (llGetSubString(cmd, 0, 0) != "@") {
  1862. DEBUG("ignoring");
  1863. STATE = DoProcess;
  1864. TimerEvent(QUICK); // this is so we do not recurse the stack
  1865. return;
  1866. }
  1867. list data = llParseString2List(cmd, ["="], []);
  1868. npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
  1869. DEBUG("Action:" + npcAction);
  1870. npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
  1871. @commands;
  1872. ProcessSensor();
  1873. if(npcAction == "@spawn") {
  1874. DEBUG("@spawn");
  1875. list spawnData = llParseString2List(npcParams, ["|"], []);
  1876. sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
  1877. list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []);
  1878. vInitialPos.x = llList2Float(spawnDest, 0);
  1879. vInitialPos.y = llList2Float(spawnDest, 1);
  1880. vInitialPos.z = llList2Float(spawnDest, 2);
  1881. DEBUG("Coords for NPC at " + (string) vInitialPos);
  1882. DoSpawn();
  1883. return;
  1884. }
  1885. if (! avatarPresent){
  1886. DoNobodyHome();
  1887. DEBUG("No avatar nearby");
  1888. return;
  1889. } else {
  1890. if ( NPCKey() == NULL_KEY) {
  1891. DoSpawn();
  1892. }
  1893. }
  1894. if(npcAction == "@stop") {
  1895. DoStop();
  1896. return;
  1897. }
  1898. else if(npcAction == "@goto") {
  1899. DEBUG("goto");
  1900. integer lastIdx = llGetListLength(lNPCScript)-1;
  1901. lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx);
  1902. // Wind commands till goto label.
  1903. @wind;
  1904. string next1 = llList2String(lNPCScript, 0);
  1905. lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
  1906. lNPCScript += next1;
  1907. if(next1 != npcParams) jump wind;
  1908. // Wind the label too.
  1909. next1 = llList2String(lNPCScript, 0);
  1910. lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
  1911. lNPCScript += next1;
  1912. // Get next command.
  1913. list data1 = llParseString2List(next1, ["="], []);
  1914. npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
  1915. npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
  1916. // Reschedule.
  1917. jump commands;
  1918. }
  1919. else if(npcAction == "@sound") {
  1920. DEBUG("sound");
  1921. llTriggerSound(npcParams,1.0);
  1922. }
  1923. else if(npcAction == "@randsound") {
  1924. DEBUG("@randsound");
  1925. integer N = llGetInventoryNumber(INVENTORY_SOUND);
  1926. integer rand = llCeil(llFrand(N)) -1; // pick a random sound
  1927. string toPlay = llGetInventoryName(INVENTORY_SOUND,rand);
  1928. llTriggerSound(toPlay,1.0);
  1929. }
  1930. else if(npcAction == "@walk") {
  1931. DEBUG("@walk");
  1932. GetDest(npcParams);
  1933. walkstate = 1;// walking
  1934. NPCWalkOption = OS_NPC_NO_FLY ;
  1935. DoWalk();
  1936. return;
  1937. }
  1938. else if(npcAction == "@fly") {
  1939. GetDest(npcParams);
  1940. walkstate = 2;// flying
  1941. NPCWalkOption = OS_NPC_FLY ;
  1942. DoWalk();
  1943. return;
  1944. }
  1945. else if(npcAction == "@run") {
  1946. DEBUG("@run");
  1947. GetDest(npcParams);
  1948. walkstate = 3;// running
  1949. NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
  1950. DoWalk();
  1951. return;
  1952. }
  1953. else if(npcAction == "@land") {
  1954. DEBUG("@land");
  1955. GetDest(npcParams);
  1956. walkstate = 4;// landing
  1957. NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
  1958. DoWalk();
  1959. return;
  1960. }
  1961. else if(npcAction == "@say") {
  1962. DEBUG("@say " + npcParams);
  1963. osNpcSay(NPCKey(), 0, npcParams);
  1964. }
  1965. else if(npcAction == "@shout") {
  1966. DEBUG("@shout");
  1967. osNpcShout(NPCKey(),0, npcParams);
  1968. }
  1969. else if(npcAction == "@whisper") {
  1970. DEBUG("@whisper " + npcParams);
  1971. osNpcWhisper(NPCKey(),0, npcParams);
  1972. }
  1973. // speak a command on a channel, so you can open doors and control stuff.
  1974. else if(npcAction == "@cmd") {
  1975. DEBUG("@cmd");
  1976. list dataToSpeak = llParseString2List(npcParams, ["|"], []);
  1977. string channel = llList2String(dataToSpeak,0);
  1978. DEBUG("Channel:"+(string) channel);
  1979. integer iChannel = (integer) channel;
  1980. string stringToSpeak = llList2String(dataToSpeak,1);
  1981. llSay(iChannel, stringToSpeak);
  1982. }
  1983. // stop everything
  1984. else if(npcAction == "@pause") {
  1985. RAMPause = (float) npcParams;
  1986. DoPause();
  1987. return;
  1988. }
  1989. else if(npcAction == "@wander") {
  1990. list wanderData = llParseString2List(npcParams, ["|"], []);
  1991. RAMwd = (float) llList2String(wanderData, 0);
  1992. RAMwc = (integer) llList2String(wanderData, 1);
  1993. vDestPos = osNpcGetPos(NPCKey()); // set the wander start
  1994. DEBUG("Starting at " + (string) vDestPos);
  1995. DoWander();
  1996. return;
  1997. }
  1998. else if(npcAction == "@rotate") {
  1999. RAMrot = (float) npcParams;
  2000. DoRotate();
  2001. }
  2002. else if(npcAction == "@sit") {
  2003. RAMsit= npcParams;
  2004. DoSit();
  2005. return;
  2006. }
  2007. else if(npcAction == "@touch") {
  2008. RAMtouch= npcParams;
  2009. DoTouch();
  2010. return;
  2011. }
  2012. else if(npcAction == "@stand") {
  2013. DoStand();
  2014. }
  2015. else if(npcAction == "@delete") {
  2016. DoDelete();
  2017. return;
  2018. }
  2019. else if(npcAction == "@animate") {
  2020. list animateData = llParseString2List(npcParams, ["|"], []);
  2021. RAManimationName = llList2String(animateData, 0);
  2022. RAManimationTime = (float) llList2String(animateData, 1);
  2023. DoAnimate();
  2024. return;
  2025. }
  2026. else if(npcAction == "@appearance" ){
  2027. DoAppearance(npcParams);
  2028. }
  2029. else if (npcAction =="@speed") {
  2030. DoSpeed(npcParams);
  2031. }
  2032. else if (npcAction =="@notecard") {
  2033. DoNewNote(npcParams);
  2034. Notecard = npcParams;
  2035. }
  2036. else if (npcAction == "@attach")
  2037. {
  2038. DoAttach(npcParams);
  2039. }
  2040. STATE = DoProcess;
  2041. TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack
  2042. }
  2043. /////////////////// UTILITY Functions, not state-like //////////////////
  2044. // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
  2045. DEBUG(string str) {
  2046. if (debug)
  2047. llOwnerSay( str); // Send the owner debug info so you can chase NPCS
  2048. if (iTitleText) {
  2049. llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
  2050. }
  2051. }
  2052. GetDest(string npcParams) {
  2053. list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
  2054. vDestPos.x = llList2Float(dest, 0);
  2055. vDestPos.y = llList2Float(dest, 1);
  2056. vDestPos.z = llList2Float(dest, 2);
  2057. }
  2058. NPCReadNoteCard(string Note) {
  2059. DEBUG("NPCReadNoteCard");
  2060. lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []);
  2061. }
  2062. integer SenseAvatar()
  2063. {
  2064. //Returns a strided list of the UUID, position, and name of each avatar in the region
  2065. list avatars = llGetAgentList(AGENT_LIST_REGION ,[]);
  2066. integer numOfAvatars = llGetListLength(avatars);
  2067. if (numOfAvatars == 0)
  2068. {
  2069. DEBUG("No people");
  2070. return 0;
  2071. }
  2072. //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's");
  2073. integer nAvatars;
  2074. integer i;
  2075. for( i = 0;i < numOfAvatars; i++) {
  2076. key aviKey = llList2Key(avatars,i);
  2077. if (!osIsNpc(aviKey)) {
  2078. list detail = llGetObjectDetails(aviKey,[OBJECT_POS]);
  2079. vector pos = llList2Vector(detail,0);
  2080. float dist = llVecDist(pos, llGetPos());
  2081. if (dist < RANGE)
  2082. {
  2083. nAvatars++;
  2084. DEBUG("In range:" + llKey2Name(aviKey));
  2085. }
  2086. }
  2087. }
  2088. //DEBUG("Located " + (string) nAvatars + " avatars");
  2089. return nAvatars;
  2090. }
  2091. // return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set.
  2092. integer checkPerms() {
  2093. integer group = (integer) KeyValueGet("pr");
  2094. if (! group)
  2095. priPub = "Owner Only";
  2096. else
  2097. priPub = "Group";
  2098. if (llDetectedKey(0) == llGetOwner()){
  2099. kUserKey = llDetectedKey(0);
  2100. return TRUE;
  2101. }
  2102. if ( group && llDetectedGroup(0)) {
  2103. kUserKey = llDetectedKey(0);
  2104. return TRUE;
  2105. }
  2106. kUserKey = llDetectedKey(0);
  2107. return FALSE;
  2108. }
  2109. NPCAnimate(string anim)
  2110. {
  2111. DEBUG("Start Anim: " + anim);
  2112. if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) {
  2113. if (lastANIM != anim) {
  2114. if(llStringLength(lastANIM)) {
  2115. osNpcStopAnimation(NPCKey(), lastANIM);
  2116. }
  2117. osNpcPlayAnimation(NPCKey(), anim);
  2118. lastANIM = anim;
  2119. }
  2120. } else {
  2121. llSay(DEBUG_CHANNEL, "No animation named " + anim);
  2122. }
  2123. }
  2124. TimerEvent(float timesent)
  2125. {
  2126. DEBUG("Setting timer: " + (string) timesent);
  2127. llSetTimerEvent(timesent);
  2128. }
  2129. // Kill a NPC by Name
  2130. Kill(string param)
  2131. {
  2132. integer count;
  2133. list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\
  2134. integer i;
  2135. integer j = llGetListLength(avatars);
  2136. for (i=0 ; i <= j; i+=3){
  2137. string desired = llList2String(avatars,i+2);
  2138. desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
  2139. if (desired == param){
  2140. vector v = llList2Vector(avatars,i+1);
  2141. key target = llList2Key(avatars,i); // get the UUID of the avatar
  2142. osNpcRemove(target);
  2143. SaveKey(NULL_KEY );
  2144. llOwnerSay("Removed " + param+ " at location " + (string) v);
  2145. count++;
  2146. }
  2147. }
  2148. NPCEnabled = FALSE; // not in world
  2149. if (count)
  2150. llOwnerSay("Removed " + (string) count + " NPC's");
  2151. else
  2152. llOwnerSay("Could not locate " + param);
  2153. }
  2154. // return a String for the position we are at. Strings used as the caller wants strings
  2155. string Pos()
  2156. {
  2157. vector where = llGetPos(); // find the box position
  2158. where.z += OffsetZ; // use the ground position + an offset
  2159. if (Editor)
  2160. where = <128,128,31 + llFrand(1)>; // center of sim for editing
  2161. // if attached the height will be too high by 1/2 the agent size
  2162. if (llGetAttached()) {
  2163. vector size = llGetAgentSize(llGetOwner());
  2164. float Z = size.z;
  2165. where.z -= Z/2;
  2166. }
  2167. // DEBUG("Pos= " + (string) where);
  2168. return (string) where;
  2169. }
  2170. // setup a menu with a timer for timeouts, called by all make*()
  2171. menu()
  2172. {
  2173. llListenRemove(iHandle);
  2174. iChannel = llCeil(llFrand(100000) + 20000);
  2175. iHandle = llListen(iChannel,"","","");
  2176. TimerEvent(30.0);
  2177. MENU = TRUE;
  2178. }
  2179. // make a text box
  2180. makeText(string Param)
  2181. {
  2182. menu();
  2183. llTextBox(kUserKey, Param, iChannel);
  2184. }
  2185. // top level menu
  2186. makeMainMenu()
  2187. {
  2188. menu();
  2189. list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"];
  2190. llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel);
  2191. }
  2192. // Rev 1.4
  2193. // top level menu for non group/ non owners
  2194. makeUserMenu()
  2195. {
  2196. if (!allowUsers) return;
  2197. menu();
  2198. list buttons = ["Start NPC","Stop NPC"];
  2199. llDialog(kUserKey,"Choose",buttons,iChannel);
  2200. }
  2201. // programmable menu for @commands
  2202. makeMenu(list buttons)
  2203. {
  2204. menu();
  2205. llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel);
  2206. }
  2207. // make one or two text boxes with prompts
  2208. Text(string cmd, string p1, string p2)
  2209. {
  2210. sCommand = cmd;
  2211. sParam2 = "";
  2212. if (llStringLength(p2))
  2213. sParam2 = p2;
  2214. makeText(p1);
  2215. }
  2216. // Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present.
  2217. ProcessSensor()
  2218. {
  2219. integer SensorOn;
  2220. if ("on" == KeyValueGet("se"))
  2221. {
  2222. SensorOn = TRUE; // we need to scan for avatars
  2223. } else {
  2224. SensorOn = FALSE; // we need to scan for avatars
  2225. }
  2226. DEBUG("Sensor:" + (string) SensorOn);
  2227. integer n = SenseAvatar();
  2228. DEBUG("Avatars:" + (string) n);
  2229. if (SensorOn && n)
  2230. avatarPresent = TRUE; // someone is here and we need to tell the system to run
  2231. else if (SensorOn && !n)
  2232. avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
  2233. else { // sensor is off, lete see if there is a NPC. If so, we are ON
  2234. DEBUG("NPCEnabled:" + (string) NPCEnabled);
  2235. if (NPCEnabled)
  2236. avatarPresent = TRUE;
  2237. else
  2238. avatarPresent = FALSE;
  2239. }
  2240. // start up from when when no one is near
  2241. if (avatarPresent && STATE == NobodyHome)
  2242. STATE = 0;
  2243. //DEBUG("Avatar Present: " + (string) avatarPresent);
  2244. }
  2245. vector CirclePoint(float radius) {
  2246. float x = llFrand(radius *2) - radius; // +/- radius, randomized
  2247. float y = llFrand(radius *2) - radius; // +/- radius, randomized
  2248. return <x, y, 0>; // so this should always happen
  2249. }
  2250. string KeyValueGet(string var) {
  2251. list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
  2252. do {
  2253. list data = llParseString2List(llList2String(dVars, 0), ["="], []);
  2254. string k = llList2String(data, 0);
  2255. if(k != var) jump continue;
  2256. //DEBUG("got " + var + " = " + llList2String(data, 1));
  2257. return llList2String(data, 1);
  2258. @continue;
  2259. dVars = llDeleteSubList(dVars, 0, 0);
  2260. } while(llGetListLength(dVars));
  2261. return "";
  2262. }
  2263. KeyValueSet(string var, string val) {
  2264. //DEBUG("set " + var + " = " + val);
  2265. list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
  2266. if(llGetListLength(dVars) == 0)
  2267. {
  2268. llSetObjectDesc(var + "=" + val);
  2269. return;
  2270. }
  2271. list result = [];
  2272. do {
  2273. list data = llParseString2List(llList2String(dVars, 0), ["="], []);
  2274. string k = llList2String(data, 0);
  2275. if(k == "") jump continue;
  2276. if(k == var && val == "") jump continue;
  2277. if(k == var) {
  2278. result += k + "=" + val;
  2279. val = "";
  2280. jump continue;
  2281. }
  2282. string v = llList2String(data, 1);
  2283. if(v == "") jump continue;
  2284. result += k + "=" + v;
  2285. @continue;
  2286. dVars = llDeleteSubList(dVars, 0, 0);
  2287. } while(llGetListLength(dVars));
  2288. if(val != "") result += var + "=" + val;
  2289. llSetObjectDesc(llDumpList2String(result, "&"));
  2290. }
  2291. // clear RAM
  2292. Clr() {
  2293. lCommands = [];
  2294. llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
  2295. makeMainMenu();
  2296. }
  2297. integer checkNoteCards()
  2298. {
  2299. // Check that they have saved an Appeaance and Path notecard
  2300. integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
  2301. integer i;
  2302. integer count;
  2303. for (; i < num; i++){
  2304. if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard)
  2305. count++;
  2306. if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance)
  2307. count++;
  2308. }
  2309. DEBUG("Checked " + (string) count + " Notecards");
  2310. // if we have both, run the NPC
  2311. return count;
  2312. }
  2313. Update(string SName) {
  2314. // delete all NPC*scripts except myself
  2315. integer i;
  2316. integer j = llGetInventoryNumber(INVENTORY_SCRIPT);
  2317. for (i = 0; i < j; i++) {
  2318. string name = llGetInventoryName(INVENTORY_SCRIPT,i);
  2319. string match = llGetSubString(name,0,2);
  2320. if (match == SName && llGetScriptName() != name)
  2321. {
  2322. llRemoveInventory(name);
  2323. llOwnerSay("Upgraded");
  2324. }
  2325. }
  2326. }
  2327. // Get all default saved params from the Description
  2328. GetSwitches()
  2329. {
  2330. string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
  2331. if (rA == "A")
  2332. relAbs = "Absolute";
  2333. else if (rA == "R")
  2334. relAbs = "Relative";
  2335. else
  2336. relAbs = "Absolute";
  2337. // reenable NPC if sensor is on.
  2338. if ("on" == KeyValueGet("se"))
  2339. {
  2340. NPCEnabled = TRUE;
  2341. mSensor = "Sense is On";
  2342. ProcessSensor(); // fake 1 avatar to get it rezzed
  2343. } else {
  2344. mSensor = "Sense is Off";
  2345. }
  2346. }
  2347. SaveKey(key akey)
  2348. {
  2349. DEBUG("Saving Key of " + (string) akey);
  2350. KeyValueSet("key", akey);
  2351. if (akey != (key) KeyValueGet("key") )
  2352. {
  2353. DEBUG("Fatal error, cannot save key");
  2354. }
  2355. gNpcKey = akey;
  2356. }
  2357. key NPCKey()
  2358. {
  2359. key akey = gNpcKey; // from cached copy
  2360. // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description
  2361. if (gNpcKey == NULL_KEY)
  2362. {
  2363. //DEBUG("Get DKey");
  2364. akey = KeyValueGet("key"); // from Description of the prim
  2365. }
  2366. // DEBUG("NPC KEY:" + (string) akey);
  2367. return akey;
  2368. }
  2369. /////////////////// CODE BEGINS //////////////////
  2370. default
  2371. {
  2372. changed(integer change) {
  2373. if(change & CHANGED_REGION_START) {
  2374. llResetScript();
  2375. }
  2376. }
  2377. on_rez(integer start_param)
  2378. {
  2379. llResetScript();
  2380. }
  2381. state_entry() {
  2382. llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
  2383. DoDelete(); // kill any NPC that is out running
  2384. Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it.
  2385. GetSwitches(); // Get all default saved params from the Description
  2386. llSetTimerEvent(TIMER);
  2387. }
  2388. touch_start(integer n)
  2389. { // if touched, make a menu
  2390. if (checkPerms()) {
  2391. if (RecordPath == STATE) {
  2392. makeMenu(lAtButtons);
  2393. } else {
  2394. makeMainMenu();
  2395. }
  2396. } else {
  2397. makeUserMenu();
  2398. }
  2399. }
  2400. // menu listener
  2401. listen(integer iChannel, string name, key id, string message) {
  2402. if (MENU) {
  2403. llListenRemove(iHandle);
  2404. MENU = 0; // menu is off
  2405. iHandle = 0;
  2406. }
  2407. if (message == "Stop NPC")
  2408. {
  2409. lNPCScript = []; // force reload of notecard
  2410. NPCEnabled = FALSE;
  2411. if (NPCKey() != NULL_KEY){
  2412. Kill(sNPCName);
  2413. sNPCName = "";
  2414. } else {
  2415. bNPC_STOP = TRUE;
  2416. makeText("Enter name of an NPC to stop");
  2417. }
  2418. }
  2419. else if (message == "Menu" ) {
  2420. makeMainMenu();
  2421. }
  2422. else if (message == "Erase RAM"){
  2423. Clr();
  2424. }
  2425. else if (message == "Relative"){
  2426. relAbs = "Absolute";
  2427. KeyValueSet("co","A"); // remember coordinates = A
  2428. Clr();
  2429. }
  2430. else if (message == "Absolute"){
  2431. relAbs = "Relative";
  2432. KeyValueSet("co","R"); // remember coordinates = R
  2433. Clr();
  2434. }
  2435. else if (message == "Recording"){
  2436. DoMenuForCommands(); // show them the recording menu
  2437. }
  2438. else if (message == "Owner Only") {
  2439. priPub = "Group";
  2440. KeyValueSet("pr","1");
  2441. llOwnerSay("Group members have control");
  2442. makeMainMenu();
  2443. }
  2444. else if (message == "Group") {
  2445. priPub = "Owner Only";
  2446. KeyValueSet("pr","0");
  2447. llOwnerSay("Only you have control");
  2448. makeMainMenu();
  2449. }
  2450. else if (message == "Sense is On") {
  2451. mSensor ="Sense is Off";
  2452. KeyValueSet("se", "off");
  2453. llOwnerSay(mSensor);
  2454. makeMainMenu();
  2455. }
  2456. else if (message == "Sense is Off") {
  2457. mSensor ="Sense is On";
  2458. llOwnerSay(mSensor);
  2459. KeyValueSet("se", "on");
  2460. NPCEnabled = FALSE;
  2461. integer count = checkNoteCards();
  2462. if (count >= 2) {
  2463. DEBUG("Notecards approved , calling DoProcessNPCLine");
  2464. DoProcessNPCLine();
  2465. return;
  2466. }
  2467. if (Editor) {
  2468. DoProcessNPCLine();
  2469. return;
  2470. }
  2471. llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
  2472. makeMainMenu();
  2473. }
  2474. else if (message == "Appearance") {
  2475. llRemoveInventory(Appearance); // delete the notecard
  2476. osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard
  2477. llOwnerSay("Your outfit has been saved");
  2478. makeMainMenu();
  2479. }
  2480. else if (message == "Save") {
  2481. if (llGetListLength(lCommands) == 0) {
  2482. llOwnerSay("Nothing recorded, you need to make a recording first");
  2483. makeMainMenu();
  2484. return;
  2485. }
  2486. DoSave();
  2487. }
  2488. else if (message == "Help"){
  2489. llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/");
  2490. makeMainMenu();
  2491. }
  2492. else if (message == "Start NPC") {
  2493. integer count = checkNoteCards();
  2494. Stopped = FALSE; // Let's run the notecard
  2495. NPCEnabled = TRUE;
  2496. if (Editor) {
  2497. DoProcessNPCLine();
  2498. return;
  2499. }
  2500. if (count >= 2) {
  2501. DEBUG("Notecards approved , calling DoProcessNPCLine");
  2502. Stopped = FALSE; // Let's run the notecard
  2503. DoProcessNPCLine();
  2504. return;
  2505. }
  2506. llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC");
  2507. }
  2508. else if (bNPC_STOP){
  2509. bNPC_STOP = FALSE;
  2510. Kill(message);
  2511. }
  2512. else if (message == ">>"){
  2513. makeMenu(lMenu2);
  2514. }
  2515. else if (message == ">>>"){
  2516. makeMenu(lMenu3);
  2517. }
  2518. else if (message == "<<") {
  2519. makeMenu(lAtButtons);
  2520. }
  2521. else if (message == "<<<") {
  2522. makeMenu(lMenu2);
  2523. }
  2524. else if (message == "@comment"){
  2525. Text("# ","Enter a comment","");
  2526. }
  2527. else if (message == "@stop"){
  2528. lCommands += "@stop"+ "\n";
  2529. makeMenu(lAtButtons);
  2530. }
  2531. else if (message == "@run"){
  2532. lCommands += "@run=" + Pos() + "\n";
  2533. llOwnerSay("Recorded position: " + Pos());
  2534. makeMenu(lAtButtons);
  2535. }
  2536. else if (message == "@fly"){
  2537. lCommands += "@fly=" + Pos() + "\n";
  2538. llOwnerSay("Recorded position: " + Pos());
  2539. makeMenu(lAtButtons);
  2540. }
  2541. else if (message == "@land"){
  2542. lCommands += "@land=" + Pos() + "\n";
  2543. llOwnerSay("Recorded position: " + Pos());
  2544. makeMenu(lAtButtons);
  2545. }
  2546. else if (message == "@walk") {
  2547. lCommands += "@walk=" + Pos() + "\n";
  2548. llOwnerSay("Recorded position: " + Pos());
  2549. makeMenu(lAtButtons);
  2550. }
  2551. else if (message == "@stop"){
  2552. lCommands += "@stop"+ "\n";
  2553. makeMenu(lAtButtons);
  2554. }
  2555. else if (message == "@sound"){
  2556. Text("@sound=","Enter a sound name or UUID to trigger","");
  2557. }
  2558. else if (message == "@randsound"){
  2559. lCommands += "@randsound"+ "\n";
  2560. makeMenu(lAtButtons);
  2561. }
  2562. else if (message == "@say") {
  2563. Text("@say=","Enter what the NPC will say","");
  2564. }
  2565. else if (message == "@whisper"){
  2566. Text("@whisper=","Enter what the NPC will whisper","");
  2567. }
  2568. else if (message == "@shout"){
  2569. Text("@shout=","Enter what the NPC will shout","");
  2570. }
  2571. else if (message == "@wander") {
  2572. Text("@wander=","Enter radius to wander","Enter number of wanders");
  2573. }
  2574. else if (message == "@pause") {
  2575. Text("@pause=","Enter time to pause","");
  2576. }
  2577. else if (message == "@rotate") {
  2578. Text("@rotate=","Enter degrees to rotate","");
  2579. }
  2580. else if (message == "@sit"){
  2581. Text("@sit=","Enter name of object to sit on","");
  2582. }
  2583. else if (message == "@touch"){
  2584. Text("@touch=","Enter name of object to touch","");
  2585. }
  2586. else if (message == "@cmd"){
  2587. Text("@cmd=","Enter cjhannel to speak on","Enter text to speak");
  2588. }
  2589. else if (message == "@stand"){
  2590. lCommands += "@stand\n";
  2591. llOwnerSay("Stand Recorded");
  2592. makeMenu(lAtButtons);
  2593. }
  2594. else if (message == "@animate"){
  2595. Text("@animate=","Enter animation name to play","Enter time to play the animation");
  2596. }
  2597. else if (message == "@attach"){
  2598. Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)");
  2599. }
  2600. else if (message == "@speed"){
  2601. Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed","");
  2602. }
  2603. // Save NPC name
  2604. else if (MakeNotecard == STATE) {
  2605. sNPCName = message; // in case we need to kill it.
  2606. vector vDest = (vector) Pos();
  2607. if (relAbs == "Relative")
  2608. {
  2609. vDest -= llGetPos(); // just an offset for relative
  2610. }
  2611. sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
  2612. integer i;
  2613. integer j = llGetListLength(lCommands);
  2614. for (; i < j; i++){
  2615. // get the command to save to the notecard
  2616. string line = llList2String(lCommands,i);
  2617. if (relAbs == "Absolute") {
  2618. sNotecard += line; // add the un-modified string to the notecard
  2619. } else {
  2620. // since we have to record absolute coords since we do not know where the box goes until they press Save,
  2621. // we process the absolute to relative conversion for walks here
  2622. list parts = llParseString2List(line,["="],[]); //get the @command
  2623. if (llList2String(parts,0) == "@walk") {
  2624. vector vec = (vector) llList2String(parts,1) - llGetPos();
  2625. sNotecard += "@walk=" + (string) vec + "\n";
  2626. }
  2627. else if (llList2String(parts,0) == "@fly") {
  2628. vector vec = (vector) llList2String(parts,1) - llGetPos();
  2629. sNotecard += "@fly=" + (string) vec + "\n";
  2630. }
  2631. else if (llList2String(parts,0) == "@run") {
  2632. vector vec = (vector) llList2String(parts,1) - llGetPos();
  2633. sNotecard += "@run=" + (string) vec + "\n";
  2634. }
  2635. else if (llList2String(parts,0) == "@land") {
  2636. vector vec = (vector) llList2String(parts,1) - llGetPos();
  2637. sNotecard += "@land=" + (string) vec + "\n";
  2638. }
  2639. else {
  2640. sNotecard += line; // add the un-modified string to the notecard
  2641. }
  2642. }
  2643. }
  2644. llRemoveInventory(Notecard); // delete the old notecard
  2645. osMakeNotecard(Notecard,sNotecard); // Makes the notecard.
  2646. llSay(0,sNotecard);
  2647. llOwnerSay("Commands notecard has been written");
  2648. STATE = 0;
  2649. } // MakeNotecard
  2650. else if (! llStringLength(sParam2)) {
  2651. lCommands += sCommand + message + "\n";
  2652. llOwnerSay("Recorded");
  2653. makeMenu(lAtButtons);
  2654. }
  2655. else if (llStringLength(sParam2)){
  2656. sCommand = sCommand + message + "|";
  2657. llOwnerSay("Recorded");
  2658. makeText(sParam2);
  2659. sParam2 = "";
  2660. }
  2661. }
  2662. timer(){
  2663. // DEBUG("tick");
  2664. // if llDialog is up, kill the listener for the dialog box.
  2665. if (iHandle) {
  2666. llOwnerSay("Menu timed out");
  2667. llListenRemove(iHandle);
  2668. iHandle = 0;
  2669. return; // ^^^^^^^^^^^^^^^^^^^^^^^
  2670. }
  2671. // if NoBodyHome, we are sensing for an avatar
  2672. else if (NobodyHome == STATE) {
  2673. ProcessSensor();
  2674. return;
  2675. }
  2676. // if we are spawning, we need time to rez the NPC, then start processing NPC Commands.
  2677. else if (Spawning == STATE) {
  2678. STATE = 0;
  2679. TimerEvent(TIMER);
  2680. }
  2681. // We end aniamtions with a timer
  2682. else if (Animate == STATE){
  2683. NPCAnimate(STAND);
  2684. TimerEvent(TIMER);
  2685. }
  2686. else if (Walking == STATE) {
  2687. if (--iWaitCounter) {
  2688. if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) {
  2689. return;
  2690. }
  2691. }
  2692. DEBUG("At Destination: " + (string) newDest);
  2693. // walk, fly, run, land
  2694. if (walkstate == 1) {
  2695. NPCAnimate(STAND);
  2696. NPCWalkOption = OS_NPC_NO_FLY;
  2697. } else if (walkstate == 2) {
  2698. // nothing
  2699. } else if (walkstate == 3) {
  2700. NPCAnimate(STAND);
  2701. NPCWalkOption = OS_NPC_NO_FLY;
  2702. } else if (walkstate == 4) {
  2703. llShout(FLIGHT,"landing");
  2704. NPCAnimate(STAND);
  2705. NPCWalkOption = OS_NPC_NO_FLY;
  2706. }
  2707. }
  2708. // Wandering timer
  2709. else if (Wander == STATE) {
  2710. if (--iWaitCounter) { // wait 60 seconds to get to a destination.
  2711. if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST)
  2712. return;
  2713. }
  2714. // see if wander counter == 0, if so, stop walking, go to stand and process next line
  2715. if(RAMwc == 0) {
  2716. NPCAnimate(STAND);
  2717. DEBUG("Wander ended, calling DoProcessNPCLine");
  2718. STATE = 0;
  2719. DoProcessNPCLine();
  2720. return;
  2721. }
  2722. // one less time to wander around
  2723. RAMwc--;
  2724. NPCAnimate(STAND);
  2725. TimerEvent(TIMER);
  2726. DoWanderhold();
  2727. return;
  2728. }
  2729. // Wandering requires us to re-wander when we reach a destination
  2730. else if (WanderHold == STATE) {
  2731. DoWander();
  2732. TimerEvent(TIMER);
  2733. return;
  2734. }
  2735. else if (DoProcess == STATE) {
  2736. TimerEvent(QUICK);
  2737. }
  2738. STATE = 0;
  2739. // We always process a NPC line at end of timer.
  2740. DEBUG("Tick end, calling DoProcessNPCLine");
  2741. DoProcessNPCLine();
  2742. }
  2743. // sensors are used for sitting on prims
  2744. // Neo Cortex: added different SensorFunc states to trigger sit or touch
  2745. sensor(integer num) {
  2746. if (SensorFunc == 1) {
  2747. osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW);
  2748. DEBUG("Seated, calling DoProcessNPCLine");
  2749. SensorFunc = 0;
  2750. } else if (SensorFunc == 2) {
  2751. osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS);
  2752. DEBUG("Touched, calling DoProcessNPCLine");
  2753. SensorFunc = 0;
  2754. }
  2755. DoProcessNPCLine();
  2756. }
  2757. no_sensor(){
  2758. DEBUG ("no target prim located, calling DoProcessNPCLine");
  2759. SensorFunc = 0;
  2760. DoProcessNPCLine();
  2761. }
  2762. link_message(integer sender, integer num, string str, key id){
  2763. DEBUG("Command In:" + str);
  2764. if (str=="@go") {
  2765. Stopped = FALSE; // Let's run the notecard
  2766. DEBUG("@go approved, calling DoProcessNPCLine");
  2767. DoProcessNPCLine();
  2768. } else {
  2769. Stack += [str]; // take anything, the controller will filter away non @ stuff
  2770. if (llGetListLength(Stack) == 1) { // 3.8
  2771. DEBUG("Stack=1");
  2772. DoProcessNPCLine();
  2773. }
  2774. }
  2775. }
  2776. }