Your second life is how you script it
Moore Thome Haigh
Scripting is the spice of Second Life, and this official guide to the Linden Scripting Language (LSL) is the complete scripting resource for making your Second Life creations come to life, interacting with players, who cover every single aspect of LSL, from the basics of avatar movement and communication, to physics and special effects, to interacting with the world outside of Second Life. This thorough reference and tutorial features detailed walk-throughs and step-by-steps that teach the “why” of scripting while explaining core LSL conventions. It includes more than 50 exclusive, brand-new custom scripts that you can use and modify right now. Make doors open automatically, put a message in a bottle and watch it float out to sea, create a tip jar, and start a storm complete with loud thunderclaps and flashing lightning. • Understand the Linden Scripting Language: Master every aspect of Second Life’s programming language • Automate with scripts: Greet visitors by name, teleport around the world, and dance with a partner • Learn physics and vehicles: Create a jet pack, a magic carpet, and a machine gun • Use environmental controls: Create moving shadows, make water splash, and have bees flit from flower to flower • Integrate multimedia: Dazzle residents with streaming TV and ambient soundscapes • Incorporate the outside world: Build a live news ticker and send e-mail
Learn LSL so you can make your world move and grow
Create stunning special effects, including rainbows and fireworks
Choreograph everything from dancing to swordplay
Companion Website and Resource Center developers at http://syw.fabulo.us. Further resources can be found in-world at Scripting Your World Headquarters, Hennepin .
About the Authors
scripting your world ®
the official guide to second life scripting
®
Find additional in-depth projects, scripts, and code, as well as forums, book updates, and a community of LSL
the official guide to scripting your world second life scripting
creating dazzling special effects, and adding realism to the virtual world. The authors are an expert team of programmers
Dana Moore is ElectricSheep Expedition in Second Life, where he runs a shop, creates clothes, and writes tons of scripts. Michael Thome (Vex Streeter) is a scripter-for-hire who develops everything from games to virtual toys. Dr. Karen Haigh (Karzita Zabaleta) enjoys writing scripts that push the boundaries of what is possible in SL. The authors have a combined 50 years of experience as enterprise software developers and are computer scientists for Internet pioneer BBN Technologies.
ISBN: 978-0-470-33983-1
$39.99 US $43.99 CAN
Category COMPUTERS/Internet/General
By Dana Moore, Michael Thome, and Dr. Karen Zita Haigh Foreword by Joe Miller, Linden Lab Vice President of Platform and Technology Development
00i-0xv Front Matter.indd 1
8/7/08 11:09:42 PM
Scripting Your World The Official Guide to Second Life ® Scripting Dana Moore Michael Thome Dr. Karen Zita Haigh
Wiley Publishing, Inc.
00i-0xv Front Matter.indd 1
8/7/08 11:09:42 PM
Senior Acquisitions Editor: Willem Knibbe Development Editor: Candace English Technical Editor: Richard Platel Production Editor: Patrick Cunningham Copy Editor: Candace English Production Manager: Tim Tate Vice President and Executive Group Publisher: Richard Swadley Vice President and Executive Publisher: Joseph B. Wikert Vice President and Publisher: Neil Edde Book Designer and Compositor: Patrick Cunningham Proofreader and Indexer: Asha Johnson Project Coordinator, Cover: Lynsey Stanford Cover Designer: Ryan Sneed Cover Image: Michael Thome Copyright © 2008 by Linden Research, Inc. Published by Wiley Publishing, Inc., Indianapolis, Indiana Published simultaneously in Canada ISBN: 978-0-470-33983-1 No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permitted under Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permission of the Publisher, or authorization through payment of the appropriate per-copy fee to the Copyright Clearance Center, 222 Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 646-8600. Requests to the Publisher for permission should be addressed to the Legal Department, Wiley Publishing, Inc., 10475 Crosspoint Blvd., Indianapolis, IN 46256, (317) 572-3447, fax (317) 572-4355, or online at http://www.wiley.com/go/permissions. Limit of Liability/Disclaimer of Warranty: The publisher and the author make no representations or warranties with respect to the accuracy or completeness of the contents of this work and specifically disclaim all warranties, including without limitation warranties of fitness for a particular purpose. No warranty may be created or extended by sales or promotional materials. The advice and strategies contained herein may not be suitable for every situation. This work is sold with the understanding that the publisher is not engaged in rendering legal, accounting, or other professional services. If professional assistance is required, the services of a competent professional person should be sought. Neither the publisher nor the author shall be liable for damages arising herefrom. The fact that an organization or Web site is referred to in this work as a citation and/or a potential source of further information does not mean that the author or the publisher endorses the information the organization or Web site may provide or recommendations it may make. Further, readers should be aware that Internet Web sites listed in this work may have changed or disappeared between when this work was written and when it is read. For general information on our other products and services or to obtain technical support, please contact our Customer Care Department within the U.S. at (800) 762-2974, outside the U.S. at (317) 572-3993 or fax (317) 572-4002. Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may not be available in electronic books. Library of Congress Cataloging-in-Publication Data Moore, Dana, 1947Scripting your world: the official guide to second life scripting / Dana Moore, Michael Thome, Karen Haigh. -- 1st ed. p. cm. ISBN 978-0-470-33983-1 (paper/website) 1. Entertainment computing. 2. Scripting languages (Computer science) 3. Second Life (Game) I. Thome, Michael, 1965- II. Haigh, Karen, 1970- III. Title. QA76.9.E57M66 2008 790.20285—dc22 2008027400
TRADEMARKS: Wiley, the Wiley logo, and the Sybex logo are trademarks or registered trademarks of John Wiley & Sons, Inc. and/or its affiliates, in the United States and other countries, and may not be used without written permission. Second Life and Linden Lab are registered trademarks of Linden Research, Inc. All other trademarks are the property of their respective owners. Wiley Publishing, Inc., is not associated with any product or vendor mentioned in this book. 10 9 8 7 6 5 4 3 2 1
00i-0xv Front Matter.indd 2
8/7/08 11:09:42 PM
CHAPTER 1
Foreword
CHAPTER 2
In 2008 the virtual world of Second Life celebrated five years of existence. During that time, we have been participants in the creation of something truly extraordinary—a completely new realm in the Internet, a cyberspace unlike any other. Linden Lab and the residents of Second Life are the creative forces at work bringing this new metaverse, this three-dimensional world we can visit together via the Web, to life. We think of Second Life as a place, although paradoxically, aside from a large set of servers and code, this new place has no physical existence outside the consensual agreement amongst a million human minds that here we dwell together in the three-dimensional Internet, the virtual world that is Second Life. For the hundreds of thousands who visit here occasionally and for those who reside and do business here daily, this place of the mind exerts a pull as real as anywhere in the physical world. For the avant-garde thinkers, creators, and entrepreneurs who live at least a portion of their lives here, making tools, artifacts, environments, experiences, and even earning a living, this is our world. Consisting of a series of sophisticated content-creation, land-management, transactional and scripting tools, the Second Life Grid is the technology platform used to power Second Life. The Linden Scripting Language (LSL), embedded in all objects created in-world, enables object interactivity. LSL is a compact programming language made for virtual-world creation. Programmers and nonprogrammers alike are capable of creating in Second Life; LSL is easy to learn and well-supported by in-world and real-world tools. With scripting, any element in Second Life can move, react, sense, change appearance and state. Without it, even the most detailed objects are akin to museum sculptures—inherently static.
CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDICES
In this Official Guide, thoroughly explained examples will prepare you to awaken objects' potential through scripting. Three seasoned software professionals coach the reader through their approach to understanding a new computer language for virtual creation in Second Life and on the Web. As the new 3D world of Second Life begins to replace familiar software like the browser, the Java VM, or Windows, a new generation of software developers will inevitably emerge. This book is written with them in mind as well as the common Second Life resident, for the future extends beyond technical specifications and interface standards—it affects virtual objects, characters, and their interactions. We at Linden Lab believe this book serves as an educational travel companion for exploration of the innovative, virtual world.
Joe Miller, Vice President of Technology and Platform Development
iii
00i-0xv Front Matter.indd 3
8/7/08 11:09:42 PM
D edication
Dedication
A cknow ledgments
To all the amazingly creative people of Second Life, making and continuously remaking the world.
A bout the Authors Introduction Contents
Acknowledgments We could not have written this book without the assistance and support of many people, both in the real world and in Second Life. First we need to thank our families for picking up the slack while we were immersed in writing: Kelly and Alicia Thome; Robert, Sonia, and Rachael Driskill; Jane and Caitlin Moore. We would like to thank our reviewers, Aaron Helsinger, Robert Driskill, Trouble Streeter, and Roisin Hotaling for their time and patience. Also, many thanks to the scripting experts on the SL forums, scripting groups, mailing lists, and wikis—you might not know that you helped, but your conversations and documentation were invaluable in gaining the breadth of knowledge we needed to produce this book. Specific callouts to Strife Onizuka, Timeless Prototype, Delta Czukor, Adam Marker, Talin Sands, and Morrigan Mathilde. Finally, a vigorous nod to our SL friends and cohorts, who have not only been interested and supportive during the writing process, but together have been a source of creativity, enthusiastic instigators for scripting projects, and tolerant models for screenshots. We would like to specifically thank Anu, Siv, Slade, Shel, Harper, Dillon, Amee, Jac, Midlou, Robert, and the Wellstone regulars. ElectricSheep would like to thank Morrigan for her friendship and conversations during the book-writing process. Of course the book wouldn't be possible at all without the Sybex team. We would especially like to thank Willem Knibbe, our acquisitions editor, for investing the faith in this project, and of course our gratitude to the editorial and production staff for helping to produce a high quality work: Candace English, Patrick Cunningham, Pete Gaughan, and Richard Platel. And no book on Second Life can fail to acknowledge the wizards behind Linden Lab, especially Philip Rosedale (Philip Linden) and Cory Ondrejka (Cory Linden).
iv
00i-0xv Front Matter.indd 4
8/7/08 11:09:42 PM
About the Authors Michael Thome (Vex Streeter) has been a computer scientist with BBN Technologies for over 20 years, and currently specializes in scalable computing technologies including parallel, distributed, and agent-based computing. Michael has degrees in cognitive science from the University of Rochester and Boston University, with specialties in computational models of human neurological systems. His interest in gaming started while he was in high school, building cellular automata systems to create terrains for the PSL Empire computer game, writing video games, and collaborating on one of the very first open source computer games, LSRHS Hack (the antecedent to the now-venerable NetHack). His first Second Life avatar was rezzed in late 2006, but he's been a true active Second Life resident as Vex since only mid 2007. He currently lives in the Boston area with his real-life family, and also on a small desert island in Camembert.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
Dr. Karen Zita Haigh's (Karzita Zabaleta) research is in machine learning for physical systems—she builds brains for robots. Karen has a Ph.D. in computer science from Carnegie Mellon University. She has worked in a variety of domains, including networking, cyber security, oil refineries, jet engines, and—far and away the most fun—the homes of elders. She was one of the analysts who looked at Space Shuttle Colombia's data after the explosion. Computer gaming in general never particularly interested her, but Second Life was different; it had just the right mix of elements—the ability to create content, stretch the limits of reality, and have fun without shooting everything she encountered. She was born in Kenya, lived in six countries, and holds citizenships in Canada, Great Britain, and the USA. Karen speaks fluent French and Mandarin Chinese. She lives in Minnesota with her husband and two daughters.
CHAPTER 15 APPENDICES
Dana Moore is a division scientist with BBN Technologies and is an acknowledged expert in the fields of peer-to-peer and collaborative computing, software agent frameworks, and sensor-imbued environments. Prior to joining BBN, Dana was Chief Scientist for Roku Technologies, and a Distinguished Member of Technical Staff at Bell Laboratories. Dana is a popular conference speaker and a university lecturer. He has written articles for numerous computing publications and published several books on topics ranging from peer-to-peer computing to rich Internet applications. Although not a gamer, Dana finds enormous value in the potential transformative value of Second Life. Dana holds a Master of Science degree in engineering and a Bachelor of Science degree in industrial design from the University of Maryland. He lives in the Annapolis, Maryland, area with his family and also in a Tudor village in Second Life.
v
00i-0xv Front Matter.indd 5
8/7/08 11:09:45 PM
D edication
Introduction
A cknow ledgments
A bout the Authors Introduction Contents
Learning the skills needed to activate and enliven Second Life has multiple benefits: You will be one of the rare folk able to lift the veil of mystery behind how things work in Second Life. And you may be able to turn your talents and newly gained prowess into a revenue stream. Additionally, you will gain the joy of creating things that other Second Life residents will use and enjoy, and may well marvel at. But there's a far greater benefit to becoming a Second Life scripter par excellence. Years ago, most software-development challenges involved translating real-world actions, events, and objects into command-line arguments. Your creativity was severely tested due to the constraints and limits on both the computer and the user. Frankly, early interactive command-line applications were far from cool—in fact, they were exactingly tedious. This evolved into a long "Middle Ages" filled with desktop applications expressed in menus and windows—still a very frustrating experience from so many perspectives. Skip ahead a few generations, and suddenly Second Life is on every pundit's radar. The overwhelming conclusion: Second Life begins to realize the promise of "cyberspace," a virtual place peopled with real humans interacting with virtual objects. Whether you are a hard-core software developer or simply an enthusiastic Second Life resident, this new 3D world gives you an opportunity to create things that operate like their real-world counterparts, or even more intriguingly, things that operate in ways that they never could in plain old reality. Scripting for Second Life affords you a whole new set of opportunities, bringing with it a whole new set of challenges and possibilities. Second Life brings us new 3D development tools and, more importantly, a new 3D development perspective. The 3D development tools are both familiar (such as language constructs and events) and strange (such as coding the many possible interactions with an expressive, dynamic 3D object that responds to real-world inputs such as touch, gravity, proximity to other objects and avatars, and conversation). Second Life development perspectives are strangely familiar—all of us live our "first life" in a 3D world but until recently we painfully translated our surroundings to a flat, 2D computing world; think menus and windows. We humans are most comfortable living in 3D, and thanks in part the way that humans interact with computers and through computers to Second Life, the future of computers and the Web will be an experience of sights, sounds, and sensation that are inherently 3D! The emerging 3D Web will offer the benefits of useful, natural metaphors to interact, exchange information, run businesses, and the like. Second Life—as the premiere 3D immersive web of people, places, and things—offers vast new opportunities. But it also creates development challenges that will demand a whole new set of skills and perspectives. This book gives you the insights, tools, and skills to take full advantage of this new 3D computing world by enabling you to build in Second Life. We provide useful, working examples that you can implement and see in action in our 3D world.
Who Should Buy This Book Most people, on becoming Second Life residents, spend their first few hours and days (once they've figured out how to maneuver and get to a sim of interest) socializing, dancing, buying sporty outfits, exploring alternate aspects of their personality, and doing sundry things analogous to their real-life activities. That's their primary filter for understanding what Second Life is and what it offers. Their first reaction is often, "Wow, look at all the neat things I can participate in and do. I wonder how many ways I can have fun?"
vi
As their comfort and experience level grows, many people begin to think, "Wow, look at all the nifty gadgets. I wonder how they work?" Initially residents look at poofers and dance pads, jukeboxes and media players, windmills and waterfalls, admiring their functionality or their beauty; later these same people look
00i-0xv Front Matter.indd 6
8/7/08 11:09:45 PM
at the same artifacts and wonder how they were made and whether they could create a better version. In fact, in many cases they are convinced they could create a better version. If this description captures your experience even a little, congratulations! You've come to the right place. You are our audience. As we wrote, we assumed the following about our readers: • You are familiar with Second Life concepts. You don't need to be an expert, but you'll get lost pretty quickly if you haven't left Orientation Island. • You understand basic SL building. Some of the scripting in this book requires some careful assembly of primitive objects, though more-complex examples are included in some of the other books in this series. • You have played with scripts enough to know that you want to know more: you know how much scripting can add to the content you are creating. We don't assume you've attended a Scripting 101 class or read any scripting tutorials, but you'll have an easier time absorbing the book's ideas if you have. You do not need to be a programmer, a mathematician, or a computer scientist—some parts of this book will be slow going if you haven't had any prior experience, but don't worry! Nothing here is rocket science, and it is easy to skip over the parts that you aren't interested in or that seem a little too difficult. You can always come back to them later. One of the great things about scripting in Second Life is that it is extremely easy to play with even advanced concepts and quickly grasp how things work: you won't hurt anything if you get it wrong the first or even the 47th time, but you'll learn a huge amount as you experiment.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDICES
Above all, enjoy the process!
What's Inside Here is a glance at each chapter's offerings: Chapter 1: Getting a Feel for the Linden Scripting Language begins the book by describing LSL and the basic concepts of Second Life scripting. It may be used both as an in-depth introduction for novices and as reference material for more-advanced scripters. We recommend that you at least skim Chapter 1 before diving into the chapters that interest you most. Chapter 2: Making Your Avatar Stand Up and Stand Out includes some of the most basic and common scripted objects in Second Life, with particular attention to scripting used to enhance avatars' appearance and behavior. Chapter 3: Communications describes a variety of ways that scripts can communicate and interact with avatars and with other scripts. Chapter 4: Making and Moving Objects covers various ways to create new objects and manipulate existing objects under script control. Chapter 5: Sensing the World includes a variety of examples and projects that focus on using scripts to react automatically to their surroundings, including objects, avatars, and the environment. Chapter 6: Land Design and Management illustrates how to manipulate the basic structure of the Second Life landscape, how to enable land security, and how to learn more about the land around you. Chapter 7: Physics and Vehicles covers the Second Life simulation of physics, and the details of how to build basic vehicles. It includes a brief description of how to make flexiprims interact with physics. Chapter 8: Inventory discusses how scripted objects can manipulate and manage their own inventory of objects, giving them to and accepting them from avatars and other objects.
00i-0xv Front Matter.indd 7
vii
8/7/08 11:09:45 PM
D edication A cknow ledgments
A bout the Authors Introduction Contents
Chapter 9: Special Effects uses the particle system, texture animation, and lighting to generate fireworks, fire, and lighting effects. Chapter 10: Scripting the Environment describes scripting solutions to react to the Second Live environmental simulation, including wind, water, and time. Chapter 11: Multimedia brings music, audio, video, and web content into the mix, describing how to present multimedia to SL users. Chapter 12: Reaching Outside Second Life introduces communications between SL scripts and the outside world. Chapter 13: Money Makes the World Go Round describes how to make your scripts deal with money, including building tip jars and vendor devices. Chapter 14: Dealing with Problems discusses the sorts of scripting problems you are likely to see and their causes, and provides hints on how to fix them. It also discusses where and how to get and give help. Chapter 15: New and Improved describes some newly implemented features and issues that scripters will be interested in, including the new Mono virtual machine. Appendix A: Setting Primitive Parameters is a reference for the complexities of the llPrimitiveParams() family of functions. It describes the parameters themselves and what they do. Appendix B: The Particle System details all of the options of the LSL function llParticleSystem(). Appendix C: SL Community Standards lists the standards of behavior expected of Second Life residents. Once you understand a given chapter's content, you should be well-equipped to attack any similar scripts. You can always go back to Chapter 1 to review, and the appendices are organized to be complete references. We've written a whole lot more content than could be squeezed into the book. There are several bonus chapters, additional examples, and LSL resources that you can download from either the book's companion website (http://syw.fabulo.us/) or the publisher's website for the book at http:// www.sybex.com/WileyCDA/SybexTitle/productCd-0470339837.html. The authors also maintain a headquarters inside Second Life at Hennepin ; you can also find it by searching for SYWHQ, where you can see and get demonstrations of just about everything in this book and the components on the website.
BUILD NOTE Build Notes (which look like this!) contain special instructions for how to build the objects that go with the nearby scripts whenever special tricks or building pitfalls are present.
How to Contact the Authors We welcome feedback from you about this book or about books you'd like to see from us in the future. You can reach us by writing to
[email protected].
viii
Sybex strives to keep you supplied with the latest tools and information you need for your work. Please check their website at www.sybex.com, where we'll post additional content and updates that supplement this book should the need arise. Enter Scripting Your World in the Search box (or type the book's ISBN—9780470339831), and click Go to get to the book's update page.
00i-0xv Front Matter.indd 8
8/7/08 11:09:46 PM
CHAPTER 1
CONTENTS
CHAPTER 2 CHAPTER 3
CHAPTER 1: GETTING A FEEL FOR THE LINDEN SCRIPTING LANGUAGE
2
SCRIPTING STRUCTURE 101
4
TYPES
8
CHAPTER 7
9
CHAPTER 8
Float
10
CHAPTER 9
Vector
10
Rotation
12
Key
13
CHAPTER 13
String
14
CHAPTER 14
List
15
CHAPTER 15
VARIABLES
19
APPENDICES
FLOW CONTROL
20
OPERATORS
21
FUNCTIONS
23
EVENTS AND EVENT HANDLERS
25
STATES
26
Integer
When to Use Multiple States (or Not!)
MANAGING SCRIPTED OBJECTS
CHAPTER 4 CHAPTER 5 CHAPTER 6
CHAPTER 10 CHAPTER 11 CHAPTER 12
28
29
Losing Moving Objects
29
Multiple Scripts
30
Resetting Scripts
31
AN LSL STYLE GUIDE
32
Cleanliness
33
Modularity
34
SUMMARY
35
CHAPTER 2: MAKING YOUR AVATAR STAND UP AND STAND OUT
36
MOVING YOUR AVATAR AROUND THE WORLD
38
Sitting
38
Teleporting
41
ix
00i-0xv Front Matter.indd 9
8/7/08 11:09:46 PM
D edication
ATTACHMENTS
A cknow ledgments
A bout the Authors Introduction Contents
46
Flip Tag
47
Face Light
48
Wind Me Up!
50
Discovering whether Objects Are Attached
53
ANIMATION
57
Animation Overrides
58
Typing Animator
61
CONTROLLING YOUR ATTACHMENTS
64
SUMMARY
65
CHAPTER 3: COMMUNICATIONS
66
TALKING TO AN OBJECT (AND HAVING IT LISTEN)
68
A Practical Joke: Mimic!
75
DIALOGS
77
Creating Objects that Communicate with Each Other
79
Chat Relay
Using Link Messages for Prim-to-Prim Communication inside an Object Associating Prims with Their Link Numbers
Email and Instant Messaging
82
83 88
89
Send Me Some Email
89
Object-to-Object Email
94
Summary
96
CHAPTER 4: MAKING AND MOVING OBJECTS
98
THE PRESTO, ABRACADABRA OF REZZING
100
Loop Rezzers
100
A Temporary Rezzer
106
A Less-Temporary Temporary Rezzer
108
Other Object-Manipulation Functions
111
x
00i-0xv Front Matter.indd 10
8/7/08 11:09:46 PM
CONTROLLING MOTION OF NONPHYSICAL OBJECTS
112
CHAPTER 1
Controlling Position
112
CHAPTER 2
Rotation with Target Omega
113
CHAPTER 3
Using Quaternions
115
CHAPTER 4
Sun, Earth, and Moon (Combining Target Omega and Quaternions)
122
CHAPTER 5
SUMMARY
123
CHAPTER 6
CHAPTER 5: SENSING THE WORLD
124
CHAPTER 8
BUILDING SENSORS
126
CHAPTER 10
Open Up! A Simple Automated Door Slider
127
CHAPTER 11
Outdoors: The Birds and the Bees
128
CHAPTER 12
Indoors: Roomba
130
CHAPTER 7
CHAPTER 9
CHAPTER 13 CHAPTER 14
DETECTION WITH COLLISIONS
134
CHAPTER 15
USING GREETERS TO WELCOME VISITORS
136
APPENDICES
A Personalized Memory Greeter
SUMMARY
CHAPTER 6: LAND DESIGN AND MANAGEMENT
137
139
140
A WATERFALL
142
SHAPING THE LAND BY TERRAFORMING
145
LAND SECURITY—ARE YOU ON THE LIST?
147
Access-Controlled Teleports
148
A Land Monitor and Ejector
150
LAND INFORMATION FUNCTIONS
152
SUMMARY
155
CHAPTER 7: PHYSICS AND VEHICLES PHYSICAL OBJECTS
156 158
Working with Acceleration
160
Physical Functions
162
Optical Illusions
165
A Human Cannon Ball
167
Damage
169
Pushing Objects: Flight Assist
170
Energy Drain
175
00i-0xv Front Matter.indd 11
xi
8/7/08 11:09:46 PM
D edication
VEHICLES
A cknow ledgments
A bout the Authors Introduction Contents
176
Vehicle Properties
178
Vehicle Flags
181
Cameras and Mouselook
182
SUMMARY
CHAPTER 8: INVENTORY
185
186
INVENTORY PROPERTIES
188
GIVING INVENTORY
189
Please Take a Note(card)
189
Inventory List: Giving Folders
192
TAKING INVENTORY
193
PERMISSIONS
195
SUMMARY
197
CHAPTER 9: SPECIAL EFFECTS
198
PARTICLE EFFECTS
200
Fireworks
201
A Rainbow
205
Kite String
207
TEXTURE ANIMATION
211
Texture Animation Modes
212
Picture Frame
218
LIGHT
221
Creating Lights
222
Reflecting Light
226
SUMMARY
CHAPTER 10: SCRIPTING THE ENVIRONMENT TIME
227
228 230
Script Time: Elapsed Time
230
Real-World Time: Analog Clock
230
Sim Time: Day, Night, and Shadows
232
xii
00i-0xv Front Matter.indd 12
8/7/08 11:09:47 PM
AIR, EARTH, WATER, AND WEATHER
236
CHAPTER 1
Air: Weather Vane
236
CHAPTER 2
Earth and Water: A Floating Bottle
237
CHAPTER 3
Weather: Snowfall
240
CHAPTER 4
240
CHAPTER 5
SUMMARY
CHAPTER 6
CHAPTER 11: MULTIMEDIA
242
CHAPTER 7
WORKING WITH SOUND
244
CHAPTER 9
Ambient-Sound Automation: Northern Winds
244
CHAPTER 10
Sound Orchestration: Rain and Thunder
247
CHAPTER 11
STREAMING MEDIA
251
Streaming Video
253
SUMMARY
256
CHAPTER 8
CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDICES
CHAPTER 12: REACHING OUTSIDE SECOND LIFE
258
LOADING WEB PAGES IN-WORLD
260
USING HTTP REQUESTS TO GET DATA FROM THE WEB
261
Getting Data from a Web Server
261
Using the Web for Persistent Storage: name2Key
263
Constructing Both Sides of a Request: Message in a Bottle
265
RSS Feeds in Second Life
268
USING XML-RPC TO CONTROL SL FROM THE OUTSIDE
272
The Server Side (LSL): Receiving Instructions from Out-World
273
The Client Side (Perl): Send Instructions into SL
276
SUMMARY
277
CHAPTER 13: MONEY MAKES THE WORLD GO 'ROUND
278
TRANSACTION BASICS
280
REDISTRIBUTING WEALTH
281
Tip Jars
281
A Shared Tip Jar
285
Charity Collectors
288
Going on the Dole: Money Trees
291
xiii
00i-0xv Front Matter.indd 13
8/7/08 11:09:47 PM
D edication
SELLING IT!
A cknow ledgments
A bout the Authors Introduction Contents
294
A Simple Vendor
295
A Multiproduct Vendor
296
Networked Vendors
300
RENTALS AND SERVICES
301
A Ticket to Ride
301
Land Rentals
303
Campers
305
Gambli...uh...Games of Skill
307
SUMMARY
308
CHAPTER 14: DEALING WITH PROBLEMS
310
WHAT COULD POSSIBLY GO WRONG?
312
Compiler Errors
312
Runtime Errors
313
Logic Errors
314
Memory
315
TOO SLOW?
316
Lag
317
Linear Execution Speed
317
Algorithms
318
Time Dilation
319
Script-Speed Governors
319
Simultaneity
319
BE PROACTIVE!
320
Don't Be Overly Clever
320
Debugging
320
Backing Up Your Scripts
322
Versioning
322
Testing
323
xiv
00i-0xv Front Matter.indd 14
8/7/08 11:09:47 PM
GETTING AND GIVING HELP
324
CHAPTER 1
Education
324
CHAPTER 2
In-World Locales
325
CHAPTER 3
Groups
328
CHAPTER 4
Mailing Lists and Forums
329
CHAPTER 5
Websites
329
CHAPTER 6
Script Libraries
330
CHAPTER 7
Bugs? In Second Life?!
330
CHAPTER 8 CHAPTER 9
PRODUCTIZING YOUR SCRIPT
331
CHAPTER 10
SUMMARY
332
CHAPTER 11 CHAPTER 12
CHAPTER 15: NEW AND IMPROVED
334
CHAPTER 13 CHAPTER 14
THE SECOND LIFE VIRTUAL MACHINE: ON TO MONO!
336
CHAPTER 15
RECENT CHANGES TO LSL
337
APPENDICES
Touch Position
337
Avatar Information
339
HTTP Server
339
ANNOUNCEMENTS OF NEW FEATURES
340
ONWARD!
340
APPENDICES
342
Appendix A: Setting Primitive Parameters
344
Appendix B: Particle System
356
Appendix C: SL Community Standards
368
INDEXES
370
Index A: Key Terms (Excluding LSL Code)
370
Index B: Complete Listing of LSL Code
375
xv
00i-0xv Front Matter.indd 15
8/7/08 11:09:47 PM
002-035 Chapter 01.indd 4
8/7/08 9:22:43 PM
002-035 Chapter 01.indd 2
8/7/08 9:22:27 PM
Chapter 1
Getting a Feel for the Linden Scripting Language
What is so compelling about Second Life? As Linden Lab Founder Philip Rosedale explained in an October 19, 2006 interview in The New York Times, in Second Life avatars can move around and do everything they do in the real world, but without constraints such as the laws of physics: "When you are at Amazon.com [using current web technology] you are actually there with 10,000 concurrent other people, but you cannot see them or talk to them," Rosedale said. "At Second Life, everything you experience is inherently experienced with others." Much of the reason Rosedale can talk convincingly about shared experience is because of scripting in Second Life. To be sure, Second Life is a place of great physical beauty: in a well-crafted SL environment, a feeling of mood and cohesive appearance lend a level of credibility to the experience of existing and interacting in a specific and sometimes unique context. But consider how sterile Second Life would be without scripting. Builders and artists create beautiful vistas, but without interaction the world is static; little more than a fancy backdrop. Scripts give the world life, they allow avatars to be more realistic, and they enhance the residents' ability to react to and interact with each other and the environment, whether making love or making war, snorkeling, or just offering a cup of java to a new friend. This chapter covers essential elements of scripting and script structure. It is intended to be a guide, and may be a review for you; if that's the case then skim it for nuggets that enhance your understanding. If you are new to Second Life scripting or even programming in general, consider this chapter an introduction to the weird, wonderful world of SL scripting and the Linden Scripting Language, LSL. If you don't understand something, don't worry! You might find it easier to skip ahead and return here to get the details later.
NOTE Throughout the book, you'll see references to the LSL wiki. There are actually several such wikis, of which http://wiki.secondlife.com/wiki/LSL_Portal is the "official" one, and http://lslwiki.net is one of many unofficial ones. Typing out the full URL is cumbersome and hard to read, so if you see a reference to the wiki, you'll see only the keyword. For example, if you see, "you'll find more about the particle system on the LSL wiki at llParticleSystem," it means http://wiki.secondlife.com/wiki/LlParticleSystem, http://www.lslwiki.net/lslwiki/wakka.php?wakka=llParticleSystem or http:// rpgstats.com/wiki/index.php?title=LlParticleSystem. All of these wikis have search functions and convenient indexes of topics. In general, all examples in this book are available at the Scripting Your World Headquarters (SYW HQ) in Second Life at Hennepin * and on the Internet at http://syw.fabulo.us. There are also several extras that didn't get included in the book due to space limitations. Enjoy browsing! * Visit http://slurl.com/secondlife/Hennepin/38/138/108/ or simply search in-world for "SYWHQ."
002-035 Chapter 01.indd 3
8/7/08 9:22:32 PM
scripting structure 101
CHAPTER 1
Scripting Structure 101 Types Variables
A script is a Second Life asset, much like a notecard or any other Inventory item. When it is placed in a prim, one of the building blocks of all simulated physical objects, it can control that prim's behavior, appearance, and relationship with other prims it is linked with, allowing it to move; change shape, color, or texture; or interact with the world.
F low C ontrol
NOTE
O perators F unctions
A prim is the basic primitive building block of SL: things like cubes, spheres, and cylinders. An object is a set of one or more linked prims. When you link the prims, the root prim is the one that was selected last; the remaining prims are called children. The root prim acts as the main reference point for every other prim in the object, such as the name of the object and where it attaches.
E vents and E vent H andlers States anaging M S cripted O bjects A n LSL Style G uide S ummary
Whether or not you've already begun exploring how to script, you've probably created a new object and then clicked the New Script button. The result is that a script with no real functionality is added to the prim's Content folder. Left-clicking on the script opens it in the in-world editor and you see the script shown in Listing 1.1. It prints "Hello, Avatar!" to your chat window and then prints "Touched." each time you click the object that's holding it.
Listing 1.1: Default New Script default { state_entry() { llSay(0, "Hello, Avatar!"); } touch_start(integer total_number) { llSay(0, "Touched."); } }
This simple script points out some elements of script structure. First of all, scripting in SL is done in the Linden Scripting Language, usually referred to as LSL. It has a syntax similar to the common C or Java programming languages, and is event-driven, meaning that the flow of the program is determined by events such as receiving messages, collisions with other objects, or user actions. LSL has an explicit state model and it models scripts as finite state machines, meaning that different classes of behaviors can be captured in separate states, and there are explicit transitions between the states. The state model is described in more detail in the section "States" later in this chapter. LSL has some unusual built-in data types, such as vectors and quaternions, as well as a wide variety of functions for manipulating the simulation of the physical world, for interacting with player avatars, and for communicating with the real world beyond SL.
4
002-035 Chapter 01.indd 4
8/7/08 9:22:43 PM
The following list describes a few of the key characteristics of an LSL script. If you're new to programming, don't worry if it doesn't make much sense just yet; the rest of this chapter explains it all in more detail. The section "An LSL Style Guide" ties things together again. • All statements must be terminated by a semicolon (;). • LSL is block-oriented, where blocks of associated functionality are delimited by opening and closing curly braces ({ block }). • Variables are typed and declared explicitly: you must always specify exactly which type a variable is going to be, such as string or integer. • At a bare minimum, a script must contain the default state, which must define at least one event handler, a subroutine that handles inputs received in a program, such as messages from other objects or avatars, or sensor signals. • Scripts may contain user-defined functions and global variables. Listing 1.2 shows a rather more complete script, annotated to point out other structural features. This script controls the flashing neon sign on the front of the Scripting Your World visitor center. Do not be discouraged if you don't understand what is going on here! Although this script is relatively complex, it is here to illustrate that you don't need to understand the details to see how a script is put together. This first discussion won't focus on the function of the neon sign, but rather on the structure commonly seen in LSL scripts. A script contains four parts, generally in the following order: • Constants (colored orange in Listing 1.2) • Variables (green)
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
• Functions (purple) • States, starting with default (light blue, with the event handlers that make a state in dark blue) While common convention uses this order for constants, variables, and user-defined functions, they are permitted to occur in any order. They must all be defined before the default state, however. Additionally, you are required to have the default state before any user-defined states.
NOTE Constants are values that are never expected to change during the script. Some constants are true for all scripts, and part of the LSL standard, including PI (3.141592653), TRUE (1), and STATUS_PHYSICS (which indicates whether the object is subject to the Second Life laws of physics). You can create named constants for your script; examples might include TIMER_INTERVAL (a rate at which a timer should fire), COMMS_CHANNEL (a numbered communications channel), or ACCESS_LIST (a list of avatars with permission to use the object). Variables, meanwhile, provide temporary storage for working values. Examples might include the name of the avatar who touched an object, counts of items seen, or the current position of an object. The section "Variables" later in this chapter describes variables in more detail. Functions are a mechanism for programmers to break their code up into smaller, more manageable chunks that do specific subtasks. They increase readability of the code and allow the programmer to reuse the same capability in multiple places. The section "User-Defined Functions" describes functions in more detail.
5
002-035 Chapter 01.indd 5
8/7/08 9:22:47 PM
Listing 1.2: Flipping Textures by Chat and by Timer Comments
constants
variables
functions
states event handlers
CHAPTER 1
S cripting Structure 101 Types Variables F low C ontrol O perators F unctions E vents and E vent H andlers States anaging M S cripted O bjects A n LSL Style G uide S ummary
// Texture Flipper for a neon sign // Constants integer TIMER_INTERVAL = 2; // timer interval string NEON_OFF_TEXTURE = "bcf8cd82-f8eb-00c6-9d61-e610566f81c5"; string NEON_ON_TEXTURE = "6ee46522-5c60-c107-200b-ecb6e037293e"; // global variables integer gOn = TRUE; // If the neon is burning integer gSide = 0; // which side to flip integer gListenChannel = 989; // control channel // functions fliptexture(string texture) { llSetTexture(texture, gSide); } usage(){ llOwnerSay("Turn on by saying: /"+(string)gListenChannel+" sign-on"); llOwnerSay("Turn off by saying: /"+(string)gListenChannel+" sign-off"); } // states default { state_entry() { llSetTimerEvent(TIMER_INTERVAL); llListen( gListenChannel, "", llGetOwner(), "" ); } listen(integer channel, string name, key id, string msg) { if (msg == "sign-on") { fliptexture(NEON_ON_TEXTURE); gOn = TRUE; llSetTimerEvent(TIMER_INTERVAL); // start the timer } else if (msg == "sign-off") { fliptexture(NEON_OFF_TEXTURE); // start the timer gOn = FALSE; llSetTimerEvent(0.0); } else { usage(); } } timer() { if (gOn){ fliptexture(NEON_OFF_TEXTURE); gOn = FALSE; } else { fliptexture(NEON_ON_TEXTURE); gOn = TRUE; } } }
6
002-035 Chapter 01.indd 6
8/7/08 9:22:51 PM
Two forward slashes (//) indicate a comment. The slashes and the entire rest of the line are completely ignored by SL. They remain part of the script but have no effect, so you can use them to add a copyright notice or a description of what's going on in the script, or even to disable lines when you are trying to debug a problem. Likewise, empty lines and extra spaces play no part in the execution of a script: indentation helps readability but SL ignores it. Declarations of global constants and variables have script-wide scope; that is, the entire rest of the script can use them. Most programmers are taught that global variables are evil, but in LSL there is no other way to communicate information between states. Since most LSL scripts are fairly short, it's relatively easy to keep track of these beasties, eliminating one of the major reasons that global variables are discouraged in other languages. Although technically the LSL compiler does not distinguish between user-defined constants and variables, the examples in this book name constants with all capital letters, and global variables using mixed case beginning with the lowercase letter g. Next you will notice a couple of code segments that seem to be major structural elements; these are called fliptexture() and usage(), respectively. These are user-defined functions. Functions are global in scope and available to all states, event handlers, and other user-defined functions in the same script. Functions can return values with a return command. The "Functions" section in this chapter provides considerably more detail. Linden Library functions are readily identifiable, as they (without exception) begin with the letters ll, as in llSetTimerEvent(). The last elements of a script are the states. A state is a functional description of how the script should react to the world. For example, you could think of a car being in one of two states: when it is on the engine is running, it is making noises, it can move, it can be driven. When it is off it is little more than a hunk of metal; it is quiet, immobile, and cannot be driven. An LSL script represents an object's state of being by describing how it should react to events in each situation. Every script must have at least the one state, default, describing how it behaves, but you can define more states if it makes sense. An event is a signal from the Second Life simulation that something has happened to the object, for example that it has moved, been given money, or been touched by an avatar. When an event happens to a Second Life object, each script in the object is told to run the matching event handler: As an example, when an avatar touches an object, SL will run the touch_start(), touch(), and touch_end() event handlers in the active state of each script in the object, if the active state has those handlers. LSL has defined a set number of event handlers. (The SYW website has a complete list of event handlers and how they are used.) The three event handlers in Listing 1.2, state_entry(), listen(), and timer(), execute in a finite state machine managed by the simulator in which the object exists. More details on the state model are presented in the section "States," as it is one of the more interesting aspects of LSL.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
You may well ask, "So what does this script do?" It's really pretty simple. Whenever a couple of seconds tick off the clock (the time interval defined by the constant TIMER_INTERVAL), the timer event fires, and the texture on the front face of the object is replaced either with the "on" texture referenced by the key in the string NEON_ON_TEXTURE or with the "off" texture, NEON_OFF_TEXTURE. The script also listens for input by anyone who knows the secret channel to talk to the object (989, declared as the variable gListenChannel). If the object hears anyone chat sign-off or sign-on on the secret channel*, it will activate or deactivate the sign. Come by SYW HQ and tell our sign to turn off (or on, as the case may be). Figure 1.1 shows the script in action. Chapter 3, "Communications," talks more about channels and how to communicate with objects.
* When typing in the chat window, the channel number is preceded by a slash, as in /989 sign-off.
002-035 Chapter 01.indd 7
7
8/7/08 9:22:55 PM
CHAPTER 1
ON
OFF
Scripting Structure 101 Types Variables F low C ontrol O perators F unctions E vents and E vent H andlers
Figure 1.1: Textureflipping in action
States anaging M S cripted O bjects A n LSL Style G uide S ummary
TYPES A type is a label on a piece of data that tells the computer (and the programmer) something about what kind of data is being represented. Common data types include integers, floating-point numbers, and alphanumeric strings. If you are familiar with the C family of languages (C, C++, C#, Java, and JavaScript) you'll notice similarities between at least a few of them and LSL. Table 1.1 summarizes the valid LSL variable types. Different data types have different constraints about what operations may be performed on them. Operators in general are covered in the "Operators" section of this chapter, and some of the sections that cover specific types also mention valid operations. Because all variables in LSL are typed, type coercion is awkward. Most coercion must be done manually with explicit casting, as in integer i=5; llOwnerSay((string)i);
In many cases, LSL does the "right thing" when coercing (implicit casting) types. Almost everything can be successfully cast into a string, integers and floats are usually interchangeable, and other conversions usually result in the null or zero-equivalent. The discussion later, in Table 1.7, of llList2() functions gives a good overview of what happens. Look on the Types page of the LSL wiki for an expanded example of coercion.
8
002-035 Chapter 01.indd 8
8/7/08 9:23:08 PM
Table 1.1: LSL Variable Types Data Type
Usage
integer
Whole number in the range –2,147,483,648 to 2,147,483,647.
float
Decimal number in the range 1.175494351E-38 to 3.402823466E+38.
vector
A three-dimensional structure in the form <x, y, z>, where each component is a float. Used to describe values such as a position, a color, or a direction.
CHAPTER 1
rotation quaternion
A four-dimensional structure consisting of four floats, in the form <x, y, z, s>, that is the natural way to represent rotations. Also known as a quaternion, the two type names are interchangeable.
CHAPTER 2
key
A UUID (specialized string) used to identify something in SL, notably an agent, object, sound, texture, other inventory item, or data-server request.
string
A sequence of characters, limited only by the amount of free memory available to the script (although many functions have limits on the size they will accept or return).
list
A heterogeneous collection of values of any of the other data types, for instance [1, "Hello", 4.5].
CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9
NOTE
CHAPTER 10
The value of a variable will never change unless your code reassigns it, either explicitly with = or implicitly with an operator such as ++, which includes a reassignment:
CHAPTER 12
a = "xyzzy"; b = a; a = "plugh";
CHAPTER 11
CHAPTER 13 CHAPTER 14
// b is also "xyzzy" // a is now "plugh" but b is still "xyzzy"!
This holds true for all types, including lists! In keeping with this immutability, all function parameters are pass-by-value (meaning only the value is sent) in LSL.
CHAPTER 15 APPENDIces
Integer Integers are signed (positive or negative) 32-bit whole numbers. LSL does not provide any of the common variations on integer types offered in most other languages. Integers are also used to represent a few specific things in LSL: • Channels are integer values used to communicate in "chat" between both objects and avatars. See the section "Talking to an Object (and Having It Listen)" in Chapter 3 for a deeper discussion of channels and their use. • Booleans are implemented as integer types with either of the constant values: TRUE (1) or FALSE (0). • Event counters are integer arguments to event handlers that indicate how many events are pending. Inside such an event handler, the llDetected*() family of library functions can be used to determine which avatars touched an object, which other objects collided with yours, or which objects are nearby. • Listen handles are returned by llListen() and enable code to have explicit control over the listen stream. (Other things you might think would be handles are actually returned as keys.) See Chapter 2, "Making Your Avatar Stand Up and Stand Out," for examples of llListen().
9
002-035 Chapter 01.indd 9
8/7/08 9:23:12 PM
• Bit patterns (or bit fields) are single integers that represent a whole set of Boolean values at once. Different bits can be combined to let you specify more than one option or fact at once. For instance, in the llParticleSystem() library function, you can indicate that particles should bounce and drift with the wind by combining the constant values PSYS_PART_BOUNCE_MASK and PSYS_PART_WIND_MASK by saying
CHAPTER 1
PSYS_PART_BOUNCE_MASK | PSYS_PART_WIND_MASK Scripting Structure 101
Float
Types Variables F low C ontrol O perators F unctions E vents and E vent H andlers States anaging M S cripted O bjects A n LSL Style G uide S ummary
A float in LSL is a 32-bit floating-point value ranging from ±1.401298464E–45 to ±3.402823466E+38. Floats can be written as numbers, such as 1.0 or 9.999, and they can be written in scientific notation, as in 1.234E–2 or 3.4E+38, meaning 1.234 × 10−2 and 3.4 × 1038. A float has a 24-bit signed mantissa (the number), and an 8-bit signed exponent. Thus for a float 1.2345E+23, the number 1.2345 is the mantissa, and 23 is the exponent. Because one bit represents the sign of the number (positive or negative), a 23-bit mantissa gives a precision equivalent to approximately 7 decimal digits—more precisely log10(223). This means values are rarely stored exactly. For example, if you do something like float foo = 101.101101;
and print the result, it will report 101.101105, so you should expect some rounding inaccuracy. Even 10E6 × 10E6 isn't 10E12, instead printing 100000000376832.000000. Often more disturbingly, addition or subtraction of two numbers of vastly different magnitudes might yield unexpected results, as the mantissa can't hold all the significant digits. When an operation yields a number that is too big to fit into a float, or when it yields something that is not a number (such as 1.0 / 0.0), your script will generate a run-time Math Error.
Vector Vectors are the currency of three-dimensional environments, and so are found throughout LSL code. In addition, anything that can be expressed as a triplet of float values is expressed in a vector type. If you were guessing about the kinds of concepts readily expressed by a set of three values, you'd probably come up with positioning and color, but there are also others, shown in Table 1.2. Table 1.2: Common uses for Vectors and What They Represent Vector Concept What Vector Represents
10
002-035 Chapter 01.indd 10
Position
Meters. Always relative to some base positioning (the sim, the avatar, or the root prim).
Size
Meters, sometimes also called scale.
Color
Red, green, blue. Each component is interpreted in a range from 0.0 to 1.0; thus yellow is vector yellow = ;
Direction
Unitless. It is usually a good idea to normalize directions (see llVecNorm()); since directions are often multiplied with other values, non-unit direction vectors can have an unexpected proportional effect on the results of such operations.
Velocity
An offset from a position in meters traveled per second. You can also think of velocity as a combination of direction and speed in meters per second.
Acceleration
Meters per second squared.
Impulse
Force (mass × velocity).
Rotation
Radians of yaw, pitch, and roll. Also known formally as the Euler form of a rotation.
8/7/08 9:23:16 PM
The x, y, and z components of a vector are floats, and therefore it is slightly more efficient to write a vector with float components—for instance, —than with integer components. Here are some examples of ways to access vectors, including the built-in constant ZERO_VECTOR for : vector aVector = ; float xPart = 1.0; vector myVec = <xPart, 2.0, 3.0>; float yPart = myVec.y; float zPart = myVec.z; myVec.y = 0.0; vector zeroVec = ZERO_VECTOR; llOwnerSay("The empty vector is "+(string)ZERO_VECTOR);
Vectors may be operated on by scalar floats (regular numbers); for instance, you could convert the yellow color vector in Table 1.2 to use the Internet-standard component ranges of 0 to 255 with the expression *255.0. Vector pairs may be transformed through addition, subtraction, vector dot product, and vector cross product. Table 1.3 shows the results of various operations on two vectors: vector a = ; vector b = ;
Table 1.3: Mathematical Operations on Vectors Operation Meaning
Vector
+
Add
a+b = < 0.0, 12.0, 103.0 >
-
Subtract
a-b = < 2.0, -8.0, -97.0>
*
Vector dot product
a*b = 319.0 (1 * -1) + (2 * 10) + (3 * 100)
%
Vector cross product
a%b = < 170.0, -103.0, 12.0 >
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
Coordinates in SL can be confusing. There are three coordinate systems in common use, and no particular annotation about which is being used at any given time. • Global coordinates. A location anywhere on the Second Life grid with a unique vector. While not often used, every place on the grid has a single unique vector value when represented in global coordinates. Useful functions that return global coordinates include llGetRegionCorner() and llRequestSimulatorData(). • Region coordinates. A location that is relative to the southwest corner of the enclosing sim (eastward is increasing x, northward is increasing y, up is increasing z), so the southwest corner of a sim at altitude 0 is . The position or orientation of objects, when not attached to other prims or the avatar, is usually expressed in terms of regional coordinates. A region coordinate can be converted to a global coordinate by adding to it the region corner of the simulator the coordinate is relative to: vector currentGlobalPos = llGetRegionCorner() + llGetPos();
11
002-035 Chapter 01.indd 11
8/7/08 9:23:20 PM
• Local coordinates. A location relative to whatever the object is attached to. For an object in a linkset, that means relative to the root prim. For an object attached to the avatar, that means relative to the avatar. For the root prim of the linkset, that value is relative to the sim (and therefore the same as the region coordinates). If the attachment point moves (e.g., the avatar moves or the root prim rotates), the object will move relative to the attachment, even though local coordinates do not change. For example, if an avatar moves her arm, her bracelet will stay attached to her wrist; the bracelet is still the same distance from the wrist, but not in the same place in the region.
CHAPTER 1
Scripting Structure 101 Types Variables F low C ontrol O perators F unctions E vents and E vent H andlers
Useful functions on vectors include llVecMag(vector v), llVecNorm(vector v), and llVecDist(vector v1, vector v2). llVecMag() calculates the magnitude, or length, of a vector—it's Pythagoras in three dimensions. These functions are really useful when measuring the distance between two objects, figuring out the strength of the wind or calculating the speed of an object. llVecNorm() normalizes a vector, turning it into a vector that points in the same direction but with a length of 1.0. The result can be multiplied by the magnitude to get the original vector back. llVecNorm() is useful for calculating direction, since the result is the simplest form of the vector. llVecDist(v1,v2) returns the distance between two vectors v1 and v2, and is equivalent to llVecMag(v1-v2).
States anaging M S cripted O bjects A n LSL Style G uide S ummary
Rotation There are two ways to represent rotations in LSL. The native rotation type is a quaternion, a fourdimensional vector of which the first three dimensions are the axes of rotation and the fourth represents the angle of rotation. quaternion and rotation can be used interchangeably in LSL, though rotation is much more common. Also used are Euler rotations, which capture yaw (x), pitch (y), and roll (z) as vector types rather than as rotation types. The LSL Object Editor shows rotations in Euler notation. Euler notation in the Object Editor uses degrees, while quaternions are represented in radians; a circle has 360 degrees or TWO_PI (6.283) radians. Euler vectors are often more convenient for human use, but quaternions are more straightforward to combine and manipulate and do not exhibit the odd discontinuities that arise when using Euler representation. For instance, in the SL build tools, small changes in object rotation can make sudden radical changes in the values indicated. Two functions convert Euler representations into quaternions (and vice versa): llEuler2Rot(vector eulerVec) and llRot2Euler(rotation quatRot). Many of your scripts can probably get away with never explicitly thinking about the guts of quaternions: // convert the degrees to radians, then convert that // vector into a quaternion rotation myQuatRot = llEuler2Rot( * DEG_TO_RAD); // convert the rotation back to a vector // (the values will be in radians) vector myEulerVec = llRot2Euler(myQuatRot);
The above code snippet converts the degrees to radians by multiplying the vector by DEG_TO_RAD. Two other constants—ZERO_ROTATION and RAD_TO_DEG—are useful for rotations. These constants are defined in Table 1.4.
12
002-035 Chapter 01.indd 12
8/7/08 9:23:24 PM
Table 1.4: Constants Useful for Manipulating Rotations Constant
Value
Description
ZERO_ROTATION
A rotation constant representing a Euler angle of .
DEG_TO_RAD
0.01745329238
A float constant that, when multiplied by an angle in degrees, gives the angle in radians.
CHAPTER 1
RAD_TO_DEG
57.29578
A float constant that, when multiplied by an angle in radians, gives the angle in degrees.
CHAPTER 2
You will find much more in-depth discussion and some examples for using rotations in Chapter 4, "Making and Moving Objects."
CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6
Key
CHAPTER 7
A key is a distinctly typed string holding the UUID for any of a variety of relatively long-lived SL entities. A UUID, or Universal Unique Identifier, is a 128-bit number assigned to any asset in Second Life, including avatars, objects, and notecards. It is represented as a string of hex numbers in the format "000000000000-0000-0000-000000000000", as in "32ae0409-83d6-97f5-80ff-6bee5f322f14". NULL_KEY is the all-zero key. A key uniquely identifies each and every long-lived item in Second Life. In addition to the unsurprising use of keys to reference assets, keys are also used any time your script needs to request information from a computer other than the one it is actually running on, for instance to web servers or to the SL dataserver, to retrieve detailed information about SL assets. In these situations, the script issues a request and receives an event when the response is waiting. This model is used to ask not just about avatars using the llRequest*Data() functions, but also to do things like read the contents of notecards with llGetNotecardLine(). Asynchronous interactions with the outside world might include HTTP requests, llHTTPRequest(), and are identified with keys so that the responses can be matched with the queries.
CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
Numerous LSL functions involve the manipulation of keys. Some of the main ones are shown in Table 1.5. Table 1.5: Sample Functions that Use Keys Function Name
Purpose
key llGetKey()
Returns the key of the prim.
key llGetCreator()
Returns the key of the creator of the prim.
key llGetOwner()
Returns the key of the script owner.
key llDetectedKey()
Returns the key of the sensed object.
string llKey2Name(key id) Returns the name of the object whose key is id. key llGetOwnerKey(key id) Returns a key that is the owner of the object id.
Note that there is no built-in function to look up the key for a named avatar who is not online. However, there are a number of ways to get this information through services, such as llRequestAgentData().
13
002-035 Chapter 01.indd 13
8/7/08 9:23:28 PM
String CHAPTER 1
Scripting Structure 101 Types Variables F low C ontrol
Strings are sequences of letters, numerals, and other characters, collected as a single value. You can specify a constant string in LSL by surrounding the sequence of characters you want with double quotes ("). You can include a double-quote symbol in a string by prefixing it with a backslash (\). Similarly, you can include a backslash with \\, a sequence of four spaces with \t, and a newline with \n. Table 1.6 shows the set of string-manipulation functions provided by LSL. String indexes are zero-based, ranges are always inclusive of the start and end index, and negative indexes indicate counting positions backwards from the end of the string (–1 is the last character of the string). Consider this example: string test = "Hello world! "; llGetSubString(test,0,0); // "H" llGetSubString(test,0,-1); // "Hello world! " llGetSubString(test, -6,-2); // "world"
O perators F unctions E vents and E vent H andlers States anaging M S cripted O bjects
String values, like all other LSL values, are immutable; that is, no function will modify the original string. Rather, functions return brand-new strings that contain transformed versions. For example, the variable test in the following snippet does not change when a word is inserted into the string: string test = "Hello world! "; string x = llInsertString(test, 6, "cruel "); // x = "Hello cruel world! ", test is unchanged
A n LSL Style G uide S ummary
Table 1.6: Functions that Manipulate Strings Function Name
Purpose
integer llStringLength(string s)
Gets the length of a string.
string llToLower(string s)
Converts a string to lowercase.
string llToUpper(string s)
Converts a string to uppercase.
integer llSubStringIndex(string s, string pattern)
Finds the integer position of a string in another string.
string llGetSubString(string s, integer start, integer end)
Extracts a part of a string; returns the part.
string llDeleteSubString(string s, integer start, integer end)
Returns a copy of the original minus the specified part.
string llInsertString(string s, integer pos, string snippet)
Inserts a string snippet into a string starting at the specified position.
string llStringTrim(string s, integer trimType)
Trims leading and/or trailing whitespace from a string. Trim types are STRING_TRIM_HEAD for the leading spaces, STRING_TRIM_ TAIL for the trailing spaces, and STRING_TRIM for both leading and trailing spaces.
string llEscapeURL(string url)
Returns the string that is the URL-escaped version of url (replacing spaces with %20, etc).
string llUnescapeURL(string url)
Returns the string that is the URL unescaped version of url (replacing %20 with spaces, etc).
14
002-035 Chapter 01.indd 14
8/7/08 9:23:32 PM
List Lists in LSL are collections of values of any other types. Lists are heterogeneous: they may contain any mixture of values of any type except other lists. This makes it important to keep track of what is stored in your lists to avoid getting into trouble, but you can type-check elements at runtime if you need to. For instance, the following code extracts elements from a list:
CHAPTER 1 CHAPTER 2
list mylist = [1, 2.3, "w00t", ]; integer count = llList2Integer(mylist, 0); float value = llList2Float(mylist, 1); string exclamation = llList2String(mylist, 2); vector color = llList2Vector(mylist, 3);
CHAPTER 3
To access the individual elements, use the llList2() functions, shown in Table 1.7.
CHAPTER 7
You can use llGetListEntryType() to find out the type of the element. For example, llList2Float(list src, integer index); returns the float value at the specified index in the list. Thus Listing 1.3 prints 4.000000 when the object is touched. Note that it is implicitly casting the original integer value into a float.
CHAPTER 8
Table 1.7: Functions that Extract Individual List Elements Function Name
Purpose
string llList2String(list l, integer index)
Returns a string element. All other types are appropriately coerced into a string.
integer llList2Integer(list l, integer index)
Returns an integer element. If the element is not coercible to an integer, it will return 0.
float llList2Float(list l, integer index)
Returns a float element. If the element is not coercible to a float, it will return 0.0.
key llList2Key(list l, integer index)
Returns a key element. If the element is a regular string, it is returned unchanged. Other non-key elements return NULL_KEY.
vector llList2Vector(list l, integer index)
Returns a vector element. If the element is not coercible to a vector, it will return ZERO_VECTOR.
rotation llList2Rot(list l, integer index)
Returns a rotation element. If the element is not coercible to a rotation, it will return ZERO_ROTATION.
integer llGetListEntryType(list l, integer index)
Gets the type of entry of an element in the list. Returns an integer constant TYPE_INTEGER, TYPE_FLOAT, TYPE_STRING, TYPE_ KEY, TYPE_VECTOR, TYPE_ROTATION, or TYPE_INVALID. Invalid occurs when the index was out of range.
CHAPTER 4 CHAPTER 5 CHAPTER 6
CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
Listing 1.3: Coercing an Element of a List into a Float list gMyNumList=[1,2,3,4,5]; default { touch_start(integer total_number) { float f = llList2Float(gMyNumList,3); llOwnerSay((string)f); } }
The upside of heterogeneous lists in the LSL context is that you can imitate associative arrays (for instance, dictionaries or reference tables) with careful searches. If you keep your lists well-structured, you're very likely to know your list contents and their types very well, even when the contents are a mixed bag. The first element in a list is at index 0, and negative indexes reference elements counting backwards from the end of the list, with –1 indicating the last one. Table 1.8 shows some of the basic list-manipulation functions.
002-035 Chapter 01.indd 15
15
8/7/08 9:23:36 PM
Table 1.8: Some Core Functions to Manipulate Lists
CHAPTER 1
Scripting Structure 101 Types Variables F low C ontrol O perators F unctions E vents and E vent H andlers
Function Name
Purpose
integer llGetListLength(list l)
Gets the number of elements in a list.
integer llListFindList(list l, list test)
Returns the index of the first instance of test in l, or –1 if it isn't found.
list llList2List(list l, integer start, integer end)
Returns the portion of a list, with the specified elements (similar to substring).
list llDeleteSubList(list l, integer start, integer end)
Removes a portion of a list, returns the list minus the specified elements.
list llListInsertList(list dest, list snippet, integer pos)
Inserts a list into a list. If pos is longer than the length of dest, it appends the snippet to the dest.
list llListReplaceList(list dest, list snippet, integer start, integer end)
Replaces a part of a list with another list. If the snippet is longer than the end-start, then it serves to insert elements too.
float llListStatistics(integer operation, list input)
Performs statistical operations, such as min, max, and standard deviation, on a list composed of integers and floats. The operation takes one of ten LIST_STAT_* constants, which are described fully on the SYW website.
States
You can use + and += as concatenation operators:
anaging M S cripted O bjects
list a = [1,2]; a = a + [3]; // a now contains [1, 2, 3] a += [4]; // a now contains [1, 2, 3, 4]
A n LSL Style G uide S ummary
While you can't make nested lists, you can insert a list into another: list myL = ["a", "b"]; list myLL = llListInsertList(myL,[1,2,3,4],1); // myLL contains [a, 1, 2, 3, 4, b]
Perhaps the most interesting set of string functions deals with converting back and forth between lists and strings, listed in Table 1.9. For example, the function llDumpList2String() concatenates the items in a list into a string with a delimiter character sequence separating the list items; llDumpList2String([1,2,3,4,5], "--") prints "1--2--3--4--5". The function llList2CSV() is a specialized version, using only commas as separators. Table 1.9: Functions that Convert Lists to Strings
16
Function Name
Purpose
string llDumpList2String(list source, string delimiter)
Turns a list into a string, with the delimiter between items.
list llParseString2List(string s, list delimiters, list spaces)
Turns a string into a list, splitting at delimiters and spaces, keeping spaces. Consecutive delimiters are treated as one delimiter. Listing 1.4 shows an example of how to use this function.
list llParseStringKeepNulls(string s, list delimiters, list spaces)
Turns a string into a list; consecutive delimiters are treated separately.
list llCSV2List(string csvString)
Converts comma-separated values (CSVs) to a list. For instance, if csvString is a string "1,2,3" then the list will be [1, 2, 3].
string llList2CSV(list l)
Converts a list to a string containing comma-separated values (CSVs).
The llParseString2List() is particularly useful because it converts a string's elements to a list of strings. Listing 1.4 shows an example of how you would call it to break a string into separate items and then print them to the chat window.
002-035 Chapter 01.indd 16
8/7/08 9:23:41 PM
Listing 1.4: llParseString2List() Example default { touch_start(integer total_number) { list l = llParseString2List ("The answer to Life, the Universe"➥ + " and Everything is 42", [" "], []); integer i; integer len = llGetListLength(l); for (i = 0; i < len; i++){ llOwnerSay("item==>"+ llList2String(l, i)); } } }
While not totally symmetrical to llDumpList2String(), it still produces useful results: [8:31] [8:31] [8:31] [8:31] [8:31] [8:31] [8:31] [8:31] [8:31] [8:31]
Object: Object: Object: Object: Object: Object: Object: Object: Object: Object:
item==>The item==>answer item==>to item==>Life, item==>the item==>Universe item==>and item==>Everything item==>is item==>42
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15
WARNING
APPENDIces
If a list came from parsing a string using llParseString2List(), be aware that using llList2() won't work. Instead you have to use an explicit cast, indicating that you want to change the type as in (sometype)llList2String(). The following snippet shows an example: list components = llParseString2List(message,["|"],[]); float gAngle = (float)llList2String(components,0); vector gPosition = (vector)llList2String(components,1);
You can imitate objects or compound structures by using a strided list, wherein the series of contained objects is repeated in a nonvarying sequence. The stride indicates how many elements are in each compound structure. Listing 1.5 creates a strided list, with a stride of 2, populating it with a string representation of the color name paired with the LSL vector of RGB values representing that color. It prints all the color names that occur between elements 2 and 4 of the list, namely green and blue.
Listing 1.5: Small Strided-List Example list COLORS = ["red", , "green", , "blue", , "yellow", , "purple", , "cyan", ]; integer STRIDE = 2; default { touch_start(integer total_number) { list keys = llList2ListStrided(COLORS, 2, 4, STRIDE); llOwnerSay(llDumpList2String(keys, ", ")); } }
002-035 Chapter 01.indd 17
17
8/7/08 9:23:45 PM
There are some provisions for manipulating strided lists in LSL, including sorting and extracting elements such as keys, shown in Table 1.10. However, you will still need to have a few more utility functions. Put Listing 1.6 on your "save for later" pile—create the script and store it in your Inventory folder under Scripts. It contains a group of utility functions that enable the fetching, update, and deletion of a specific record. CHAPTER 1
Table 1.10: Functions that Manipulate Strided Lists
S cripting Structure 101 Types Variables F low C ontrol O perators F unctions E vents and E vent H andlers States anaging M S cripted O bjects A n LSL Style G uide S ummary
18
Function Name
Purpose
list llList2ListStrided(list src, integer start, integer end, integer stride)
Returns a list of all the entries whose index is a multiple of stride, in the range of start to end inclusive.
list llListSort(list l, integer stride, integer ascending)
Sorts a list ascending (TRUE) or descending (FALSE), in blocks of length stride. Works unreliably for heterogeneous lists.
list llListRandomize(list l, integer stride)
Randomizes a list in blocks of length stride.
Listing 1.6: Managing Records in a Strided List list gColors = ["red", , "green", , "blue", ]; integer STRIDE = 2; integer getPosition(integer recordNumber) { return recordNumber * STRIDE; } list getRecord(integer recordNumber) { integer pos = getPosition(recordNumber); if (pos < (llGetListLength(gColors) + STRIDE - 1)) return llList2List(gColors, pos, pos + STRIDE - 1); else return []; } deleteRecord(integer recordNumber) { integer pos = getPosition(recordNumber); if (pos < (llGetListLength(gColors) + STRIDE - 1)) { gColors = llDeleteSubList(gColors, pos, pos + STRIDE - 1); } } updateRecord(integer recordNumber, list newRecord) { integer pos = getPosition(recordNumber); if (pos < (llGetListLength(gColors) + STRIDE - 1)) { gColors = llListReplaceList(gColors, newRecord, pos, pos + STRIDE - 1); } } default { // embed some test code in a touch_start event handler // get record # 2 touch_start(integer total_number) { integer i = 0; list l = getRecord(2); llOwnerSay(llDumpList2String(l, " + ")); } }
002-035 Chapter 01.indd 18
8/7/08 9:23:50 PM
VARIABLES Variables provide a place to store temporary working values. Variables can be both global and local. Global variables are available to everything in the script, while local variables are available only to the block they were defined in. A variable name must start with an ASCII letter or an underscore. Numerals may be part of a variable name after the first character, and non-ASCII letters, while allowed, are ignored. Thus, valid declarations include the following: integer integer integer integer integer integer integer
x; y1; _z; thisIs_ALongComplicatedVariable_42; enumerator; _enumerator; énumérateur; // same as numrateur
However, you can not also declare an integer numrateur because LSL ignores the é, and thus thinks the name has previously been declared in scope; that is, numrateur is the same variable as énumérateur. Global variables (and constants) must be defined before the default state, as shown in the following code: integer gFoo = 42; default { on_rez(integer start_param) { llOwnerSay("gFoo is "+(string)gFoo); } }
Variables can not be defined directly inside states (they must appear inside blocks, functions, or event handlers). Thus, a slight rearrangement of the preceding code will generate a syntax error: default { integer gFoo = 42; on_rez(integer start_param) { llOwnerSay("gFoo is "+(string)gFoo); } }
Local variables must always be declared within the scope (enclosing curly braces) of a function or event handler but needn't be declared before the first executable statement. Just declare it prior to its first use and you'll be fine. Consider the following example: foo() { integer bar = 0; llOwnerSay("bar is "+(string)bar); integer baz = 0; llOwnerSay("baz is "+(string)baz); }
002-035 Chapter 01.indd 19
8/7/08 9:23:56 PM
Using a local variable before it has been declared will generate the error "Name not defined in scope." Variables are scoped to the closest enclosing code block. Variables are not accessible outside this scope. After the function foo() returns, the variables bar and baz are unavailable. Thus, in the following code snippet the variable baz is not available outside the else branch of the if (gBoolean) test: CHAPTER 1
integer gBoolean = TRUE; foo() { string bar = "Ford Prefect"; if (gBoolean) { string bar = "Slarty Bartfast"; llOwnerSay(bar); } else { string baz = "Zaphod Beeblebrox"; llOwnerSay(baz); } }
Scripting Structure 101 Types Variables F low C ontrol O perators F unctions E vents and E vent H andlers States anaging M S cripted O bjects A n LSL Style G uide
The snippet also demonstrates that you can shadow a variable from an outer code block with a redefinition of the same name. The innermost wins; that's why in this code example, llOwnerSay(bar) will always tell you "Slarty Bartfast" and ignore poor "Ford Prefect." It is also useful to know that global variables can be set to the value returned by a function, but they can not be intialized to the value of a function. Similarly, LSL does not allow any code evaluation outside blocks, generating a syntax error for the following snippet: float gHalfPi = PI/2; default { }
S ummary
FLOW CONTROL Flow control is the process of directing the computer through your program in ways other than simply executing each line one after the other. If you have some experience with other computer languages, LSL flow control will come naturally to you, as most of the familiar constructs are here. Conditionals are represented with if...else statements: they allow the conditional execution of code depending on the value of the expression following the word if: • if ( condition ) trueBranch • if ( condition ) trueBranch else falseBranch The branches can be single statements; block statements (enclosed in braces, {block}); or null statements (empty or just a semicolon ";"). There is no LSL equivalent to the switch or case statements found in many other languages. LSL provides a standard set of loop statements as well: for, do...while, and while: • do { loop } while ( condition );
20
002-035 Chapter 01.indd 20
• for ( initializer; condition; increment ) { loop } • while ( condition ) { loop }
8/7/08 9:24:06 PM
Loop statements may also be single statements, block statements, or the empty statement ";". Loops may not declare variables in the initializer. That is, the following snippet is not allowed: for (integer i=0; i 10.0) jumpdist = 10.0; integer steps = llCeil(dist / jumpdist) + 1; vector distanceVector = relativeDestination / steps; integer i; vector currPosition = origin; list params = []; for (i=0; i<steps; i++) { currPosition += distanceVector; params += [PRIM_POSITION, currPos]; } llSetPrimitiveParams(params); // actually move the prim } teleport(key av) { vector origin = llGetPos(); moveTo(origin, DEST, 10.0); // no need to sleep -- llSetPrimParams has 0.2s delay llUnSit(av); moveTo(DEST, origin, 10.0); } default { state_entry() { llSitTarget(SITPOS, ZERO_ROTATION); llSetSitText("Teleport!"); } changed(integer changebits) { if (changebits & CHANGED_LINK) { key av = llAvatarOnSitTarget(); if (av != NULL_KEY) { teleport(av); } } } }
Quicker Intrasim Teleport A popular optimization of this technique makes use of the fact that if you try to move an object by more than 10m, the effect will be to move it exactly 10m toward the target. This means you can avoid computing all of the intermediate steps, and instead use the same [PRIM_POSITION, dest] expression for every tuple in the list. It also allows you to build up the list of teleportation steps much more quickly by doubling the list each time instead of inserting one element at a time, as shown in Listing 2.4.
44
036-065 Chapter 02.indd 44
8/7/08 9:35:02 PM
Listing 2.4: Teleport3—Optimized Intrasim Teleport moveTo(vector origin, vector destination ) { // removed jumpdist float dist = llVecDist(origin, destination); integer passes = llCeil( llLog(dist/10.0) / llLog(2.0) ); integer i; list params = [PRIM_POSITION, destination]; for (i=0; i<passes; i++) { params = (params=[]) + params + params; } llSetPrimitiveParams(params); }
We construct the params list by doubling it each time through the loop. The local variable passes is set to the smallest whole number exponent of 2 that can be used to divide the distance to be covered into chunks of less than 10m; i.e., 2passes > (dist ÷ 10.0).
CHAPTER 1
CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7
NOTE
CHAPTER 8
The odd-looking extra (params=[]) in Listing 2.4 is a trick to get the LSL compiler to save a significant amount of memory when manipulating large lists. The most robust version of this technique can be found on the LSL wiki as LibraryWarpPos.
CHAPTER 10
CHAPTER 9
CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
Intersim Teleporter As of the writing of this book, you cannot teleport across sim boundaries as easily as you can within a sim. One of your options is to have your object give the avatar a landmark to the destination point (as shown in Chapter 8, "Inventory"). While it isn't a teleport, it is simple to program. Another option is to expose a map of the destination point using llMapDestination() when your teleporter object is touched, as shown in Listing 2.5.
CHAPTER 15 APPENDIces
DEFINITION llMapDestination(string simname, vector position, vector lookat) Exposes a map showing the specified point. This works only within one of the three touch() events. simname — The name of the destination sim. position — The position of your destination in the named sim. lookat — A point within the sim that the map should indicate is being looked at; currently ignored.
Listing 2.5: Teleport4—Intersim Teleport Assistant string SIM = "Hennepin"; vector LOC = ; // LM for Scripting Your World default { touch_start(integer n) { llMapDestination(SIM, LOC, ZERO_VECTOR); // 3rd param ignored } }
036-065 Chapter 02.indd 45
45
8/7/08 9:35:04 PM
It is possible to use the prim-movement techniques to cross sim boundaries if enough care is taken. However, sim crossings are perilous, and thus we suggest you wait to try it until you are an expert scripter.
CHAPTER 2
M oving Your Avatar
ATTACHMENTS
around the
World
Attachments A nimation C ontrolling Your Attachments S ummary
An attachment is an object made from one or more prims that you wear on your avatar. Many clothes are attachments, as are most accessories, such as handbags, hats, and earrings. Even some body parts, like hair, can be attached to your avatar. Wherever you go, your attachments go with you. There are many places you can attach something—about 30 different places on your avatar's body, and also eight heads-up display (HUD) locations. A HUD is like a controller board that appears only to you—other residents won't see it. (The SYW website has a section on bowling; it presents a HUD that controls the fine-grained direction of the bowling ball.) Every object in your inventory can be attached. Most people try to make sure the attachment location makes sense aesthetically: a skirt on the pelvis and a hat on the head, for example. (Note, however, that a hat might actually be attached to the chin or the nose, allowing something else, like hair, to be attached to the head.) Attachments can even be made invisible—really useful for attachments that do something but don't need to be seen, such as flip tags (discussed in the "Flip Tag" section of this chapter). Full avatar building is beyond the scope of this book but we can give you a taste of what is possible. Human avatars generally have their shapes changed with a combination of basic appearance controls, the wearing of layers of clothing (including the skin layer), and prim-based add-ons like clothing or prim hair. Human prim clothing and body parts are rarely heavily scripted; normally they're limited to customization (for instance, coloring hair). Sexual body parts are the notable exception. For an example of a scripted item of clothing, see the "Dialogs" section of Chapter 3, "Communications." You can also extend prim clothing to seemingly change the shape of avatar bodies, surrounding the "natural" body parts with differently shaped parts and adding totally new parts, as shown with the Frog Prince in Figure 2.3. This costume is available for free at enkythings, Orbiuna .
46
036-065 Chapter 02.indd 46
Figure 2.3: The Frog Prince is constructed by creating body parts that surround the standard avatar body.
8/7/08 9:35:11 PM
The rest of this section describes attachments that are fun to add to your avatar: a descriptive tag, a face light, and a key for a wind-up toy. (Additionally, the SYW website has a discussion of auto-deploying wings.) This section also shows how to figure out whether an object is attached, and how to automatically attach it where it belongs.
CHAPTER 1
CHAPTER 2
Flip Tag Flip tags are very common avatar attachments that allow you to express yourself more persistently than chat, but more dynamically than group membership. They are essentially invisible (or hidden) objects with a script that translates chatted commands so that they set the object's "floating text" property, as shown in Figure 2.4. Listing 2.6 shows a simple flip tag.
CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15
Figure 2.4: This avatar is wearing an object that announces he is fully scripted!
APPENDIces
Listing 2.6: Flip Tag—Label Yourself default { on_rez(integer start_param) { llResetScript(); } state_entry() { llListen(9, "", llGetOwner(), ""); } listen(integer channel, string name, key id, string message) { llSetText(message, , 1.0); } }
BUILD NOTE There are several places you can add the script from Listing 2.6. You can add it to an object you are already wearing, such as your hair or glasses. You can create an object to hold it, and then wear the object on an attachment point, such as your nose or ear, and then move it into the place you want. You probably will want to make the prim invisible using a full alpha texture once you like the position, as wearing a plywood box on your head isn't the most aesthetic of fashion statements. (Use Ctrl+Alt+T to keep seeing it.) You can also wear it on a HUD point, but as with all HUD objects, you'll be the only one who can see it.
036-065 Chapter 02.indd 47
47
8/7/08 9:35:14 PM
CHAPTER 2
This script listens for commands typed by the owner and displays them as visible text. To use the script, chat /9Hello World! when wearing your flip tag. The 9 specifies which channel to chat on. llListen() sets up a listener event, listen(), that is called every time a chatted message passes the filters specified in llListen(). This script ensures that the listener pays attention to only channel number 9, and only when messages come from the owner of the containing object by specifying llGetOwner(). Chapter 3 describes the llListen() family of functions in more detail.
M oving Your Avatar
DEFINITION
around the
World
Attachments
key llGetOwner()
A nimation C ontrolling Your Attachments
Returns the key of the owner of the scripted object.
S ummary
This script's listen() event handler sets the prim's floating text using llSetText(). Note that since the llListen() call listens only to the owner, there is no need to check whether the source of the chat is the right person. If the llListen() call specified NULL_KEY instead of llGetOwner(), the listen() event handler would accept changes from anyone chatting nearby on channel 9. Floating text is a persistent feature of the prim, in that it will stick even if the script is deleted or ownership is transferred. Just as for the sit target and sit text, it needs to be explicitly removed from the object.
DEFINITION llSetText(string text, vector color, float alpha) Sets the floating text associated with the enclosing prim. This is a persistent feature of the prim. text — The text for the prim, or "" to turn it off. New lines are OK, but lines must contain at least one character (e.g., a space) and the total must be less than 256 characters. color — The color with which to display the text. alpha — The alpha for the displayed text (0.0 = transparent, 1.0 = opaque).
A common enhancement to the flip-text script is to allow the owner to change the text display color. You will find this augmented listing at SYW HQ; Listing 2.7 uses a similar mechanism to parse the user's command.
Face Light Second Life environments can get very dark, and while darkness can be beautiful, scary, or romantic, it's often annoying when it is so dark that avatars cannot recognize each other's faces, as shown in Figure 2.5. A favorite remedy is a face light—a gadget you can have your avatar wear that illuminates their face with light from an invisible source. The required code is shown in Listing 2.7.
48
036-065 Chapter 02.indd 48
8/7/08 9:35:16 PM
CHAPTER 1
CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5
Figure 2.5: Face lighting makes it much easier to see avatars in the dark.
CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9
Listing 2.7: Face Light—Illuminate Me
CHAPTER 10
vector gColor = ; integer gLightOn = FALSE;
CHAPTER 11
lightControl() { llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 0.0, PRIM_POINT_LIGHT, gLightOn, gColor, 1.0, 10.0, 0.75]); }
CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
vector parseColor(string text) { list rgb = llParseString2List(text, [","," ",""], []); vector color256 = ; return color256/255.0; } default { on_rez(integer start_param) { llResetScript(); } state_entry() { llListen(9, "", llGetOwner(), ""); gLightOn = FALSE; lightControl(); } listen(integer channel, string name, key id, string message) { if (llGetSubString(message, 0, 0)=="=") { gColor = parseColor(llGetSubString(message,1,-1)); } else { if (message == "on") { gLightOn = TRUE; } else if (message == "off") { gLightOn = FALSE; } } lightControl(); } }
036-065 Chapter 02.indd 49
49
8/7/08 9:35:19 PM
CHAPTER 2
All the listen() components and color parsing are the same as in the flip-tag code. The script interprets the message as either a color (this time, to be used as the color of the projected light), or as an "on" or "off" command. If it sees a color specification it parses the color as in the flip tag; otherwise it updates the state. Then the script updates the light effect by invoking the new function lightControl(). The lightControl() function invokes a new mechanism of the llSetPrimitiveParams() function, PRIM_POINT_LIGHT:
M oving Your Avatar
llSetPrimitiveParams([PRIM_POINT_LIGHT, gLightOn, // Boolean, TRUE means turn light on gColor, // light color vector 1.0, // intensity (0.0-1.0) 10.0, // radius (0.1 - 10.0) 0.75]); // falloff (0.01 - 2.0)
around the World
Attachments A nimation C ontrolling Your Attachments S ummary
PRIM_POINT_LIGHT is described in more detail in the "Lights" section of Chapter 9, "Special Effects." Appendix A, "Setting Primitive Parameters," describes all the prim properties you can set using llSetPrimitiveParams(). You will want to play with the intensity, radius, and falloff values to find the most pleasing effect for your situation.
BUILD NOTE The simplest way to build a face light is to rez a plain box and drop in the code from Listing 2.7. Wear the box on the attachment point you'd like (such as the nose), then edit the box while you are wearing it, moving it to about a meter in front of your face. Change the size to be a 1cm cube. The script automatically makes the box transparent in lightControl() by setting the PRIM_COLOR attribute to a 0.0 alpha.
Wind Me Up! Listing 2.8 animates a key that sticks out of the back of an avatar like a wind-up toy, as shown in Figure 2.6. This script has two pieces: it spins the key according to simple rules, and it allows other avatars to wind you up, altering the parameters of the spinning key to make it spin faster.
50
036-065 Chapter 02.indd 50
Figure 2.6: Wind me up and let me go.
8/7/08 9:35:22 PM
Listing 2.8: Wind Me Up! // rotate around (original) z axis (this is a direction!) vector SPINAXIS = ; float float float float
MAXRATE = 10.0; DECAYTIME = 10.0; DECAYLEVEL = 0.8; WINDLEVEL = 2.0;
float gSpin = MAXRATE; float gMinSpin = 0.1;
// // // //
maximum spin rate—radians/sec seconds to wake between slowing down each decay cuts the speed by this much how much does each wind add
// how fast are we currently spinning? // keep spinning a little when wound down
CHAPTER 1
CHAPTER 2 CHAPTER 3 CHAPTER 4
spin() { llTargetOmega(SPINAXIS, gSpin, 1.0); } default { // no on_rez—we want to maintain spins level across rezzes state_entry() { spin(); llSetTimerEvent(DECAYTIME); } timer() { gSpin *= DECAYLEVEL; if (gSpin"+gOpenClosed); if (gOpenClosed==CLOSE) { doorman(OPEN); llSay(gOutboundCmdChannel, OPEN); } else { doorman(CLOSE); llSay(gOutboundCmdChannel, CLOSE); } }
80
066-097 Chapter 03.indd 80
9/24/08 12:46:43 PM
listen(integer channel, string name, key id, string message) { llOwnerSay("Listen event handler. fired on channel "+ (string) channel+message); if (channel==gInboundCmdChannel && message == OPEN) { doorman(OPEN); gOpenClosed = OPEN; } else if (channel==gInboundCmdChannel && message == CLOSE) { doorman(CLOSE); gOpenClosed = CLOSE; } else { // do nothing } } }
Rotating one door 90 degrees in the vertical plane is only half the job. The other part of the job is to make the other door operate in concert. The controller has two communication channels, inboundCmdChannel (990) and outboundCmdChannel (991). The right door will listen on its inboundCmdChannel and speak on its outboundCmdChannel. Note that randomly generated channels won't work in this case because both doors need to know both values. The script shown is for the left door; the right door reverses the two channels, listening on 991 and speaking on 990. In either case, when the door script is activated, the door posts a listen event via llListen(inboundCmdChannel, "", NULL_KEY, "");
After the doorman() function has completed, the script sends a command to the other door using llSay(). The listen() event handler on either door validates the message, making sure the message matches either the OPEN or CLOSE string. The other door then takes the appropriate and symmetrical action, and presto—your doors or gates swing open or snap shut in unison.
CHAPTER 1 CHAPTER 2
CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
BUILD NOTE If you use simple rectangles for the doors with these scripts, they will swing at their centerlines rather than at their outer edges. The simplest fix for this problem is to make the doors from rectangles that are twice as wide as you want (the y coordinate) and then apply a path cut begin of 0.375 and end of 0.875. This leaves the rotation point halfway up the edge of the door instead of right in the middle. You can achieve a similar effect in a different way by linking each door to an invisible root prim for the script that will act as a hinge.
Note the redundant test for the command channel: if (channel==gInboundCmdChannel && message ==OPEN) {
In this script there is only one llListen(); therefore the listen() event handler does not need to check whether the channel is gInboundCmdChannel. However, scripts that have multiple listens on multiple channels should always check which channel the message came from. You may want to add additional effects, such as a creaking-door sound that you trigger with llTriggerSound() when the door is touched (see Chapter 11, "Multimedia"), or a locking system that lets only the doors' owner lock and unlock the gate (see Chapter 6, "Land Design and Management"). Come by SYW HQ to pick up a copy of that version.
81
066-097 Chapter 03.indd 81
9/24/08 12:46:44 PM
Chat Relay CHAPTER 3
Talking to an O bject (A nd H aving it L isten) D ialogs C reating O bjects that Communicate with E ach O ther U sing L ink M essages for P rim -to -P rim Communication I nside an O bject E mail and I nstant M essaging S ummary
One of the issues you may encounter with interobject communication is that basic interobject conversations are limited to the ranges described in Table 3.1. Sometimes it can be useful to have a chat relay that allows chatted conversations to go farther than the 20m limit of llSay(). While llShout() goes up to 100m, how would you relay a chat over longer distances? llRegionSay() transmits a message to everyone (including listen() events) within a region, but won't cross sim boundaries. Listing 3.7 shows a script you can place into multiple objects to have the objects relay a chat across arbitrary distances (with an object placed every 75 or 100m apart). Make two prims, put them 75m apart, and have a friend chat into one box while you listen in the other. Because the script relies on llShout() to relay the messages, the maximum distance is 100m. Also, if the relays are less than 40m apart, an avatar standing in the middle will hear relays from both sides, and you probably want to avoid that.
Listing 3.7: Long-Distance Chat Relay integer ECHOCHANNEL = 197583;
// Pick your own obscure value
string gMyName; sayAs(string name, string message) { string realname = llGetObjectName(); llSetObjectName(name); llSay(0, "(Relaying)"+message); llShout(ECHOCHANNEL, name+"|"+message); llSetObjectName(realname); } default { on_rez(integer _x) { llResetScript(); } state_entry() { gMyName = llGetObjectName(); llListen(0, "", NULL_KEY, ""); // listen to open chat // listen to the ECHOCHANNEL for objects with the same name llListen(ECHOCHANNEL, gMyName, NULL_KEY, ""); } listen(integer channel, string name, key id, string message) { if (channel == ECHOCHANNEL) { // relay the message integer bar = llSubStringIndex(message, "|"); string avName = llGetSubString(message, 0, bar - 1); string avMsg = llGetSubString(message, bar + 1, -1); sayAs(avName, avMsg); } else { // only repeat messages from avatars! key kowner = llList2Key(llGetObjectDetails(id, [OBJECT_OWNER]),0); if (kowner == id) { // if the speaker owns itself (== avatar!) llShout(ECHOCHANNEL, name+"|"+message); } } } }
82
This script starts by setting up listen() events on both the public chat channel and on the ECHOCHANNEL. Chatting avatars (and possibly scripts) will chat on channel 0. If you've picked a really obscure ECHOCHANNEL, only your objects will chat on it. The listen() event handler checks first to see
066-097 Chapter 03.indd 82
9/24/08 12:46:45 PM
which channel the message was received on. If the channel was the ECHOCHANNEL, it parses the received message to pull out the avatar's name and the original chatted message, and calls the user-defined function sayAs() to repeat the message as if the original avatar had spoken. (Note the similarity to Listing 3.4.) If the channel wasn't the ECHOCHANNEL, it double-checks that an avatar spoke the message, because you don't want this script to relay other objects. To determine if something is an object or an avatar, the script calls llGetObjectDetails() asking for the OBJECT_OWNER. (Chapter 5 describes this function in more detail.) The useful thing to note is that avatars always own themselves, and thus the key of the owner, kowner, will equal the key of the chatter, id. If in fact it was an avatar chatting on channel 0, the script repeats the message with the avatar's name by shouting on the ECHOCHANNEL for other relays to hear. Note that the script calls llResetScript() in on_rez(): while open listeners don't care if the owner changed, the name of the object may have changed when it was sitting in the owner's inventory, thus breaking an un-reset listener! You could easily set up a long-distance relay using this script. However, you'd have to augment it with checks to make sure a given message is repeated only once by each relay—you probably don't want a message to stay in the relays indefinitely. The ECHOCHANNEL in Listing 3.7 is an example of one place you can't use a randomly generated value for the channel; all of the relay boxes need to use the same value. The script in Listing 4.4 in the following chapter shows how to have objects communicate with a randomly generated channel. If you were to do it here, the concept would be as follows: • Have a script rez the relay boxes, telling them the communication channel (mechanism described in Listing 4.4). • Have the relay script move the relay to its target location (using the moveTo() function from Listing 2.5), because the autorezzer can only make objects within 10m.
CHAPTER 1 CHAPTER 2
CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
One other use for chat relays in SL is as chat catchers. Essentially, the idea is that chat catchers listen to the public channel and forward the conversation to either email or IM. Chat catchers have a long Second Life heritage. Dr. Dobb's Journal has a version in a Linux article at www.ddj.com/linux-opensource/198800545?pgno=4. You can also report the chat to a blog using the ideas presented in Chapter 12, "Reaching outside Second Life." Examples include blogHUD (http://www.bloghud. com/) and Twitterbox (http://ordinalmalaprop.com/twitter/).
Using Link Messages for Prim-to-Prim Communication inside an Object When prims in a single object need to talk to each other, you use the llMessageLinked() family. Why use link messages and not chat? It may seem that chat works for everything you need. However, link messages have the following advantages: • Link messages are much more private (and hence more secure). • Regular chat messages have a built-in delay of 0.2 seconds; link messages are much faster. • Link messages don't have chat's limit of 255 characters. That's particularly useful if you're trying to pass data structures from prim to prim.
066-097 Chapter 03.indd 83
83
9/24/08 12:46:49 PM
NOTE You can cast any structure into a string, and then on the receiving end, correctly cast it back, as in this example:
CHAPTER 3
// Transmitter vector v = ; string s = (string)v; // Receiver vector v2 = (vector)s;
Talking to an O bject (A nd H aving it L isten)
If you are passing lists from prim to prim, you'll need the pair of functions llDumpList2String() and llParseString2List():
D ialogs C reating O bjects that Communicate with E ach O ther
// Transmitter list myList = [ "word", , 0.25 ]; string s = llDumpList2String( myList, "|" ); // Receiver list myList = llParseString2List( s, ["|"], []); vector v2 = (vector)llList2String( myList, 1 );
U sing L ink M essages for P rim -to -P rim Communication I nside an O bject E mail and I nstant M essaging S ummary
Now that you've been thoroughly convinced that link messages are the best thing to use whenever you can, recall that multiple objects attached to an avatar cannot be linked to each other—you'll need to use regular chat channels amongst them. Additionally, objects more than 10m apart can not be linked. Recall that objects are composed of one or more prims: together, all the prims form the link set of the object, and one of the prims is the root prim. When building an object from components, the root prim is the very last prim to be selected. When highlighting a complex object, the root is selected in yellow rather than the blue of all the others. Additionally, every prim has an object-unique link number that isn't visible through the Second Life client. As every prim in a link set has physical properties, it also has its own inventory of interesting things: every prim in a link set may have running scripts. A prim's link number is the only way for a script to directly reference another specific prim in the object. You'll find more descriptions on the LSL wiki under Link. The key point is that linked prims can have names, keys, and link numbers that are visible to other prims in the link set, and all these pieces of information may be used to coordinate actions between the elements of a link set. All LSL functions that operate on linked prims* take a link number as an argument to direct the control to that specific prim. There are some quirks, though. First, in an object made up of many objects, the root prim always has link number 1 (or the constant LINK_ROOT), but an object made from a single prim has link number 0. In addition, a few link-number constants have special meaning: LINK_SET indicates every prim in the set gets the same effect, LINK_ALL_OTHERS affects all prims except for the one holding the script, LINK_ALL_CHILDREN affects all prims except for the root, and LINK_THIS affects only the prim the script is in. A common use for link messages is to allow any part of a linked prim to be touched and have the touched part send a message to the other linked parts. One of the parts is (generally) the actual target, although nothing precludes the existence of multiple targets.
84
* Chapter 4's section on "Making the flower petals into a flower" presents these functions and how to manipulate linked objects. This chapter focuses on the communication between the linked prims.
066-097 Chapter 03.indd 84
9/24/08 12:46:51 PM
The scripts in Listings 3.8 and 3.9 illustrate how scripts inside one object can communicate, and you'll find that useful everywhere. When placed inside a lamp, Listing 3.8 sends a message to the root prim when the lamp is touched. Listing 3.9 receives the messages and turns the light on or off.
CHAPTER 1 CHAPTER 2
BUILD NOTE Insert the script of Listing 3.8 into each part of the lamp except the light bulb. Place the contents of Listing 3.9 into the bulb (which should be the root). If you've named each of the prims, the scripts will announce which prims received the initial touch.
CHAPTER 3 CHAPTER 4 CHAPTER 5
Listing 3.8: Touch Relay in a Linked Set
CHAPTER 6
default { touch_start( integer numDetected ) { llOwnerSay("Touched the "+llGetObjectName()); llMessageLinked(LINK_ROOT, 0, "Touched.", NULL_KEY); } }
CHAPTER 7
Listing 3.9: A Light Controlled by a Touch Relay Link Message integer gLightState;
CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
lamplighter() { if ( gLightState == FALSE) { llSetPrimitiveParams([PRIM_FULLBRIGHT,ALL_SIDES,FALSE, PRIM_COLOR,ALL_SIDES,,1.0, PRIM_POINT_LIGHT, FALSE, , 0.0, 0.0, 0.5 ]); } else { llSetPrimitiveParams([PRIM_FULLBRIGHT,ALL_SIDES,TRUE, PRIM_COLOR,ALL_SIDES,,1.0, PRIM_POINT_LIGHT,TRUE, , 1.0, 10.0, 0.6 ]); } } default { on_rez(integer num) { gLightState = FALSE; lamplighter(); } state_entry() { gLightState = FALSE; lamplighter(); } touch_start(integer total_number) { gLightState = !gLightState; // toggles between TRUE and FALSE lamplighter(); } link_message(integer sender_num, integer num, string str, key id) { gLightState = !gLightState; lamplighter(); } }
066-097 Chapter 03.indd 85
CHAPTER 15 APPENDIces
85
9/24/08 12:46:52 PM
CHAPTER 3
Listing 3.8 is wonderfully simple; it only sets up the relay messages using the function llMessageLinked(). You'll notice there is no corresponding event handler. That is because the handler, link_message(), needs to appear in the prims of the linked object that will hear the message (in this case the bulb). In llMessageLinked(), the first parameter is either a linked set constant or the specific link number of the prim that will be receiving the message. In this script we use LINK_ROOT to send a message to the root prim bulb; however, if you wanted, say, three light bulbs, then you'd probably use LINK_SET (or make three calls, each with the specific link number of one of the bulbs).
Talking to an O bject (A nd H aving it L isten)
DEFINITION
D ialogs C reating O bjects that Communicate with E ach O ther
llMessageLinked( integer linkNum, integer num, string message, key id)
U sing L ink M essages for P rim -to -P rim Communication I nside an O bject
Sends the num, message, and id to the prim specified by its linkNum. linkNum — Linked-set constant describing which prim(s) to send the message to. One of LINK_ ROOT, LINK_SET, LINK_ALL_OTHERS, LINK_ALL_CHILDREN, LINK_THIS, or the integer link number of the receiving prim.
E mail and I nstant M essaging
num, message, id — Up to the scripter's discretion.
S ummary
EVENT DEFINITION link_message( integer senderNum, integer num, string message, key id) {} This event is invoked in a script when the containing prim receives a message via llMessageLinked(). The SYW website provides additional documentation. senderNum — The link number of the sending prim. num, message, id — Up to the scripter's discretion.
The other three parameters of llMessageLinked() are passed directly to the link_ message() event, and are up to the scripter's discretion, meaning any value can be passed. This can be really useful. For example, if you want your object to respond slightly differently depending on the parameters: you would set up the link_message() event handler to do different things depending on the input parameters. When the root prim receives the link message, it toggles the lamp's state between the values of TRUE and FALSE. This trick can be a useful shortcut to the writing it out, like this: if (gLightState == TRUE) { gLightState = FALSE; } else { gLightState = TRUE; }
86
066-097 Chapter 03.indd 86
Figure 3.5 shows the result of this set of linked scripts.
9/24/08 12:46:54 PM
CHAPTER 1 CHAPTER 2
Figure 3.5: The avatar touched the shade to turn the light off, then touched it again to turn it back on.
CHAPTER 3 CHAPTER 4 CHAPTER 5
Prims with no touch event handler will report the touch event to the root prim. Therefore, technically the script in Listing 3.8 is not needed, because this script does nothing except catch the touch event and report it. However, in a longer, more complex script, child prims may want to do something when touched, in addition to reporting the event to the root prim. You can accommodate this using llPassTouches(), which allows both the touched child prim and the root to react to touch events.
CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11
DEFINITION
CHAPTER 12 CHAPTER 13
llPassTouches(integer pass)
CHAPTER 14 CHAPTER 15
When called with TRUE in a child prim, will allow touch events to be passed through to the root prim even when scripts in the child have touch event handlers. This function has no effect in a root prim or when the child has no touch event handlers.
APPENDIces
pass — TRUE if you want touch events to be passed through to the root prim.
Another approach to making sure the target prim gets the message while avoiding the necessity of putting llMessageLinked() relays in all parts of the build is to place a transparent prim around the entire build and let it send the message to the LINK_SET or the target prim. Figure 3.6 shows an elongated sphere around a complicated build, highlighted so you can see it.
Figure 3.6: To simplify touch management, you can place a transparent prim around a complicated build.
066-097 Chapter 03.indd 87
87
9/24/08 12:46:59 PM
A slight disadvantage with this approach is that if other residents enable Highlight Transparent in their client preferences, your object may appear strangely shaped. For most objects this is a nonissue, but for certain types of avatar attachments, wearing a huge bulbous prim may result in personal embarrassment. CHAPTER 3
Associating Prims with Their Link Numbers Talking to an O bject (A nd H aving it L isten) D ialogs C reating O bjects that Communicate with E ach O ther U sing L ink M essages for P rim -to -P rim Communication I nside an O bject E mail and I nstant M essaging S ummary
Scripts do not automatically know what link number is associated with what prim. Thus, if your script wants to talk to a specific prim, it needs to know what link number to use. Assuming you've named each of the prims in your object, the script in Listing 3.10 will create a strided list of pairs [objectname, linknum]. You could then use llListFindList() to look for the name (and then retrieve its link number) of the specific prim you want to send messages to. An observant reader will notice that the strided list happens to be constructed in order of link numbers; however, because you may not always want to cache the entire list, it is useful to remember the link numbers explicitly.
Listing 3.10: Matching Link Numbers to Prim Names list gLinkInfo; default { state_entry() { gLinkInfo = []; integer n = llGetNumberOfPrims(); if (n==1) { llOwnerSay("This is a prim, not a linked object"); } else { integer i; for (i = 1; i 0) { llSetTimerEvent(1); } else { llSetTimerEvent(0); } } on_rez(integer n) { llResetScript(); } attach(key k) { llResetScript(); } timer() { if (llGetAgentInfo(llGetOwner()) & AGENT_FLYING) { state flying; } } }
CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
state flying { state_entry() { llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS); // perms implicitly granted. take them. llTakeControls(gPermissions, TRUE, TRUE); // DON'T HAVE TO SET TIMER -- IT's CARRIED OVER // But just in case it's actually a bug... llSetTimerEvent(2); } timer() { integer nowFlying = llGetAgentInfo(llGetOwner()) & AGENT_FLYING; if (!nowFlying) { state default; } }
171
156-185 Chapter 07.indd 171
8/7/08 10:37:37 PM
attach(key k) { // detach if (k == NULL_KEY) { state default; } } control(key id, integer held, integer change) { if (held & CONTROL_UP) { llPushObject(id, VERTICAL_PUSH, ZERO_VECTOR, FALSE); } else if (held & CONTROL_DOWN) { llPushObject(id, -VERTICAL_PUSH, ZERO_VECTOR, FALSE); } // other keys controlled by avatar directly } state_exit() { llReleaseControls(); }
CHAPTER 7
P hysical O bjects Vehicles S ummary
}
The state_entry() event handler for the flying state requests PERMISSION_TAKE_ CONTROLS. The "Animation Overrides" section of Chapter 2, "Making Your Avatar Stand Up and Stand Out," has more details on llRequestPermissions(). Because the jet pack is attached, permissions are implicitly granted, so the script can immediately call llTakeControls() to grab the flying-control commands. Note that the third parameter is TRUE, which allows the avatar to receive the commands too. The state_entry() handler also sets a timer to watch for when the avatar stops flying.
NOTE The call to llSetTimerEvent() in the flying state is redundant. timer() events carry across state transitions! But just in case it's a bug that Linden Lab will correct, the script sets the timer event on state_entry() anyway.
DEFINITION llTakeControls( integer controls, integer accept, integer giveToAvatar) Takes controls from the agent task if PERMISSION_TAKE_CONTROLS has been granted. The control() event handler receives the keystrokes. controls — Bitmask of control fields, CONTROL_FWD, CONTROL_BACK, CONTROL_LEFT, CONTROL_RIGHT, CONTROL_ROT_LEFT, CONTROL_ROT_RIGHT, CONTROL_ UP, CONTROL_DOWN, CONTROL_LBUTTON, CONTROL_ML_LBUTTON. accept — Boolean for whether controls are wanted (TRUE) or should be ignored (FALSE). giveToAvatar — Boolean for whether to also send the signals to the avatar.
172
The control() event handler receives the user's keystrokes for each of the items that were grabbed, but it only acts on CONTROL_UP and CONTROL_DOWN. (Listing 7.5 will act on all of the user's keystrokes that match the list of controls in gPermissions.) This script sets the push based on which controls are currently in effect (held). The amount of vertical push is controlled by the value
156-185 Chapter 07.indd 172
8/7/08 10:37:39 PM
VERTICAL_PUSH. It would be an interesting extension to change the value to give more push at higher altitudes or to increase push slowly the longer keys are held down.
EVENT DEFINITION
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5
control(key id, integer held, integer recentChange) {}
CHAPTER 6
Triggered when one or more of the keyboard or mouse controls "taken" previously by llTakeControls() are pressed, held, or released. id — The UUID identity of the avatar using the controls. held — The bitfield of the keys being held down. recentChange — The bitfield of the keys changed since the last control event. You can work out whether a button has been held or touched by combining the held and recentChange parameters, as in the following example: if (held & recentChange & CONTROL_FWD) llOwnerSay("forward just pressed"); if (~held & recentChange & CONTROL_FWD) llOwnerSay("forward just released"); if (held & ~recentChange & CONTROL_FWD) llOwnerSay("forward held down; saw on previous call to control"); if (~held & ~recentChange & CONTROL_FWD) llOwnerSay("forward untouched");
CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
The state_exit() event handler allows the script to do some cleanup before transitioning out of the default state. Here, it just releases the controls that it has grabbed.
DEFINITION llReleaseControls() Stops taking inputs that were taken with llTakeControls(). Removes remaining control events from the queue.
A simple but useful extension to Listing 7.4 is to add an altimeter. The timer() handlers are a great place to make this function call: altimeter() { vector pos = llGetPos(); llSetText("Altitude is " + (string)((integer)pos.z), , 1 ); }
Flight Assist with Hover While the flight assist from Listing 7.4 allows you to go as high as you'd like, it won't keep you there. When you release the keys you'll slowly drift back to somewhere near 160m altitude. That's rather annoying
156-185 Chapter 07.indd 173
173
8/7/08 10:37:41 PM
CHAPTER 7
if you're trying to build a high-altitude project such as a mountain getaway or a sky platform, and you don't want to stand on it as shown in Figure 7.3. To solve this problem, the additions in Listing 7.5 use key releases to add an llMoveToTarget() that keeps the avatar at the most recent location. Then, when keys are pressed again, it calls llStopMoveToTarget() to allow the avatar to move around. Note that if Listing 7.4 had grabbed only the CONTROL_UP and CONTROL_DOWN keys, the hover assist would not work except for those two controls.
P hysical O bjects Vehicles S ummary
Figure 7.3: A jet pack with hover control makes it easer to work with a sky platform at high altitudes.
Listing 7.5: Flight Assist with Hover // Replace the control() handler from Listing 7.4 control(key id, integer held, integer change) { if (held & change & gPermissions) { // just touched llStopMoveToTarget(); } if (held & CONTROL_UP) { llPushObject(id, VERTICAL, ZERO_VECTOR, FALSE); } else if (held & CONTROL_DOWN) { llPushObject(id, -VERTICAL, ZERO_VECTOR, FALSE); } else if (~held & change & gPermissions) { // released llMoveToTarget(llGetPos(), 1.0); } // other keys controlled by avatar directly } // Replace the state_exit() handler from Listing 7.4 state_exit() { llReleaseControls(); llStopMoveToTarget(); }
A fun extension to this script would be to use the jet pack to get to high altitude, and then stop flying. You can then have a parachute or paraglider automatically deploy as you fall.
174
156-185 Chapter 07.indd 174
8/7/08 10:37:44 PM
CHAPTER 1
Energy Drain
CHAPTER 2
Most of the physics functions in LSL drain energy from the object. If an object runs out of energy, it ceases to be able to cause physical effects. For small objects, you generally don't have to worry: they replenish energy faster than they expend it. Most of the physical motion functions reduce the amount of energy available to the script. The SYW website has a table showing the available energy immediately after a physical function call for an object of mass 640.0, and every second thereafter until either energy restabilizes at 1.0, or a continuous drain is evident. Results will vary depending on a variety of factors, including sim load, but the trend will hold. llPushObject(), llApplyImpulse(), and llApplyRotImpulse() have the same instantaneous drain. On the day of the tests, llSetForce() had no measurable effect and llSetTorque() had a small effect, but the combined llSetForceAndTorque() had much more drain than either component independently. llMoveToTarget() showed a continuous drain. Although llSetHoverHeight() and llGroundRepel() have similar effects on the objects, llGroundRepel() drains less energy from the script. The amount of energy used depends on the object's mass. However, by simply staying in-world, objects can earn energy at a rate of 200 ÷ mass units per second. So, for example, an object with a mass of 270 would earn energy at a rate of about 0.741 units per second. The more massive the object, the longer it takes to earn energy. Table 7.5 shows the correlation between an object's mass, its energy expenditure, and its energy re-acquisition.
CHAPTER 4 CHAPTER 5 CHAPTER 6
CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15
Table 7.5: Energy Expenditure and Reacquisition After Calling
APPENDIces
llApplyImpulse(*llGetMass()) Object Mass
CHAPTER 3
Available Energy Immediately Following Call to llApplyImpulse()
Seconds to Return to Energy = 1.0
1.25
0.99875
0.0
270.00
0.98650
0.0
640.00
0.96800
0.1
1,250.00
0.93750
0.4
2,160.00
0.89200
1.2
3,430.00
0.82850
2.9
5,120.00
0.74400
6.6
7,290.00
0.63550
13.3
10,000.00
0.50000
25.0
It can also be useful to know that energy is earned and owned by an object and spent by scripts, so multiple scripts in one object can observe each other's expenditure.
NOTE If an object doesn't have enough energy to move, look for any prims in the object that can be hollowed. That will significantly reduce the weight and make it both easier to move and faster to earn energy.
175
156-185 Chapter 07.indd 175
8/7/08 10:37:46 PM
VEHICLES
CHAPTER 7
P hysical O bjects Vehicles S ummary
You've seen several methods for making objects move. LSL also provides extensive support for custom vehicles. With relatively natural physics you can make a vehicle that flies, floats, or glides. Your avatar sits on or in the vehicle, and pilots using the arrow keys and Page Up/Page Down. Your vehicle will bank, turn, hover, dive, and respond to gravity and friction. The LSL wiki has a good vehicle tutorial that goes into much more detail. A vehicle is created with llSetVehicleType(); you can make sleds, cars, boats, airplanes, and balloons. Each comes with a default model of what physics makes sense for that vehicle. An airplane uses linear deflection for lift, doesn't hover, and banks when it turns. The full set of default settings is described on the wiki, one page for each type of vehicle.
DEFINITION llSetVehicleType(integer type) Defines an object as a vehicle. type — One of VEHICLE_TYPE_NONE, VEHICLE_TYPE_AIRPLANE, VEHICLE_TYPE_ BALLOON, VEHICLE_TYPE_BOAT, VEHICLE_TYPE_CAR, VEHICLE_TYPE_SLED.
Listing 7.6 shows a very basic flying vehicle. The state_entry() event handler sets up some basic object properties. When the avatar sits down or stands up, the vehicle enables. The control() event handler deals with the details of what happens while flying.
Listing 7.6: Basic Vehicle float X_THRUST = 20; // forward/back float Y_THRUST = TWO_PI; // turning float Z_THRUST = 15; // up/down set_airplane_properties() { llSetVehicleType( VEHICLE_TYPE_AIRPLANE ); llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0 ); llSetVehicleFloatParam( VEHICLE_BUOYANCY, 0.997 ); llSetVehicleFloatParam( VEHICLE_BANKING_TIMESCALE, 0.01); llRemoveVehicleFlags( VEHICLE_FLAG_LIMIT_ROLL_ONLY ); }
176
default { state_entry() { llSetStatus(STATUS_PHYSICS, FALSE); llSetSitText("Fly"); vector size = llGetScale(); llSitTarget(, ZERO_ROTATION); set_airplane_properties(); }
156-185 Chapter 07.indd 176
8/7/08 10:37:52 PM
on_rez(integer num) { llResetScript(); } changed(integer change) { key agent = llAvatarOnSitTarget(); if (change & CHANGED_LINK) { if (agent == NULL_KEY) { vector currPos = llGetPos(); llReleaseControls(); llSetStatus(STATUS_PHYSICS, FALSE); llSetPos(currPos); // to deal with drift } else if (agent == llGetOwner()) { key pilot = llAvatarOnSitTarget(); llRequestPermissions(pilot, PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION); } } } run_time_permissions(integer perm) { if (perm & (PERMISSION_TAKE_CONTROLS)){ llTakeControls(CONTROL_UP | CONTROL_DOWN | CONTROL_FWD | CONTROL_BACK | CONTROL_RIGHT | CONTROL_LEFT | CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT, TRUE, FALSE); llSetStatus(STATUS_PHYSICS, TRUE); } if (perm & PERMISSION_TRIGGER_ANIMATION) { llStartAnimation("sit_ground"); llStopAnimation("sit"); } } control(key id, integer held, integer diff) { vector linearMotor; if (held & CONTROL_FWD) linearMotor.x = X_THRUST; else if (held & CONTROL_BACK) linearMotor.x = -X_THRUST;
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6
CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
if (held & CONTROL_UP) linearMotor.z = Z_THRUST; else if (held & CONTROL_DOWN) linearMotor.z = -Z_THRUST; llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, linearMotor); vector angularMotor; if (held & (CONTROL_RIGHT|CONTROL_ROT_RIGHT)) { angularMotor.x = Y_THRUST; } if (held & (CONTROL_LEFT|CONTROL_ROT_LEFT)) { angularMotor.x = -Y_THRUST; } llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, angularMotor); } }
The function set_airplane_properties() causes the vehicle to become an airplane using llSetVehicleType(), and overrides a couple of the default values using llSetVehicleParam(). Each vehicle type has a set of default values, so this script only overrides a few to get the desired behavior. A full description of the 28 available vehicle properties and their behavior is coming in the next section, "Vehicle Properties." Details on the 10 vehicle flags follow in the section "Vehicle Flags." This script's properties make it act reasonably well as a magic carpet, like the one in Figure 7.4.
156-185 Chapter 07.indd 177
177
8/7/08 10:37:54 PM
CHAPTER 7
P hysical O bjects Vehicles S ummary
Figure 7.4: Magic carpets are an extremely popular SL vehicle.
When the avatar sits down, the changed() event handler requests permissions to take controls and trigger animations. When permissions are granted in run_time_permissions(), the script grabs control of the appropriate motion-control keys using llTakeControls(), turns on the vehicle by setting STATUS_PHYSICS to TRUE, and finally changes the sitting animation to be sit_ground rather than a plain sit. When the avatar stands up, it releases controls using llReleaseControls() then uses llSetStatus() to set physics status to FALSE. Due to timing issues when setting status, turning off physics sometimes doesn't take effect; the call to the nonphysical function llSetPos() helps ensure physics gets turned off correctly. Just as in Listing 7.4, permissions are implicitly granted in Listing 7.6 because the avatar sat on the vehicle. A small optimization, therefore, would be to move the action from run_time_ permissions() into changed(). The control() event handler receives the user's keystrokes for each of the items that was grabbed. This script doesn't care whether keys are still being held—it just sets the motors based on which controls are currently in effect. The linear motor moves the vehicle in a straight line, while the angular motor turns it. This script only sets the x and z values of the linear motor (forward/back and up/down); setting the y value would allow the vehicle to slide left and right. The left/right control keys affect only the y value of the angular motor.
Vehicle Properties In general, the default settings for a particular vehicle are not exactly what you want. Moreover, default settings may change, so it can be a good idea to set all properties manually. You'll usually start with a basic vehicle type and change some of its default parameter settings. Unlike the functions llSetPrimitiveParams() and llParticleSystem(), which take a list of concatenated parameters, there are three function calls you need (one for each type of parameter): • llSetVehicleRotationParam() • llSetVehicleVectorParam() • llSetVehicleFloatParam()
178
156-185 Chapter 07.indd 178
They each take two parameters: the name of the vehicle property and its value.
8/7/08 10:37:56 PM
You can set the linear direction and speed of your vehicle with llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <x,y,z>);
The x direction is forward and back, y is left and right, and z is up and down. Note that these are local to the vehicle, not the world. If you don't want your car to fly, you should never set the z motor; you should, however, set the vehicle flag VEHICLE_FLAG_LIMIT_MOTOR_UP. Most vehicles prefer going forward or backward (the preferred axis of motion), so the y value, which slides left and right, will often be 0. A skateboard whose wheels can turn would have a linear y motor. A shopping cart with really rotten steering might pick a random value for its y vector.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6
The linear motor can change the direction the vehicle moves, but will not change the direction the vehicle is facing. For that, you need an angular motor: llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, <x,y,z>);
If you want to tip the avatar sitting on the vehicle in Listing 7.4 in the direction of acceleration, use the forward and backward controls to adjust the angular motor: if (held & CONTROL_FWD) angularMotor.y = 3; if (held & CONTROL_BACK) angularMotor.y = -3;
Similarly, if you want the vehicle to turn much faster than what the x value of angular motor yields, you could add a little push around the z-axis. Normally, a vehicle's "zero" orientation is that of its root prim. If you plan carefully, every vehicle's x, y, and z should make sense when it moves. Sometimes it may make sense to pick a root prim with a different orientation than the overall vehicle. Maybe, for example, you build a row boat whose root prim is the seat, set up so the avatar sits backward. In these cases, you'll need to use llSetVehicleRotationParam() with a new VEHICLE_REFERENCE_FRAME rotated appropriately.
CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
Table 7.6 summarizes these motor direction and rotation properties. Table 7.6: Vehicle Properties: Direction and Rotation Constant
Description
VEHICLE_ANGULAR_MOTOR_DIRECTION vector
Angular velocity the vehicle will try to achieve (turning).
VEHICLE_LINEAR_MOTOR_DIRECTION vector
Linear velocity the vehicle will try to achieve (range = 0 to 30 m/s).
VEHICLE_LINEAR_MOTOR_OFFSET vector
Offset from the vehicle's center of mass where the linear motor is applied (range = 0 to 100m).
VEHICLE_REFERENCE_FRAME rotation
Rotation of vehicle axes relative to the local frame.
Many of the parameters are of the form VEHICLE__TIMESCALE, measured in seconds. Shown in Table 7.7, the timescale properties control how rapidly the behavior occurs. A small value means you'll see the behavior very quickly, while a large value means it will take longer to see the behavior's full effect. The valid range is from 0.07 seconds to practical infinity. One pair of useful parameters is the motor timescale, VEHICLE_LINEAR_MOTOR_TIMESCALE, and the motor decay timescale, VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, both measured in seconds. The motor timescale indicates how quickly the motors cause the effect. The motor decay timescale is the exact opposite: after you apply a motor push, its effectiveness decays. For example, a large heavy cement truck with great brakes would have a large timescale value for getting up to speed and a small timescale value for stopping. A boat might have a pair of larger values so you can see the boat slowly accelerating, and then it
156-185 Chapter 07.indd 179
179
8/7/08 10:37:58 PM
quietly keeps sailing after you turn off the engine. Another useful timescale is for vertical attraction, which helps keep vehicles rightside up.
CHAPTER 7
P hysical O bjects Vehicles S ummary
There are also timescales for banking and hover behavior. Banking means leaning into the turn, and is what airplanes and motorcycles do when they turn—if they turn left, they lean left. Since physics isn't real in SL, you can lean out of the turn too, and maybe create some interesting vehicles. Hover indicates the height at which your vehicle tries to hover above the ground (or water). Table 7.7: Vehicle Properties: Timescale Constant
Description
VEHICLE_LINEAR_MOTOR_TIMESCALE float
Exponential timescale for the vehicle to achieve its full linear motor velocity.
VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE float
Exponential timescale for the linear motor's effectiveness to decay toward zero.
VEHICLE_LINEAR_FRICTION_TIMESCALE vector
Vector of timescales for exponential decay of linear velocity along the three vehicle axes.
VEHICLE_LINEAR_DEFLECTION_TIMESCALE float
Exponential timescale for the vehicle to redirect its velocity to be along its x-axis (preferring to travel forward).
VEHICLE_ANGULAR_MOTOR_TIMESCALE float
Exponential timescale for the vehicle to achieve its full angular motor velocity.
VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE float
Exponential timescale for the angular motor's effectiveness to decay toward zero.
VEHICLE_ANGULAR_FRICTION_TIMESCALE vector
Vector of timescales for exponential decay of angular velocity along the three vehicle axes.
VEHICLE_ANGULAR_DEFLECTION_TIMESCALE float
Exponential timescale for the vehicle to achieve full angular deflection.
VEHICLE_BANKING_TIMESCALE float
Exponential timescale for the banking behavior to take full effect.
VEHICLE_HOVER_TIMESCALE float
Period of time for the vehicle to achieve its hover height.
VEHICLE_VERTICAL_ATTRACTION_TIMESCALE float
Exponential timescale for the vehicle to make itself upright.
Another set of parameters, shown in Table 7.8, are of the form VEHICLE__ EFFICIENCY, measured from 0.0 to 1.0, indicating the range of none to a lot. VEHICLE_HOVER_ EFFICIENCY, for example, can be thought of as a slider between bouncy (0.0) and smoothed (1.0). The DEFLECTION efficiency parameters have slightly different semantics, more similar to the TIMESCALEs, in that they range from zero to maximum deflection. Table 7.8: Vehicle Properties: Efficiency Constant
Description
VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY float
Slider between 0 (no deflection) and 1 (maximum strength).
VEHICLE_BANKING_EFFICIENCY float
Slider between –1 (leans out of turns), 0 (no banking), and +1 (leans into turns).
VEHICLE_HOVER_EFFICIENCY float
Slider between 0 (bouncy) and 1 (critically damped) hover behavior.
VEHICLE_LINEAR_DEFLECTION_EFFICIENCY float
Slider between 0 (no deflection) and 1 (maximum strength).
VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY float
Slider between 0 (bouncy) and 1 (firm) to keep vehicle vertical.
180
156-185 Chapter 07.indd 180
8/7/08 10:37:59 PM
The last set of useful parameters provides more control over the realism (or nonrealism) of your vehicle. You're already familiar with Banking and Hover. Buoyancy indicates how much gravity affects your vehicle. SL has complete antigravity. While buoyancy and hover height are independent, you may need to set a high buoyancy if hover height is low. In addition to appropriate TIMESCALE and EFFICIENCY parameters, banking, buoyancy, and hover properties can be controlled with the values shown in Table 7.9. Table 7.9: Vehicle Properties: Banking, Hover, and Buoyancy Constant
Description
VEHICLE_BANKING_MIX float
Slider between 0 (banking allowed when stopped) and 1 (banking only when moving forward).
VEHICLE_BUOYANCY float
Slider between –1 (double-gravity) and 1 (full antigravity).
VEHICLE_HOVER_HEIGHT float
Desired height for the vehicle's center of mass to hover (ground-, water-, or globalrelative); maximum 100m.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6
CHAPTER 7 CHAPTER 8 CHAPTER 9
WARNING
CHAPTER 10
You may notice that you can also set many vehicle parameters with other LSL calls that push your objects around, such as llSetBuoyancy() and llSetTorque(). Don't mix and match. You'll get very unpredictable behavior.
CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
Vehicle Flags In addition to a set of properties, vehicles have a set of flags (detailed in Table 7.10) that can be added with llSetVehicleFlags() and removed with llRemoveVehicleFlags(). These flags control things like whether ground-based vehicles can fly, whether airplanes can bank, and what to consider when hovering. Table 7.10: Vehicle Flags Constant
Description
VEHICLE_FLAG_NO_DEFLECTION_UP
No linear deflection up. (No flying cars.)
VEHICLE_FLAG_LIMIT_ROLL_ONLY
For vehicles with a vertical attractor that want to be able to climb/dive; for instance, airplanes that want to use the banking feature.
VEHICLE_FLAG_LIMIT_MOTOR_UP
Prevents ground vehicles from motoring into the sky.
VEHICLE_FLAG_HOVER_WATER_ONLY
Ignore terrain height when hovering.
VEHICLE_FLAG_HOVER_TERRAIN_ONLY
Ignore water height when hovering.
VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT
Hover at global height instead of height above ground or water.
VEHICLE_FLAG_HOVER_UP_ONLY
Hover doesn't push down.
VEHICLE_FLAG_MOUSELOOK_STEER
Steer the vehicle using mouselook (first-person view). Use this flag to make the angular motor try to make the vehicle turn such that its local x-axis points in the same direction as the client-side camera.
VEHICLE_FLAG_MOUSELOOK_BANK
Same as above but relies on banking. It remaps left-right motions of the client camera to rotations about the vehicle's local x-axis.
VEHICLE_FLAG_CAMERA_DECOUPLED
Makes mouselook camera rotate independently of the vehicle. By default the client mouselook camera will rotate about with the vehicle, but when this flag is set the camera direction is independent of the vehicle's rotation.
181
156-185 Chapter 07.indd 181
8/7/08 10:38:01 PM
DEFINITION llSetVehicleFlags(integer flags)
CHAPTER 7
Sets each flag specified in flags to TRUE flags — Bitmask of VEHICLE_FLAG_* flags shown in Table 7.10.
P hysical O bjects Vehicles
DEFINITION
S ummary
llRemoveVehicleFlags(integer flags) Sets each flag specified in flags to FALSE flags — Bitmask of VEHICLE_FLAG_* flags shown in Table 7.10.
NOTE There are many interesting ways you can augment the script in Listing 7.4. Many of the magic carpets in SL are controlled by a script originally developed by Cubey Terra because it is freely distributed. Cubey Terra's script is much more complete and complex than Listing 7.4; you can get a copy of it in many places around SL, including at SYW HQ. A complete suite of scripts for a sailboat is described at http://rpgstats.com/wiki/index.php?title=LibrarySailboat. Also, you can get a working Flying Tako Sailboat, with modifiable scripts, at Grey . You can allow other avatars (not the owner) to drive your vehicle by removing the condition if(agent == llGetOwner()) in the changed() event handler. If you do this, add a "Lo-Jack" beacon to tell the owner where the vehicle was abandoned. Email works nicely. Another nice touch is particle scripts for exhaust on a car, magic fairy dust on a carpet, flames on a rocket, or a contrail on an airplane. Since the particles won't be emitted from the root prim, you'll need to add a set of linked messages to turn the particles on and off at appropriate times. You can pass information such as the state of the vehicle (in the message) and the name of the pilot (in the key).
Cameras and Mouselook You should probably set your camera position and angle to make it easier to drive the vehicle. A good position is slightly above and behind your avatar. Camera offsets are persistent features of the object, and need to be changed explicitly: vector offset = ; llSetCameraEyeOffset(offset); vector angle = ; llSetCameraAtOffset(angle);
182
156-185 Chapter 07.indd 182
8/7/08 10:38:04 PM
You can also use mouselook to drive the vehicle (type m to enter mouselook), using the MOUSELOOK vehicle flags. It's hard to get it right, but when you do you can achieve a "driver's-eye view." By default the mouselook direction will change when the vehicle you are sitting on moves: since this is likely to cause motion sickness, disconnect the mouselook camera from the vehicle's rotation by setting the VEHICLE_ FLAG_CAMERA_DECOUPLED flag. To modify Listing 7.4 for a working mouselook vehicle—albeit in need of other work—first comment out the last line in control() that sets the angularMotor, and then add the following lines to set_airplane_properties():
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6
llSetVehicleFlags(VEHICLE_FLAG_MOUSELOOK_BANK | VEHICLE_FLAG_CAMERA_DECOUPLED); llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, );
There are seven camera control functions in LSL, listed in Table 7.11. They can be used for objects other than vehicles, but there aren't many real examples. Table 7.12 shows a list of camera-related constants that support the function llSetCameraParams().
CHAPTER 7
Table 7.11: Camera-Control Functions and the Permissions that Must Be Requested
CHAPTER 8
Function
Description
llClearCameraParams()
Resets all camera parameters PERMISSION_CONTROL_CAMERA to default values and turns off scripted camera control.
vector llGetCameraPos()
rotation llGetCameraRot()
Returns a vector that is the current camera position for the agent the task has permissions for.
Permission Required
PERMISSION_TRACK_CAMERA
CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
Returns a rotation that is PERMISSION_TRACK_CAMERA the current camera orientation for the agent the task has permissions for.
llForceMouselook(integer mouselook)
Sets whether a sitting avatar should be forced into mouselook when they sit on this prim. If mouselook is TRUE, the avatar is forced into mouselook mode when they sit.
—
llSetCameraAtOffset(vector offset)
Sets the camera at offset from the object's center when an avatar sits on it.
—
llSetCameraEyeOffset(vector offset)
Sets the camera eye offset — from the object's center when an avatar sits on it.
llSetCameraParams(list properties)
PERMISSION_CONTROL_CAMERA Sets multiple camera parameters at once. properties is a strided list of stride 2 with the format [property_1, data_1, property_2, data_2, ..., property_n, data_n]. Each property is a named constant, as shown in Table 7.12.
CHAPTER 15 APPENDIces
183
156-185 Chapter 07.indd 183
8/7/08 10:38:05 PM
Table 7.12: Camera-Property Constants for llSetCameraParams()
CHAPTER 7
P hysical O bjects Vehicles
Property
Default Value
Value R ange
Description
CAMERA_ACTIVE integer isActive
FALSE
TRUE or FALSE
Sets scripted control of the camera on or off.
CAMERA_BEHINDNESS_ANGLE float degrees
10.0
0 to 180
Sets the angle in degrees within which the camera is not constrained by changes in target rotation.
CAMERA_BEHINDNESS_LAG float seconds
0.0
0 to 3
Sets how strongly the camera is forced to stay behind the target if outside of the "behindness" angle.
CAMERA_DISTANCE float meters
3.0
0.5 to 10
Sets how far away the camera should be from its target.
CAMERA_FOCUS vector position
—
—
Sets camera focus (target position) in region coordinates.
CAMERA_FOCUS_LAG float seconds
0.1
0 to 3
Sets how much the camera lags as it tries to aim toward the target.
CAMERA_FOCUS_LOCKED integer isLocked
FALSE
TRUE or FALSE
Locks the camera focus so it will not move.
CAMERA_FOCUS_OFFSET vector meters
to
Adjusts the camera focus position relative to the target.
CAMERA_FOCUS_THRESHOLD float meters
1.0
0 to 4
Sets the radius of a sphere around the camera's target position within which its focus is not affected by target motion.
CAMERA_PITCH float degrees
0.0
–45 to 80
Adjusts the angular amount the camera aims straight ahead versus straight down, maintaining the same distance; analogous to "incidence."
CAMERA_POSITION vector position
—
—
Sets camera position in region coordinates.
CAMERA_POSITION_LAG float seconds
0.1
0 to 3
Sets how much the camera lags as it tries to move toward its "ideal" position.
CAMERA_POSITION_LOCKED integer isLocked
FALSE
TRUE or FALSE
Locks the camera position so it will not move.
CAMERA_POSITION_THRESHOLD float meters
1.0
0 to 4
Sets the radius of a sphere around the camera's ideal position, within which it is not affected by target motion.
S ummary
184
156-185 Chapter 07.indd 184
8/7/08 10:38:07 PM
PHYSICS AND FLEXIPRIMS If you've played with a magic carpet, you may have noticed that the tassels are not flexible. You may have even tried to make them flexible (as in the graphic here) and found that the vehicle no longer works. You can't add a flexiflag for your favorite sports team, you can't add streamers for newlyweds, and you can't add a banner behind your airplane— at least, not without some work. The difficulty is probably a bug: it seems you can't use llSetStatus(STATUS_PHYSICS, TRUE) when the linkset contains a flexiprim. The workaround, therefore, is to enable physics when there are no flexiprims, then add the flexiprims to the linkset. However, you can't change links when the avatar is seated, so you have to do it before they sit down. The basic approach is as follows: 1. Have the avatar touch or say something to "turn on" the engines. 2. Break all links to flexiprims. 3. Enable physics. 4. Relink the flexiprims.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6
CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
5. Allow the avatar to sit down.
CHAPTER 15
You'll have several challenges to deal with, including these:
APPENDIces
• Making sure you recognize the difference between linking prims and the avatar sitting. • Making sure the vehicle doesn't drift away from the flexiprims (physics may be on just long enough). • Dealing with the fact that linking takes a while and the avatar needs to know when they can sit; they shouldn't be allowed to sit unless the vehicle is completely ready to fly. • Correctly handling permissions requests—just remember that permissions do not accumulate. The full script is available at SYW HQ. It's a hack, but it works.
SUMMARY Figuring out the way the physics engine works and how to manipulate it allows you to create all kinds of interesting objects. The effort is well worth it! Be aware, though, that the SL physics engine is probably the buggiest part of the system, despite the transition from the Havok1 to the Havok4 engine. If you find yourself frustrated about something not working the way you expect it to, browse the JIRA pages (https://jira.secondlife.com/secure/Dashboard.jspa) to see if any bug reports have been made.
156-185 Chapter 07.indd 185
185
8/7/08 10:38:15 PM
Inventory
Chapter 8
Much as avatars collect, use, and manage sets of things in their inventory, objects themselves have an inventory that can be manipulated manually by the owner or via script control. Of course, you have been using inventory all along in this book: scripts can run only when they are in the inventory (or Contents folder) of an object that is either rezzed in-world or attached to an avatar. Scripts have also accessed items in inventory, mainly to hold the assets that your scripts need to do their job, such as animations for your avatar in Chapter 2, "Making Your Avatar Stand Up and Stand Out," or prims to rez in Chapter 4, "Making and Moving Objects." This chapter focuses on the process of handling inventory, including transfers and permissions.
186-197 Chapter 08.indd 186
9/24/08 12:40:45 PM
186-197 Chapter 08.indd 187
9/24/08 12:40:51 PM
INVENTORY PROPERTIES
CHAPTER 8
Inventory P roperties iving G I nventory Taking I nventory P ermissions S ummary
A prim's inventory folder can contain every kind of SL inventory except calling cards. Table 8.1 shows the supported set of items. The descriptions should be self-evident from the names of the constants. Note that these values are not bit flags: you cannot combine these values in a single call to the inventory functions to select, for instance, notecards and objects. Because the inventory functions do not distinguish between textures and snapshots, you will always use INVENTORY_TEXTURE for snapshots as well. Finally, because object inventories cannot currently hold calling cards, there is no constant defined for them. Table 8.1: Inventory Types Constant Name
Value
INVENTORY_NONE
–1
INVENTORY_ALL
–1
INVENTORY_TEXTURE
0
INVENTORY_SOUND
1
INVENTORY_LANDMARK
3
INVENTORY_CLOTHING
5
INVENTORY_OBJECT
6
INVENTORY_NOTECARD
7
INVENTORY_SCRIPT
10
INVENTORY_BODYPART
13
INVENTORY_ANIMATION
20
INVENTORY_GESTURE
21
Listing 8.1 shows how to find the number of items in inventory and several properties about each item. The functions used here are described in Table 8.2. The function llRequestInventoryData() goes to the SL dataserver to find more information about the inventory item. Currently, it is used only for landmark objects, and will return the landmark's region coordinates.
Listing 8.1: Getting Properties of Inventory Items default { state_entry() { integer i; integer n = llGetInventoryNumber(INVENTORY_ALL); for (i=0; i MEMORY_LENGTH) { gAvatars = llDeleteSubList(gAvatars,0,0); } }
186-197 Chapter 08.indd 189
189
9/24/08 12:41:00 PM
CHAPTER 8
Inventory P roperties iving G I nventory Taking I nventory P ermissions S ummary
default { state_entry() { llVolumeDetect(TRUE); } touch_start(integer num) { // report who received notecard if (llDetectedKey(0) == llGetOwner()){ integer i; integer numAvs = llGetListLength(gAvatars); for (i=0; i contains all parameters passed across the network. The single <param> here contains a composite object delimited by the <struct> tag pair. The <struct> has two element slots (each identified by a <member> tag), one being a string value for the channel, $CHANNEL, and the other being the flag you want to change to, $FLAG_NAME. The server invocation is packaged and sent, and the client waits for its reply.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9
SUMMARY
CHAPTER 10 CHAPTER 11
This chapter covered the basics of communicating into and out from Second Life using both Web techniques and the XML-RPC model. Email, a particularly useful method for external communications, was covered in Chapter 3, "Communications." CHAPTER 12
WHEN TO USE EMAIL, HTTP, OR XML-RPC
CHAPTER 13 CHAPTER 14
Email is a fast way to get inter-object or inter-world communication. It supports one-way messages. Each call to send an email has a 20-second delay penalty to the script; you can reduce this penalty with a suite of worker scripts. Reading email can happen with no artificial script delay. Email is limited to 4,096 characters and queues up to 100 emails. Email is a sequence of one-shot messages between objects in-world, or an object in-world and something out-of-world.
CHAPTER 15 APPENDIces
HTTP is a call from SL to the real world, with a follow-up connected response. It can not be the server. It is the fastest way to interact. Requests are limited to 25 per 20 seconds; the delay comes from waiting for the web server to respond. Limited to 2,048 characters. HTTP is often used to store persistent data—the data is pushed to the server and retrieved when needed (as shown in Listing 12.3). XML-RPC is the only way for SL to be the server side of the interaction. By allowing continuing conversations, it supports the largest amount of data transfer in and out of Second Life: each message is limited to 255 characters plus one 32-bit integer, but many messages can be transferred on the same channel. Only one message per channel can be queued at a time; by using the key returned by llOpenRemoteDataChannel(), you can manage multiple outstanding requests. This is the slowest of the three interactions. Each message has a three-second delay; your script cannot under any circumstances respond to more than one message every three seconds. Note that both email and XML-RPC can be used point-to-point in-world. If you want to implement a server that responds to requests, email is often better than XML-RPC. (HTTP is not an option.) The wiki page http://lslwiki.net/lslwiki/wakka. php?wakka=communications has a useful summary that describes the tradeoffs in more detail.
For further reading, Chapter 15, "New and Improved," has a section on a new LSL feature that allows your script to act as an HTTP server to the rest of the Internet as well as for SL web clients. Finally, the SYW website has a bonus chapter that discusses interacting with the Second Life world through modified and alternative clients instead of with the official SL viewer.
277
258-277 Chapter 12.indd 277
8/7/08 11:48:36 PM
278-309 Chapter 13.indd 278
8/8/08 12:04:24 AM
Chapter 13
Money Makes the World Go 'Round
Second Life gives you several ways to interact with other people and objects using money. You can give money directly to other avatars and automatically accept money given to you. (You don't script such interactions because they are avatar-to-avatar; there is no object involved to host a script.) You can also pay an object or have an object move money around on your behalf. In Chapter 13 we'll cover the scripting behind financial transactions.
278-309 Chapter 13.indd 279
8/8/08 12:04:29 AM
TRANSACTION BASICS
CHAPTER 13
ransaction T B asics Redistributing Wealth S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
When you pay an object, you are transferring money from your account into the account of the person or group that owns the object. The scripts in a paid object are notified of the transfer details and can react to it, for example by giving an object to a paying avatar. Scripts may also give money to avatars by transferring money out of your account to theirs: you can program objects to give rewards, to pay salaries, or even to act as a bank teller by sharing a single account with multiple avatars. Who gets the money? It's transferred to the object's owner. If the object is group-deeded, the money temporarily goes into the group's account, to be disbursed when there is enough to distribute to all members who have the Make/Receive Group Payments option enabled. Figure 13.1 shows a pop-up menu you might see when you pay an object. This is the default menu, with four buttons and a box for entering your own amounts. You can change or hide any of these amounts if, for instance, you want to accept payment of only one specific price.
Figure 13.1: Paying an object
280
Second Life allows scripted objects to move money around on your behalf. Because you wouldn't want objects asserting free rein over your account without your knowledge, you'll be asked to grant objects special permissions before they request money from your account. Figure 13.2 shows how this looks: the menu's yellow background is clearly different from the rest of the permissions menus. If you grant permission, you really want to get it right; remember, you are playing with real money. You'll want to thoroughly debug your scripts before you grant permission, because one endless loop around a payment or one misplaced decimal point can clean out your account remarkably quickly.
278-309 Chapter 13.indd 280
8/8/08 12:04:36 AM
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11
Figure 13.2: Granting debit permission
You also get feedback when these operations go through. All transactions are logged in your account's transaction history page*, and you'll see your account balance changing in the upper-right corner of your viewer window. Furthermore, when you grant debit permission you will get a message in your chat log indicating that you've done so.
CHAPTER 12
CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
REDISTRIBUTING WEALTH This section is devoted to moving money around—accepting money from avatars, distributing money to other avatars, and even giving money away.
Tip Jars The simplest scripted money-transfer devices are tip jars. People rez these in all sorts of places as a way for residents to give thanks for a job well done, whether as a good tour guide or club host, or as a contributor to the maintenance of a beautiful or fun environment. Simply pay the tip jar, and your money is redirected to the correct place. There are, of course, lots of variations: simple personal ones like the hat that a street performer might pass, ones that perform a little service or put on a show, and ones that collect tips for different avatars depending on who is working or even split collected proceeds between collaborators. All you really need to make this work is a script with a money() event handler.
* https://secondlife.com/account/transactions.php
278-309 Chapter 13.indd 281
281
8/8/08 12:04:43 AM
EVENT DEFINITION money(key payer, integer amount)
CHAPTER 13
This event handler is called whenever the object is paid. Without this event handler, the Pay menu is not available and the object cannot be paid.
ransaction T B asics
payer — The key of the avatar that is making the payment.
Redistributing Wealth
amount — How many Linden dollars (L$) were paid.
S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
Unlike with chat listeners, you do not need to explicitly tell the script to subscribe to money() events, and there aren't any filters. If a script has a money() event handler, it will be invoked when the object is paid. If the paid prim doesn't have a money() event handler, the scripts in the root prim receive the event.
NOTE The money() event handler only notes payments. Like a bank transaction statement, it cannot change the transfer of funds in any way (such as refuse the transfer or accept only a portion), because it is only a notification that a transfer has already happened. Your script can undo the effect of a payment by refunding money, but it is important to remember you are then conducting a completely separate transaction, not changing the original one. (See the discussion of llGiveMoney() later in this chapter.)
Although a money() event handler alone is sufficient, it isn't very interesting. Listing 13.1 is a flexible personal tip jar that will be used as the basis for extensions later.
Listing 13.1: Simple Tip Jar integer SUGGESTED = 50;
// the suggested donation
displayLabel() { llSetText(llGetObjectName(), , 1.0); } thankYou(key giver, integer love) { llSay(0, "Thank you "+llKey2Name(giver) + "!"); displayLabel(); poof(giver, love); } poof(key giver, integer love) { llParticleSystem([]); llParticleSystem([PSYS_PART_FLAGS, PSYS_PART_EMISSIVE_MASK, PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_EXPLODE, PSYS_PART_START_COLOR, ]); llSleep(0.5); llParticleSystem([]); }
282
278-309 Chapter 13.indd 282
8/8/08 12:04:45 AM
default { on_rez(integer p) { llResetScript(); } state_entry() { llSetPayPrice(SUGGESTED,[SUGGESTED/2,SUGGESTED,SUGGESTED*2,SUGGESTED*4]); llSetClickAction(CLICK_ACTION_PAY); displayLabel(); } money(key giver, integer love) { thankYou(giver, love); } }
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9
The displayLabel() function is a placeholder for any number of displays, mainly to let the tip jar indicate what is going on with it. This version puts only the name of the object in floating text.
CHAPTER 10
The thankYou() function is called directly by the money() event handler to make the tip jar react in some way to getting tipped. This one calls llSay() to chat a thank-you message, calls displayLabel() to update the floating text label in case the text needs updating, and calls the poof() function to make a nice special effect.
CHAPTER 12
The poof() function provides visible feedback, encouraging onlookers to tip. A nice, short, particle explosion is fine for this example, but anything fun will do: the lessons from Chapter 9, "Special Effects," could be especially useful here to launch fireworks for big tippers, or a particle stream of hearts toward the avatar that just paid you. The main new element here is in the state_entry() event handler: it uses llSetPayPrice() to set up the payment menu to contain values of your choosing. If you do not call llSetPayPrice(), you will see a default menu like the one shown in Figure 13.1.
CHAPTER 11
CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
DEFINITION llSetPayPrice(integer entryDefault, list quickPays) Defines the options used in the payment menu when an avatar selects Pay from the pie menu, or when the default touch action is Pay and the object is clicked. The pay price is a persistent feature of the prim, not the script—once set, it remains set even if the script is removed. entryDefault — The default value to appear in the text-entry payment box, where the user may choose any amount to enter (in Figure 13.1, this is the box with the number 50 in it). If specified as PAY_HIDE, there will be no text-entry box. quickPays — A list of four integer values to be used as quick-payment buttons. Any or all may be specified as PAY_HIDE to avoid showing that specific button.
The entryDefault parameter allows you to set a default amount that displays when the user clicks Pay. (The user can change that amount.) The first call to llSetPayPrice() in the following code will produce a payment menu identical to the one shown in Figure 13.1: // A dialog that is the same as the default llSetPayPrice(1, [1,5,10,20]); // A blank payment dialog. You can't pay it! llSetPayPrice(PAY_HIDE, [PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE]);
278-309 Chapter 13.indd 283
283
8/8/08 12:04:46 AM
// A dialog with the entry box initialized to L$10 and no buttons. llSetPayPrice(10, [PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE]); // A dialog with one button for L$10 llSetPayPrice(PAY_HIDE, [10, PAY_HIDE, PAY_HIDE, PAY_HIDE]); CHAPTER 13
You can call llSetPayPrice() as many times as you need over an object's lifetime. This probably isn't useful for tip jars, but it is critical for advanced vendors that sell products of different prices.
ransaction T B asics
NOTE
Redistributing Wealth S elling I t ! R entals S ervices
Keep in mind that limits on how many pending events can be queued are still in force (as discussed in Chapter 1, “Getting a Feel for the Linden Scripting Language”). This isn’t much of a problem if avatars are paying your object unless your money event handlers take a long time to run. If you expect your object to deal with hundreds of payments per second, you must avoid using any functions that introduce long delays, or you risk losing events!
and
G ambl ... uh ... G ames of S kill S ummary
state_entry() also calls llSetClickAction(), so that a mouse click will pay the object instead of touch it. You can, of course, accomplish this using the SL build tools, but in viewers since version 1.19.1, you can also have your script do it for you.
DEFINITION llSetClickAction (integer action) Sets the action to take when an avatar clicks on the prim that contains the script. The default behavior is CLICK_ACTION_TOUCH, indicating that a click should trigger the touch(), touch_start() and touch_end() event handlers. action — A CLICK_ACTION_* value from the following table. CLICK_ACTION_PLAY and CLICK_ACTION_OPEN_MEDIA currently act by triggering the parcel media stream*.
Constant
Value
Effect of Clicking on the Prim
CLICK_ACTION_NONE
0
Perform the default action.
CLICK_ACTION_TOUCH
0
Touch the prim.
CLICK_ACTION_SIT
1
Sit on the prim.
CLICK_ACTION_BUY
2
Buy the object.
CLICK_ACTION_PAY
3
Pay the prim.
CLICK_ACTION_OPEN
4
Open the prim, showing the contents.
CLICK_ACTION_PLAY
5
"Play" the parcel media stream on the prim, as though the media Play button in the viewer were pressed.
CLICK_ACTION_OPEN_MEDIA 6
Open the parcel media stream in the user's preferred browser.
* See the "Streaming Media" section of Chapter 11, "Multimedia" for details on setting up parcel media streams. Future SL releases may support per-prim media streams, in which case, the behavior of action may change.
284
278-309 Chapter 13.indd 284
8/8/08 12:04:48 AM
As usual, the script itself is only part of the package. Figure 13.3 shows an unusual personal tip jar in the shape of a cow: if you tip it enough Linden dollars, the cow will actually tip over!
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11
Figure 13.3: DJ Rocky’s tipping cow at the Lonely Yak, BlaksleeWorld
CHAPTER 12
CHAPTER 13
A Shared Tip Jar The next example expands on the tip jar in a few ways: first, whereas the previous example is rezzed by the avatar who receives the tips, this one is intended to be placed by a club owner. The owner wants to make it easy to tip the club's hosts and DJs, and often wants to take a cut of the tips. To support this, all payments pass through the owner's account. The script in Listing 13.2 implements this mechanism, adding tip redistribution, a tax function, and a method for your hosts to log in and out of the tip jar.
CHAPTER 14 CHAPTER 15 APPENDIces
Listing 13.2: A Shared Tip Jar integer SUGGESTED = 50; integer TAX = 10;
// suggested tip // the house's tip tax (percent)
key gHost = NULL_KEY; integer gLast = 0; integer gHostTotal = 0; displayLabel() { if (gHost == NULL_KEY) { llSetText(llGetObjectName(), , 1.0); } else { string text = llKey2Name(gHost)+"'s "+llGetObjectName()+"\n"+ " Last tip "+ (string)gLast + ", total tips " + (string)gHostTotal; llSetText(text, , 1.0); } } // poof() and thankYou() unchanged login(key host) { gHost = host; gHostTotal = 0; gLast = 0; displayLabel(); }
278-309 Chapter 13.indd 285
285
8/8/08 12:04:51 AM
CHAPTER 13
ransaction T B asics Redistributing Wealth S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
default { on_rez(integer p) { llResetScript(); } state_entry() { llRequestPermissions(llGetOwner(), PERMISSION_DEBIT); } run_time_permissions(integer perms) { if (perms & PERMISSION_DEBIT) { list buttons = [SUGGESTED/2, SUGGESTED, SUGGESTED*2, SUGGESTED*4]; llSetPayPrice(SUGGESTED, buttons); displayLabel(); } else { llOwnerSay("No debit permission"); } } money(key giver, integer love) { gLast = love; if (gHost != NULL_KEY) { integer toHost = (love * (100 - TAX))/100; llGiveMoney(gHost, toHost); llInstantMessage(gHost,llKey2Name(giver)+" tipped you L$"+(string)love); gHostTotal += toHost; } thankYou(giver, love); } touch_start(integer count) { key id = llDetectedKey(0); if (llDetectedGroup(0)) { if (gHost == NULL_KEY) { login(gHost); llInstantMessage(id, "You are logged in"); } else if (id == gHost) { llInstantMessage(id, "Thank you for hosting! You made L$"+ (string) gHostTotal); login(NULL_KEY); } else { llInstantMessage(id, "Sorry, "+llKey2Name(gHost)+" is logged in"); } } } }
displayLabel() prints more information than before, including the size of the last tip and the running total—all the better to encourage more people to tip. The new integer variables gLast and gHostTotal support this additional information by tracking the values to be printed. The poof() and thankYou() functions are identical to those in the simple tip jar. The next change is to support tip-jar-user login. This allows the host on duty to claim the tips generated during their shift. When someone touches the tip jar, the touch_start() event handler uses llDetectedGroup() to see if the person who touched it is a member of the same group as the jar. If so and the jar is unclaimed, it logs in that person by calling the login() function. If that person had already claimed the jar, it logs them out using the call login(NULL_KEY). Finally, if someone else already claimed the jar, it tells the current person that they may not log in. The login() function simply resets the tracking variables and sets the gHost variable to the specified key, either the new host key or NULL_KEY.
286
278-309 Chapter 13.indd 286
8/8/08 12:04:52 AM
The most substantial change from the first tip jar is to redistribute payments to the logged-in avatar. To support this, the jar must be able to not only accept money from tipping customers, but also to pay money to the logged-in host out of the owner's account. This is accomplished by combining PERMISSION_DEBIT with the library function llGiveMoney(). PERMISSION_DEBIT is requested just like any other permission (see Chapter 2, "Making Your Avatar Stand Up and Stand Out"). Note that like permissions for attaching and changing links, a script may request the permission only from the owner of the containing object.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7
NOTE
CHAPTER 8 CHAPTER 9
A granted PERMISSION_DEBIT does not transfer with an object—a security measure that prevents inadvertently giving away objects that can take money from you whenever the new owner wants. It is a good idea to reset any script that uses PERMISSION_DEBIT in on_rez() or make certain the owner hasn’t changed. If you don’t take measures to address this issue, your script will stop working when transferred.
The script requests PERMISSION_DEBIT in the state_entry() event handler, and when the permission is granted, the run_time_permissions() event handler calls llSetPayPrice() to make it possible to pay the jar. Finally, in the money() event handler, instead of just thanking the tipper, the script pays the logged-in avatar the tip less TAX percent with the llGiveMoney() library function.
DEFINITION
CHAPTER 10 CHAPTER 11 CHAPTER 12
CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
integer llGiveMoney(key destination, integer amount) Transfers the specified amount of money from the object owner’s account of the into the destination avatar’s account. The script must have already acquired the PERMISSION_DEBIT permission. Always returns 0. destination — The key of the avatar that will receive the money. amount — The number of Linden dollars to transfer; must be greater than zero (amount > 0).
One downside of this implementation is that the host will see tips coming from the tip-jar owner rather than the avatar who paid the tip jar. Therefore, to let the host know who originated each tip, the jar sends an IM to the host on each transaction.
Error-Checking for a Shared Tip Jar What happens, though, when the host forgets to log out, or when their SL session crashes, or when the host abandons the club in a fit of pique? The preceding script would require the owner to reset the jar so another user could log into it. Listing 13.3 adds a check that automatically logs off the avatar if they disappear.
287
278-309 Chapter 13.indd 287
8/8/08 12:04:54 AM
Listing 13.3: Shared Tip Jar with Error Checking // These are changes and additions only to Listing 13.2 float SCANRADIUS = 30.0; CHAPTER 13
ransaction T B asics
login(key host) { gHost = host; gHostTotal = 0; gLast = 0; displayLabel(); if (host != NULL_KEY) { // ADD - keep track of the host llSensorRepeat("", gHost, AGENT, SCANRADIUS, PI, 30.0); } else { llSensorRemove(); }
Redistributing Wealth S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
} default { no_sensor() { if (gHost != NULL_KEY && gHostTotal>0) { llInstantMessage(gHost, "Logged out, with L$"+ (string) gHostTotal+" in tips"); login(NULL_KEY); } } }
This variation adds a periodic scan for the logged-in host. If it fails to detect the avatar it will log them out automatically, freeing up the tip jar for a more dependable, or at least present, replacement host.
NOTE As with llListen(), any performance problems you see when using llSensorRepeat() are caused by not being specific enough in the filtering criteria. The script carefully looks for the one avatar logged into the tip jar, and scans only once every 30 seconds. If you expect to ever hit the maximum of 16 detected objects on a sensor scan, each scan will probably become expensive.
Charity Collectors In addition to keeping payments for the owner, objects can transfer the entire donation amount to a third party, such as a charity. Throughout SL you can find charity-collection boxes like the one for the American Cancer Society's 2008 Relay for Life, shown in Figure 13.4. The money-handling parts of a charity box are no different from those of a tip jar. What does change is that the beneficiary of the payments is usually an account that doesn't have a real person attached to it—they are often solely used for donation collection. Large charity drives usually provide a charity box that has the target account written directly into the script so that it cannot be changed or tapped. A useful alternative for smaller charity drives is a generic money collector the owner configures, indicating to which avatar payments should be directed. Listing 13.4 adds this feature during a configuration pass.
288
278-309 Chapter 13.indd 288
8/8/08 12:04:56 AM
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12
Figure 13.4: Collecting for a good cause at the Galleries of Camazotz (Camazotz ).
CHAPTER 13 CHAPTER 14
Listing 13.4: A Charity-Collection Box
CHAPTER 15
integer SUGGESTED = 50; // suggested donation string N2KURL = "http://w-hat.com/name2key";
APPENDIces
integer gLast = 0; integer gTotal = 0; key gCharity = NULL_KEY; string gCharityName; string gPendingName; key gPendingKey;
// set during name2key lookup // set while we're checking the key for accuracy
displayLabel() { string text = llGetObjectName()+"\nAll proceeds go to "+gCharityName+ "\nLast donation L$"+(string)gLast+", Total donations "+ (string) gTotal; llSetText(text, , 1.0); } // poof() is the same as in tip jars thankYou(key giver, integer love) { // same as tipjar 1 llSay(0, "Thank you for donating, "+llKey2Name(giver) + "!"); displayLabel(); poof(giver, love); } checkKey(key k) { llOwnerSay("Double checking the key. Please wait"); gPendingKey = k; llRequestAgentData(gPendingKey, DATA_NAME); }
289
278-309 Chapter 13.indd 289
8/8/08 12:04:59 AM
CHAPTER 13
ransaction T B asics Redistributing Wealth S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
default { on_rez(integer p) { llResetScript(); } state_entry() { llOwnerSay("Initializing"); llListen(0, "", llGetOwner(), ""); if (gCharity != NULL_KEY) { checkKey(gCharity); } else { llOwnerSay("Say the name of the charity avatar to start"); } } listen(integer ch, string name, key id, string m) { if (m == "go") { if (gCharity == NULL_KEY) { llOwnerSay("Say the name of the charity avatar"); } else { state running; } } else { gPendingName = m; llOwnerSay("Looking up key for "+m); llHTTPRequest(N2KURL + "?terse=l&name="+llEscapeURL(m), [], ""); } } http_response(key id, integer status, list meta, string body) { if ( status != 200 ) { llOwnerSay("name2key error "+ (string) status); } else { key k = (key) body; if (body == NULL_KEY) { llOwnerSay("Couldn't find key for "+gPendingName); } else { llOwnerSay(gPendingName+"'s key appears to be "+(string)k); checkKey(k); } } } dataserver(key qid, string data) { if (gPendingName == "") { gPendingName = data; } if (gPendingName == data) { gCharity = gPendingKey; gCharityName = gPendingName; llOwnerSay("Charitable donations will go to " + gCharityName+ " ("+(string)gCharity+") say \"go\" to start."); } else { llOwnerSay("Warning! Name lookup mismatch. Expected "+gPendingName+ " but found "+data); } } }
290
278-309 Chapter 13.indd 290
8/8/08 12:05:00 AM
state running { on_rez(integer p) { llResetScript(); } state_entry() { llRequestPermissions(llGetOwner(), PERMISSION_DEBIT); } run_time_permissions(integer perms) { if (perms & PERMISSION_DEBIT) { list buttons = [SUGGESTED/2, SUGGESTED, SUGGESTED*2, SUGGESTED*4]; llSetPayPrice(SUGGESTED, buttons); displayLabel(); } else { llOwnerSay("No debit permission"); } } money(key giver, integer love) { gLast = love; gTotal += love; llGiveMoney(gCharity, love); thankYou(giver, love); } }
When this collection box is rezzed, it expects the owner to chat the name of the avatar that should receive the proceeds. It then uses an external name2key service to look up the named avatar's key (see Chapter 12, "Reaching outside Second Life," for a deeper discussion of this). Then, because the script will give lots and lots of money to the target, it uses a dataserver lookup to double-check that Second Life agrees the key is associated with the correct name. If it didn't do this, the external service could theoretically redirect funds to the wrong avatar, allowing that avatar to steal the funds. Your transaction logs would expose the fraud pretty quickly, but some damage could be done to your reputation. It is far better to keep everyone honest. The script needs to use llRequestAgentData(id, AGENT_NAME) because the much faster llKey2Name(id) will work only if the identified avatar is in the same sim as the charity collector.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12
CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
When the owner is happy with the selected target account, they can chat go to switch the script to the running state. This is a perfect use for multiple states: the default state is now solely for configuration and the running state is nearly identical to any of the tip jar's default states. There is no need to turn off the listener, because the state transition to running will close the one set up by default. In every state, the script has an on_rez() event handler with llResetScript() so the owner can rerez the object and start over. An alternative would be to transition back to the default state without resetting, retaining the previously set beneficiary and running totals.
Going on the Dole: Money Trees Another sort of charity is to support other avatars: one of the first things that new Second Life residents discover is that it costs money to do much of anything. While it is possible to get by on the cheap in SL, even the "freebie" products are more accurately described as "dollarbies," costing L$1 each. So, the first problem new residents are faced with is how to get enough money to get off the ground. The most straightforward way to get small amounts of money is to look for handouts—scripted objects that give you a Linden dollar or two just for asking. Our favorite example of this sort of thing is the money tree (Figure 13.5); we also have a pot of gold beneath the rainbow at SYW HQ.
278-309 Chapter 13.indd 291
291
8/8/08 12:05:01 AM
Listings 13.5 and 13.6 illustrate one way to implement a simple money tree. The basic idea is first to accept donations such as with a tip jar, then every once in a while, rez a leaf, usually in the shape of a Linden dollar (Figure 13.5). If anyone clicks on the leaf it will give them a dollar. Listing 13.5 is the script for the trunk of the tree itself. CHAPTER 13
ransaction T B asics Redistributing Wealth S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
Listing 13.5: Money Tree float integer string list integer integer
PERIOD = 600.0; // seconds between leaf rez LEAFMAX = 10; // max number of leafs outstanding LEAF = "leaf"; // inventory object to rez gLeaves; // active leaves gBalance = 0; // how much money left to give out gChannel;
default { on_rez(integer p) { llResetScript(); } state_entry() { gChannel = -(10000 + (integer) llFrand(10000.0)); llRequestPermissions(llGetOwner(), PERMISSION_DEBIT); llSetTimerEvent(PERIOD); llListen(gChannel, LEAF, NULL_KEY, ""); llSetText("Pay to donate", , 1.0); } money(key av, integer donation) { gBalance += donation; llSay(0, "Thanks for your contribution, "+llKey2Name(av)); } timer() { if (gBalance>0 && llGetListLength(gLeaves) < LEAFMAX) { vector where = llGetPos() + ; vector offset = - ; llRezObject(LEAF, where+offset, ZERO_VECTOR, ZERO_ROTATION, gChannel); } } object_rez(key leaf) { gLeaves += [leaf]; gBalance--; } listen(integer ch, string name, key id, string message) { integer i = llListFindList(gLeaves, [id]); if (i!= -1) { gLeaves = llDeleteSubList(gLeaves, i, i); llGiveMoney((key) message, 1); } } }
The donation part is similar to the personal tip jar: it collects money, accepting donations with the money() event handler, and adds it to the amount outstanding. In state_entry(), the script chooses a random channel to communicate with the leaves, starts a timer to go off every PERIOD seconds (10 minutes in this case), and also listens for communication from leaves that indicate they've been plucked.
292
If there is a positive balance in the account when the timer goes off, and too many leaves aren't already rezzed, the script rezzes a new leaf from inventory in a random spot above the tree. When it does choose to rez a new leaf, it watches for the rez to happen using the object_rez() event handler. When notified of the new object, it notes the leaf's key in its list of active leaves and decrements the balance in the account.
278-309 Chapter 13.indd 292
8/8/08 12:05:03 AM
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12
Figure 13.5: In SL, money does grow on trees. CHAPTER 13
The leaf script shown in Listing 13.6 gets the channel to speak on the on_rez() parameter. The only other thing the leaf does is wait for a touch_end() event when an avatar plucks the leaf. It will chat the key of the avatar that touched it on a communications channel and self-destruct.
Listing 13.6: Money-Tree Leaf
CHAPTER 14 CHAPTER 15 APPENDIces
integer gChannel; default { on_rez(integer p) { gChannel = p; } touch_end(integer count) { llSay(gChannel, (string) llDetectedKey(0)); llDie(); } }
When the tree hears a message from a leaf, it makes certain it is one of the leaves that it actually created. It then removes that leaf from the list and pays L$1 to the avatar identified by the chatted message. Just make certain the “leaf” object is placed in the inventory of the money tree.
WARNING A word on security: always keep in mind that chat channels are insecure. If it is possible for someone to listen in on the right channel and steal from you, someone will do it. Even worse, if you sell a script that is not secure, the bad guys will steal from your customer, which is not very good for business.
This script depends on your rezzing being secure, and on the tree being certain that when an object says the right thing on the right channel, it is authorized to do so. It would be convenient to make each leaf pay the touching avatar, but that would require each individual leaf to have debit permissions.
278-309 Chapter 13.indd 293
293
8/8/08 12:05:06 AM
CHAPTER 13
ransaction T B asics Redistributing Wealth S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
Most money trees in SL give money only to new avatars. You can implement this restriction by firing off a call to llRequestAgentData(id, DATA_BORN), parsing the resulting date string in the dataserver event handler, and only then paying the avatar if they are younger than 30 days (rather than paying the target directly in the listen() event handler). This is complicated because dataserver queries can take long enough that several people might click on different leaves before you can check for the age of the first avatar, so you need to keep track of outstanding age queries, paying only avatars that are young enough. An even subtler problem is that avatars who are older than 30 days can waste the leaves—they will not get paid, but since the leaves die after the chat, nobody else can take them either. The simplest way to fix this is to add L$1 back to the balance if you refuse to pay the avatar. That way although the leaf has disappeared, it will eventually be replaced. Other options are to do the age check in the leaf and then have the leaf die only if the avatar passes your criteria, or having a more complex communications channel between leaf and tree to allow the tree to do all the complex work, and instruct the leaf when and if to die. The topic of securing communications in Second Life could fill an entire chapter in a more advanced book than this. For more information, refer to Ordinal Malaprop's article on secure communications at http://ordinalmalaprop.com/engine/2007/10/23/secure-inworld-passwordnonsense/ or search the wiki for llMD5String().
SELLING IT! The easiest way to sell something is to mark it for sale (see Figure 13.6). You can specify the price and indicate whether the buyer gets the original object or a copy. There is nothing wrong with this approach, but it is limiting, mainly in that the actual object needs to be rezzed in-world for you to buy it, so you can easily run into prim limits: a high-quality prim hair unit can easily be over 200 prims! While there are no prim-limits on objects attached to avatars, rezzing more than a few in a typical storefront would quickly exhaust the prim allotments of even the largest store*. That's where vendors come in; they're objects that sell other objects in SL, and they're pretty simple to create.
NOTE It is important to set the correct permissions on objects you sell or give away, no matter your method. We recommend giving away full-permission (Copy/Mod/Trans) scripted objects only when they are trivial or unsupported—it is impossible to provide support for full-permission items, and unless you are selling something specifically for resale, there is no reason for anyone to come back to you for repeat business. Of course, you need both Copy and Transfer permissions on an object to put it in a vendor. Finally, always test everything before you go public: make an alternate avatar for testing purposes, who doesn’t have any special rights, and have them buy your products and operate your vendors to make sure everything works properly.
294
* Vex's hair, for instance, is made from 221 prims, which would just barely be rezzable on a 1,024-square-meter region with a limit of 234 prims.
278-309 Chapter 13.indd 294
8/8/08 12:05:11 AM
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12
Figure 13.6: Setting an object for sale CHAPTER 13 CHAPTER 14
A Simple Vendor
CHAPTER 15 APPENDIces
The first vendor script, shown in Listing 13.7, is really just a combination of an object giver (see Chapter 3, "Communications,") that is triggered by a money event rather than by touches or senses.
Listing 13.7: A Simple Vendor integer gPrice = 100; string gProduct;
// price in Linden Dollars
default { on_rez(integer p) { llResetScript(); } state_entry() { llSetPayPrice(PAY_HIDE, [gPrice, PAY_HIDE, PAY_HIDE, PAY_HIDE]); gProduct = llGetInventoryName(INVENTORY_OBJECT,0); string title = gProduct+"(L$"+(string)gPrice+")"; llSetText(title, , 1.0); } money(key customer, integer amount) { llInstantMessage(customer, "Thank you, "+llKey2Name(customer)+ "! Enjoy your purchase of "+gProduct); llGiveInventory(customer, gProduct); } }
In state_entry(), the script sets up the payment menu so that only one quickpay button is active. It then identifies the first object in inventory as the product to sell, and sets the floating text to inform nearby avatars what is being sold. When a money() event occurs, the script needs to do nothing but give the product to the customer.
278-309 Chapter 13.indd 295
295
8/8/08 12:05:14 AM
BUILD NOTE The script is simple, but there are many variations on how to house the script. Because the object to be sold must be in inventory, you need to make certain that potential buyers know where to buy the items they want. Common approaches are to use an object that looks like the product itself, a placard displaying a picture of the product, or a “for sale” tag near the rezzed object.
CHAPTER 13
ransaction T B asics Redistributing Wealth S elling I t ! R entals S ervices
and
This sort of vendor is great when you want to have one vendor rezzed per product to be sold, for instance at a clothing store or in an art gallery, as in Figure 13.7. The most common extension is to have the vendor figure look at a configuration notecard to find the object price, and perhaps a texture to display on the face of the vendor.
G ambl ... uh ... G ames of S kill S ummary
Figure 13.7: The Riverbend Art Gallery at Addu sells copies of displayed works of art using simple vendors.
NOTE The best way to package a product for sale depends on the details of the product itself and the vendors you have available. It is convenient for buyers to receive multiple-part products (for example outfits) as a folder, so that their new purchase is ready to go straight from the vendor rather than the buyer having to rez a package that then needs to be unpacked. Single vendors that use llGiveInventoryList() are great for this sort of thing. However, most commercial vendor systems will not give object folders, because managing multiple collections in one vendor’s inventory is difficult. The best solution is often to package everything in a gift box that a buyer can rez and open when they get home.
A Multiproduct Vendor 296
With the right script, you sell many different products from one vendor. Listing 13.8 uses a main panel to display a photo of the current product for sale, and two arrow buttons to advance through the products contained in the vendor (see Figure 13.8 for an example).
278-309 Chapter 13.indd 296
8/8/08 12:05:17 AM
Listing 13.8: A Multiple-Product Vendor key gOwner = NULL_KEY; string CONFIGNOTE = ".config"; integer DISPLAYFACE = 0; integer qLine = 0; // which config line are we on? list gParameters; // [tag, product, price] integer gParamLen = -1; integer gCurrent = 0; integer gPrice = -1; string gProduct; string gTag; grokConfigLine(string data) { if (llStringLength(data)==0 || "/" == llGetSubString(data,0,0)) { // ignore comments and empty lines } else { list kvl = llParseString2List(data, ["="], []); string tag = llList2String(kvl,0); list params = llParseString2List(llList2String(kvl,1), [","], []); string product = llList2String(params,0); integer price = llList2Integer(params,1); gParameters += [tag, product, price]; } } displayProduct() { if (gCurrent >= gParamLen) { gCurrent = 0; } else if (gCurrent < 0) { gCurrent = gParamLen - 1; } integer i = gCurrent*3; gTag = llList2String(gParameters, i); gProduct = llList2String(gParameters, i + 1); gPrice = llList2Integer(gParameters, i + 2); llSetTexture(gProduct+".texture", DISPLAYFACE); llSetPayPrice(PAY_HIDE, [gPrice, PAY_HIDE, PAY_HIDE, PAY_HIDE]); llSetText(gTag+" (L$"+(string)gPrice+")", , 1.0); llSetObjectName("Vendor for "+gTag); }
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12
CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
default // default is unconfigured { on_rez(integer p) { llResetScript(); } state_entry() { state configuring; } }
297
278-309 Chapter 13.indd 297
8/8/08 12:05:18 AM
CHAPTER 13
ransaction T B asics Redistributing Wealth S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
state configuring { on_rez(integer p) { llResetScript(); } state_entry() { gParameters = []; qLine = 0; llGetNotecardLine(CONFIGNOTE, qLine); } dataserver(key requested, string data) { if (data == EOF) { gParamLen = llGetListLength(gParameters) / 3; state running; } else { data = llStringTrim(data, STRING_TRIM); grokConfigLine(data); qLine++; llGetNotecardLine(CONFIGNOTE, qLine); } } } state running { on_rez(integer p) { llResetScript(); } state_entry() { gCurrent = 0; displayProduct(); } money(key giver, integer amount) { llGiveInventory(giver, gProduct); llInstantMessage(giver, "Thank for purchasing "+ gProduct+" for L$"+(string) gPrice); } link_message(integer link, integer num, string m, key id) { if (m == "R") { gCurrent ++; } else if (m == "L") { gCurrent --; } displayProduct(); } }
298
278-309 Chapter 13.indd 298
Figure 13.8: A multiproduct vendor in action
8/8/08 12:05:21 AM
The script reads a notecard that configures the products for sale, including their prices. Then it allows the user to view all options and purchase just the one desired. To support these modes, the script has three states: a default state solely to get things off the ground, a configuring state for use while the vendor is reading its configuration notecard, and a running state while the vendor is idling, waiting for a sale. The list of products and attributes is kept in a strided list, gParameters, where each group of three elements is [tag, ObjectNameInInventory, price]. The notecard reader uses the grokConfigLine() function in the configuring state to parse each line of the configuration notecard and fill up gParameters with the product information. In the configuring state, the state_entry() and dataserver() event handlers implement a standard notecard reader, simply walking through the lines. The .config notecard might have a line like "Nice House=Victorian House 37,10000" which would result in the vendor describing the object "Victorian House 37" as a "Nice House" and setting the price at L$10,000. The displayProduct() function will look for a texture with the same name as the product name with a ".texture" suffix ( such as Victorian House 37.texture) for display purposes. The function grokConfigLine() also ignores empty lines and lines starting with // to make it easy to format and control the contents. The displayProduct() function figures out which product should be displayed, then looks for a texture in inventory that has the name of the tag, paints it on the vendor's face, sets the sales price, changes the floating text to describe the object, and changes the name of the vendor to include the product information in the payment dialog. The running state is like previous vendors, simply reacting to money() events by giving the selected product to the purchaser. It also responds to "L" and "R" messages from the left and right button objects that are linked in as controls. The buttons, carefully named L and R, use the script in Listing 13.9.
Listing 13.9: Button Script for Multiple-Product Vendor
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12
CHAPTER 13 CHAPTER 14 CHAPTER 15 APPENDIces
default { touch_end(integer c) { llMessageLinked(LINK_ROOT, 0, llGetObjectName(), NULL_KEY); } }
The button script simply link-messages the prim name to the main vendor when a button is touched. This allows the vendor to display the next or previous product in the list, resetting the prices and displaying texture as appropriate.
BUILD NOTE The vendor object is made up of three prims. Make the main vendor out of a box shaped into a flat panel however large you’d like, containing the script from Listing 13.8. The two buttons should be triangles or arrows, each with the button script from Listing 13.9. One button should be named “L” (for left) and the other “R” (for right). Position the two buttons in front of the main panel and link the three prims together, making certain the main panel is the root. Load up the vendor with one of each of the products you want to sell, one texture for each product, and a .config notecard that holds it all together. Then reset the script in the vendor; the notecard should load and you should be ready for business. Figure 13.7 shows one example of this.
299
278-309 Chapter 13.indd 299
8/8/08 12:05:22 AM
Networked Vendors CHAPTER 13
The previous chapter discussed using email, HTTP, and XML-RPC to allow in-world objects to communicate in both directions with the outside world. A number of scripters have built vendor systems that use these out-of-world communication paths to link up multiple sales points to object-distribution points for a much more flexible and portable system. The basic design looks something like Figure 13.9.
ransaction T B asics Redistributing Wealth
Second life
S elling I t ! R entals S ervices
internet
and
G ambl ... uh ... G ames of S kill
Vendor 1
Browser
chat
S ummary
Vendor 2
Inventory Web Server
Vendor 3
HTTP/XMLRPC
Figure 13.9: Design of a networked vendor system
The Inventory objects hold the products to be sold, along with the images, prices, and descriptions used to display them. The Vendors are very much like the vendor in Listing 13.8 except that instead of containing the objects to be sold in its own inventory, it asks the Inventory point for information on the products to display. When a purchase is made, it asks the Inventory point to give the item to the purchaser through its own inventory. There are several options for connecting vendors to Inventory points: they may send each other email using the llEmail() family of functions (Vendor 1), or they may use HTTP or XML-RPC to communicate with an external system acting as an intermediary (Vendor 3), or if they are near each other they can use chat commands like llShout() or llRegionSay() (Vendor 2). Often vendor systems use a combination of these options to balance performance with dependability. Vendor networks that use an external system as an intermediary have the additional benefit that you can often manage the system from a web application with a standard browser, managing the inventory, tracking sales, and keeping your records without having to actually log into Second Life. Figure 13.10 shows several networked vendor systems that have been placed at SYW HQ as examples.
300
278-309 Chapter 13.indd 300
8/8/08 12:05:24 AM
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7
Figure 13.10: High-end networked vendors. The lower one, from OnRez, sells a variety of trees and special effects; the upper vendor, from Hippo Technologies, rents real estate.
CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12
CHAPTER 13
RENTALS AND SERVICES We've talked about passing money directly to another avatar, and about exchanging money for objects. The other main use for scripted money is to support rentals, either of services or of land.
CHAPTER 14 CHAPTER 15 APPENDIces
A Ticket to Ride Paying for access to a ride is pretty common. Rezzable's Surfline sims at Surfline Aloha Rezzable let you rent a surfboard to try out on their giant waves, you can go skydiving at Abbotts Aerodrome (Abbotts ) , practice batting at a re-creation of historic Ebbets Field (Ebbets Field ), or play golf at Holly Kai Golf Club (Hollywood ). Listing 13.10 implements a very simple example of a ride that will not let you play with it until you've paid.
Listing 13.10: Skyhook Ride string gName; key gKey = NULL_KEY; integer gAltitude = 0; displayLabel() { string text = llGetObjectName(); if (gKey == NULL_KEY) { text += "\nPay me L$1 per 100 meters and then sit for a ride"; } else { text += "\nReserved for "+gName+ ": sit for a ride!"; } llSetText(text, , 1.0); } // insert moveTo(vector origin, vector destination) {} from Listing 2.4
278-309 Chapter 13.indd 301
301
8/8/08 12:05:30 AM
CHAPTER 13
ransaction T B asics Redistributing Wealth S elling I t ! R entals S ervices
and
G ambl ... uh ... G ames of S kill S ummary
302
default { state_entry() { displayLabel(); llSetPayPrice(10, [PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE]); llSitTarget(, ZERO_ROTATION); } money(key who, integer lindens) { gName = llKey2Name(who); gKey = who; gAltitude += lindens; llSay(0, "OK, "+gName+"! now sit and get a ride to "+ (string)(gAltitude*100) + " meters!"); llSetTimerEvent(30.0); displayLabel(); } changed(integer bits) { if (bits & CHANGED_LINK) { key av = llAvatarOnSitTarget(); if (av == gKey && av != NULL_KEY) { llSay(0, "Hold on!!!"); integer i; for (i=10; i>0; i--) { llSay(0, (string)i+"..."); llSleep(1.0); } llShout(0, "Blast off!"); vector home = llGetPos(); vector dest = home+; moveTo(home, dest); llUnSit(av); moveTo(dest, home); gKey = NULL_KEY; gName = ""; gAltitude=0; } else { llSay(0, "Please pay me first"); llUnSit(av); } displayLabel(); } } timer() { if (gKey != NULL_KEY) { llSay(0, "Timing out: "+gName+" seems to have lost interest"); gKey = NULL_KEY; gName = ""; gAltitude = 0; displayLabel(); } llSetTimerEvent(0.0); } }
This is really just a high-altitude intrasim teleporter, using the moveTo() function from Chapter 2. The difference is that it won't operate until you've paid it. It sets up a payment box in state_entry() to reserve the device. Once an avatar has paid it, he can sit and be transported straight up 100 meters for every Linden dollar paid (to a maximum of 4,096m). When the avatar reaches the paid-for height, the script unsits the passenger and returns home to reset. You could slow it down and offer the passenger a parachute on the way up, or follow a path through a psychedelic panorama.
278-309 Chapter 13.indd 302
8/8/08 12:05:31 AM
Land Rentals Many residents want to have a region of the world to build a more permanent presence on. Build a house, open an office or an art gallery—the possibilities are endless. However, residents need to have a Premium account to actually own mainland real estate. Land isn't cheap, and you must pay a monthly maintenance fee (called tier) based on the amount of land you own over the course of the month. Due to the cost, the longer-term commitment, and the added complexity of owning land, many residents opt to rent instead of buy. While rentals can certainly be handled manually by exchanging money avatar-to-avatar, most are handled automatically with rental boxes—scripted systems that accept payment for space, monitor use of the rented land, and offer the owner tools for managing rentals so they need not spend time in Second Life teleporting around the world taking care of rental properties. Listing 13.11 illustrates how to institute a simple rental system.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12
Listing 13.11: Region Rental float SCANRATE = 10.0; float SCANRANGE = 30.0; key string integer string
gRenter = NULL_KEY; gName = ""; gRemain = 0; gParcelName = "";
CHAPTER 13 CHAPTER 14 CHAPTER 15
label() { if (gRenter == NULL_KEY) { llSetText("Pay L$1 per minute for region access", , 1.0); } else { llSetText("Region rented by "+gName+" for another "+(string)gRemain +" minutes", , 1.0); } }
APPENDIces
string parcelName(vector p) { return llList2String(llGetParcelDetails(p, [PARCEL_DETAILS_NAME]), 0); } default { on_rez(integer p) { llResetScript(); } state_entry() { llSensorRepeat("", NULL_KEY, AGENT, SCANRANGE, PI, SCANRATE); llSetPayPrice(10, [PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE]); label(); llRequestPermissions(llGetOwner(), PERMISSION_DEBIT); gParcelName = parcelName(llGetPos()); } money(key av, integer cash) { if (gRenter != NULL_KEY) { if (gRenter == av) { gRemain += cash; } else { llSay(0, "Sorry, region is already rented by "+gName); llGiveMoney(av, cash); }
278-309 Chapter 13.indd 303
303
8/8/08 12:05:32 AM
} else { gRenter = av; gName = llKey2Name(av); gRemain = cash; } label();
CHAPTER 13
} sensor(integer n) { integer i; for (i=0; i(). Both of the "set" functions cause the script to pause for 0.2 seconds, irrespective of the number of properties being set; note that this can be significantly cheaper than a sequence of several calls to the alternate functions. Table A.1 lists the properties that can be set, alternative functions (if available), and the values that need to appear for each property. The table uses alphabetical order, but specific properties can be listed in any order in a function call. Each property is discussed in further detail after the table. Table A.1: Primitive Properties that Can Be Set or Retrieved Property
Description
Usage
PRIM_BUMP_SHINY
Sets the face's shiny & bump.
[ PRIM_BUMP_SHINY, integer face, integer shiny, integer bump ]
PRIM_COLOR
Sets the face's color.
[ PRIM_COLOR, integer face, vector color, float alpha ]
PRIM_FLEXIBLE
Sets the prim as flexible.
[ PRIM_FLEXIBLE, integer boolean, integer softness, float gravity, float friction, float wind, float tension, vector force ]
PRIM_FULLBRIGHT
Sets the face's fullbright flag.
[ PRIM_FULLBRIGHT, integer face, integer boolean ]
PRIM_GLOW
Sets the face's glow attribute.
[ PRIM_GLOW, integer face, float intensity ]
PRIM_MATERIAL
Sets the prim's material.
[ PRIM_MATERIAL, integer type ]
PRIM_PHANTOM
Sets the object's phantom status.
[ PRIM_PHANTOM, integer boolean ]
PRIM_PHYSICS
Sets the object's physics status.
[ PRIM_PHYSICS, integer boolean ]
Alternate Functions
llSetColor() llSetAlpha() llGetColor() llSetColor() llSetLinkAlpha() llSetLinkColor()
llSetStatus() llGetStatus()
344
342-369 Appendices.indd 344
9/24/08 12:57:05 PM
Property
Description
Usage
PRIM_POINT_LIGHT
Sets the prim as a point light.
[ PRIM_POINT_LIGHT, integer onOff, vector color, float intensity, float radius, float falloff ]
Alternate Functions
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5
PRIM_POSITION
Sets the prim's position.
[ PRIM_POSITION, vector position ]
llSetPos() llGetPos()
PRIM_ROTATION
Sets the prim's rotation. (Note: This is buggy. Use the alternate functions.)
[ PRIM_ROTATION, rotation rot ]
llSetRot() llGetRot()
PRIM_SIZE
Sets the prim's size.
[ PRIM_SIZE, vector size ]
llGetScale() llSetScale()
PRIM_TEMP_ON_REZ
Sets the object's temporary status. [ PRIM_TEMP_ON_REZ, integer boolean ]
PRIM_TEXGEN
Sets the face's texture mode.
[ PRIM_TEXGEN, integer face, integer type ]
PRIM_TEXTURE
Sets the prim's texture attributes.
[ PRIM_TEXTURE, integer face, string texture, vector repeats, vector offsets, float rot ]
PRIM_TYPE
Sets the prim's shape.
[ PRIM_TYPE, integer typeflag ] + flag_parameters
CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
llSetTexture() llScaleTexture() llOffsetTexture() llRotateTexture() llSetLinkTexture() llGetTexture() llGetTextureRot() llGetTextureScale()
CHAPTER 15
APPENDICES
WARNING These functions may silently fail in several different ways. Examples include missing a value in a long sequence or using a position value such as (which, you’ll note, is actually a 4D rotation and not a 3D vector). If something isn’t working the way you expect, double-check your parameter lists and types! It also helps to pull values out of the list and into their own typed parameters.
PRIM_BUMP_SHINY PRIM_BUMP_SHINY is used in Listing 9.13. • integer face is the number of the face. ALL_SIDES affects the entire object. • integer shiny values are PRIM_SHINY_NONE, PRIM_SHINY_LOW, PRIM_SHINY_ MEDIUM, PRIM_SHINY_HIGH. • integer bump values are shown in Table A.2.
345
342-369 Appendices.indd 345
9/24/08 12:57:07 PM
Table A.2: Values for the bump Property of PRIM_BUMP_SHINY
APPENDICES
Constant
Value
Description
PRIM_BUMP_NONE
0
None: no bump map.
PRIM_BUMP_BRIGHT
1
Brightness: generate bump map from highlights.
PRIM_BUMP_DARK
2
Darkness: generate bump map from lowlights.
PRIM_BUMP_WOOD
3
Wood grain.
PRIM_BUMP_BARK
4
Bark.
A ppendix A
PRIM_BUMP_BRICKS
5
Bricks.
A ppendix B
PRIM_BUMP_CHECKER
6
Checker.
A ppendix C
PRIM_BUMP_CONCRETE
7
Concrete.
PRIM_BUMP_TILE
8
Crusty tile.
PRIM_BUMP_STONE
9
Cut stone blocks.
PRIM_BUMP_DISKS
10
Disks: packed circles.
PRIM_BUMP_GRAVEL
11
Gravel.
PRIM_BUMP_BLOBS
12
Petri dish: blobby amoebalike shapes.
PRIM_BUMP_SIDING
13
Siding.
PRIM_BUMP_LARGETILE
14
Stone tile.
PRIM_BUMP_STUCCO
15
Stucco.
PRIM_BUMP_SUCTION
16
Small rings that look like suction cups.
PRIM_BUMP_WEAVE
17
Weave.
PRIM_COLOR PRIM_COLOR is used in Listings 2.7, 3.9, 7.3, 9.9, 9.10, 9.11, 9.12, and 10.3. • integer face is the number of the face. ALL_SIDES affects the entire object. • vector color is the color of the object, in RGB: . Each value is scaled from 0.0 to 1.0, but often scaling the values from 0 to 255 can be useful since each component of the color vector is represented with 8 bits (28=256). The following snippet illustrates possible formats: vector vector vector vector vector vector
red green blue purple yellow cyan
= = = = = =
; // most efficient ; // equally efficient ; // conversion to float is inefficient ; / 255.0; // divide is not efficient /255; // extremely inefficient
• float alpha is the translucency of the face, ranging from 0.0 (fully transparent) to 1.0 (fully opaque). You can create a list of colors, but the parser sometimes has trouble with the order. Logically, the following snippet would make all faces white, and then face number 1 red, but you cannot always predict the order in which the parser will apply the transitions: llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 1.0, PRIM_COLOR, 1, , 1.0]);
This error can be especially disconcerting if some of the sides are intended to be transparent. You can either specify each individual side, or make two sequential calls to llSetPrimitiveParams().
346
342-369 Appendices.indd 346
9/24/08 12:57:08 PM
PRIM_FLEXIBLE Flexible prims are discussed throughout this book.
CHAPTER 1 CHAPTER 2
• integer boolean is TRUE when the prim is flexible, and FALSE when rigid.
CHAPTER 3
• integer softness affects how floppy the object is. Ranges from 0 to 3.
CHAPTER 4
• float gravity indicates how much gravity will affect the prim. Ranges from –10.0 to 10.0.
CHAPTER 5
• float friction acts like a damper on motion. Ranges from 0.0 to 10.0.
CHAPTER 6
• float wind indicates how much the wind should cause the object to move. Ranges from 0.0 to 10.0. • float tension indicates how much bounce or spring the object has. Ranges from 0.0 to 10.0. • vector force indicates how much force to apply to the prim. Each component of the vector ranges from –10.0 to 10.0. Flexible prims (flexiprims) are a client-side effect and are automatically made phantom and nonphysical. However, flexiprims can be linked to a solid, physical root prim and will thus take on some of those properties as well (though it's buggy). See Chapter 7, "Physics and Vehicles," for more discussion.
CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15
PRIM_FULLBRIGHT PRIM_FULLBRIGHT is used in Listings 3.9, 9.10, and 10.3. • integer face is the number of the face. ALL_SIDES affects the entire object. • integer boolean is TRUE when the prim is at its maximum brightness, and FALSE otherwise.
APPENDICES
PRIM_GLOW PRIM_GLOW is used in the "Creating Lights" section of Chapter 9, "Special Effects." • integer face is the number of the face. ALL_SIDES affects the entire object. • float intensty is the strength of the glow, from 0.0 to 1.0. If your compiler complains about this named constant, use its integer value of 25 instead. (Linden Lab is in the process of updating the software for the compilers, viewers, and editors.)
PRIM_MATERIAL • integer type is one of the following: PRIM_MATERIAL_STONE, PRIM_MATERIAL_ METAL, PRIM_MATERIAL_GLASS, PRIM_MATERIAL_WOOD, PRIM_MATERIAL_FLESH, PRIM_MATERIAL_PLASTIC, and PRIM_MATERIAL_RUBBER. Material affects a physical object's friction but not its mass. Glass has the least friction, followed by metal, plastic, wood, stone, rubber, and flesh. Material also affects an object's collision sounds and its bounce (collision handling).
347
342-369 Appendices.indd 347
9/24/08 12:57:09 PM
PRIM_PHANTOM PRIM_PHANTOM is used in Listings 4.6 and 9.11. • integer boolean is TRUE if the object is phantom, and FALSE otherwise. Note that if one prim is phantom, the entire object is phantom.
APPENDICES
PRIM_PHYSICS A ppendix A
• integer boolean is TRUE if physics is enabled, and FALSE otherwise.
A ppendix B A ppendix C
PRIM_POINT_LIGHT PRIM_POINT_LIGHT is used in Listings 2.7, 3.8, 9.10, and 9.12. • integer onOff is TRUE when the prim is a light source, and FALSE otherwise. Other properties are irrelevant when onOff is FALSE. • vector color is the color of the light, in RGB . • float intensity is the intensity of the light, and ranges from 0.0 to 1.0. • float radius is the radius of the light, and ranges from 1.0 to 10.0 meters. • float falloff is the rate of falloff of the light source, and ranges from 0.01 to 1.0.
PRIM_POSITION PRIM_POSITION is used in Listings 2.3, 2.4, 4.4, 9.9, and 10.4. The listings in Chapter 2, "Making Your Avatar Stand Up and Stand Out," are particularly interesting because they supply a sequence of PRIM_POSITION items. • vector position is the position of the prim. • In llSetPrimitiveParams(), the coordinates are local to the attachment point (the region, the avatar, or the root prim). • In llGetPrimitiveParams(), the coordinates are regional.
PRIM_ROTATION PRIM_ROTATION is used in Listing 10.4. • rotation rot is the prim's rotation. This property is buggy—it does not handle rotations correctly. Chapter 10, "Scripting the Environment," describes the bug, and Listing 10.4 shows an example of the workaround. Normally if you want to set the prim rotation to rot; you can either set it to rot/llGetRootRotation() in llSetPrimitiveParams() or use llSetLocalRot(rot) to get the right behavior.
348
342-369 Appendices.indd 348
9/24/08 12:57:11 PM
PRIM_SIZE
CHAPTER 1
PRIM_SIZE is used in Listings 9.9 and 10.4. • vector size is the size of the object.
CHAPTER 2 CHAPTER 3 CHAPTER 4
PRIM_TEXGEN
CHAPTER 5
• integer face is the number of the face. ALL_SIDES affects the entire object. • integer type is either PRIM_TEXGEN_DEFAULT or PRIM_TEXGEN_PLANAR. The default maps to curves better, but the planar maps to larger objects better.
CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10
PRIM_TEMP_ON_REZ
CHAPTER 11
PRIM_TEMP_ON_REZ is used in Listings 4.4 and 5.4, and in the code snippet on page 170. • integer boolean is TRUE if the object will die approximately 60 seconds after setting this property, and FALSE otherwise.
CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15
PRIM_TEXTURE PRIM_TEXTURE is used in Listing 9.9. It refers to an image that is placed on the specified face of the object, as in Figure A.1.
APPENDICES
Figure A.1: This texture of the Earth has key ec751148-03b4-7876-78a4-b360884d1262.
• integer face is the face's number. ALL_SIDES affects the entire object. • string texture is the name of a texture in inventory, or a UUID. If it is not a texture or a UUID and it is missing from the prim's inventory, then an error is shouted on DEBUG_CHANNEL. • vector repeats indicates how many repeats of the texture to place on each of the x and y axes (z is ignored). Negative values will flip the texture. There appears to be no compiler limit on x or y (you will hit visual limits much faster). • vector offsets is the texture offset on each of the x and y axes (z is not used). Range is –1.0 to 1.0 for both x and y. • float rot is rotation of the texture in radians.
349
342-369 Appendices.indd 349
9/24/08 12:57:12 PM
PRIM_TYPE PRIM_TYPE is used in Listing 9.8, the code snippet later in this section, and the sidebar at the end of this appendix. • integer typeflag is any one of the constants in Table A.3.
APPENDICES
• list flag_parameters is the list from the Parameters column of Table A.3. A ppendix A
Table A.3: PRIM_TYPE Flags
A ppendix B
Constant
Value
Parameters
A ppendix C
PRIM_TYPE_BOX
0
integer holeshape, vector cut, float hollow, vector twist, vector taper_b, vector topshear
PRIM_TYPE_CYLINDER
1
integer holeshape, vector cut, float hollow, vector twist, vector taper_b, vector topshear
PRIM_TYPE_PRISM
2
integer holeshape, vector cut, float hollow, vector twist, vector taper_b, vector topshear
PRIM_TYPE_SPHERE
3
integer holeshape, vector cut, float hollow, vector twist, vector dimple
PRIM_TYPE_TORUS
4
integer holeshape, vector cut, float hollow, vector twist, vector holesize, vector topshear, vector profilecut, vector taper_a, float revolutions, float radiusoffset, float skew
PRIM_TYPE_TUBE
5
integer holeshape, vector cut, float hollow, vector twist, vector holesize, vector topshear, vector profilecut, vector taper_a, float revolutions, float radiusoffset, float skew
PRIM_TYPE_RING
6
integer holeshape, vector cut, float hollow, vector twist, vector holesize, vector topshear, vector profilecut, vector taper_a, float revolutions, float radiusoffset, float skew
PRIM_TYPE_SCULPT
7
string map, integer sculpttype
NOTE You can find good tutorials on making sculpty prims (prims of the type PRIM_TYPE_SCULPT) at http://amandalevitsky.googlepages.com/sculptedprims and http://bentha. net/sculpted-tuto/Blender-export-template-tut.html.
The following snippet shows two example calls to llSetPrimitiveParams() for PRIM_TYPE. // this is a pyramid float hollow = 0.0; vector cut = ; vector twist = ; vector taper_b = ; // a basic cube would be vector topshear = ; llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_BOX, PRIM_HOLE_CIRCLE, cut, hollow, twist, taper_b, topshear]);
350
342-369 Appendices.indd 350
9/24/08 12:57:14 PM
// this makes a nice expanding spiral vector cut = ; // 0.0 to 1.0 float hollow = 0.0; // 0.0 to 0.95 vector twist = ; // -1.0 to 1.0 vector holesize = ; // max X:1.0 Y:0.5 vector topshear = ; // -0.5 to 0.5 vector profilecut = ; // 0.0 to 1.0 vector taper_a = ; // 0.0 to 1.0 float revolutions = 3.0; // 1.0 to 4.0 float radiusoffset = 1.0; // -1.0 to 1.0 float skew = 0.0; // llSetPrimitiveParams( [PRIM_TYPE, PRIM_TYPE_TORUS, PRIM_HOLE_DEFAULT, cut, hollow, twist, holesize, topshear, profilecut, taper_a, revolutions, radiusoffset, skew] );
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11
NOTE
CHAPTER 12
Prim torture is the art of shaping a prim and then changing its type, producing shapes that are otherwise impossible. You essentially take advantage of the strange (possibly buggy) way that properties transfer from one shape to another. Look on the wiki for LibraryPrimTorture for a discussion and scripting examples.
CHAPTER 14
CHAPTER 13
The following list indicates the behavior of each parameter in Table A.3, in alphabetical order by parameter name.
CHAPTER 15
APPENDICES
• cut (all shapes except PRIM_TYPE_SCULPT) is a vector that causes a pie shape to be cut out of the shape around the z-axis; the removed portion ranges from x to y (think of x as the start and y as the end of the pie slice). x and y range from 0.0 to 1.0; x must be at least 0.05 smaller than y (z is ignored). Figure A.2 shows a torus with a cut of . On a sphere, the official mathematical name for a cut is spherical wedge.
Figure A.2: A cut removes a pie slice from the object.
• dimple (only spheres) is a vector that removes pie slices around the sphere's x-axis. x and y range from 0.0 to 1.0; x must be at least 0.05 smaller than y (z is ignored). Figure A.3 shows a sphere with dimple = (and cut = ). The official mathematical name for a dimple is spherical cone.
Figure A.3: Dimples remove spherical cones around a sphere’s x-axis.
351
342-369 Appendices.indd 351
9/24/08 12:57:17 PM
• holeshape (all shapes except PRIM_TYPE_SCULPT) is an integer that must be one of the following constants: PRIM_HOLE_DEFAULT, PRIM_HOLE_SQUARE, PRIM_HOLE_CIRCLE, PRIM_HOLE_TRIANGLE. It controls the shape of the hollow area inside the object, as shown in Figure A.4. APPENDICES
A ppendix A
Figure A.4: A PRIM_HOLE_SQUARE can cause interesting effects in a torus.
A ppendix B A ppendix C
• holesize (torus, tube, and ring) is a vector that indicates the size of the empty part in the middle. x ranges from 0.05 to 1.0; think of it a bit like the thickness of a torus, or the vertical gap between revolutions. y controls the size of the inside hole, and ranges from 0.05 (large hole) to 0.50 (no hole), as shown in Figure A.5 (z is ignored). A corkscrew might have x = 0.50 and y = 0.25, with 3 revolutions (similar to what's shown in Figure A.11).
Figure A.5: The tube on the left has a holesize.y of 0.25, while the tube on the right has a holesize.y of 0.05.
• hollow (all shapes except PRIM_TYPE_SCULPT) is a float that ranges from 0.0 (solid) to 0.95 (thin shell). Figure A.6 shows six prims with different hollow values and different effects. In boxes, cylinders, and prisms the hole goes all the way through the object (acting sort of like the holesize for tori, tubes, and rings). In spheres, tori, tubes, and rings, the hollow stays entirely within the outer shell. Any number greater than 0.95 is treated as 0.95; watch for accidental use of the GUI's 0.0-to-95.0 scale.
Figure A.6: From left to right, these objects have hollow = 0.0, 0.25, and 0.75.
NOTE
352
342-369 Appendices.indd 352
hollow is not the same as holesize; the object in Figure A.4 has holesize=, while the objects in Figure A.5 could easily be hollow—the space would be inside the shell, where you can’t see it. Just to make sure Linden Lab’s naming scheme is sufficiently confusing, holeshape deals with the shape of the hollow, not the hole. The child’s toy shown here is a series of tori with increasing size and increasing holesize. All of them are hollow = 0.95. To keep the walls of the tori approximately the same width, they have increasing holesize.y values. For comparison, the purple torus has the same holesize.y as the orange one; you can see that its walls are fatter.
9/24/08 12:57:23 PM
• map (only PRIM_TYPE_SCULPT) is the string name of a displacement map (Figure A.7) in inventory or a UUID key of a displacement map*. If map is not a displacement map or if it is named and missing from the prim's inventory, then an error is shouted on DEBUG_CHANNEL.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8
Figure A.7: Displacement maps for sculpted prims are uploaded as textures; each color channel represents an axis in 3D space, where red, green, and blue map to x, y, and z respectively. The apple and banana displacement maps are in your Inventory’s Library folder.
• profilecut (torus, tube, and ring) is a vector that slices away parts of the shape around the x-axis from x to y (think of x as the start and y as the end of the pie slice). The center of the pie lies where the center of the hole would be if the object were hollow. Figure A.8 shows the results for a range of profilecut.x values, and Figure A.9 shows the results for a range of profilecut.y values. x and y range from 0.0 to 1.0; x must be at least 0.05 smaller than y (z is ignored).
x=0.8
x=0.1
x=0.7
x=0.2
x=0.6
x=0.3
CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15
APPENDICES
x=0.5
x=0.4
Figure A.8: A profilecut starts at the angle specified by the x value. Increasing values rotates the cut clockwise around the x-axis. All tori in this image are exactly the same size.
* A good source for experimenting with displacement map textures is http://wiki.secondlife.com/wiki/ Sculpted_Prims_Textures
342-369 Appendices.indd 353
CHAPTER 9
353
9/24/08 12:57:27 PM
x=0.9
x=0.8
x=0.7
x=0.6
APPENDICES
A ppendix A A ppendix B A ppendix C
x=0.2
x=0.3
x=0.4
x=0.5
Figure A.9: A profilecut ends at the angle specified by the y value. Increasing values rotates the cut counterclockwise around the x-axis. All tori are exactly the same size.
• radiusoffset (torus, tube, and ring) is a float that controls the distance between where an arc starts and where it ends, from the perspective of the y-axis. Zero means that it is a circle; 1.0 has the arc start very near the center and end at the size of the torus (as shown in Figure A.10); –1.0 has the arc start at the size of the torus and end at the center. Its effect depends on holesize.y and revolutions.
Figure A.10: radiusOffset controls how far apart the torus start and end points are, around the y-axis.
• revolutions (torus, tube, and ring) is a float that indicates how many times to rotate the object around the x-axis, as shown in Figure A.11. Valid values range from 1.0 to 4.0.
Figure A.11: This tube has three revolutions. (holesize.x is 0.5; if holesize.x were 1.0, then from this angle you would see none of the internal portions of the spiral.)
• sculpttype (only PRIM_TYPE_SCULPT) is an integer that is one of the following constants: PRIM_SCULPT_TYPE_SPHERE, PRIM_SCULPT_TYPE_TORUS, PRIM_SCULPT_ TYPE_PLANE, PRIM_SCULPT_TYPE_CYLINDER. • skew (torus, tube, and ring) is a float that controls the distance between where an arc starts and where it ends, from the perspective of the x-axis. It ranges from –0.95 to 0.95. Figure A.12 shows the middle of that range.
354
342-369 Appendices.indd 354
Figure A.12: Skew controls how far apart the torus start and end points are, around the x-axis. This figure has skew = 0.5.
9/24/08 12:57:33 PM
• taper_a (torus, tube, and ring) is a vector that adjusts the height (x) or thickness (y) of the side walls of the torus, ring, or tube. x and y range from 0.0 to 1.0. Figure A.13 shows the effect of taper_a on a cylinder.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4
Figure A.13: The tube on the left shows taper_a = ; the tube on the right shows taper_a = . This parameter does interesting things when interacting with revolutions in the GUI: try adding 3 revolutions, then returning to 1—you won’t get what you started with.
CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9
• taper_b (box, cylinder, and prism) is a vector that adjusts the size of the top of the object, ranging from 0.0 (no top) to 2.0 (no bottom); z is ignored. For example, a cube with taper_b = appears as a pyramid. Note that the GUI values are different; they range from –1.0 (no bottom) to 1.0 (no top). Figure A.14 shows an example of a tapered cube.
CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
Figure A.14: This cube has taper_b = . The red side meets at the top (0.0) while the blue side doesn’t (0.25). Note that the GUI shows these values as x = 1.0 and y = 0.75.
• topshear (box, cylinder, prism, torus, tube and ring) is a vector that controls an object's tilt, and ranges from –0.5 to 0.5 for both x, and y (z is ignored). Figure A.15 shows the effect of topshear on a cube.
CHAPTER 15
APPENDICES
Figure A.15: A topshear of tilts this cube into a parallelogram.
• twist is a vector; each component is a multiplier of a full rotation around the z axis (1.0 = 360 degrees). Component use depends on prim type, but x is always how much to twist the beginning end of the prim (usually minimum z) and y is how much to twist the opposite end (usually maximum z): • Box, cylinder, and prism: Ranges from –0.5 to 0.5. Figure A.16 shows the effect of twist on a cube. • Sphere: Ranges from –0.5 to 0.5. When twist = , the sphere turns inside out; this trick can be used to make tiny prims (see the sidebar "Making Tiny Prims"). Values outside this range cause strange shapes rather than errors. • Tube, torus, and ring: Ranges from –1.0 to 1.0. Twists in these objects do beautiful things.
Figure A.16: twist causes the cube to turn on itself.
The Ivory Tower Library of Primitives (Figure A.17) at Natoma has wonderful examples of each of these settings.
342-369 Appendices.indd 355
355
9/24/08 12:57:39 PM
APPENDICES
A ppendix A
Figure A.17: The second floor of the Ivory Tower Library of Primitives has samples of the effects of all primitive parameters.
A ppendix B A ppendix C
MAKING TINY PRIMS Careful use of primitive parameters can yield prims that are significantly smaller than the minimum size. Torley Linden has a wonderful video tutorial at http://wiki. secondlife.com/wiki/Video_Tutorials (search for “How to make tiny prims”). Natalia Zelmanov has a good web tutorial at http://www.mermaiddiaries.com/2006/11/build. html (look for “Jewelry Part 3”). Both tutorials use the object editor in the GUI, but you can accomplish the same thing with scripts. The following code snippet makes the flat triangle shown here: 10mm tall by 2mm wide, and a scant .2mm thick! The box on the left is a plain cube. The box on the right shows that it has been hollowed to the maximum value, then cut and taper make it into a triangle that measures only . makeTinyTriangle() { float hollow = 0.95; vector cut = ; // edge 2mm wide vector twist = ; vector taper_b = ; // make triangular //vector taper_b = ; // make square vector topshear = ; llSetPrimitiveParams( [PRIM_SIZE, , PRIM_TYPE, PRIM_TYPE_BOX, PRIM_HOLE_DEFAULT, cut, hollow, twist, taper_b, topshear]); }
This next code snippet makes a sphere about 5mm in diameter. It creates a tiny hollow and then turns the sphere inside out with twist. The outside shell is made transparent in a second call to llSetPrimitiveParams() because the parser sometimes gets confused about when to do it.
356
342-369 Appendices.indd 356
makeTinySphere() { vector cut = ; float hollow = 0.05; // bigger hollow means bigger sphere vector twist = ; vector dimple = ; llSetPrimitiveParams([ PRIM_TYPE, PRIM_TYPE_SPHERE, PRIM_HOLE_DEFAULT, cut, hollow, twist, dimple, PRIM_SIZE, , PRIM_COLOR, ALL_SIDES, , 1.0 ]); llSetPrimitiveParams([ // not part of first call PRIM_COLOR, 0, , 0.0 // outside of sphere ]); }
9/24/08 12:57:42 PM
CHAPTER 1 CHAPTER 2
APPENDIX B: PARTICLE SYSTEM
CHAPTER 3 CHAPTER 4 CHAPTER 5
Second Life's particle system allows you to create a wide variety of effects, including shiny jewelry; flames and smoke; water splashes and bubbles; falling leaves and petals; and weather effects such as rain, snow, fog, and lightning. You can stretch it further to make butterflies, rainbows, or the string of a kite.
CHAPTER 6
The llParticleSystem() function creates a particle system within the prim that contains the script. Each prim has only one particle emitter, located at its geometric center and aligned along the z-axis. Multiple scripts in the same prim all operate on the same emitter; therefore if you want to make one object appear to have multiple particle systems, you need to make multiple prims. A particle system is saved in the object's state, which means deleting the script does not stop the particle flow.
CHAPTER 9
CHAPTER 7 CHAPTER 8
CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
DEFINITION
CHAPTER 15
llParticleSystem(list rules) Defines a particle system for the containing prim based on a list of rules. Turn the emitter off using llParticleSystem([]).
APPENDICES
rules — Particle-system rules list in the format [ property_1, data_1, property_2, data_2, ..., property_n, data_n ]
This appendix lists the various properties and their potential data values. The wiki lists several unimplemented properties—both flags and constants. Example use occurs in Listings 9.1, 9.2, 9.4, 9.5, and 10.7. Jopsy Pendragon has developed a wonderful tutorial at the Particle Laboratory at Teal . The lab has many free scripts and many interactive displays like the one in Figure B.1. Torley Linden also has a good video tutorial in his "Tip of the Week" series that samples some particle-system ideas; search for Particle Editing Magic! at http://wiki.secondlife.com/wiki/Video_Tutorials.
Figure B.1: One of many interactive particlesystem demonstrations at the Particle Laboratory. This one shows what happens with a PSYS_SRC_TARGET_KEY (green particles travel toward a target) and without (red particles float free).
342-369 Appendices.indd 357
357
9/24/08 12:57:49 PM
Flags APPENDICES
Various flags control the particle system's behavior. There is only one named property; its values are a bitmask.
PSYS_PART_FLAGS A ppendix A A ppendix B A ppendix C
An integer value specified as one or more values from Table B.1 ORed together (using the | operator). Table B.1: Particle-System Flags (Bitmask) Flag
Description
Value
PSYS_PART_INTERP_COLOR_MASK
When set, particle color and alpha transition from PSYS_PART_ START_COLOR to PSYS_PART_END_COLOR (and PSYS_ PART_START_ALPHA to PSYS_PART_END_ALPHA) during the particle's lifetime. The transition is a smooth interpolation.
0x001
PSYS_PART_INTERP_SCALE_MASK
When set, particle scale transitions from its PSYS_PART_ START_SCALE to PSYS_PART_END_SCALE setting during the particle's lifetime.
0x002
PSYS_PART_BOUNCE_MASK
When set, particles bounce off a plane at the emitter's z height. On bounce, each particle reverses velocity and angle. Particles must be emitted upward so they can fall down to the plane.
0x004
PSYS_PART_WIND_MASK
When set, wind affects particle movement. Wind is applied as a secondary force on the particles.
0x008
PSYS_PART_FOLLOW_SRC_MASK
When set, particles move relative to the emitter's position. Otherwise, the emitter's position and movement do not affect particle position and movement. This flag disables the PSYS_ SRC_BURST_RADIUS rule.
0x010
PSYS_PART_FOLLOW_VELOCITY_MASK
When set, particles rotate to orient their "top" toward the direction of movement. Otherwise, particles always face up.
0x020
PSYS_PART_TARGET_POS_MASK
When set, emitted particles change course during their lifetime, attempting to move toward the target specified by the PSYS_ SRC_TARGET_KEY rule by the time they expire.
0x040
PSYS_PART_TARGET_LINEAR_MASK
When set, emitted particles move in a straight line toward the target specified by the PSYS_SRC_TARGET_KEY rule. In this mode, PSYS_SRC_ACCEL, PSYS_SRC_BURST_RADIUS, and possibly other rules are ignored.
0x080
PSYS_PART_EMISSIVE_MASK
When set, particles are fullbright and are unaffected by global lighting (sunlight). Otherwise, particles are lit depending on the current global lighting conditions. Note that point lights do illuminate nonemissive particles.
0x100
WARNING Particle system float values need to be floats, and integer values need to be integers. This is one place where LSL will not cast implicitly. Vectors, however, can still be mixed integer and float, since vector components are always cast to float.
358
342-369 Appendices.indd 358
9/24/08 12:57:50 PM
CHAPTER 1
Particle Placement
CHAPTER 2
The five properties in this section control the pattern and initial placement of particles.
CHAPTER 3 CHAPTER 4
PSYS_SRC_PATTERN
CHAPTER 5
Specifies the general emission pattern, using one (and only one) of the integer constants shown in Table B.2. The explode pattern tends to be overused, in the sense that often particles are lost into the ground or behind a wall (and therefore wasted). In these cases, use the angle cone instead and focus your particles where they can be seen.
Constant
B ehavior
Value
PSYS_SRC_PATTERN_DROP
Presents particles by dropping them at the emitter position with no force. Ignores the PSYS_SRC_BURST_RADIUS, PSYS_SRC_ BURST_SPEED_MIN, and PSYS_SRC_BURST_SPEED_MAX rules.
0x01
Presents particles by shooting them out in all directions according to the 0x02 burst motion rules. Ignores PSYS_SRC_ANGLE_BEGIN, PSYS_SRC_ ANGLE_END, and PSYS_SRC_ANGLE_OMEGA.
PSYS_SRC_PATTERN_ANGLE
Presents particles in a 2D circular section as defined by PSYS_SRC_ ANGLE_BEGIN and PSYS_SRC_ANGLE_END, as shown in Figure B.2. The plane lies on the emitter's y-axis.
0x04
PSYS_SRC_PATTERN_ANGLE_CONE
Presents particles in a 3D spherical section, as defined by PSYS_SRC_ ANGLE_BEGIN and PSYS_SRC_ANGLE_END.
0x08
_S
RC
NG
LE
_B
CHAPTER 12
CHAPTER 14 CHAPTER 15
APPENDICES
IN
0 PI/2
PS
CHAPTER 11
EG
Particles Emitted ND E_E
NGL
C_A
CHAPTER 9
No Particles Emitted
_A
SR YS_
CHAPTER 8
CHAPTER 13
PSYS_SRC_PATTERN_EXPLODE
YS
CHAPTER 7
CHAPTER 10
Table B.2: Particle-System Patterns (Not a Bitmask, despite Appearances)
PS
CHAPTER 6
Particles Emitted -PI/2
PI No Particles Emitted
Figure B.2: PSYS_SRC_PATTERN_ANGLE emits particles on the y-axis between PSYS_SRC_ANGLE_ BEGIN and PSYS_SRC_ANGLE_END. 0.0 radians is “up” on the z-axis. For a PSYS_SRC_PATTERN_ ANGLE_CONE pattern, rotate the emitter zone around the z-axis.
PSYS_SRC_BURST_RADIUS A float that specifies the distance in meters from the emitter where particles will be created. This rule is ignored when the PSYS_PART_FOLLOW_SRC_MASK flag is set. Maximum value is 50.00.
342-369 Appendices.indd 359
359
9/24/08 12:57:51 PM
PSYS_SRC_ANGLE_BEGIN
APPENDICES
A ppendix A A ppendix B A ppendix C
A float that specifies a half angle, in radians, of a circular or spherical "dimple," or conic section, starting from 0.0 degrees on the emitter's z-axis within which particles will not be emitted. Valid values are 0.0 to π (PI). 0.0 causes no dimple, and π would allow only a straight line in the direction opposite where the emitter is facing. If the pattern is PSYS_SRC_PATTERN_ANGLE, the presentation is a 2D flat circular section. If PSYS_SRC_PATTERN_ANGLE_CONE is used, the presentation is a 3D spherical section. Note that the value of PSYS_SRC_ANGLE_BEGIN and PSYS_SRC_ANGLE_END are reordered internally such that PSYS_SRC_ANGLE_BEGIN gets the smaller of the two values.
PSYS_SRC_ANGLE_END A float that specifies a half angle, in radians, of a circular or spherical "dimple," or conic section, starting from 0.0 degrees on the emitter's z-axis, within which particles will be emitted. Valid values are 0.0 to π (PI); 0.0 results in particles being emitted in a straight line in the direction the emitter is facing, and π results in particles being emitted in a full circular or spherical arc around the emitter, not including the dimple defined by PSYS_SRC_ANGLE_BEGIN. If the pattern is PSYS_SRC_PATTERN_ANGLE, the presentation is a 2D flat circular section. If PSYS_SRC_PATTERN_ANGLE_CONE or PSYS_SRC_ PATTERN_ANGLE_CONE_EMPTY is used, the presentation is a 3D spherical section. Note that the value of PSYS_SRC_ANGLE_END and PSYS_SRC_ANGLE_BEGIN are reordered internally such that PSYS_SRC_ANGLE_END gets the larger of the two values.
PSYS_SRC_OMEGA A vector that specifies the emitter's rotational spin in radians per second along each axis. Prim spin (via llTargetOmega()) has no effect on emitter spin because the spin defined by PSYS_SRC_OMEGA is relative to the region coordinate system, not the prim's local coordinate system. There is no way to set the emitter's orientation.
Particle Flow Four properties control the flow rate of particles: the emitter's age, the particles' age, particle-burst frequency, and number of particles per burst. A single particle system can manage only 4,096 particles at a time. The total number of concurrent particles is calculated by the following equation: PSYS_PART_MAX_AGE ÷ PSYS_SRC_BURST_RATE × PSYS_SRC_BURST_PART_COUNT
With multiple emitters (especially textured ones), your viewer may suffer. Particles are a client-side effect, so they do not cause sim lag. You can set an internal particle-count limit in your viewer preferences: Edit Preferences Graphics tab and using the Max Particle Count slider. The default value is 4,096, and can be raised to 8,192 directly. It can also be increased in settings.ini.
PSYS_SRC_MAX_AGE
360
A float that specifies the length of time, in seconds, for which the emitter will operate upon coming into view range (if the particle system is already set) or upon execution of this function (if already in view range). Upon expiration, no more particles will be emitted, except as specified in the next paragraph. An emitter age of 0.0 gives the particle system an infinite duration.
342-369 Appendices.indd 360
9/24/08 12:57:52 PM
When using particle systems that have a nonzero emitter age, the particle system may restart without any scripted trigger going off. This is due to a bug that causes the emitter to reset when any of the prim properties are updated or otherwise sent to the viewer. As a result, you may have to use a timer or a forced sleep and then clear the particle system using llParticleSystem([]) once the age has expired.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4
PSYS_PART_MAX_AGE A float that specifies the lifetime, in seconds, of each particle emitted. Maximum is 30.0 seconds. During this time the particle will appear, change appearance, move according to the parameters specified in the other sections, then disappear.
CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8 CHAPTER 9
PSYS_SRC_BURST_RATE A float that specifies the time interval, in seconds, between bursts of particles being emitted. Specifying a value of 0.0 causes particles to emit as fast as the viewer can support.
PSYS_SRC_BURST_PART_COUNT
CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15
An integer that specifies the number of particles emitted in each burst.
Particle Appearance Three flags affect the appearance of the particles:
APPENDICES
• PSYS_PART_INTERP_COLOR_MASK • PSYS_PART_INTERP_SCALE_MASK • PSYS_PART_EMISSIVE_MASK By using these flags along with the following seven properties, you can control the glow, size, color, transparency, and texture of your particles.
PSYS_PART_START_SCALE A vector that specifies the scale or size of the particles upon emission. Valid values for each direction are 0.03125 to 4.0, in meters. The vector's z component is ignored and can be set to 0.0.
PSYS_PART_END_SCALE A vector that specifies the particles' scale or size at the end of their lifespan. Used only if the PSYS_ PART_INTERP_SCALE_MASK flag is set. Valid values are the same as PSYS_PART_START_SCALE.
PSYS_PART_START_COLOR A color vector that specifies the particles' color upon emission.
PSYS_PART_END_COLOR A color vector that specifies the particles' color at the end of their lifetime. Used only if the PSYS_ PART_INTERP_COLOR_MASK flag is set.
342-369 Appendices.indd 361
361
9/24/08 12:57:54 PM
PSYS_PART_START_ALPHA A float that specifies the particles' alpha upon emission. Valid values are in the range 0.0 (transparent) to 1.0 (opaque). APPENDICES
A ppendix A A ppendix B A ppendix C
PSYS_PART_END_ALPHA A float that specifies the particles' alpha at the end of their lifespan. Used only if the PSYS_PART_ INTERP_COLOR_MASK flag is set. Valid values are the same as PSYS_PART_START_ALPHA.
PSYS_SRC_TEXTURE A string that specifies the name of a texture in the prim's inventory to use for each particle. Alternatively, you may specify an asset key UUID for a texture. An emitter emits particles with only one texture at a time. The default texture is a white circle, with not quite transparent corners.
NOTE Your Inventory’s Library contains interesting textures in the Textures folder, and a couple of sample particle scripts in the Waterfalls folder. Jopsy Pendragon has some excellent particle textures for sale at the Particle Laboratory at Teal . Textures are available all over SL, but it might take some work to find freebies that are good for particles. The following table shows a handful of useful textures. They are all Trans/Copy/Mod, given out as freebies.
Name
Creator
Key
Red Arrow
— (Library)
a990f9a1-ab01-5071-444e-552b0f3f4983
Water
— (Library)
4c6bee64-0869-63da-e2ba-71c984721a23
Mist
— (Library)
dcab6cc4-172f-e30d-b1d0-f558446f20d4
Smoke
— (Library)
b4ba225c-373f-446d-9f7e-6cb7b5cf9b3d
Snowflake
Kinn Gray
60ec4bc9-1a36-d9c5-b469-0fe34a8983d4
Lightning
Chrischun Fassbinder
a9b196ef-d358-e3bf-240f-c5773bfd4e05
Lava
Thili Playfair
9db6a042-add2-5352-08c5-af70899ee86c
Sparks
Jopsy Pendragon
181c6b1d-c2d0-70ba-bbf2-52ccc31687c6
Clown Fish
Arazael Maracas
13c20952-8cce-27a8-8e18-05c19bc59147
Fern Leaf
Karzita Zabaleta
b404c9f4-74d2-1f91-abc9-d36e5da0f263
Ghost
Karzita Zabaleta
7b0f3873-ff44-fd8c-fa6c-c813dd0501b7
Pink Rose Petal
Vex Streeter
7d03784c-38f7-b722-7c9f-5f7fa6fde53d
Smiley Face
Vex Streeter
6b4c8a9d-e51c-109f-e266-375738afc1bc
Red Heart
Jopsy Pendragon
5b3f3df0-b20b-5dc4-b49e-377c5805a0e3
Be Mine! Valentine Message
Karzita Zabaleta
2047827b-65ae-cad0-9f4a-267480b03f86
Question Mark
Vex Streeter
377d2ee4-2394-0f73-aaf5-be7113c438e9
Don’t forget that an emitter can use the name of a texture in its inventory—that’s usually much easier than dealing with keys.
362
342-369 Appendices.indd 362
9/24/08 12:57:55 PM
Particle Motion Six of the flags in Table B.1 control particle motion:
CHAPTER 1 CHAPTER 2 CHAPTER 3
• PSYS_PART_FOLLOW_VELOCITY_MASK
CHAPTER 4
• PSYS_PART_WIND_MASK
CHAPTER 5
• PSYS_PART_BOUNCE_MASK
CHAPTER 6
• PSYS_PART_TARGET_LINEAR_MASK • PSYS_PART_TARGET_POS_MASK • PSYS_PART_FOLLOW_SRC_MASK Additionally, there are four properties that control the particle's speed, acceleration, and final position.
PSYS_SRC_TARGET_KEY Specifies the key of a target object or agent toward which the particles will travel. They attempt to end up at the target's geometric center at the end of their lifetime. Particles will track a moving target. Requires the PSYS_PART_TARGET_POS_MASK flag to be set. Particles moving toward a humanoid avatar end at the geometric center of the avatar's bounding box, which, unfortunately, makes them appear to be striking the avatar in the groin. If you want them to end up at another point on a target avatar, you have to place a target prim where you wish them to end up, and use that prim's key.
CHAPTER 7 CHAPTER 8 CHAPTER 9 CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14 CHAPTER 15
APPENDICES
If PSYS_PART_TARGET_LINEAR_MASK is set, the particles will move evenly in a straight line toward the target, ignoring velocity and acceleration. If no target is specified, the target moves out of range, or an invalid target is specified, the particles target the prim itself.
PSYS_SRC_BURST_SPEED_MIN A float that specifies the minimum value of a random range of values that is selected for each particle in a burst as its initial speed upon emission, in meters per second. Note that the value of PSYS_SRC_ BURST_SPEED_MIN and PSYS_SRC_BURST_SPEED_MAX are reordered internally such that PSYS_ SRC_BURST_SPEED_MIN gets the smaller of the two values.
PSYS_SRC_BURST_SPEED_MAX A float that specifies the maximum value of a random range of values that is selected for each particle in a burst as its initial speed upon emission, in meters per second. Note that the value of PSYS_SRC_ BURST_SPEED_MAX and PSYS_SRC_BURST_SPEED_MIN are reordered internally such that PSYS_ SRC_BURST_SPEED_MAX gets the larger of the two values.
PSYS_SRC_ACCEL A vector that specifies a directional acceleration vector applied to each particle as it is emitted, in meters per second. Valid values are 0.0 to 100.0 for each direction, as region coordinates.
363
342-369 Appendices.indd 363
9/24/08 12:57:56 PM
Particle-System Cheat Sheet APPENDICES
The following code snippets are complete particle systems, albeit not very exciting ones. If you're having trouble seeing how the particles change, add PSYS_SRC_ACCEL, to the rule.
Respond to Wind A ppendix A
llParticleSystem([PSYS_PART_FLAGS, PSYS_PART_WIND_MASK]);
A ppendix B A ppendix C
Respond to Gravity llParticleSystem([PSYS_SRC_ACCEL, ]);
Change Size llParticleSystem([ PSYS_PART_FLAGS, PSYS_PART_INTERP_SCALE_MASK, PSYS_PART_START_SCALE, , PSYS_PART_END_SCALE, ]);
Change Color llParticleSystem([ PSYS_PART_FLAGS, PSYS_PART_INTERP_COLOR_MASK, PSYS_PART_START_COLOR, , PSYS_PART_END_COLOR, ]);
Change Transparency llParticleSystem([ PSYS_PART_FLAGS, PSYS_PART_INTERP_COLOR_MASK, PSYS_PART_START_ALPHA, 1.0, PSYS_PART_END_ALPHA, 0.0]);
Use a Texture llParticleSystem([PSYS_SRC_TEXTURE, "My Particle Texture"]); llParticleSystem([ PSYS_SRC_TEXTURE, a990f9a1-ab01-5071-444e-552b0f3f4983"]);
Make a Spiral
364
342-369 Appendices.indd 364
llParticleSystem([ PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE, PSYS_SRC_BURST_PART_COUNT, 10, PSYS_SRC_ANGLE_BEGIN, PI_BY_TWO, PSYS_SRC_ANGLE_END, PI_BY_TWO, PSYS_SRC_OMEGA, ]);
9/24/08 12:57:57 PM
Travel toward a Target
CHAPTER 1 CHAPTER 2
llParticleSystem([ PSYS_PART_FLAGS, PSYS_PART_TARGET_POS_MASK, PSYS_SRC_TARGET_KEY, llGetOwner()]);
CHAPTER 3 CHAPTER 4 CHAPTER 5
Stop Emitting
CHAPTER 6 CHAPTER 7
llParticleSystem([]);
CHAPTER 8 CHAPTER 9
Default Values
CHAPTER 10
You can create a particle system that relies on the default values for many parameters (as evidenced by the simple examples just provided). Many people write their code to make changes to default values easy to see, as in this snippet: llParticleSystem([ PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE, PSYS_SRC_BURST_PART_COUNT, 10, PSYS_SRC_ANGLE_BEGIN, PI_BY_TWO, PSYS_SRC_ANGLE_END, PI_BY_TWO, PSYS_SRC_OMEGA, ]);
CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
// // // // //
DROP 1 0.0 0.0
Also common in particle scripts is a complete list of the parameters, as shown in Listing B.1. Scripters often alter a few specific ones of interest to change the particle system's behavior. This approach has two advantages. First, scripters never have to go hunting for the precise spelling of a parameter they weren't previously using—it's already there in the script with its default value. Second, if Linden Lab ever changes the default settings, the particle system won't change.
CHAPTER 15
APPENDICES
Create a script in your inventory named Complete Particle Script, put Listing B.1 inside, and it will be there for the next time you want to make particles. If you run this directly, it makes an extremely unexciting glowing white ball—the particles don't even move. Many of the particle scripts floating around SL are (rightly) based off a template by Jopsy Pendragon. There are two differences between his and listing B.1: Jopsy's template defines all the values for the properties as variables before the llParticleSystem() call, and includes many more comments about the behavior of each property.
Listing B.1: A Complete Particle System with Only Default Values makeParticleSystem() { integer InterpColorMask integer InterpScaleMask integer EmissiveMask integer FollowSrcMask integer FollowVelocityMask integer WindMask integer BounceMask integer TargetPosMask integer TargetLinearMask
= = = = = = = = =
FALSE; FALSE; FALSE; FALSE; FALSE; FALSE; FALSE; FALSE; FALSE;
365
342-369 Appendices.indd 365
9/24/08 12:57:58 PM
llParticleSystem([ PSYS_PART_FLAGS, ( ( EmissiveMask ( BounceMask ( InterpColorMask ( InterpScaleMask ( WindMask ( FollowSrcMask ( FollowVelocityMask ( TargetLinearMask ( TargetPosMask
APPENDICES
A ppendix A A ppendix B A ppendix C
//Placement PSYS_SRC_PATTERN, PSYS_SRC_BURST_RADIUS, PSYS_SRC_ANGLE_BEGIN, PSYS_SRC_ANGLE_END, PSYS_SRC_OMEGA,
* * * * * * * * *
PSYS_PART_EMISSIVE_MASK ) | PSYS_PART_BOUNCE_MASK ) | PSYS_PART_INTERP_COLOR_MASK ) | PSYS_PART_INTERP_SCALE_MASK ) | PSYS_PART_WIND_MASK ) | PSYS_PART_FOLLOW_SRC_MASK ) | PSYS_PART_FOLLOW_VELOCITY_MASK ) | PSYS_PART_TARGET_LINEAR_MASK ) | PSYS_PART_TARGET_POS_MASK ) ),
PSYS_SRC_PATTERN_DROP, 0.00, 0.00, 0.00, < 0.00, 0.00, 0.00 >,
//Flow PSYS_SRC_MAX_AGE, PSYS_PART_MAX_AGE, PSYS_SRC_BURST_RATE, PSYS_SRC_BURST_PART_COUNT, //Appearance PSYS_PART_START_SCALE, PSYS_PART_END_SCALE, PSYS_PART_START_COLOR, PSYS_PART_END_COLOR, PSYS_PART_START_ALPHA, PSYS_PART_END_ALPHA, PSYS_SRC_TEXTURE,
0.0, 10.00, 0.10, 1,
< 1.0, < 1.0, < 1.0, < 1.0, 1.00, 1.00, "",
//Movement PSYS_SRC_BURST_SPEED_MIN, PSYS_SRC_BURST_SPEED_MAX, PSYS_SRC_ACCEL, PSYS_SRC_TARGET_KEY, ]);
1.0, 1.0, 1.0, 1.0,
0.0 0.0 1.0 1.0
>, >, >, >,
1.00, 1.00, < 0.00, 0.00, 0.00 >, llGetKey() // come back to itself
} init(integer delay) { makeParticleSystem(); llSetTimerEvent( delay ); } default { state_entry() { init( 30 ); } on_rez(integer t) { llResetScript(); } timer() { llParticleSystem([]); llSetTimerEvent( 0 ); } }
// turn on the particle system // set up a timer to turn it off
// turn off the particle system // turn off the timer
366
342-369 Appendices.indd 366
9/24/08 12:58:00 PM
COMMON PROBLEMS
CHAPTER 1
Extracted from a notecard by Jopsy Pendragon, Particle Laboratory, Teal
CHAPTER 3
CHAPTER 2
CHAPTER 4
Particles too small?
CHAPTER 5 CHAPTER 6
• Particles must be at least 0.04 wide and tall at some point in their life to be displayed.
CHAPTER 7
• Make sure you have INTERP_SCALE_MASK enabled.
CHAPTER 8 CHAPTER 9
Particles too faint? • Is your ALPHA setting really low (below 0.1)? • Make sure you have INTERP_COLOR_MASK enabled.
CHAPTER 10 CHAPTER 11 CHAPTER 12 CHAPTER 13
Particle drought?
CHAPTER 14 CHAPTER 15
• Is MAX_AGE really small? Is PART_COUNT = 0? Is your BURST_RATE really long? These can trip you up too. • Try setting your emitter prim transparent, the particles may just be trapped inside!
Particles going nowhere?
APPENDICES
• Unless you use PATTERN_DROP, particles must have a SPEED_MIN and SPEED_MAX of at least 0.01, or they don’t appear.
Particles going too far, too fast to be seen? • What’s your SPEED_MIN set to? They might be too fast to see. • Check your BURST_RADIUS setting. Look further away from the emitter for your particles.
Emitter not being rendered? • Small prims far from the viewer often don’t get drawn, and when they don’t, neither do the particles on that prim. You can use this to your advantage! Make small delicate particle effects on small prims so that they don’t show up unless someone is nearby and can appreciate it!
Particle overload? • Each client can only display so many particles at a time. If there are several emitters near a viewer all pumping out lots of particles, your emitter may be creating so few that it seems broken. Give a hoot, don’t pollute!
Typo? • If all else fails, look for misplaced decimal points.
367
342-369 Appendices.indd 367
9/24/08 12:58:01 PM
APPENDIX C: SL COMMUNITY STANDARDS
APPENDICES
A ppendix A A ppendix B A ppendix C
Welcome to the Second Life world! We hope you'll have a richly rewarding experience, filled with creativity, self expression and fun. The goals of the Community Standards are simple: treat each other with respect and without harassment, adhere to local standards as indicated by simulator ratings, and refrain from any hate activity which slurs a real-world individual or real-world community.
Behavioral Guidelines—The "Big Six" Within Second Life, we want to support Residents in shaping their specific experiences and making their own choices. The Community Standards sets out six behaviors, the "Big Six," that will result in suspension or, with repeated violations, expulsion from the Second Life community. All Second Life Community Standards apply to all areas of Second Life, the Second Life Forums, and the Second Life website. 1. Intolerance Combating intolerance is a cornerstone of Second Life's Community Standards. Actions that marginalize, belittle, or defame individuals or groups inhibit the satisfying exchange of ideas and diminish the Second Life community as whole. The use of derogatory or demeaning language or images in reference to another Resident's race, ethnicity, gender, religion, or sexual orientation is never allowed in Second Life. 2. Harassment Given the myriad capabilities of Second Life, harassment can take many forms. Communicating or behaving in a manner which is offensively coarse, intimidating or threatening, constitutes unwelcome sexual advances or requests for sexual favors, or is otherwise likely to cause annoyance or alarm is Harassment. 3. Assault Most areas in Second Life are identified as Safe. Assault in Second Life means: shooting, pushing, or shoving another Resident in a Safe Area (see Global Standards below); creating or using scripted objects which singularly or persistently target another Resident in a manner which prevents their enjoyment of Second Life. 4. Disclosure Residents are entitled to a reasonable level of privacy with regard to their Second Life experience. Sharing personal information about a fellow Resident—including gender, religion, age, marital status, race, sexual preference, and real-world location beyond what is provided by the Resident in the First Life page of their Resident profile is a violation of that Resident's privacy. Remotely monitoring conversations, posting conversation logs, or sharing conversation logs without consent are all prohibited in Second Life and on the Second Life Forums.
368
5. Indecency Second Life is an adult community, but Mature material is not necessarily appropriate in all areas (see Global Standards below). Content, communication, or behavior which involves intense language or expletives, nudity
342-369 Appendices.indd 368
9/24/08 12:58:06 PM
or sexual content, the depiction of sex or violence, or anything else broadly offensive must be contained within private land in areas rated Mature (M). Names of Residents, objects, places, and groups are broadly viewable in Second Life directories and on the Second Life website, and must adhere to PG guidelines. 6. Disturbing the Peace Every Resident has a right to live their Second Life. Disrupting scheduled events, repeated transmission of undesired advertising content, the use of repetitive sounds, following or self-spawning items, or other objects that intentionally slow server performance or inhibit another Resident's ability to enjoy Second Life are examples of Disturbing the Peace.
CHAPTER 1 CHAPTER 2 CHAPTER 3 CHAPTER 4 CHAPTER 5 CHAPTER 6 CHAPTER 7 CHAPTER 8
Policies and Policing
CHAPTER 9
Global Standards, Local Ratings All areas of Second Life, including the www.secondlife.com website and the Second Life Forums, adhere to the same Community Standards. Locations within Second Life are noted as Safe or Unsafe and rated Mature (M) or non-Mature (PG), and behavior must conform to the local ratings. Any unrated area of Second Life or the Second Life website should be considered non-Mature (PG).
CHAPTER 10
Warning, Suspension, Banishment Second Life is a complex society, and it can take some time for new Residents to gain a full understanding of local customs and mores. Generally, violations of the Community Standards will first result in a Warning, followed by Suspension and eventual Banishment from Second Life. In-World Representatives, called Liaisons, may occasionally address disciplinary problems with a temporary removal from Second Life.
CHAPTER 15
Global Attacks Objects, scripts, or actions which broadly interfere with or disrupt the Second Life community, the Second Life servers or other systems related to Second Life will not be tolerated in any form. We will hold you responsible for any actions you take, or that are taken by objects or scripts that belong to you. Sandboxes are available for testing objects and scripts that have components that may be unmanageable or whose behavior you may not be able to predict. If you chose to use a script that substantially disrupts the operation of Second Life, disciplinary actions will result in a minimum two-week suspension, the possible loss of in-world inventory, and a review of your account for probable expulsion from Second Life.
CHAPTER 11 CHAPTER 12 CHAPTER 13 CHAPTER 14
APPENDICES
Alternate Accounts While Residents may choose to play Second Life with more than one account, specifically or consistently using an alternate account to harass other Residents or violate the Community Standards is not acceptable. Alternate accounts are generally treated as separate from a Resident's principal account, but misuse of alternate accounts can and will result in disciplinary action on the principal account. Buyer Beware Linden Lab does not exercise editorial control over the content of Second Life, and will make no specific efforts to review the textures, objects, sounds, or other content created within Second Life. Additionally, Linden Lab does not certify or endorse the operation of in-world games, vending machines, or retail locations; refunds must be requested from the owners of these objects. Reporting Abuse Residents should report violations of the Community Standards using the Abuse Reporter tool located under the Help menu in the in-world tool bar. Every Abuse Report is individually investigated, and the identity of the reporter is kept strictly confidential.
369
342-369 Appendices.indd 369
9/24/08 12:58:07 PM
INDEX A: KEY TERMS (excluding lsl CODE) A About Land menu: 243 Academy of Second Learning: 326 acceleration: 159, 160-162 access list: 148 active object: 210 add: 11 Advanced Scripters of Second Life: 328 aether: 236 air: 236-237 airplanes: 176 AJAX: 96 algorithms: 318 Altassian software: 330-331 alternate accounts: 369 alternate avatar: 323 ambient sounds: 244-250 American Cancer Society: 288298 Animania: 129 animation: 57-63; of textures 211221; of water 144-147 animation balls: 305 animation overrides: 57-63 annoyances: 316; see also problems AO: see animation overrides application virtual machine: 336; see also virtual machine arc of the covenent: 222 arctan (inverse tangent): 236 articulation: 121, 123 ASCII: 19 assault: 368 asset server: 317 associating prims with link numbers: 88 associative arrays: 15 asynchronous communications: 25 asynchronous data: 259 asynchronous request: 92, 93 attached objects: 53 attaching objects to body parts: 53
attachment points: 46-47 attachments: 46-57 Audacity: 243 audio: streamed 243; see also sound autogeneration: 100 automatic prim rezzer: 232 automation: 139 avatar death: 169 avatar health: 169 avatar information: 339 avatar name: 254-265 AVI: 243 axel: making 166 axial orientation: 113 axis: 114-123 axis-aligned textured polygon: 206
B backslash: 74 backup: 322 balloons: 176 banishment: 369 banking: 180-181 bee: 129-130 Beta Grid: 323-324 Big Six, the: 368-369 bitmap: 243 bitwise arithmetic: 42 Blender: 57, 243 bling: 22, 200, 201 blocks: 5 blogHUD: 83 boats: 176 body parts: attaching objects to 53 books: 325 Boolean operators: 21, 86-87 Boolean values: 73 bowling: 46 braces: 5, 19, 20; mismatched 313 brackets: 33; mismatched 313 browsers: in-world 260, external 261 Bu, Baba: 223
bugs: 235, 312, 330-331, 345, 348; communication: 67-96; among bug reporting 316, 330-331 objects 79-83; channels 106, 107-108; email and IM 89-96; built-in functions: see functions in dialogs 77-79; inside an bumpiness: 226 object 83-89; to objects 68buoyancy: 159, 162, 181, 239 76; protocols: 323 butterflies: 200 community standards: 368 buttons: 77-79 competitions: 307 buyer beware: 369 compiler errors: 312-313 conditionals: 20-21 constants: 5, 7; attach points 53; C avatar status 63; link number 84; link set 86; names vs. C family of languages: 8 values 55; permissions 56 California time: 230 contacts roster: 89 calling cards: 188, 195 container: 192-193 camera: 182-184; controls 183184; property constants: 184 containing prim: 39 Content folder : 4, 39, 102, 107 campers: 305-307 contour line: 238 campfire: see fire conversational interfaces: 67-96 cannon: for fireworks 203 coordinates: 11-12 Carnegie Mellon University’s CyLab: 312 copyright laws: 250, 330 cars: 176 Creating Your World: The Official Guide to Advanced Content Case, Max Creation: 325 channels: 9, 68 Cubic Effects: 225 charity: 288-291 customer bell: 89-90 chat catcher: 83 chat channels: 68 chat relay: 82-83 chess: 307 child prim: 4, 87, 134 client-dependent effect: 225 client-side effect: 113, 158, 201 clock: 120-122, 230-232 clouds: 240-241, 247-250 club host: 285, 287-288 club owner: 285 coding styles: 33 coercion: see type coercion College of Scripting, Music, and Science: 326 collision: 134-137, 158, 169 color: 205, 222, 223, 224, 225, 226-227; interpolation 205; parsing 50 common denominator casing: 76
D damage: 169-170 damage-enabled land: 169 dance: 320 dance ball: 305 databases: 263 day: 223, 232-235 death: 169 debit permission: 280-281 debugging: 29, 68, 280, 320-322, 336 degrees: 12, 115-116 delays: in scripts 319 delta rotation: 117-118 Design Discussion: 340 destination coordinates: 40 detected objects: 52-53 dialogs: 77-79 digger: 145-147
370
370-386 Index and BoB Ad.indd 370
8/8/08 2:08:01 AM
digital rights management laws: 250; see also copyright disclosure: 368 disturbing the peace: 369 DJ: 285 documentation for scripts: 331 door opening: 80-81, 127-128 double quote: 74 Dr. Dobb’s Journal: 83 drag effects: 144 duplicate keys: 151 dust devil: 113-114
E Earth: 122-123, 237-239 ECHOCHANNEL: 82-83 economy: 279-308; statistics: 308 Edit tab: 39 education: 324-325 efficiency: 316ejecting avatars: 150-151 email: 89-96, 259, 273, 277, 300, 339; object to object 94-96; retrieving: 95 emitter: 200, 201, 204, 205, 206 energy: 159, 175, 236; drain: 175 enkythings: 46 Entrepreneur’s Guide to Second Life: Making Money in the Metaverse: 209 environment: 229-241 error: checking for tip jars 287288; icon 313; in listeners: 73; messages: 312-314; see also problems, bugs; specific error escape characters: 74 Euler rotation: 12, 115-116 event-driven finite state machines: 26 events: 25; counters: 9; excess queued 316; handlers: 5, 7, 25 explicit casting: 8 exponent: 10 external backup: 322 external editors: 322 external web browser: 261 external web server: 191
F face light: 48-50, 54, 222 fade: 221 failed calls: 316 Fairlight Lake: 337
Fassbinder, Chrischun: 362 Feature Requests: 340 filtered light: 225-226 fire: 199, 200, 212, 214-215 firepit: see fire fireplace: see fire fireworks: 201-204 flame: see fire Flash: 243, 261 flexible prims: see flexiprims flexiprims: 143-144, 158, 185, 207, 208, 347; physics hack 185 flight: 170-172 flip tag: 47-48 floating: 239 floating text: 47 flow control: 20-21 flower petals 103-104 Flying Tako Sailboat: 182 flying vehicle: 176-178 focus: 221 Fonzarelli, Guzar: 92 force functions: 163 force: 159 forums: 329, 330, 368-369 fountains: 142; see also water Fowler, Martin: 32 frame rate: 212 free money: 291-294 freebies: 291; textures 362 friction: 159, 347 Frog Prince: 46 function length: 3 functions: 5, 23-24; animation overrides 60; built in 23; detected objects 52-53; llSay family 68; math 22-23; naming 34; that convert lists to strings 16; that extract list elements 15; that manipulate lists 16; that manipulate strided lists 18; that manipulate strings 14; that use keys 13; user defined 23
G Galleries of Camazotz: 289 gambling: 307-308 games: 307-308 garbage-collection system: 106, 108, 110 GBIV: 205-207 gedit: 322 generator prim: 100 GIMP: 113, 243
giveaways: 307 global attacks: 369 global constants: see constants global coordinates: see coordinates global standards: 369 global variables: see variables GMT: 230 GNU Scripters of Second Life: 328 golf: 301 Graphics Interchange Format: 243 gravity: 159, 160 Gray, Kinn: 362 green type: 76 greeters: 136-139 GreyGooFence: 110, 319 griefer/griefing: 73,76, 93, 110, 150, 195 ground: 237-238; ground repel 159 group privileges: 152 group-deeded object: 195, 197, 280, 322, 323
H hack: 185 harassment: 368 Havok1 physics engine: 157, 185 Havok4 physics engine: 157, 185, 331, 335 heads up display: see HUD health: 169 help: getting and giving 324-331 heterogeneous: 15 highlight transparent: 225 hill: 238 Hippo Technologies: 301, 304 Hoare, Sir Tony: 316 hollow prims: 175 Holly Kai Golf Club: 201 hopping balls: 106-107 hover: 159, 162, 173-174, 180-181 hovering: HTML source: 260 HTTP: 277, 300; requests 89; server 339 HUD: 46-47, 53, 54, 60, 64-65, 152, 169; attach points 64 human cannon ball: 167-169
I idea: 236 IEEE: 312 IM see instant messaging implicit casting: 8 implicit permissions: 55, 64 impulse functions: 163 impulse: 159 indecency: 368 indentation: 33 instant messaging: 89-96 integer: 9-10 integer arithmetic: 314 intermediate values: 315 intolerance: 368 intrasim teleports: 44-46 inventory 186-197; giving 189193; permissions 195-197; taking 193-195 Inventory folder: 30 Inventory:Trash: 103 inverse tangent (arctan): 236 in-world browser: 260 in-world help: 325-327 Ivory Tower Library of Primitives: 327, 355
JK Jabber (XMPP): 96 Java/JavaScript: 8, 261, 336 jet pack: 169, 174 JIRA bug: 235 JIRA: 35, 185, 315, 329, 330, 337, 339 JPEG: 243 key: 9, 13 kite: 200, 207-211 Knuth, Donald: 316
L lag: 68, 126, 201, 316, 317, 336 land ban list: 150 land design: 141-155 land information functions: 152155 land management: 141-155 land ownership: 108, 243, 253, 303 land pass list: 150 land rental: 303-305 land security: 141, 147-155 land settings: 169
371
370-386 Index and BoB Ad.indd 371
8/8/08 2:08:01 AM
landmark 45; objects: 188 leaves: 20 LED news ticker: 267-272 legal issues: 208 libraries: of scripts 330 Library folder: 142, 212 library functions: 318 Light Learning Lab: 223 light: 221-227; properties 222 Lindeman’s Design Beach: 212 Linden dollars: 282, 285, 287, 291, 292, 295, 302, 305, 307 Linden office hours: 325 Linden Script Tutorial Group: 328 Linden, Kelly: 339 Linden, Qarl: 337 Linden, Sean: 339 Linden, Torley: 250, 356, 357 line wrap: 33 linear execution speed: 317-318 linear motor: 179 link messages: 83-89, 219 link number: 84; associating prims with 88 linked prims: 84 link-number constants: 84 linkset 104-105 Linux: 83, 322, 336 list: 9, 15-18 listen: 68-96; filters 70-72; handles 9 local coordinates: see coordinates local ratings: 369 logic errors: 314-315 login: for tip jar users: 286-287 Lo-Jack beacon: 182 loop rezzers: 100-105 LSL math functions: 22-23 LSL media types: 243 LSL Object file: 336 LSL style: 32-34 LSL teachers: 325 LSL wiki: 3, 8, 45, 59, 73, 78, 84, 313-333 LSLEditor: 312 lslint: 313 LSO: 336
M Mac OS X: 336 Macintosh PICT: 243 magic carpet: 177-178, 182, 185 mailbox: 193-195, 204 mailing lists: 329
Malaprop, Ordinal: 294 mantissa: 10 Maracas, Arazael: 362 marketing scripts: 331 mass: 159, 175 master/slave sounds 247-250 Media options menu: 243 media types: 243 memory errors: 315 menu: for payments 280, 283284 message in a bottle: 265-268 metaverse: 340 Microsoft: 336 mimic: 75-76 Mischief Cove: 305-306, 325 missing arguments: 313 modeling: 221 modularity: 34, 207 money: 279-308; debit permissions 280-281; safeguarding in scripts 323324; trees 291-294; see also Linden dollars Mono scripting engine: 315, 318, 335-337 mood: 221 moon: 122-123, 222,: 235 Morane, Danna 75-76 mouse click: 284 mouselook: 182-184 movement limits: 43 MP3: 243 Mpeg: 243 multimedia: 243-256 multiple states: 28 multiproduct vendor: 296-299
N naming conventions: for constants 7, for variables 7 NCI: 325 negative channels: 68, 77 .NET: 336 network lag: 317 networked vendor: 300-301 New Citizens Incorporated: 325 new line: 74 new residents: 291, 294, 305, 307 New Script command: 39 news feeds: 89 news ticker: 260 night: 223, 232-235 nonphysical objects: 134;
controlling motion of 112-123 normalizing axis: 52 notecards: 148-150, 189-191, 193, 195, 204, 299, 316 Notepad: 322 Novell: 336
permissions: 54-57: animation override 59-60; debiting money 280-281; explicit 64; for inventory 195-197; for objects 195; for scripts 195; for textures 220, 211; implicit 55; to attach objects 54; who granted 56-57 persistence of vision: 212 O persistence: in listeners 72; of particle effects 201 objects: 4; attaching to body parts 53; communicating with persistent data: 263 67-96; controlling motion persistent features: 40, 48, 48 of 112-123; detaching 55; personalizing greeters: 137-139 inventory 187-197, 218; mass petals: 200 159; object to object email: phantom: 113 94-96 phantom objects: 133, 135, 136, offset: 74, 118-119 158 omega: 159 phantom prim: 224 Online Status Indicator: 92 Photoshop: 113, 243 OnRez: 272-273, 301, 325 PHP: 259, 267-268 OpenSimulator project: 340 Physical check box: 158 open-source software: 336 physical objects: 134, 157-175; operations, mathematical: 11 moving 163-164, monitoring operators: 21-23, 42, 57, 59, 63, 163-164 74, 76 physical properties, setting: 162 optical illusions: 165-167 physics engine: 157, 185, 166 OS X: 322 physics hack: 185 overloaded common resources: physics: 157-185 317 picture frame: 218-221, 225 pie menu: 38-40; changing text in 39-40 P pitch: 115-116 paid object: 280 PJIRA: 330-331; see also JIRA parachute: 174 Playfair, Thili: 362 paraglider: 174 point light: 222-223 parallel execution: 207 policing: 369 parcel flags: 153 policy: 307-308, 369 parcel information: 152-153 polygon: 206 parentheses: mismatched 313 pool: 145-147 parser: 73, 76 pools: see water particle: count 203-204; effects popularity: 305-307 200-211; emitters 240-241 pop-up window: 77 Particle Laboratory, the: 213, 326, port key: 148 357, 367 portable network graphics: 243 particle scripts: 182 Poser: 57 particle system: 236, 357-367 postures: 59 passive object: 210 practical joke: 75-76 paying: see money pranks: 75-76 paying campers: 305-307 preload texture: 218 payment menu 280-283-284 Premium account: 303 pending events: 894 prims: 4, 84; associating with Pendragon, Jopsy: 357, 362, 367 link numbers 88; budget 100; periodic sensor repeat: 210 clothing 46; hair 46; persistent Perl: 259, 267-268, 272, 276-277 features of 48; torture 351 Perlis, Alan J: 311 prim-local: 41; converting from sim-local: 41
372
370-386 Index and BoB Ad.indd 372
8/8/08 2:08:01 AM
problems: in scripts: 310-333 projectile aiming: 121 push: 162, 169 push-limited land: 169; see also assault Python: 267-268, 272
Q QAvimator: 243 quaternion: see rotation query string: 264 qui: 236 QuickTime: 243, 261 quintessence: 236
R radar: 151-152 radians: 12, 115-116 radio: 251-252 rain: see water rainbows: 200, 205-207 random floating point number: 143 random number: 74,77 randomness: for sounds 247-250 readability: 3 RealNetworks stream: 243 recent changes to LSL: 337-340 reflection: 227 region coordinates: see coordinates region flags: 153 region information: 152-153 regression tests: 324 Relay for Life: 288-298 remote control: 272-277 removing listens: 73 rentals: land 303-305 renting: 301-309 reporting abuse: 369 reporting bugs: 330-331 rezzing: 100-110; failure to rez 102-103 rides: 301-302 Riverbend Art Gallery: 296, rivers: see water Robin (Sojourner) Wood’s Texture Tutorials: 327 roll: 115-116 Roomba: 130-133 root prim: 4, 12, 38, 84, 87, 134 Rosedale, Philip: 3
rotate mode (texture animations): 216-217 rotation: 9, 12-13, 162; combining Target Omega and quaternions 122-123; shortcuts for 121;using quaternions 115; with Target Omega 113-114 rotational math: 323 ROY: 205, 207 royalties: 250 RSS feeds: 89, 268-272 runtime errors: 313-314
S scale mode (texture animations): 215-216 scaling issues: 317 School of Mischief: 305-306, 325 scope: 19 Script Academy: 328 Script Crypt: 328 Script Database Group: 328 script editor: 312; external 313 script inventory: 187-197 script libraries: 330 script pauses: 43 script placement: 47 scripted objects, managing: 29 Scripters of Second Life: 328 scripting groups: 328 Scripting Tips Forum: 329 Scripting Your World Headquarters: 3, 46, 47, 48, 52, 53, 60, 64, 65, 69, 81, 86, 93, 103, 113, 130, 136, 142, 182, 185, 204, 222, 225, 238, 244, 256, 268, 300, 325, 327, 328, 329; website 38, 42, 121, 123, 134, 144, 164, 194, 197, 244, 262, 322,: 329, 340 Scripts (group): 328 scripts: multiple 30-31; resetting 31-32; worker scripts 34 scripts in this book: accesscontrolled teleport 148-149; animating a campfire 214; automatic doors 80-81; basic mailbox 194; basic sound automation--wind effects 244; bee 129; better sound automation 246; button script for multiple-product vendor 299; chain of temporary hopping balls 106-107; changeflag.pl, an XML-RPC flag changer client (Perl) 276;
charity collection box 289; chatty light switch 71; clock hands 232; clock root prim 231; collisions 134; color changing spotlight controller 224; complete particle system with only default values: 365-366; cone of light 224; customer bell: 90-91; default new script 4; dialogbased colorizer 77; dying leaf 131-132; face light 49; find a key for an avatar name 264; fireworks cannon 204; fireworks--a big ball of small, slow-moving particles 203; fireworks--an explosion of red shooters 201; flip tag 47; flower loop rezzer 100-101; flower petal 103; flying kite 209-210; getting properties of inventory items: 188-189; giving a list of inventory 192; giving usage hints 74; how many residents are inworld 261; HUD in AO 65; improved water animation 143; intrasim teleport 44; intersim teleport assistant 45; kite string 208-209; land monitor and ejector 151; leaf rezzer 131; light controlled by touch relay link message 85; light-emitting prim 224; linking objects 105; local teleporter 41; long-distance chat relay 82; making a clock face using a rotation shortcut function 122; managing records in a strided list 18; message in a bottle--server side (php) 267; message in a bottle-the client on the SL side 266; mimic someone 75-76; money tree 292; money-tree leaf 293; multiple-product vendor 297; neon texture flipper as multiple states: 28; notecard giver 189; object to object email 95-96; optimized intrasim teleport 45; perpetual temp rezzer 109; personalized memory greeter 138-139; picture frame 219; picture frame images 220221; rainbow 205-206; region rental 303; retrieving email 95; Roomba 132-133; rotate around a point 120; rotating a sign around a post 117; RSS news ticker 269-270; RSS news ticker LED 271; self-attaching face light 54;
shared tip jar 285; shared tip jar with error checking 288; sidewalk light 233; sidewalk light shadow 234; simple greeter- -using sensing 137; simple tip jar 282; simply sitting 39; simple vendor 295; skyhook ride 301; sliding doors 127; small strided list 17; storm master 248; storm slave 249; SYW-TV 254; terraforming pool digger 145146; texture changing object 227; texture flipper 6; the moon’s rotation 123; touch relay in a linked set 85; traffic rewarder 306; tunable radio 252; typing animator 61-62; URL loader 260; using a debugging function 321; using a timer to reset and object’s position and orientation 29; water animation 142; water floaty 238-239; watery pool controller 147; waving waters 144; whirling trash using Target Omega 114; wind-up key 51; XML-RPC flag changer (LSL server) 273; yoga float animation override 58-59 script-speed governors: 319 sculpted prims: 335 sculpty prims: 350 search interface: 305 Second Life blog: 329, 340 Second Life days: 232 Second Life time: 230 Second Life wiki: 340 Second Life: The Official Guide: 325 Secondlifescripters: 329 securing communications: 294 selling scripts: 331-332 selling: 294-301; services 301-308; see also money sensors: 125-139 services, selling 301-308 shadow variable: 20 shadows: 222, 223, 225, 232-235 shininess: 226 short-circuiting: 21 shortcuts: rotational 121 sidewalk light: 233-235 signs: 113 Silicon Graphics: 243 sim boundaries: 211 sim-local: 41; converting to primlocal: 41 simulator lag: 317
373
370-386 Index and BoB Ad.indd 373
8/8/08 2:08:02 AM
simulator revision: 324 simultaneity: 319-320 sit balls: 40 Sit Here option: 39 sitting: 38-40; sit offsets 40 skyhook: 301-302 SL GUI: 145 SLDev: 329 sleds: 176 slerp: 121 SLExchange: 272-273, 325 slow scripts: 316-320 smooth mode (texture animations): 217 snow: 240-241 Snow Crash: 340 Solaris: 336 sound: 244-250; built in clips 244; creation and editing 250; emitters 248soundscapes 244-250; volume 246-247; wind 244, 246 spam: 93 special effects: 199-227 spherical linear inerpolation: 121 spin: 162,165 spiral texture: 165 spotlight: 223-225 sprites: 206-207 stained glass: 225 standard mode (texture animations): 213-215 states: 4, 5, 7, 26-28 statistics bar: 319 Stephenson, Neal: 340 storm: 247-250 streaming media: 251-256 streaming video: 253-256 Streeter, Vex: 362 stride: 17 strided list: 17-18, 77-78, 88, 110, 262-263, 299 string: 9, 14, 313 style: 32-34 subtract: 11 suggestion box: 193 sun: 122-123, 222; sun direction 232-233 Sun Microsystems: 336 support: for your marketed scripts 331 surface binormal: 338 surface coordinates: 338 surface normal: 338 surfboard: 301
Surfline Aloha Rezzable: 128, 301 suspension: 369 synchronizing events: 319-320 syntax highlighting: 313 SYW: see Scripting Your World SYW-TV: 254-256
T tab: 74 Tagged Image File Format: 243 taking controls: 172-173 Targa: 243 tax: 285, 287; see also money teachers: 325 teleport: 41-46; intrasim 44-46; speed limitations 43 temporary objects 106-110 temporary rezzer: 106-110 Terdiman, Daniel: 208 Terms of Service: 74, 150, 250, 307-308 Terra, Cubey: 182 terraforming: 145-147 test jigs: 324 testing: 323-324 text display color: 48 text files: 243 TextEdit: 322 textures: 192-193, 195, 196; animation 62, 211-22; coordinates 338; modes: 213217; permissions: 220, 221 theft: 293, 340 thunder: 247-250 time dilation: 233, 319 time: 230-235; of day 138 time-limited restrictions: 150 timeout interval: 93 timescales: 179-180 tiny prims: 356 tip jar user login: 286-287 tip jars: see tipping tipping: 281-288; for teachers 325; tipping cow: 285 torque: 159 touch position: 337-339 traffic: 306-307 transaction basics: 280-281 transaction history: 281 transition effects: 221 transparency: 212, 214 transparent prim: 87-88 trivia: 307 tuples: 43, 344
tutorials: 68, 176, 325-329, 331, 350, 357 Twitterbox: 83 type coercion: 8 type mismatches: 313 types: 8-9 typing animator: 61-62 typos: 313, 315
W
warning: 369 water: 141-144, 237-239; animation: 142-144; effects 200; mill 142; waterfalls 141144 WAV: 243 waves: see water weather: 240-241; effects 200; sounds 244-250; vane 236 U web: 89, 260-277; for scripting help 329 uniqueness: 37 wheels: 216 unit testing: 324 whirlwind: 113-114 Universal Unique Identifier: see Wikipedia: 73, 121, 336, UUID wildcard: 72, 94 UNIX: 336 wildlife: 128 unsitting: 40, 42 wind: 211; direction 236-237; updates: for your marketed effects 144 scripts 331-332 winding key script: 50-51 user preferences: 201, 222 user-defined functions: 5; see also WindLight viewer: 335 Windows: 322, 336 functions wipe: 221 user-defined state: 5 UUID: 13, 42, 52, 55, 63, 92, 104, worker scripts: 34 105, 149, 151, 152, 211, 244, World Wide Web Consortium: 245, 247, 261, 349, 353, 362 263 wrestling: 307 Wright, Steven: 110
V
variables: 5, 7-9, 19-20; naming 33-34 vector: 9-12 vector cross product: 11 vector dot product: 11 vehicles: 176-185; direction 179; efficiency 180; flags 181-182; properties 178-181; rotation 179 velocity: 159 vendor script: vendors: 97, 196, 295-301; simple 295-296; multiproduct 296299; networked 300-301; vending machines 193 versioning: 322-323 vi: 322 video, streamed 243 viewer lag: 317 Vint’s primskirtbuilder sandbox: 100 virtual machine: 335-337 visibility: 221 VM: see virtual machine Volcán Tenorio: 200 volcanoes: 200, 212
XYZ XML: 270-277 XML-RCP: 259, 272-277, 300, 339 XMPP (Jabber): 96 yaw: 115-116 yoga float 68-69 YouTube: 253 Zabaleta, Karzita: 362 ZHAO-II scripts: 60 zoom effect: 221
374
370-386 Index and BoB Ad.indd 374
8/8/08 2:08:02 AM
INDEX B: COMPLETE LISTING OF LSL CODE ReturnType Code -- (Decrement) - (Subtract) ! (Not) != (Comparison not equal) % (Modulus, Cross Product) %= (Assignment) & (Bitwise AND) && (Comparison AND) * (Multiply, Dot Product, Add) *= (Assignment) . (Dot) / (Divide, Subtract) // (Comment) /= (Assignment) \" (Quoted Quotation mark) \\ (Quoted Backslash) \n (Quoted Newline) \t (Quoted Tab) ^ (Bitwise XOR) | (Bitwise OR)
|| (Comparison OR) ~ (Bitwise complement) + (Add, Concatenate) ++ (Increment) += (Assignment) < (Less than) = (Greater than or equal to) >> (Right Shift) ACTIVE (0x2) AGENT (0x1) AGENT_ALWAYS_RUN (0x1000) AGENT_ATTACHMENTS (0x0002) AGENT_AWAY (0x0040) AGENT_BUSY (0x0800) AGENT_CROUCHING (0x0400) AGENT_FLYING (0x0001) AGENT_IN_AIR (0x0100) AGENT_MOUSELOOK (0x0008) AGENT_ON_OBJECT (0x0020) AGENT_SCRIPTED (0x0004) AGENT_SITTING (0x0010)
Page(s) 22 11, 22, 117 21, 22 22 11, 22 22 22, 63, 173, 314 22, 314 11, 22, 117 22 22 22, 117 7 22 14, 74 14, 74 14, 74 14, 74 22 22, 59, 128, 129, 132, 213, 314 22, 314 21, 22, 173 11, 16, 21-22, 117 22 16, 22 22 22 22 22, 76 22, 314 22, 314, 316 22 22 22 53, 126, 129, 132, 133 53, 126 63 63 63 63 63 63, 171 63 63 63 63 63
ReturnType Code AGENT_TYPING (0x0200) AGENT_WALKING (0x0080) ALL_SIDES (-1)
ANIM_ON (0x1) at_rot_target() event at_target() event attach() event ATTACH_BACK (9) ATTACH_BELLY (28) ATTACH_CHEST (1) ATTACH_CHIN (12) ATTACH_HEAD (2) ATTACH_HUD_BOTTOM (37) ATTACH_HUD_BOTTOM_LEFT (36) ATTACH_HUD_BOTTOM_RIGHT (38) ATTACH_HUD_CENTER_1 (35) ATTACH_HUD_CENTER_2 (31) ATTACH_HUD_TOP_CENTER (33) ATTACH_HUD_TOP_LEFT (34) ATTACH_HUD_TOP_RIGHT (32) ATTACH_LEAR (13) ATTACH_LEYE (15) ATTACH_LFOOT (7) ATTACH_LHAND (5) ATTACH_LHIP (25) ATTACH_LLARM (21) ATTACH_LLLEG (27) ATTACH_LPEC (30) ATTACH_LSHOULDER (3) ATTACH_LUARM (20) ATTACH_LULEG (26) ATTACH_MOUTH (11) ATTACH_NOSE (17) ATTACH_PELVIS (10) ATTACH_REAR (14) ATTACH_REYE (16) ATTACH_RFOOT (8) ATTACH_RHAND (6) ATTACH_RHIP (22) ATTACH_RLARM (19) ATTACH_RLLEG (24) ATTACH_RPEC (29) ATTACH_RSHOULDER (4) ATTACH_RUARM (18) ATTACH_RULEG (23) CAMERA_ACTIVE (12) CAMERA_BEHINDNESS_ANGLE (8) CAMERA_BEHINDNESS_LAG (9) CAMERA_DISTANCE (7) CAMERA_FOCUS (17)
Page(s) 62, 63 63 49, 62, 213, 214217, 221, 234, 271 213, 214, 215-217, 221 164, 25, 164, 209 55, 170, 171 53 53 53 53, 54 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 53 184 184 184 184 184
375
370-386 Index and BoB Ad.indd 375
8/8/08 2:08:08 AM
ReturnType Code CAMERA_FOCUS_LAG (6) CAMERA_FOCUS_LOCKED (22) CAMERA_FOCUS_OFFSET (1) CAMERA_FOCUS_THRESHOLD (11) CAMERA_PITCH (0) CAMERA_POSITION (13) CAMERA_POSITION_LAG (5) CAMERA_POSITION_LOCKED (21) CAMERA_POSITION_THRESHOLD (10) changed() event
CHANGED_ALLOWED_DROP (0x40) CHANGED_COLOR (0x2) CHANGED_INVENTORY (0x1) CHANGED_LINK (0x20) CHANGED_OWNER (0x80) CHANGED_REGION (0x100) CHANGED_SCALE (0x8) CHANGED_SHAPE (0x4) CHANGED_TELEPORT (0x200) CHANGED_TEXTURE (0x10) CLICK_ACTION_BUY (2) CLICK_ACTION_NONE (0) CLICK_ACTION_OPEN (4) CLICK_ACTION_OPEN_MEDIA (6) CLICK_ACTION_PAY (3) CLICK_ACTION_PLAY (5) CLICK_ACTION_SIT (1) CLICK_ACTION_TOUCH (0) collision() event collision_end() event collision_start() event control() event
CONTROL_BACK (0x2) CONTROL_DOWN (0x20) CONTROL_FWD (0x1) CONTROL_LBUTTON (0x10000000) CONTROL_LEFT (0x4) CONTROL_ML_LBUTTON (0x40000000) CONTROL_RIGHT (0x8) CONTROL_ROT_LEFT (0x100) CONTROL_ROT_RIGHT (0x200) CONTROL_UP (0x10) DATA_BORN (3) DATA_NAME (2) DATA_ONLINE (1) DATA_PAYINFO (8) DATA_RATING (4) DATA_SIM_POS (5) DATA_SIM_RATING (7) DATA_SIM_STATUS (6) dataserver() event DEBUG_CHANNEL (2147483647) default state
Page(s) 184 184 184 184 184 184 184 184 184 42, 104, 177, 178, 182, 194, 195 194-195 194-195 42
284 284 284 284 284 284 284 284 135, 136, 160, 168 135 135, 136 59, 60, 172, 173, 176, 177, 178, 183 172 172, 174 172 172 172 172 172 172 172 172, 174 92, 294 92 92 92 92
91, 92, 93, 150, 294, 298, 299 68, 320, 321 5, 19, 23, 26, 28, 31, 75, 92, 170, 291, 297, 298
ReturnType Code DEG_TO_RAD (0.0174532925199 ➥ 43295769236907684886) do...while email() event EOF (\n\n\n) Events at_rot_target() at_target() attach() changed()
collision() collision_end() collision_start() control()
dataserver() email() http_response()
land_collision() land_collision_end() land_collision_start() link_message() listen()
money()
moving_end() moving_start() no_sensor() not_at_rot_target() not_at_target() object_rez() on_rez()
remote_data() run_time_permissions()
Page(s) 12-13, 115 20-21, 271 95 149-150 164 25, 164, 209 55, 170, 171 42, 104, 177, 178, 182, 194, 195 135, 136, 160, 168 135 135, 136 59, 60, 172, 173, 176, 177, 178, 183 91, 92, 93, 150, 294, 298, 299 95 25, 262, 264, 265, 266, 268, 269, 270, 290 135, 168, 169 169 169 85, 86, 208, 232, 271, 298 7, 23, 25, 48, 50, 69, 70, 71, 72, 73, 76, 77, 79, 81, 82, 93, 102, 294 25, 281, 282, 283, 286, 287, 292, 295, 299, 303 112, 164 112, 164 127, 129, 288 164 133, 164 101, 102, 210, 316 31, 32, 83, 92, 102, 120, 171, 207, 291, 293, 323 274 54, 55, 56, 59, 177, 178, 287
376
370-386 Index and BoB Ad.indd 376
8/8/08 2:08:08 AM
ReturnType Code sensor()
state_entry()
state_exit() timer()
touch()
touch_end()
touch_start()
FALSE (0) float for HTTP_BODY_MAXLENGTH (2) HTTP_BODY_TRUNCATED (0) HTTP_METHOD (0) HTTP_MIMETYPE (1) http_response() event
HTTP_VERIFY_CERT (3) if...else integer INVENTORY_ALL (-1) INVENTORY_ANIMATION (20) INVENTORY_BODYPART (13) INVENTORY_CLOTHING (5) INVENTORY_GESTURE (21) INVENTORY_LANDMARK (3) INVENTORY_NONE (-1) INVENTORY_NOTECARD (7) INVENTORY_OBJECT (6) INVENTORY_SCRIPT (10) INVENTORY_SOUND (1)
Page(s) 127, 128, 129, 130, 132, 137, 138, 151, 211, 264, 304 4, 7, 26-28, 30, 32, 41, 75, 92, 110, 120, 143, 170, 171, 172, 176, 202, 209, 219, 221, 283, 284, 287, 292, 295, 299, 302, 303, 321 26-27, 170, 173 7, 30, 52, 60, 63, 79, 93, 110, 122, 152, 160, 172, 173, 219, 226, 230, 246, 271 7, 45, 52, 135, 262, 264, 338, 339 7, 45, 51, 52, 79, 293, 321, 338 4, 7, 30, 45, 52, 93, 108, 122, 160, 252, 269, 286, 338 9, 17, 21, 64, 73, 86 10 20-21 263 263, 266 263, 266 25, 262, 264, 265, 266, 268, 269, 270, 290 263 20, 315 188 188 188 188 188 188 188 188, 190 188 188 188
ReturnType Code INVENTORY_TEXTURE (0) jump key land_collision() event land_collision_end() event land_collision_start() event LAND_LARGE_BRUSH (Do not use this named constant; use the value instead: 3) LAND_LEVEL (0) LAND_LOWER (2) LAND_MEDIUM_BRUSH (Do not use this named constant; use the value instead: 2) LAND_NOISE (4) LAND_RAISE (1) LAND_REVERT (5) LAND_SMALL_BRUSH (Do not use this named constant; use the value instead: 1) LAND_SMOOTH (3) LINK_ALL_CHILDREN (-3) LINK_ALL_OTHERS (-2) link_message() event LINK_ROOT (1) LINK_SET (-1) LINK_THIS (-4) list LIST_STAT_GEOMETRIC_MEAN (9) LIST_STAT_MAX (2) LIST_STAT_MEAN (3) LIST_STAT_MEDIAN (4) LIST_STAT_MIN (1) LIST_STAT_NUM_COUNT (8) LIST_STAT_RANGE (0) LIST_STAT_STD_DEV (5) LIST_STAT_SUM (6) LIST_STAT_SUM_SQUARES (7) listen() event
integer float
float
llAbs(integer val) llAcos(float val) llAddToLandBanList(key avatar, float hours) llAddToLandPassList(key avatar, float hours) llAdjustSoundVolume(float volume) llAllowInventoryDrop(integer add_ boolean) llAngleBetween(rotation a, rotation b) llApplyImpulse(vector force, integer local_boolean) llApplyRotationalImpulse(vector force, integer local_boolean)
float float
llAsin(float val) llAtan2(float y, float x) llAttachToAvatar(integer attachPoint_named_constant)
Page(s) 188 21 13 135, 168 147 147 147 147 147 147 147 147 147 84, 86, 231, 270 84, 86, 219 85, 86, 208, 232, 271, 298 84, 86 84, 86, 87, 88, 89 84, 86, 88, 89 15
7, 23, 25, 48, 50, 69, 70, 71, 72, 73, 76, 77, 79, 81, 82, 93, 102, 294 22, 215 23 150 150 246, 247 194, 195 121 162, 163, 168, 169, 175 162, 163, 165, 166, 175 23 23 54
377
370-386 Index and BoB Ad.indd 377
8/8/08 2:08:08 AM
ReturnType Code key llAvatarOnSitTarget() rotation llAxes2Rot(vector fwd, vector left, vector up) rotation llAxisAngle2Rot(vector axis, float angle) integer llBase64ToInteger(string str) string llBase64ToString(string str) llBreakAllLinks() llBreakLink(integer linknum) integer llCeil(float val) llClearCameraParams() llCloseRemoteDataChannel(key channel) float
key vector integer
llCloud(vector offset) llCollisionFilter(string name, key id, integer accept_boolean) llCollisionSound(string impact_ sound, float impact_volume) llCollisionSprite(string impact_ sprite) llCos(float theta) llCreateLink(key target, integer parent_boolean) llCSV2List(string src) llDeleteSubList(list src, integer start, integer end) llDeleteSubString(string src, integer start, integer end) llDetachFromAvatar() llDetectedCreator(integer number) llDetectedGrab(integer number) llDetectedGroup(integer number)
key
llDetectedKey(integer number)
integer string
llDetectedLinkNumber(integer number) llDetectedName(integer number)
key vector
llDetectedOwner(integer number) llDetectedPos(integer number)
float list list string
rotation llDetectedRot(integer number) vector vector vector integer vector vector integer vector
string integer
11DetectedTouchBinormal(integer number) llDetectedTouchFace(integer number) llDetectedTouchNormal(integer number) llDetectedTouchPos(integer number) llDetectedTouchST(integer number) llDetectedTouchUV(integer number) llDetectedType(integer number) llDetectedVel(integer number) llDialog(key avatar, string message, list buttons, integer channel) llDie() llDumpList2String(list src, string separator) llEdgeOfWorld(vector pos, vector dir)
Page(s) 40, 41, 42, 44, 177 121 121
104 104 22, 74 183 273, 275
241 134 134
23 104, 105 16 16, 292 14, 271 54 52 53, 286 52, 134, 152 13, 52, 132, 190, 260, 264 53 52, 130, 134, 137, 264 52, 134, 52, 130, 134 52, 130, 134 339 338 338 338 338 338 53 52, 134 77, 78, 323 102, 103, 132, 202 16, 17, 84 153,
ReturnType Code llEjectFromLand(key pest) llEmail(string address, string subject, string message) string llEscapeURL(string url) rotation llEuler2Rot(vector v) float integer float
llFabs(float val) llFloor(float val) llForceMouselook(integer mouselook_boolean) llFrand(float mag)
Page(s) 150, 151, 197, 304 91, 93, 94, 95, 300 14, 264 12, 110, 115 22 22 183
23, 74, 76, 143, 246 vector llGetAccel() 160, 161 string llGetAgentLanguage(key avatar) 339 integer llGetAgentInfo(key id) 62, 63, 170, 171 vector llGetAgentSize(key id) 160 float llGetAlpha(integer face) 111 float llGetAndResetTime() 230 string llGetAnimation(key id) 58, 60 list llGetAnimationList(key id) 60 integer llGetAttached() 53, 54, 58, 171 list llGetBoundingBox(key object) 160 vector llGetCameraPos() 183 rotation llGetCameraRot() 183 vector llGetCenterOfMass() 160 vector llGetColor(integer face) 111 key llGetCreator() 13, 134 string llGetDate() 230 float llGetEnergy() 159, 160 vector llGetForce() 160 integer llGetFreeMemory() 315 vector llGetGeometricCenter() 160 float llGetGMTclock() 230 key llGetInventoryCreator(string item) 189, 220 key llGetInventoryKey(string name) 189, 219 string llGetInventoryName(integer type_ 109, 149, named_constant, integer number) 189, 190, 192, 295 integer llGetInventoryNumber(integer type_ 109, 189, named_constant) 192 integer llGetInventoryPermMask(string item, 196, 197 integer mask_named_constant) integer llGetInventoryType(string name) 189 key llGetKey() 13, 88 key llGetLandOwnerAt(vector pos) 90, 153 key llGetLinkKey(integer linknum) 88, 105 string llGetLinkName(integer linknum) 88, 89, 105 integer llGetLinkNumber() 105, 210 integer llGetListEntryType(list src, 15 integer index) integer llGetListLength(list src) 16 vector llGetLocalPos() 112, 113, 121 rotation llGetLocalRot() 115, 117 float llGetMass() 159, 160, 165, 166 llGetNextEmail(string address, 94, 95 string subject) key llGetNotecardLine(string name, 13, 149, integer line) 298 key llGetNumberOfNotecardLines(string 149, 150 name) integer llGetNumberOfPrims() 88, 105, 106
378
370-386 Index and BoB Ad.indd 378
8/8/08 2:08:09 AM
ReturnType integer string list float string integer integer vector key
key list integer integer integer list integer key vector
Code llGetNumberOfSides() llGetObjectDesc() llGetObjectDetails(key id, list list_of_named_constant_params) llGetObjectMass(key id) llGetObjectName() llGetObjectPermMask(integer mask_ named_constant) llGetObjectPrimCount(key object_ id) llGetOmega() llGetOwner()
llGetOwnerKey(key id) llGetParcelDetails(vector pos, list list_of_named_constant_ params) llGetParcelFlags(vector pos) llGetParcelMaxPrims(vector pos, integer sim_wide_boolean) llGetParcelPrimCount(vector pos, integer category_named_constant, integer sim_wide_boolean) llGetParcelPrimOwners(vector pos) llGetPermissions() llGetPermissionsKey() llGetPos()
Page(s) 111 111 83, 132, 133 159, 160, 163 75, 89, 111 196, 197 105 160 13, 32, 47, 48, 54, 90, 92, 105, 171, 182, 197, 286, 291, 292, 303 13, 134 153, 303 152-153, 154, 211 153 153
153 56, 57, 105 56 30, 41, 103, 112, 113, 122 list llGetPrimitiveParams(list list_of_ 40, 42, named_constant_params) 111, 113, 115, 158, 211, 344 integer llGetRegionAgentCount() 339 vector llGetRegionCorner() 11, 153 integer llGetRegionFlags() 152-153, 154, 170 float llGetRegionFPS() 153 string llGetRegionName() 153 float llGetRegionTimeDilation() 233, 319 vector llGetRootPosition() 113 rotation llGetRootRotation() 115, 235, 237 rotation llGetRot() 30, 41, 115, 128 vector llGetScale() 40, 111, 219, 224 string llGetScriptName() 34, 111, 192 integer llGetScriptState(string name) 111 string llGetSimulatorHostname() 317 integer llGetStartParameter() 102, 103, 106, 107, 202 integer llGetStatus(integer status_ 158, 159 bitwise_named_constant) string llGetSubString(string src, 14, 76, 271 integer start, integer end) vector llGetSunDirection() 233, 234, 235 string llGetTexture(integer face) 211 vector llGetTextureOffset(integer face) 211
ReturnType float vector float float string vector integer vector float
integer
float vector vector
vector key
string
string string
Code llGetTextureRot(integer side) llGetTextureScale(integer side) llGetTime() llGetTimeOfDay() llGetTimestamp() llGetTorque() llGetUnixTime() llGetVel() llGetWallclock() llGiveInventory(key destination, string inventory) llGiveInventoryList(key avatar, string folder, list inventory) llGiveMoney(key destination, integer amount) llGodLikeRezObject(key inventory, vector pos) llGround(vector offset) llGroundContour(vector offset) llGroundNormal(vector offset) llGroundRepel(float height, integer water_boolean, float tau) llGroundSlope(vector offset) llHTTPRequest(string url, list parameters_strided, string body)
llInsertString(string dst, integer pos, string src) llInstantMessage(key user, string message) llIntegerToBase64(integer number) llKey2Name(key id)
string float
llList2CSV(list src) llList2Float(list src, integer index) integer llList2Integer(list src, integer index) key llList2Key(list src, integer index) list llList2List(list src, integer start, integer end) list llList2ListStrided(list src, integer start, integer end, integer stride) rotation llList2Rot(list src, integer index) string llList2String(list src, integer index) vector llList2Vector(list src, integer index) integer llListen(integer channel, string name, key id, string msg)
llListenControl(integer number, integer active_boolean)
Page(s) 211 211 161, 230 233 230 160 230 160 230 189-190, 191, 296, 316, 319 192, 193, 316 286, 287, 292, 303, 316, 323 237, 238 238 238 162, 175 238 13, 25, 262, 263, 264, 265, 266, 270, 272, 290, 319, 332 14 68, 90, 91, 94, 266, 286, 288 13, 134, 153, 264, 291, 302 16 15 15 15 16 17, 77, 78, 252 15 15, 17, 252, 297 15, 79 9, 25, 48, 69, 70, 71, 72, 73, 76, 77, 80, 81, 91, 93, 106, 108, 110, 288, 292, 323 69, 73
379
370-386 Index and BoB Ad.indd 379
8/8/08 2:08:09 AM
ReturnType Code llListenRemove(integer number) integer
llListFindList(list src, list test)
list
llListInsertList(list dest, list src, integer start) llListRandomize(list src, integer stride) llListReplaceList(list dest, list src, integer start, integer end) llListSort(list src, integer stride, integer ascending_boolean) llListStatistics(integer operation_named_constant, list src) llLoadURL(key avatar_id, string message, string url) llLog(float val) llLog10(float val) llLookAt(vector target, float strength, float damping) llLoopSound(string sound, float volume) llLoopSoundMaster(string sound, float volume) llLoopSoundSlave(string sound, float volume) llMapDestination(string simname, vector pos, vector _look_at_ignored) llMD5String(string src, integer nonce) llMessageLinked(integer linknum, integer num, string str, key id)
list list list float
float float
string
integer
integer
list list list
Page(s) 69, 73, 77, 91, 94 16, 79, 88, 138, 262, 271, 292 16
ReturnType Code llParticleSystem(list rules)
17, 244 16 17
float
16 260, 261 23 23 163, 210, 211 245, 246, 250 248, 249 249 45 323
83, 85, 86, 87, 209, 219, 220, 231, 232, 269, 270, 271, 320 llMinEventDelay(float delay) 25 llModifyLand(integer action_named_ 145, 146, constant, integer brush_0_to_2) 147 llModPow(integer base, 23 integer exponent, integer mod) llMoveToTarget(vector target, 130, 132, float tau) 133, 163, 165, 166, 174, 175, 209, 210, 239 llOffsetTexture(float u, 211, 271, float v, integer face) 272 llOpenRemoteDataChannel() 273, 274, 275 llOverMyLand(key id) 151 llOwnerSay(string msg) 8, 20, 23, 68, 74, 197, 265, 274, 289, 320, 321, 322, 323 llParcelMediaCommandList(list 253, 254, commandList) 255 llParcelMediaQuery(list query) 253 llParseString2List(string src, 16-17, 49, list separators, list spacers) 84, 297 llParseStringKeepNulls(string src, 16 list separators, list spacers)
key key
key
llPassCollisions(integer pass_ boolean) llPassTouches(integer pass_ boolean) llPlaySound(string sound, float volume) llPlaySoundSlave(string sound, float volume) llPointAt(vector pos) llPow(float base, float exponent) llPreloadSound(string sound) llPushObject(key target, vector impulse, vector ang_impulse, integer local_boolean) llRefreshPrimURL() llRegionSay(integer channel, string msg) llReleaseCamera(key avatar) llReleaseControls() llRemoteDataReply(key channel, key message_id, string sdata, integer idata) llRemoteDataSetRegion() llRemoteLoadScript(key target, string name, integer running, integer start_param) llRemoteLoadScriptPin(key target, string name, integer pin, integer running_boolean, integer start_param) llRemoveFromLandBanList(key avatar) llRemoveFromLandPassList(key avatar) llRemoveInventory(string item) llRemoveVehicleFlags(integer flags_bitwise_named_constants) llRequestAgentData(key id, integer data_named_constant) llRequestInventoryData(string name) llRequestPermissions(key agent, integer perm_bitwise_named_ constants)
llRequestSimulatorData(string simulator, integer data_named_ constant) llResetLandBanList() llResetLandPassList() llResetOtherScript(string name) llResetScript() llResetTime() llRezAtRoot(string inventoryname, vector position, vector velocity, rotation rot, integer startparam)
Page(s) 10, 178, 200, 202, 208, 326, 357 134 87 245, 250 249 23 246, 247 154, 162, 163, 169, 170, 172, 175 68, 82, 300 172, 173, 178 273, 275 275
111, 195, 332 150 150 196 181, 182 13, 90, 289, 291, 294 188, 189 54, 55, 56, 58, 59, 104, 105, 171, 172, 286, 291, 292, 303 11 150 150 32, 291 26, 31, 32, 55, 83 230 101, 102
380
370-386 Index and BoB Ad.indd 380
8/8/08 2:08:09 AM
ReturnType Code llRezObject(string inventoryname, vector pos, vector vel, rotation rot, integer startparam) float vector vector
llRot2Angle(rotation rot) llRot2Axis(rotation rot) llRot2Euler(rotation rot)
vector vector vector
llRot2Fwd(rotation rott) llRot2Left(rotation rott) llRot2Up(rotation rot) llRotateTexture(float rotation, integer face) rotation llRotBetween(vector start, vector end) llRotLookAt(rotation target, float strength, float damping) integer llRotTarget(rotation rot, float error) llRotTargetRemove(integer number) integer llRound(float val) integer llSameGroup(key agent) llSay(integer channel, string msg)
integer key
llScaleTexture(float u, float v, integer face) llScriptDanger(vector pos) llSendRemoteData(key channel, string dest, integer idata, string sdata) llSensor(string name, key id, integer type type_bitwise_named_ constants, float range, float arc) llSensorRemove() llSensorRepeat(string name, key id, integer type type_ bitwise_named_constants, float range, float arc, float rate)
Page(s) 101, 102, 107, 108, 110, 131, 146, 204, 210, 292, 316, 319 121 121 12, 115, 116 121, 122 121 121 211 121 132, 163 163, 164 163, 164 22 151, 152 68, 75, 79, 80, 81, 82, 283, 304, 320, 321 211, 213, 271, 272
ReturnType Code llSetInventoryPermMask(string item, integer mask_named_ constant, integer value) llSetLinkAlpha(integer linknumber, float alpha, integer face) llSetLinkColor(integer linknumber, vector color, integer face) llSetLinkPrimitiveParams(integer linknumber, list rules) llSetLinkTexture(integer linknumber, string texture, integer face) llSetLocalRot(rotation rot) llSetObjectDesc(string desc) llSetObjectName(string name) llSetParcelMusicURL(string url) llSetPayPrice(integer price, list four_quick_pay_buttons) llSetPos(vector pos)
llSetPrimitiveParams(list rules)
275 25, 126, 127, 128, 133
126, 288 25, 126, 127. 128, 131, 135, 136, 137, 138, 151, 210, 303, 304 llSetAlpha(float alpha, 62, 63, 75, integer face) 221 llSetBuoyancy(float buoyancy) 162, 181, 239 llSetCameraAtOffset(vector offset) 183 llSetCameraEyeOffset(vector 183 offset) llSetCameraParams(list rules_ 183 strided) llSetClickAction(integer action_ 283, 284 named_constant) llSetColor(vector color, integer face) llSetDamage(float damage) 169, 170 llSetForce(vector force, 160, 162, integer local_boolean) 163, 169, 175 llSetForceAndTorque(vector force, 162, 163, vector torque, integer local_ 175 boolean) llSetHoverHeight(float height, 162, 175 integer water_boolean, float tau)
llSetRemoteScriptAccessPin(intege r pin) llSetRot(rotation rot) llSetScale(vector scale) llSetScriptState(string name, integer run_boolean) llSetSitText(string text) llSetSoundQueueing(integer queue_ boolean) llSetSoundRadius(float radius) llSetStatus(integer status_ bitwise_named_constants, integer value_boolean) llSetText(string text, vector color, float alpha) llSetTexture(string texture, integer face) llSetTextureAnim(integer mode_ bitwise_named_constants, integer face, integer sizex, integer sizey, float start, float length, float rate) llSetTimerEvent(float sec) llSetTorque(vector torque, integer local_boolean) llSetTouchText(string text)
Page(s)
105 105 105, 113, 115, 144, 344 105, 211 115, 117, 122, 232, 235 111 75, 111 197, 251, 252 283, 284, 286, 287, 291, 295, 302, 303 30, 43, 112, 113, 118, 121, 128, 144, 178, 232, 316 43, 44, 49, 50, 106, 107, 111, 113, 115, 119, 158, 167, 170, 178, 211, 221, 222, 224, 233, 234, 235, 316, 344 111 30, 115 147 32, 111 39, 40 250 246, 247 158, 160, 161, 165, 167, 170, 177, 178, 185, 238 48, 108, 192 23, 211, 272 61, 62, 142, 143, 144, 212213, 214217 7, 28, 30, 128, 129, 143, 230 162, 163, 166, 181 40
381
370-386 Index and BoB Ad.indd 381
8/8/08 2:08:09 AM
ReturnType Code llSetVehicleFlags(integer flags_ bitwise_named_constants) llSetVehicleFloatParam(integer param_named_constant, float value) llSetVehicleRotationParam(integer param_named_constant, rotation rot) llSetVehicleType(integer type) llSetVehicleVectorParam(integer param_named_constant, vector vec) llShout(integer channel, string msg) float llSin(float theta) llSitTarget(vector offset, rotation rot) llSleep(float sec) llSound(string sound, float volume, integer queue_boolean, integer loop_boolean) llSoundPreload(string sound) float llSqrt(float val) llStartAnimation(string anim) llStopAnimation(string anim) llStopHover() llStopLookAt() llStopMoveToTarget() llStopPointAt() llStopSound() integer string string integer
float integer
llStringLength(string str) llStringToBase64(string str) llStringTrim(string src, integer type_named_constant) llSubStringIndex(string source, string pattern) llTakeCamera(key avatar) llTakeControls(integer controls_ bitwise_named_constants, integer accept_boolean, integer pass_on_boolean) llTan(float theta) llTarget(vector position, float range) llTargetOmega(vector axis, float spinrate, float gain)
string string
string float float vector
llTargetRemove(integer number) llTeleportAgentHome(key agent) llToLower(string src) llToUpper(string src) llTriggerSound(string sound, float volume) llTriggerSoundLimited(string sound, float volume, vector top_north_east, vector bottom_south_west) llUnescapeURL(string url) llUnSit(key id) llVecDist(vector vec_a, vector vec_b) llVecMag(vector vec) llVecNorm(vector vec) llVolumeDetect(integer detect_ boolean)
Page(s) 176, 181, 182, 183 176, 178 178, 176, 177 177, 178179, 183 68, 82, 300 23 39, 40, 44 25, 42, 158
23 58, 60 58, 60 162 163 163, 174, 239 244, 246, 247, 248 14, 269 14 14, 271 59, 171, 172 23 25, 132, 133, 163, 164, 239 51, 113, 114, 122123, 162 163, 164 150, 170 14, 76 14 81, 244, 245 245
14 41, 42, 168 12 12, 168 12 135, 136, 151
ReturnType Code float llWater(vector offset) llWhisper(integer channel, string msg) vector llWind(vector offset) string llXorBase64StringsCorrect(string str1, string str2) LOOP (0x2)
Page(s) 237, 238 68 236, 237 323
213, 214217, 221 MASK_BASE (0) 197 MASK_EVERYONE (3) 197 MASK_GROUP (2) 197 MASK_NEXT (4) 197 MASK_OWNER (1) 197 money() event 25, 281, 282, 283, 286, 287, 292, 295, 299, 303 moving_end() event 112, 164 moving_start() event 112, 164 no_sensor() event 127, 129, 288 not_at_rot_target() event 164 not_at_target() event 133, 164 NULL_KEY (00000000-0000-0000-0000- 13, 15, 42, 000000000000) 48, 55, 69, 129, 132, 133, 231, 265, 285, 286, 288, 289, 292, 303, 304 OBJECT_CREATOR (8) 134 OBJECT_DESC (2) 134 OBJECT_GROUP (7) 134 OBJECT_NAME (1) 132 OBJECT_OWNER (6) 83 OBJECT_POS (3) 132 object_rez() event 101, 102, 210, 316 OBJECT_ROT (4) 132 OBJECT_UNKNOWN_DETAIL (-1) OBJECT_VELOCITY (5) on_rez() event 31, 32, 83, 92, 102, 120, 171, 207, 291, 293, 323 PARCEL_COUNT_GROUP (2) 153 PARCEL_COUNT_OTHER (3) 153 PARCEL_COUNT_OWNER (1) 153 PARCEL_COUNT_SELECTED (4) 153 PARCEL_COUNT_TEMP (5) 153 PARCEL_COUNT_TOTAL (0) 153 PARCEL_DETAILS_AREA (4) 153 PARCEL_DETAILS_DESC (1) 153 PARCEL_DETAILS_GROUP (3) 153 PARCEL_DETAILS_NAME (0) 153, 303 PARCEL_DETAILS_OWNER (2) 153 PARCEL_FLAG_ALLOW_ALL_OBJECT_ENTRY 154, 211 (0x8000000) PARCEL_FLAG_ALLOW_CREATE_GROUP_ 154 OBJECTS (0x4000000) PARCEL_FLAG_ALLOW_CREATE_OBJECTS 154 (0x40) PARCEL_FLAG_ALLOW_DAMAGE (0x20) 154 PARCEL_FLAG_ALLOW_FLY (0x1) 154
382
370-386 Index and BoB Ad.indd 382
8/8/08 2:08:10 AM
ReturnType Code PARCEL_FLAG_ALLOW_GROUP_OBJECT_ ENTRY (0x10000000) PARCEL_FLAG_ALLOW_GROUP_SCRIPTS (0x2000000) PARCEL_FLAG_ALLOW_LANDMARK (0x8) PARCEL_FLAG_ALLOW_SCRIPTS (0x2) PARCEL_FLAG_ALLOW_TERRAFORM (0x10) PARCEL_FLAG_LOCAL_SOUND_ONLY (0x8000) PARCEL_FLAG_RESTRICT_PUSHOBJECT (0x200000) PARCEL_FLAG_USE_ACCESS_GROUP (0x100) PARCEL_FLAG_USE_ACCESS_LIST (0x200) PARCEL_FLAG_USE_BAN_LIST (0x400) PARCEL_FLAG_USE_LAND_PASS_LIST (0x800) PARCEL_MEDIA_COMMAND_AGENT (7) PARCEL_MEDIA_COMMAND_AUTO_ALIGN (9) PARCEL_MEDIA_COMMAND_DESC (12) PARCEL_MEDIA_COMMAND_LOOP (3) PARCEL_MEDIA_COMMAND_LOOP_SET (13) PARCEL_MEDIA_COMMAND_PAUSE (1) PARCEL_MEDIA_COMMAND_PLAY (2) PARCEL_MEDIA_COMMAND_SIZE (11) PARCEL_MEDIA_COMMAND_STOP (0) PARCEL_MEDIA_COMMAND_TEXTURE (4) PARCEL_MEDIA_COMMAND_TIME (6) PARCEL_MEDIA_COMMAND_TYPE (10) PARCEL_MEDIA_COMMAND_UNLOAD (8) PARCEL_MEDIA_COMMAND_URL (5) PASSIVE (0x4) PAY_DEFAULT (-2) PAY_HIDE (-1) PAYMENT_INFO_ON_FILE (1) PAYMENT_INFO_USED (2) PERM_ALL (0x7FFFFFFF) PERM_COPY (0x8000) PERM_MODIFY (0x4000) PERM_MOVE (0x80000) PERM_TRANSFER (0x2000) PERMISSION_ATTACH (0x20) PERMISSION_CHANGE_JOINTS (0x100) PERMISSION_CHANGE_LINKS (0x80) PERMISSION_CHANGE_PERMISSIONS (0x200) PERMISSION_CONTROL_CAMERA (0x800) PERMISSION_DEBIT (0x2)
Page(s) 154 154 154 154 154 154 154 154 154 154 154 254, 255 254, 255 253, 255 255 253, 255 255 254, 255 253, 255 254, 255 253, 254, 255 255 253, 255 255 253, 254, 255 53, 126, 129, 132, 133 283, 295, 302, 303 196 196 196 196 196 54, 55, 56
ReturnType Code PING_PONG (0x8) PRIM_BUMP_BARK (4) PRIM_BUMP_BLOBS (12) PRIM_BUMP_BRICKS (5) PRIM_BUMP_BRIGHT (1) PRIM_BUMP_CHECKER (6) PRIM_BUMP_CONCRETE (7) PRIM_BUMP_DARK (2) PRIM_BUMP_DISKS (10) PRIM_BUMP_GRAVEL (11) PRIM_BUMP_LARGETILE (14) PRIM_BUMP_NONE (0) PRIM_BUMP_SHINY (19) PRIM_BUMP_SIDING (13) PRIM_BUMP_STONE (9) PRIM_BUMP_STUCCO (15) PRIM_BUMP_SUCTION (16) PRIM_BUMP_TILE (8) PRIM_BUMP_WEAVE (17) PRIM_BUMP_WOOD (3) PRIM_CAST_SHADOWS (24) PRIM_COLOR (18)
PRIM_FLEXIBLE (21) PRIM_FULLBRIGHT (20) PRIM_GLOW (25) PRIM_HOLE_CIRCLE (0x10) PRIM_HOLE_DEFAULT (0x00) PRIM_HOLE_SQUARE (0x20) PRIM_HOLE_TRIANGLE (0x30) PRIM_MATERIAL (2) PRIM_MATERIAL_FLESH (4) PRIM_MATERIAL_GLASS (2) PRIM_MATERIAL_METAL (1) PRIM_MATERIAL_PLASTIC (5) PRIM_MATERIAL_RUBBER (6) PRIM_MATERIAL_STONE (0) PRIM_MATERIAL_WOOD (3) PRIM_PHANTOM (5) PRIM_PHYSICS (3) PRIM_POINT_LIGHT (23)
56, 105 PRIM_POSITION (6) 56, 183 56, 286, 287, 291, 292, 303
PERMISSION_RELEASE_OWNERSHIP (0x40) PERMISSION_REMAP_CONTROLS (0x8) PERMISSION_TAKE_CONTROLS (0x4) 56, 59, 171, 172 PERMISSION_TRACK_CAMERA (0x400) 56, 183 PERMISSION_TRIGGER_ANIMATION (0x10) 56, 59 5, 128, PI (3.141592653589793238462643➥ 129, 132, 3832795) 133, 303, 304 PI_BY_TWO (1.570796326794896619➥ 79 2313216916398)
PRIM_ROTATION (8) PRIM_SCULPT_TYPE_CYLINDER (4) PRIM_SCULPT_TYPE_PLANE (3) PRIM_SCULPT_TYPE_SPHERE (1) PRIM_SCULPT_TYPE_TORUS (2) PRIM_SHINY_HIGH (3) PRIM_SHINY_LOW (1) PRIM_SHINY_MEDIUM (2) PRIM_SHINY_NONE (0) PRIM_SIZE (7)
Page(s) 213, 214217 346 346 346 346 346 346 346 346 346 346 346 226, 227, 344, 345346 346 346 346 346 346 346 346 49, 50, 221, 224, 234, 344, 346 344, 347 222, 344, 347 222, 344, 347 352 352 352 352 344, 347 347 347 347 347 347 347 347 344, 348 344, 348 49, 50, 222, 224, 345, 348 43, 44, 221, 234, 345, 348 235, 345, 348 354 354 354 354 345 345 345 345 234, 345, 349
383
370-386 Index and BoB Ad.indd 383
8/8/08 2:08:10 AM
ReturnType Code PRIM_TEMP_ON_REZ (4)
Page(s) 107, 108, 131, 170, 221, 345, 349 PRIM_TEXGEN (22) 345, 349 PRIM_TEXGEN_DEFAULT (0) 349 PRIM_TEXGEN_PLANAR (1) 349 PRIM_TEXTURE (17) 211, 213, 221, 345, 349 PRIM_TYPE (9) 345, 350355 PRIM_TYPE_BOX (0) 350 PRIM_TYPE_CYLINDER (1) 350 PRIM_TYPE_PRISM (2) 350 PRIM_TYPE_RING (6) 350 PRIM_TYPE_SCULPT (7) 350 PRIM_TYPE_SPHERE (3) 350 PRIM_TYPE_TORUS (4) 350 PRIM_TYPE_TUBE (5) 350 PSYS_PART_BOUNCE_MASK (4) 10, 358, 363 PSYS_PART_EMISSIVE_MASK (256) 201, 202, 358, 361 PSYS_PART_END_ALPHA (4) 201, 362 PSYS_PART_END_COLOR (3) 201, 206, 207, 361 PSYS_PART_END_SCALE (6) 203, 206, 207, 209, 361 PSYS_PART_FLAGS (0) 202 PSYS_PART_FOLLOW_SRC_MASK (16) 209, 358, 363 PSYS_PART_FOLLOW_VELOCITY_MASK (32) 201, 202, 205, 206, 209, 358, 363 PSYS_PART_INTERP_COLOR_MASK (1) 201, 205, 358, 361, 362 PSYS_PART_INTERP_SCALE_MASK (2) 203, 205, 358, 361 PSYS_PART_MAX_AGE (7) PSYS_PART_START_ALPHA (2) 201, 362 PSYS_PART_START_COLOR (1) 201, 206, 207, 361 PSYS_PART_START_SCALE (5) 203, 206, 207, 209, 361 PSYS_PART_TARGET_LINEAR_MASK (128) 358, 363 PSYS_PART_TARGET_POS_MASK (64) 358, 363 PSYS_PART_WIND_MASK (8) 10, 202, 209, 358, 363 PSYS_SRC_ACCEL (8) 202, 363, 364 PSYS_SRC_ANGLE_BEGIN (22) 203, 360 PSYS_SRC_ANGLE_END (23) 203, 360 PSYS_SRC_BURST_PART_COUNT (15) 361 PSYS_SRC_BURST_RADIUS (16) 203, 206, 207, 359 PSYS_SRC_BURST_RATE (13) 361 PSYS_SRC_BURST_SPEED_MAX (18) 363 PSYS_SRC_BURST_SPEED_MIN (17) 363 PSYS_SRC_MAX_AGE (19) 361 PSYS_SRC_OMEGA (21) 360 PSYS_SRC_PATTERN (9) 359 PSYS_SRC_PATTERN_ANGLE (4) 205, 359
ReturnType Code PSYS_SRC_PATTERN_ANGLE_CONE (8) PSYS_SRC_PATTERN_DROP (1) PSYS_SRC_PATTERN_EXPLODE (2) PSYS_SRC_TARGET_KEY (20) PSYS_SRC_TEXTURE (12) PUBLIC_CHANNEL (0) quaternion: see rotation RAD_TO_DEG (57.2957795130823208➥ 76798154814105) REGION_FLAG_ALLOW_DAMAGE (0x1) REGION_FLAG_ALLOW_DIRECT_TELEPORT (0x100000) REGION_FLAG_BLOCK_FLY (0x80000) REGION_FLAG_BLOCK_TERRAFORM (0x40) REGION_FLAG_DISABLE_COLLISIONS (0x1000) REGION_FLAG_DISABLE_PHYSICS (0x4000) REGION_FLAG_FIXED_SUN (0x10) REGION_FLAG_RESTRICT_PUSHOBJECT (0x400000) REGION_FLAG_SANDBOX (0x100) remote_data() event REMOTE_DATA_CHANNEL (1) REMOTE_DATA_REPLY (3) REMOTE_DATA_REQUEST (2) return REVERSE (0x4) ROTATE (0x20) rotation run_time_permissions() event SCALE (0x40) SCRIPTED (0x8) sensor() event
SMOOTH (0x10) SQRT2 (1.414213562373095048801➥ 6887242097) state state_entry() event
state_exit() event STATUS_BLOCK_GRAB (0x40) STATUS_CAST_SHADOWS (0x200)
Page(s) 203, 359 359 202, 359 208, 209, 363 362 12-13, 115 154 154 154 154 154 154 154 154 154 274 274 274 274 7, 21, 24 213, 214217, 221 213, 214217 9, 12-13, 98-123 54, 55, 56, 59, 177, 178, 287 213, 214217, 221 53, 126 127, 128, 129, 130, 132, 137, 138, 151, 211, 264, 304 213, 214217, 221 21, 26-28 4, 7, 26-28, 30, 32, 41, 75, 92. 110, 120, 143, 170, 171, 172, 176, 202, 209, 219, 221, 283, 284, 287, 292, 295, 299, 302, 303, 321 26-27, 170, 173 158 158
384
370-386 Index and BoB Ad.indd 384
8/8/08 2:08:10 AM
Get more out of your Second Life. ®
978-0-470-17114-1
978-0-470-17914-7
978-0-470-22775-6
Whether you want to design breathtaking buildings, start a fashion trend, or cash in on your creativity, you’ll get more out of the metaverse with our exclusive guides to Second Life.
Available at wiley.com or wherever books are sold. Second Life is a registered trademark of Linden Research, Inc.
Combat
PROJECT 3
Second Life is not a game...but it is a fine platform for implementing games: not just social ones, but also action games with violence, weapons, and injury. This chapter discusses the combat system built into Second Life: the Linden Lab Combat System or LLCS and touches on some of the alternatives. We do not cover much in the way of so-called "advanced" SL weapons, as they generally work by exploiting what are arguably bugs in the platform. Instead the focus here is on using LSL to script relatively realistic dangerous games*.
* See also http://wiki.secondlife.com/wiki/Combat and http://wiki.secondlife.com/wiki/Weapon.
LLCS BASICS
PROJECT 3
LLCS directly supports concepts such as injury and damage. It integrates well into the world, relying on land settings to enable the system and relatively simple scripting support to let you build weapons that will interact with avatars. The combat model relies heavily on the physics engine, described in Chapter 7, "Physics and Vehicles."
L LCS B asics aking M LLCS Weapons N on -LLCS Combat S ummary
NOTE Damage-enabled land: Land may be damage-enabled or not. While this property must be set manually, it can be queried using llGetRegionFlags() and llGetParcelFlags(vector pos). See Chapter 6, “Land Design and Management,” for more details. The important thing here is that if an avatar isn’t on damage-enabled land, they cannot be damaged. Push-restricted land: Land may be marked as having restricted pushing, preventing and attenuating calls to llPushObject(). This feature is less important to realistic simulation of combat, but heavy pushing can be used to simulate the effects of explosions and magical weapons, and of course to annoy other people. Avatar health and death: Avatars have a health parameter that is visible when they are on damageenabled land (it’s a little heart with a number from 0 to 100). Health is recovered automatically. If your health drops to 0 you are teleported home, similar to the effect of llTeleportAgentHome(key avatar). Damage and collisions: Objects may set how much damage they will cause on collision with an agent (avatar). Objects with nonzero damage will die when colliding with another object. Damage is a simple value that is subtracted from the target’s health on collision. Values above 100 will also kill the avatar. Collisions with very large or fast objects or with the ground (falling from high up) can cause damage.
MAKING LLCS WEAPONS There just isn't a whole lot to the LLCS interface. If you want to implement a damage-enabled weapon, you have exactly two scripting-related considerations: how to inflict damage directly on an opponent and how to deliver the damaging object to the target. Of course, there is plenty of nonscripting detail outside the scope of this book, not the least of which is building an object that looks like a gun, a crossbow, or a sword. Building a gun-and-bullet set like the one in Figure P3.1 is a simple matter. The scripts in this chapter will work just as well with half-meter plywood boxes, but won't look nearly as cool.
4
CH02 ADD.
A Bullet
CH06 ADD.
The most important consideration in dealing with damage-enabled objects is that only physical objects can damage avatars, and then only if they collide: all damaging objects are bullets, in a real sense. Listing P3.1 is a basic bullet script that doesn't actually cause any damage, although it will move the target avatar or object around a little, solely due to the physical interactions between objects. This script is the basis for rest of the damagers in this chapter, so it is worth looking at in detail.
CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Figure P3.1: Start by building a basic bullet and gun.
BUILD NOTE Make a small spherical prim that is longer along the z-axis than the other two, for instance . If you’d like, make something that actually looks like a bullet with a linked cylinder and hemisphere. Make sure the object is not rotated incorrectly: the script below will make the bullet fly with its original top pointed at the target. Make sure the bullet object is physical and drop in the script in Listing P3.1. (We strongly suggest naming the object something more descriptive than “Bullet”, let alone “Object”.) Most people make bullets much larger than realistic in order to make them easier to see.
Listing P3.1: Bullet float BUOYANCY = 1.0; float TIMEOUT = 10.0;
// how does it fall? //seconds till auto-death
setup() { // called by state entry llSetBuoyancy(BUOYANCY); } affectDetected(integer i) { // called when it hits someone llWhisper(0, "Ouch!"); }
5
affectLand(vector pos) { // called when it hits land llShout(0, "Splat"); } fizzle() { // invoked when it doesn't hit anything llShout(0,"Fizzle"); }
PROJECT 3
L LCS B asics aking M LLCS Weapons N on -LLCS Combat S ummary
default { on_rez(integer param) { if (param) { // fired? Zero means rez from inventory llSetStatus(STATUS_DIE_AT_EDGE, TRUE); llSetPrimitiveParams([PRIM_TEMP_ON_REZ, TRUE]); llSetTimerEvent(TIMEOUT); } } state_entry() { setup(); } collision_start(integer count) { integer i; for (i = 0; i < count; i++) { affectDetected(i); } llDie(); } land_collision_start(vector pos) { affectLand(pos); llDie(); } timer() { fizzle(); llDie(); } }
Because bullets must be physical to collide with your target, you need to consider all the usual physical modeling issues. To keep a fired bullet from falling to the ground, we set the BUOYANCY constant to 1.0—this will result in a bulletlike effect where the projectile flies in the direction it is fired without sinking. A very low bullet velocity with neutral buoyancy could give you an effect like "bullet time" in the Matrix movies. For an arrow you might want a lower buoyancy value and a relatively low speed. You also usually want to avoid having deadly bullets flying around for hours; the timeout will cause bullets to self-destruct after TIMEOUT seconds. The value we set here of 10 seconds is probably overly long for realism, but is fine for housekeeping purposes. To simplify changing the behavior of bullets, this script defines an extra setup function and three affect functions. The setup() function is called only by state_entry() to set the parameters of the bullet. This is a good place to define the basic behavior of the bullet. It is most important to set damage, but there are other options. The three affect functions are called in different situations depending on what the bullet collides with: affectDetected() is called when it collides with an avatar or object, affectLand() is called when it collides with the ground, and fizzle() is called when it times out having not hit anything. This version chats with users to indicate what is going on, but you could replace the chat with functions to implement explosions or other effects.
6
NOTE
CH02 ADD. CH06 ADD.
The collision event handlers are described in detail in Chapter 5, “Sensing the World.” llRezObject(), meanwhile, is described in Chapter 4, “Making and Moving Objects.”
CH08 ADD. CH12 ADD. CHAPTER 16
The parameter passed to the on_rez(integer param) event handler is used to determine if you are just rezzing the bullet to work with it (default or manual rez sets param = 0) or if it has actually been fired (param = 1, set by the llRezObject() call in the gun, as in Listing P3.2). Essentially, the parameter acts like a safety. If the bullet has been fired, the script does three things. First it sets the status flag so that it self destructs if it tries to cross a sim border. Then it sets the PRIM_TEMP_ON_REZ property so it's less likely to run out of prims if we fire lots of bullets. (Even though the bullets won't live long enough for the sim's garbage collector to clean it up, PRIM_TEMP_ON_REZ objects don't count toward your prim count.) Finally it starts the fizzle timer so that it self-destructs if it lives long enough. Obviously, much of the code in this script is devoted to cleaning up after itself and making certain stray bullets cannot cause problems by escaping their region of origin. Because damage-enabled land is relatively uncommon in Second Life, people nearby don't get hurt, and with PRIM_TEMP_ON_REZ and timeouts, the bullets shouldn't last long enough to cause litter. This bullet isn't very interesting, but it is both a good starting point for the fancier ones and, most importantly, it can be fired with a gun!
PROJECT 1 PROJECT 2
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
A Gun for Your Bullet In Second Life, much as in the real world, a gun is just a tool for controlling the release of bullets. Listing P3.2 is a very simple gun script that will serve as an example for this chapter. Add the script into a gun like the one in Figure P3.1 or as fancy a gun as you'd like—for instance, the ones in Figure P3.2.
BUILD NOTE Make a cylinder or any gunlike object with the approximate dimensions of . You will want the up direction to be where the bullet should appear. Drop the code from Listing P3.2 into the gun, and the fully scripted bullet into its inventory.
Figure P3.2: “We need guns...lots of guns.”
7
Listing P3.2: A Simple Gun Script // Simple script to fire an object in the direction we are pointing. vector AXIS_UP = ; string AIMANIMATION = "aim_R_handgun"; float BULLETSPEED = 10.0; // m/sec
PROJECT 3
integer gControls; integer gPermissions; L LCS B asics aking M LLCS Weapons N on -LLCS Combat
fire() { rotation rot vector dir = vector vel = vector pos =
= llGetRot(); llRot2Fwd(rot); dir * BULLETSPEED; llGetPos() + *rot;
string bullet = llGetInventoryName(INVENTORY_OBJECT, 0);
S ummary
llOwnerSay("Bang!"); llRezObject(bullet, pos, vel, rot, 1); } default { state_entry() { gControls = CONTROL_ML_LBUTTON; gPermissions = PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION; } attach(key on) { if (on != NULL_KEY) { if (llGetPermissions() != gPermissions ) { llRequestPermissions(on, gPermissions); } else { llTakeControls(gControls, TRUE, TRUE); } } else { llReleaseControls(); llStopAnimation(AIMANIMATION); } } run_time_permissions(integer perms) { if (perms == gPermissions) { llStartAnimation(AIMANIMATION); llTakeControls(gControls, TRUE, TRUE); } } control(key owner, integer held, integer recentChange) { if ((recentChange & CONTROL_ML_LBUTTON) && (held & CONTROL_ML_LBUTTON)) { fire(); } } }
This script uses mouselook for fine-aiming control: you attach the gun to your right hand and enter mouselook by typing m. Then you aim at what you want to shoot and click the left mouse button to fire. Chapter 7, "Physics and Vehicles," provides more detail on using mouselook controls. For a different approach, the web chapter "Project 2: Bowling," uses a HUD.
8
The mathematics at the beginning of fire() figures out where the bullet should be rezzed and how it should fly. The call to llGetRot() tells the script exactly where the avatar is pointing. This value is converted from a rotation to a unit vector pointing in the right direction using llRot2Fwd(). Multiplying this unit vector by the desired speed gives you the correct velocity (including direction) for the bullet. Finally, you don't want to rez the bullet right on top of the gun, so instead it rezzes the bullet one meter away in the appropriate direction (*rot). The bullet will also be rotated to point in the same direction.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1
The script then rezzes the first object found in inventory using the parameters it has chosen. The last argument to llRezObject() is passed along as the parameter to the on_rez() event handler of the new object. The script passes in the value 1 to tell the bullet it is actually being fired rather than rezzed by hand, matching what the bullet script in Listing P3.1 expects.
PROJECT 2
NOTE
PROJECT 3
The global variables gControls and gPermissions in Listing P3.2 are really constants. They are initialized in state_entry() because LSL requires that global variables be initialized to constant and literal values (not expressions). The bitwise “or” operator (|) in gPermissions would cause a syntax error if used at the top.
PROJECT 4 PROJECT 5 APPENDIX D
A Target A target is easy! Just make any small physical object, maybe a cylinder, like an empty can. Place it on a box or a fence post and then use your gun to fire bullets at it. With a little practice you should be able to shoot your can off its pedestal. A nice target-practice fixture is a platform that rezzes a new target when the previous one is knocked off, such as the stack of cans in Figure P3.3. This could be implemented using the llRezObject() like the flower petal rezzer in the print version of Scripting Your World, Chapter 4, "Making and Moving Objects," Listings 4.1 and 4.2 or the Money Tree in Chapter 13, "Money Makes the World Go 'Round," Listings 13.5 and 13.6.
Figure P3.3: Target practice
9
More-Interesting Bullets PROJECT 3
L LCS B asics aking M LLCS Weapons N on -LLCS Combat S ummary
The bullet doesn't actually do much. Yes, you can fire it from your gun and it can knock cans off fences, but it will have very little effect on your partners in shoot-’em-up role-playing games. In particular, while bullets like this can push avatars around a little bit if they get hit, they are unlikely to cause any damage at all, even on damage-enabled land. This problem can be remedied with this simple change to the bullet script: // modify the setup() function from Listing P3.1: setup() { llSetBuoyancy(BUOYANCY); llSetDamage(100.0 * llFrand(1.0) * llFrand(1.0)); }
NOTE Avatars can also be injured on damage-enabled land by falling a long way or getting hit by very large and high-velocity objects. This latter effect is negligible—while it can happen, you really don’t need to worry about it.
This version calls llSetDamage() to define how much damage the bullet will cause if it collides with an avatar in a damage-enabled region. The argument is a simple value that will be deducted from the injured avatar's health. This script will choose a random amount of damage from 0.0 up to (but not quite including), 100.0 damage, with low damage values much more common. The technique of multiplying two probabilities is rather like rolling two six-sided dice—getting a total of 2 is much less likely than a total of 7.
DEFINITION llSetDamage(float injury) Sets the amount of injury to be inflicted on the avatar the bullet collides with. The normal valid range for injury is from 0.0 to 100.0. A value of 100.0 should be assumed to kill an avatar, whereas 0.0 will have no effect.
You can supply higher values, too—anything over 100.0 is quite literally overkill, but you will see scripts that supply very high values. A killer bullet is easy, but not very interesting, as shown here: // modified from P3.1 Bullet setup() { llSetBuoyancy(BUOYANCY); llSetDamage(100.0); }
Also useful is a bullet that pushes the target, with or without causing damage. The next variation of the bullet just pushes the avatar it hits without injuring him, eliminating the call to llSetDamage() and adding an llPushObject() on the avatar the bullet hits, pushing it in the direction the bullet was traveling, with a bit of a spin.
10
// modified from P3.1 Bullet setup() { llSetBuoyancy(BUOYANCY); // llSetDamage(llFrand(50.0)); }
CH02 ADD. CH06 ADD. CH08 ADD.
affectDetected(integer i) { vector dir = llRot2Fwd(llGetRot()) * 100.0; vector spin = * 100.0; llPushObject(llDetectedKey(i), dir, spin, FALSE); }
One final example is a version of a bullet that adds some special effects: a smoke trail, as illustrated in Figure P3.4, and a collision sound of breaking glass (Listing P3.3). Check out Chapter 9: "Special Effects," and Appendix B, "The Particle System," in the print version of SYW for details on the llParticleSystem() calls.
CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Figure P3.4: Smoke trails // modified from P3.1 Bullet setup() { llSetBuoyancy(BUOYANCY); // breaking glass llCollisionSound("85cda060-b393-48e6-81c8-2cfdfb275351", 1.0); // Light gray smoke trail llParticleSystem([ PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | PSYS_PART_EMISSIVE_MASK, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, PSYS_PART_START_COLOR, ]); } affectLand(vector pos) { llSetStatus(STATUS_PHYSICS, FALSE); llParticleSystem([ PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | PSYS_PART_EMISSIVE_MASK, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE, PSYS_PART_START_COLOR, ]); llSleep(2.0); llParticleSystem([]); }
11
PROJECT 3
L LCS B asics aking M LLCS Weapons N on -LLCS Combat S ummary
This last version of the bullet adds a call to llCollisionSound() that changes the sound used when the bullet collides with something to a clip of breaking glass: perfect if you are using objects that look like glass bottles for target practice. We also add a nice light-colored smoke trail for the bullet in flight with the particle system in setup() and then reset the system with a few seconds of expanding black smoke where it hits. One important trick here is to turn off physics so that the bullet doesn't keep moving, since you want the smoke to stay near the target. A bullet can look like just about anything: a missile that flies around looking for a particular avatar; an arrow that will stick into the avatar, objects, or land it hits; a flaming skull that screams as it flies; squid that wave their tentacles threateningly as they approach their target; or even, as you'll see later when we discuss melee weapons (in the "A Sword" section), totally invisible bullets. Furthermore, although bullets must be physical to actually collide with avatars, they need not be physical the whole time—a bullet that uses nonphysical movement to get close to an avatar (using llSetPos() for instance) and only then turns physical will let bullets go through shields—not very sporting perhaps, but useful in certain circumstances.
A Stand-Alone Gun Sometimes you want a gun that stands by itself: imagine a security system that fires on your enemies automatically, an evil creature lurking in the corridors of a dark installation on Mars, or a dangerous barrier for your obstacle course. The script in Listing P3.3 implements a simple robotic gun that waits for someone to "touch" it, then points at them and fires a bullet in their direction.
BUILD NOTE The script will rotate the object so that the top points toward the target before firing. Perhaps build a one-meter sphere with some short tubes pointing out the top.
Listing P3.3: Automatic Gun // Simple auto gun - fire a random bullet from inventory at the toucher float BULLETSPEED = 10.0; // m/sec float gGunsize; vector AXIS_UP = ; fireAt() { rotation rot vector dir = vector vel = vector pos =
= llGetRot(); llRot2Up(rot); dir * BULLETSPEED; llGetPos() + * rot;
integer selection = (integer) llFrand(llGetInventoryNumber(INVENTORY_OBJECT)); string bullet = llGetInventoryName(INVENTORY_OBJECT, selection); // llSay(0, "Firing "+bullet); llRezObject(bullet, pos, vel, rot, 1); }
12
default { on_rez(integer p) { llResetScript(); } state_entry() { llSetText("Don't Touch!", , 1.0); list bb = llGetBoundingBox(llGetKey()); vector maxCorner = llList2Vector(bb,1); gGunsize = maxCorner.z + 0.2; } touch_start(integer count) { llSay(0, "Death to "+llDetectedName(0)+"!"); llLookAt(llDetectedPos(0),1.0,1.0); fireAt(); } }
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2
The fire() function is very similar to the one from the manual gun in Listing P3.2. The main difference is that it computes the bullet release offset using a distance gGunsize computed in state_ entry() (remember to reset the script if you change the shape of the gun!). The variable is initialized by finding the maximum z offset of the object's bounding box plus a little extra to accommodate the size of the bullet. To make it a little more interesting, it also randomly chooses a bullet from the objects in inventory to fire; thus a gun with a large number of rotten vegetables in its inventory could create quite a mess!
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
When someone touches the gun, the script rotates the gun so that the gun's "up" axis is pointed at the toucher, and then fires the gun at them.
An Armed Sentry Robot The script in Listing P3.4 implements something like an armed sentry robot: it is essentially an automatic gun that is both more threatening and more automatic than the one in Listing P3.3. When enabled, it will track the nearest avatar within its sensor range and fire on him if he gets too close. You can use the same build as before or make it look more like a robot. This version is shown in action in Figure P3.5.
Figure P3.5: An automatic sentry robot firing on an intruder
13
Listing P3.4: Sentry
PROJECT 3
// additions and changes to Listing P3.3 float PERIMETER = 20.0; // track closest av within this dist float FIREDIST = 5.0; // fire on avs within this distance string WARNTEXT = "Danger! Stay back!"; string IDLETEXT = "Safe"; float SLOW_SENSOR_SCAN = 10.0; // no avs in range float FAST_SENSOR_SCAN = 0.5; // an avatar in range.
L LCS B asics
integer gTracking = FALSE;
aking M LLCS Weapons
sensorActive(float rate) { llSensorRepeat( "", NULL_KEY, AGENT, PERIMETER, PI, rate); }
N on -LLCS Combat S ummary
updateText(string s) { llSetText(s, , 1.0); } default { state_entry() { updateText(IDLETEXT); // don't change the rest of state_entry list bb = llGetBoundingBox(llGetKey()); vector maxCorner = llList2Vector(bb,1); gGunsize = maxCorner.z + 0.2; } touch_start(integer count) { gTracking = !gTracking; if (gTracking) { updateText(WARNTEXT); sensorActive(SLOW_SENSOR_SCAN); } else { updateText(IDLETEXT); llSensorRemove(); llRotLookAt(, 1.0, 0.1); } } sensor(integer c) { integer fire = FALSE; if (llVecDist(llDetectedPos(0), llGetPos()) < FIREDIST) { fire = TRUE; } llLookAt(llDetectedPos(0), 1.0, 1.0); sensorActive(FAST_SENSOR_SCAN); if (fire) { updateText("Firing on "+llDetectedName(0)); fireAt(llDetectedPos(0)); } else { updateText("Tracking "+llDetectedName(0)); } } no_sensor() { updateText(WARNTEXT); llRotLookAt(, 1.0,0.1); sensorActive(SLOW_SENSOR_SCAN); } }
14
This script is similar to Listing P3.3 except that touch turns it on and off, and fire control is done with a two-stage repeating sensor. When it is turned on it periodically looks for avatars within its security perimeter. When it sees someone within the perimeter it scans much more frequently to track the avatar, keeping the gun pointed at the intruder. (Recall that the detected items for the sensor() are sorted by distance.) If the closest sensed avatar is too close, the gun will begin firing at him. Once the intruder leaves the area the no_sensor() event handler will be called and the sentry will go back to an idle mode.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1
A Sword So far this chapter has discussed only projectile weaponry. One would think that swords and such would be simpler, but in Second Life they are actually somewhat more difficult to implement because the LLCS model is really designed solely with projectiles in mind. To implement a melee weapon such as a sword, you must take a somewhat different approach. Listing P3.5 pushes the opponent, while Listings P3.6 and P3.7 rez invisible damager projectiles.
PROJECT 2
PROJECT 3 PROJECT 4
NOTE
PROJECT 5 APPENDIX D
Listing P3.5 is a simplified version of a rather old unattributed script that was later extended and highly commented by Jigsaw Partridge as part of his JPPillowfighter pillow-fighting attachment. This script is available for free in many locations, including at SYW HQ and in online Second Life stores. The full script makes for really good reading…and the pillow-fight object is a whole lot of fun.
Listing P3.5: Basic Melee Weapon integer CONTROLS; integer PERMISSIONS; float PUSHSTRENGTH = 1000.0; default { state_entry() { CONTROLS = (CONTROL_ML_LBUTTON | CONTROL_LBUTTON | CONTROL_FWD); PERMISSIONS = PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION; llSetStatus(STATUS_BLOCK_GRAB, TRUE); } attach(key on) { if (on != NULL_KEY) { if (llGetPermissions() != PERMISSIONS ) { llRequestPermissions(on, PERMISSIONS); } else { llTakeControls(CONTROLS, TRUE, TRUE); } } else { llReleaseControls(); } } run_time_permissions(integer perms) { if (perms == PERMISSIONS) { llTakeControls(CONTROLS, TRUE, TRUE); } }
15
control(key owner, integer held, integer recentChange) { if (held & (CONTROL_ML_LBUTTON | CONTROL_LBUTTON)) { if (recentChange & CONTROL_FWD) { llStartAnimation("sword_strike_R"); llSleep(0.5); // sense the sword strike llSensor("", NULL_KEY, AGENT, 4.0, PI_BY_TWO*0.5); } } } sensor(integer tnum) { vector dir = llDetectedPos(0) - llGetPos(); dir.z = 0.0; dir = llVecNorm(dir); rotation rot = llGetRot(); dir += llRot2Up(rot); dir *= PUSHSTRENGTH; llPushObject(llDetectedKey(0), dir, ZERO_VECTOR, FALSE); }
PROJECT 3
L LCS B asics aking M LLCS Weapons N on -LLCS Combat S ummary
}
The bulk of this script is devoted to managing permissions and controls, but the relevant new logic is very clever. When the avatar clicks the left mouse button while moving forward, the script plays the built-in sword-strike animation and then senses for agents in a small cone within four meters in front the avatar. If it detects someone inside the strike zone, it pushes the closest avatar away. Listing P3.5 is a damage weapon, but it is limited because of this catch-22: only physical objects can cause damage directly, but attached objects cannot be physical! (Even worse, attached objects are always phantom!) Even if attachments were physical, remember that animations are rendered on the client side: just because your avatar appears to hit another with a sword during a lunge animation doesn't mean the agents have actually interacted at all on the server. Listing P3.5 solves the basic interaction problem by using the sensor trick and pushing the other avatar, but real collisions between attachments and avatars just don't happen in Second Life. The workaround for this problem is to have the visible weapon emit invisible bulletlike damager objects to cause damage on behalf of the sword. Figure P3.6 shows an example of just such a sword. This damager script is Listing P3.6. It still needs to be in a physical object, but the object may be small and invisible. (Take care not to lose it!) Later scripts also assume the object containing the script is named "Damager".
16
Figure P3.6: This sword damages without visible projectiles.
Listing P3.6: Damager for Melee Weapons // very much like the bullet from Listing P3.1 default { on_rez(integer param) { llSetBuoyancy(1.0); // float if (param) { llSetStatus(STATUS_DIE_AT_EDGE, TRUE); llSetTimerEvent(0.5); llSetDamage(llFrand(50.0)); llSetPrimitiveParams([PRIM_TEMP_ON_REZ, TRUE]); } } timer() { llDie(); } }
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2
Next, make a small modification to the sword script: all you need to change is to rez the damager object on top of your opponent on a successful strike, as shown in Listing P3.7.
PROJECT 3
Listing P3.7: Hand-Held Weapon That Damages Opponent
PROJECT 4
// change Listing P3.5's sensor event handler default { // Add llRezObject to rez the damager object on top of the target sensor(integer tnum) { llRezObject("Damager", llDetectedPos(0), ZERO_VECTOR, ZERO_ROTATION, 1); vector dir = llDetectedPos(0) - llGetPos(); dir.z = 0.0; dir = llVecNorm(dir); rotation rot = llGetRot(); dir += llRot2Up(rot); dir *= PUSHSTRENGTH; llPushObject(llDetectedKey(0), dir, ZERO_VECTOR, FALSE); } }
PROJECT 5 APPENDIX D
You can use the same technique to implement other sorts of attacks, including magical weapons: build your objects with the animations, sounds, and special effects that you want, and if you want to damageenable them, just rez damagers when you need to. Remember that llRezObject() only has a range of 10 meters, so you might have to use something more like a normal bullet that first moves to the target avatar and then becomes physical to damage them.
Area-of-Effect Damage The damager approach can be used to injure avatars in other ways too. Forest fire? Poisonous swamp gas? Radiation? Hard vacuum? All these and more can work just like our damage-enabled sword. Simply detect all the avatars within the radius of effect and rez damagers at them until they leave the area...or die!
17
PROJECT 3
L LCS B asics aking M LLCS Weapons N on -LLCS Combat S ummary
Figure P3.7: This poison gas will kill you if you get too close!
Shields Up! What fun are weapons without shields? The bullets we scripted earlier die on collision with objects as well as avatars, so obviously you can protect yourself by hiding behind some nonphantom object. But because attachments cannot collide with anything, you cannot wear an object that can protect you in the same way. However, you can protect yourself with an attachment that looks for incoming projectiles and rezzes a shield object between the bullet and you. Note that this approach works only for projectile weapons, and then only if your shield can react quickly enough. Although such shields are usually invisible, Figure P3.8 illustrates how an LLCS shield such as the one in Listing P3.8 would look if you used multiple visible blocking objects.
18
Figure P3.8: This shield rezzes four physical objects between the avatar and the incoming projectile. (Note that shields are usually invisible.)
Listing P3.8: A Shield against Physical Bullets integer CHANNEL = 66; default { on_rez(integer param) { llResetScript(); } state_entry() { llListen(CHANNEL,"",llGetOwner(),""); } listen(integer ch, string n, key id, string msg) { if (msg == "on") { llSensorRepeat("", NULL_KEY, ACTIVE, 10, PI, 0.1); llOwnerSay("Shield up"); } else if (msg == "off") { llSensorRemove(); llOwnerSay("Shields down"); } } sensor(integer count) { float speed = llVecMag(llDetectedVel(i)); if (speed > 5.0) { vector dir = llVecNorm(llDetectedPos(i) - llGetPos()); llRezObject("Blocker", llGetPos()+dir*2.0, ZERO_VECTOR, ZERO_ROTATION, 0); } } }
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
The logic in Listing P3.8 is pretty straightforward: if the shield is enabled, it activates a lag-inducing sensor repeat looking for active objects nearby. This bears repeating: short-duration repeating sensors are extremely expensive! Any sensed objects that are moving faster than five meters per second will cause the script to rez a Blocker object from inventory in between the avatar and the detected object. The Blocker should clean up after itself like the bullets, but it need not be physical. Another option would be to use llPushObject() on the detected object to push it in another direction.
NOTE Another trick for avoiding damage is to sit on a phantom object. For most purposes, when you sit on an object you become part of that object’s linkset. If the object is phantom you become phantom too, and cannot be harmed by physical objects.
NON-LLCS COMBAT As you can see, the Linden Lab Combat System is pretty limited; it boils down to a damage add-on to the physics model. Many people have found it useful to write their own combat systems, usually by adding combat support to much richer role-playing systems. Most of these systems are proprietary to reduce the ease of cheating, but the following are some common characteristics:
19
PROJECT 3
L LCS B asics aking M LLCS Weapons N on -LLCS Combat S ummary
• Players wear an attachment that tracks their own game statistics and manages interactions with other players and weapons. Usually these attachments communicate periodically with off-world databases to store an avatar's progress and status. These attachments usually figure out injury and other effects for the wearing avatar. For instance, a werewolf game attachment might accept damage from only silver weapons. • Weapons usually need to be specially scripted to interact properly with the system. Any of the weapons in this section could be modified to communicate with the target instead of inflicting LLCS damage. For instance, a weapon could chat a message to the target avatar that includes the possible range of injury to be expected: a thrown punch from a weak character would lead to minimal damage, while a poisoned arrow would be much worse. • Healing is possible as well as injury. A doctor or a wizard may have special healing abilities. A vampire who drinks blood from a victim can gain strength as the target loses it. • Environmental effects can play a role: characters could drown underwater, be poisoned by gas, or be healed by a magic pool.
SUMMARY The projects presented in this online section extend your scripting knowledge to cover the basics of using the Second Life physical simulation system for weapons. While most multiplayer online games revolve around violence in one form or another, SL is much more about role playing and interaction. Effective use of the LLCS or other combat systems can add a sense of danger to SL games and scenarios that would be missing otherwise.
20
A Traffic Signal
PROJECT 4
No one likes traffic in real life, but the feeling of traffic and the controls that go along with it can add vitality to any Second Life land parcel. We'll start Project 4 with a basic traffic signal. For extra fun, we'll add spatialized audio to create the illusion of traffic stopping and starting. Along the way, you'll learn more about the use of strided lists—those oftenoverworked substitutes for object structures and collection classes.
MAKING A WORKING TRAFFIC SIGNAL
PROJECT 4
Making a Working Traffic S ignal
Creating a functioning traffic signal is a pretty easy affair, as you can learn from Listing P4.1. The basic control system consists of flipping textures on cue, such that the four faces of the stoplight each show the appropriate green, yellow, or red light.
A dding S ound
Listing P4.1: Traffic Lamp
S ummary
float LONGCYCLE = 30.0; // seconds to wait float SHORTCYCLE = 10.0; integer integer integer integer
MAINGREEN MAINYELLOW CROSSGREEN CROSSYELLOW
= = = =
1; 2; 3; 4;
string REDLIGHT = "traffic-red"; string YELLOWLIGHT = "traffic-yellow"; string GREENLIGHT = "traffic-green";
// textures
integer gStride = 5; integer gMainFace = 2; integer gCrossFace = 1; integer gCurrentState = MAINGREEN; list gParams = [ // CURRENT STATE, NEXT STATE, NEXT MAIN COLOR, NEXT CROSS COLOR, NEXT DURATION MAINGREEN, MAINYELLOW, YELLOWLIGHT, REDLIGHT, SHORTCYCLE, MAINYELLOW, CROSSGREEN, REDLIGHT, GREENLIGHT, LONGCYCLE, CROSSGREEN, CROSSYELLOW, REDLIGHT, YELLOWLIGHT, SHORTCYCLE, CROSSYELLOW, MAINGREEN, GREENLIGHT, REDLIGHT, LONGCYCLE ]; set_lamps() { integer i = 0; list states = llList2ListStrided(gParams, 0, -1, gStride); integer len = llGetListLength(states); for (i =0; i < len; i++){ if (gCurrentState == llList2Integer(states,i)) { list currentParams = llList2List(gParams, i*gStride, (i+1)*gStride - 1); llSetTexture(llList2String(currentParams,2), gMainFace); llSetTexture(llList2String(currentParams,2), gMainFace + 2); llSetTexture(llList2String(currentParams,3), gCrossFace); llSetTexture(llList2String(currentParams,3), gCrossFace + 2); gCurrentState = llList2Integer(currentParams, 1); llSetTimerEvent(llList2Integer(currentParams, 4)); return; } } }
4
default { state_entry() { set_lamps(); } timer() { set_lamps(); } }
The basic building block of the traffic-signal build is a simple cubic form, textured top and bottom with a simple texture colored to harmonize with the rest of the lamp surfaces. These are faces 0 and 6 of the six-sided prim, which aren't used by the traffic-control system. Opposing face pairs 1 and 3 and 2 and 4 (the faces that display the lights) are used, however. The prim's inventory contains a set of three textures for each of the three states—green, yellow, and red—possible for the combination of main and crossstreet signals. Figure P4.1 shows the three traffic-lamp textures.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3
PROJECT 4 PROJECT 5 APPENDIX D
Figure P4.1: Place these three textures in the inventory of the traffic lamp’s root prim.
The control system works rather like the real-life implementation. One set of faces shows green for 30 seconds, followed by a short (10-second) period of yellow, then 30 seconds of red. While one direction's light is green, its cross street's light is red until its green-yellow cycle is triggered. Figure P4.2 shows the traffic signal at work in its home at SYW HQ. As you can see in Listing P4.1, the face-changing is effected through a sequence of llSetTexture() function calls that simply substitute the appropriate lamp face onto the correct side of the cube.
5
PROJECT 4
Making a Working Traffic S ignal A dding S ound S ummary
Figure P4.2: The working traffic lamp
The system works via a timer (as in real life). When the specified time elapses, the current system state is inspected, a transition to the next state is extracted from a strided list (gParams), and a transition is made. The current state is held in a global variable, gCurrentState, and thus the first thing that the set_lamps() function does is get a list of all possible current states from the gParams strided list so it can look up the current state and discover the next state to which a transition should be made. It does this via a strided-list extraction, shown here: list states = llList2ListStrided(gParams, 0, -1, gStride);
llList2ListStrided(), described in Scripting Your World's Chapter 1, "Getting a Feel for the Linden Scripting Language," derives a list of the first members of the large gParams list. The arguments passed in this case instruct the function to start at the beginning of the list (0) and proceed to its end (the -1 argument), picking off every "stride'th" member. As you can see from the list, the stride or logical segmentation of the list is five items, as specified in gStride. You can think of each segment as being like a tuple, or row in a database. Thus, the resulting list of states is constructed from only the four states of interest, and looks like this: [MAINGREEN, MAINYELLOW, CROSSGREEN, CROSSYELLOW];
When you know the current state (gCurrentState)—for example, MAINYELLOW—you then want to fetch its row from gParams and begin setting textures and timers appropriately. You do that by retrieving the record in gParams associated with gCurrentState using llList2List(): list currentParams = llList2List(gParams, i*gStride, (i+1)*gStride - 1);
If gCurrentState were MAINYELLOW the list of currentParams would be [MAINYELLOW, CROSSGREEN, REDLIGHT, GREENLIGHT, LONGCYCLE];
The terms of this list are, respectively, the current state, the transition state, the texture to show on the main-street lamps, the texture to show on the cross-street lamps, and how long to show them. After you have the correct set of parameters, it's a simple matter to set the lamps and set up a timer event.
6
Each call to the llSetTexture() function delays the script for 0.2 seconds. Thus you may notice that if the environment is extremely laggy, the new face may take a fraction of a second to appear; however, because textures are cached the changes are generally instantaneous.
CH02 ADD. CH06 ADD. CH08 ADD.
ADDING SOUND
CH12 ADD. CHAPTER 16
What's an intersection without the sound of traffic? Not very authentic! Let's add that now. Audio is easy to find; there are several sources of traffic audio on the Web, including http://www.grsites.com/ archive/sounds and http://www.soundsnap.com. For this build we found some representative car sounds on the Web, tailored them with the excellent Audacity tool*, then added them to the inventory of the root prim. In the build at Scripting Your World Headquarters, we added sounds to the inventory of the traffic lamp (the entire inventory is shown in Figure P4.3).
PROJECT 1 PROJECT 2 PROJECT 3
PROJECT 4 PROJECT 5 APPENDIX D
Figure P4.3: The inventory contents of the traffic lamp’s root prim
The car_idle sound (shown in Listing P4.2) provides an ambient backdrop (which seems appropriate, since most of what we do in city traffic involves sitting at idle). It's playing in a loop that is terminated when other sounds start. When it is called, it plays a low thrumming tone at a muted volume.
* http://audacity.sourceforge.net/
7
Listing P4.2: An Idle Sound idle() { llLoopSound("car_idle", 0.5); }
Next you might add to the script the accelerate() function shown in Listing P4.3. It emulates a four-speed manual transmission by playing the sound four times, but we used an interesting technique to sonically portray an engine going up through its RPM range.
PROJECT 4
Making a Working Traffic S ignal A dding S ound S ummary
Listing P4.3: An Acceleration Sound accelerate() { llStopSound(); integer i; for (i = 1; i < 5; i++){ llPlaySound("car_accelerate", 1.0 / i); llSleep(2.5); } }
For each invocation of llPlaySound() in the loop, the volume is decreased by dividing the volume by the loop-control variable to emulate the Doppler effect of a sound moving away. Full volume is represented as 1.0. Thus the first time through, the sound is not reduced at all; the second time it's at 50 percent; and so on. The expression 1.0/i controls the volume, and as i increases, the volume decreases. Notice that the script is paused with each sonic "upshift" with llSleep() to allow the sound clip to play through before the next iteration of the for loop. Otherwise the loop would begin its next execution immediately after the return from llPlaySound(), overloading the currently playing clip and destroying the linear effect you intend to achieve. We timed the sound clip through a single playing, which is how the duration of 2.5 seconds was determined. Here again, aesthetics come into play—remember that scripting is a fine blending of science and art. Next a braking effect is added with the brake function shown in Listing P4.4. car_braking is a shorter-duration clip than car_accelerate, so the sleep is slightly shorter. The particular sound we used was grating, so we adjusted the sound volume accordingly.
Listing P4.4: A Braking Sound brake() { llStopSound(); llPlaySound("car_braking", 0.5); llSleep(2.0); }
Finally, a downshift sequence, shown in Listing P4.5, was encoded in the downshift() function, differing from the brake() function only in its implementation details.
Listing P4.5: A Downshifting Sound downshift() { llStopSound(); llPlaySound("car_downshift", 0.5); llSleep(4.0); }
8
Now it's time to put these all together by adding a few lines to the existing set_lamps() function. Since it's almost impossible to pinpoint the source of audio, it's not necessary to cover every state sonically. Think of a storyboard approach and cover only what's needed to tell the story. Here, the following storyboard was used: whenever the main street has a green light, have a car accelerating away. When the light turns yellow, have a car brake then come to an idle. For the cross street, occasionally have someone
downshift then come to idle, and finally simply fill in with an idle effect. The lines in bold in Listing P4.6 were added to call the various sound functions.
Listing P4.6: Traffic Lamp—set_lamps() Revised set_lamps() { integer i = 0; list states = llList2ListStrided(gParams, 0, -1, gStride); integer len = llGetListLength(states); for (i=0; i < len; i++){ if (gCurrentState == llList2Integer(states, i)){ // first 7 lines are the same as in Listing 6.7 list currentParams = llList2List(gParams, i*gStride, (i+1)*gStride - 1); llSetTexture(llList2String(currentParams,2), gMainFace); llSetTexture(llList2String(currentParams,2), gMainFace + 2); llSetTexture(llList2String(currentParams,3), gCrossFace); llSetTexture(llList2String(currentParams,3), gCrossFace + 2); gCurrentState = llList2Integer(currentParams, 1); llSetTimerEvent(llList2Integer(currentParams, 4)); // now the new lines if (gCurrentState == MAINGREEN) { accelerate(); } else if (gCurrentState == MAINYELLOW) { brake(); idle(); } else if (gCurrentState == CROSSYELLOW) { if (randBetween(0.0, 1.0) > 0.75) { downshift(); } idle(); } else idle(); return;
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3
PROJECT 4 PROJECT 5 APPENDIX D
} } }
The effect appears suitably realistic and is extremely simple to create. Stop by SYW HQ to judge the effect for yourself.
SUMMARY There are some ways to improve the build a bit. You could have more randomization of effect or you could layer the traffic more effectively by implementing a more sophisticated soundscape. You could also use state transitions to implement the logic of the four basic states. If you need any textures or sounds to complete your own version of the traffic-signal scene, you will find them at SYW HQ! This project showed how to construct a complete build with coordinated activity on multiple fronts. As your builds become more complex, you will find yourself relying on these concepts more and more frequently.
9
Articulation
PROJECT 5
Having read Chapter 4, "Making and Moving Objects," you've learned how to use quaternions and how to rotate around an arbitrary point. You've also learned that SL does not support hierarchically linked prims. You can make a foot and link it to a calf, and you can get the foot to rotate nicely relative to the calf. But you can't then take that foot-calf and link it to a thigh, then easily get it to rotate (together) relative to the thigh. Instead you have to manage articulation the hard way. There are two key conceptual components to managing articulation. The first is the management of the prim hierarchy: which joint attaches to which joint. This project shows how to build and manage the strided-list data structure that contains the prim hierarchy. The second component is the actual rotation of the body parts. This project shows how to rotate the parts correctly so that the body looks like it stays connected. This project also discusses how to refactor (or reorganize) your code so that it runs faster and uses less memory.
MANAGING THE PRIM HIERARCHY
PROJECT 5
Managing the P rim Hierarchy Rotating the B ody Parts R efactoring : S peeding up E xecution S ummary
There are many ways to manage the prim's hierarchical structure. The most general approach might automatically calculate the proximity of joints in the linkset. The easiest way, however, is manual hardcoding: if you're scripting a leg's articulation, name each body part and each joint. A part named Hip would state the position of two joints that correspond to each thigh relative to the center of the hip's prim. A Hand would name the relative position of the five fingers that would hang off it. Each nonroot prim could transmit this position information to the root, both when rezzed and if it hears a message to retransmit; Listing P5.1 shows a transmission from an example two-fingered hand.
Listing P5.1: Transmitting Body-Part Information (Hand) transmit_info() { // this transmit is just for a 2-fingered hand vector localPos = llGetLocalPos(); vector wristPos = localPos + < ... >; // hardcoded positions vector thumbPos = localPos + < ... >; // relative to center vector indexPos = localPos + < ... >; string children = ":Thumb:"+(string)thumbPos+ ":Index:"+(string)indexPos+":"; string msg = llGetObjectName() + "|"+ (string)localPos + "|"+ (string)llGetLocalRot() + "|"+ (string)wristPos + "|"+ children + "|"; llMessageLinked(LINK_ROOT,llGetLinkNumber(),msg,NULL_KEY); }
Each body part, including the root, will have its own transmit_info() function, along with the following default state: default { on_rez(integer n) { transmit_info(); } state_entry() { transmit_info(); } link_message(integer sender, integer num, string msg, key id) { if (msg == "RETRANSMIT") { transmit_info(); } } }
4
NOTE
CH02 ADD. CH06 ADD.
Whatever you do, the build is very fiddly. In general you’ll find it easier if all the prims in the object have zero rotation relative to the root. While you can compensate for an object’s nonzero rotation, using zero rotation gives you one less thing to worry about. It may mean that you start with somewhat odd-looking objects, such as the insect shown here; don’t worry, though: you can move the legs to more-reasonable positions later.
CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4
PROJECT 5 APPENDIX D
The complete script for managing articulation is long and complex, mostly because of having to manage which joint attaches to which joint. The management data structure is a strided list. Listing P5.2 shows the top-level code for managing this data structure. gBodyStructure is a strided list whose stride is length 7, and stores the part's name, link number, position, and rotation, the link number of its parent (the part it hangs from), the joint that attaches it to its parent, and the list of its children (all the parts that hang from it).
Listing P5.2: Managing the Data Structure of Joints integer integer integer integer integer integer integer integer integer
BODY_STRUCT_STRIDE = 7; NAMEIDX = 0; LINKIDX = 1; PRIMCENTERIDX = 2; ROTNIDX = 3; PARENTIDX = 4; PARENTJOINT = 5; CHILDRENIDX = 6; CHILDREN_STRUCT_STRIDE = 2;
// // // // // // // // //
number of slots in record the name is record-slot #zero the linknum is slot #1 the center of the prim is #2 the rotation of the prim is #3 the parent prim's linknum is #4 the parent prim's joint loc is #5 the list of children are slot#6 each child contains # descriptors
integer gNumBodyParts = 0; list gBodyStructure = [];
5
PROJECT 5
Managing the P rim Hierarchy Rotating the B ody Parts R efactoring : S peeding up E xecution S ummary
// returns the index of the record; if no record exists for the // input name, it creates an empty record integer createOrGetRecord(string name) { integer index = getIndexByName( name ); if (index < 0) { // make a blank record list newRecord = ["", // name (-1), // linkNum ZERO_VECTOR, // pos ZERO_ROTATION, // rot (-1), // parent ZERO_VECTOR, // parent joint "::" // children ]; gNumBodyParts++; integer start = llGetListLength(gBodyStructure); gBodyStructure = llListInsertList(gBodyStructure, newRecord, start); return gNumBodyParts - 1; } else { return index; } } // returns the actual record list getRecord( integer index ) { integer start = index * BODY_STRUCT_STRIDE; integer end = start + BODY_STRUCT_STRIDE - 1; return llList2List(gBodyStructure, start, end); } // lookup the name in gBodyStructure, and adjust for stride integer getIndexByName( string name ) { integer idx = llListFindList(gBodyStructure, [name]); integer recordIndex = -1; if (idx >= 0) { recordIndex = idx / BODY_STRUCT_STRIDE; } else { // might be error, might be insert llOwnerSay("Part "+name+" not found."); } return recordIndex; }
Three functions access the top-level data in gBodyStructure. getIndexByName() looks up the body part; it takes the name of the body part and returns the index of the record, recordIndex. createOrGetRecord() creates an empty record, newRecord, when there is no record that matches the name. getRecord() returns the data in the record. The functions in Listing P5.3 handle the getting and setting of specific values in the record. getName() and setName(), for example, retrieve or replace the name of the body part with the given index.
6
Listing P5.3: Getting and Setting Slots within a Record string getName( integer index ) { integer start = index * BODY_STRUCT_STRIDE + NAMEIDX; return llList2String( gBodyStructure, start ); } setName( integer index, string value ) { integer start = index * BODY_STRUCT_STRIDE + NAMEIDX; gBodyStructure = llListReplaceList(gBodyStructure, [value], start, start); } // The following functions mimic getName() and setName() with // different IDX constants // integer getLinkIndex( integer index ) // setLinkIndex( integer index, integer value ) // vector getPosition( integer index ) // setPosition( integer index, vector value ) // vector getPrimCenter( integer index ) // setPrimCenter( integer index, vector value ) // rotation getOrientation( integer index ) // setOrientation( integer index, rotation value ) // integer getParent( integer index ) // setParent( integer index, integer value ) // vector getJointToParent( integer index ) // setJointToParent( integer index, vector value )
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4
PROJECT 5 APPENDIX D
// turns the string ":name:jointlocation:name:jointlocation:" // into a list ["Name", "Vector", "Name", "Vector"] list getChildren( integer index ) { integer start = index * BODY_STRUCT_STRIDE + CHILDRENIDX; string childrenDescr = llList2String( gBodyStructure, start ); list childrenByName = llParseString2List(childrenDescr, [":"], []); return childrenByName; } setChildren( integer index, string value ) { integer start = index * BODY_STRUCT_STRIDE + CHILDRENIDX; gBodyStructure = llListReplaceList(gBodyStructure, [value], start, start); } integer getNumChildren( list children ) { integer numChildren = llGetListLength(children); return numChildren / CHILDREN_STRUCT_STRIDE; } // for a given child, first find it's name, then look up // its complete record integer getChildIndex( list children, integer childIdx ) { integer idx1 = childIdx * CHILDREN_STRUCT_STRIDE; string name = llList2String( children, idx1 ); return getIndexByName( name ); }
7
vector getChildJoint( list children, integer childIdx ) { integer start = childIdx * CHILDREN_STRUCT_STRIDE; integer end = start + CHILDREN_STRUCT_STRIDE - 1; // get the record for this child list childInfo = llList2List( children, start, end ); // extract the joint location vector v = (vector)(llList2String(childInfo,1)); return v; } setJointChild( integer index, list oldChildren, integer childIdx, vector position ) { integer start = index * BODY_STRUCT_STRIDE; integer end = start + BODY_STRUCT_STRIDE - 1;
PROJECT 5
Managing the P rim Hierarchy Rotating the B ody Parts R efactoring : S peeding up E xecution
// replace the one joint value for the specific child list fixedChildren = llListReplaceList( oldChildren, [(string)position], childIdx * CHILDREN_STRUCT_STRIDE + 1, childIdx * CHILDREN_STRUCT_STRIDE + 1);
S ummary
string children = ":"+llDumpList2String(fixedChildren,":")+":"; setChildren( index, children ); }
Except for the list of children, all the other record elements have equivalent get*() and set*() accessor functions. The children are more complex because LSL lists cannot contain other lists, so the children are stored as a string. Inside gBodyStructure, the children string is formatted as follows: ":name:jointlocation:name:jointlocation:"
The function getChildren() parses that string into a list, and returns ["name","jointlocation","name","jointlocation"]
The function getChildJoint() retrieves the desired joint location out of this list, and setChildJoint() replaces it. Inside the default state, shown in Listing P5.4, when the link_message() event handler receives some data from another prim, it extracts the information from the received message and then calls a series of set*() functions to set the data correctly in the data structure, gBodyStructure. Note that the script calls createOrGetRecord() before setting the values: integer index = createOrGetRecord(name); setName( index, name );
Listing P5.4: The default state—Filling in the Data Structure
8
default { state_entry() { llMessageLinked(LINK_SET, 0, "RETRANSMIT", NULL_KEY); } on_rez(integer _param) { llMessageLinked(LINK_SET, 0, "RETRANSMIT", NULL_KEY); } link_message(integer sender, integer num, string msg, key id) { if (msg == "RETRANSMIT") { transmit_info(); } else { //llOwnerSay(llGetObjectName()+" heard:"+(string)sender // +" NUM="+(string)num+" MSG="+msg); list components = llParseString2List(msg, ["|"], []);
string name = llList2String(components, 0); vector pos = (vector)llList2String(components, 1); rotation rot = (rotation)llList2String(components, 2); vector jointParent = (vector)llList2String(components, 3); string jointChildren = (string)llList2String(components, 4); integer index = createOrGetRecord(name); setName(index, name); setLinkIndex(index, num); setPosition(index, pos); setOrientation(index, rot); setJointToParent(index, jointParent); setChildren(index, jointChildren);
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4
} } }
During debugging, it is extremely useful to have a function printRecord(), shown in Listing P5.5, that prints the requested record in a nicely formatted fashion. As you develop the script, you will find yourself calling this function frequently to ensure that the get*() and set*() functions are working correctly.
Listing P5.5: Printing Records
PROJECT 5 APPENDIX D
printRecord(integer i) { llOwnerSay((string)getLinkIndex(i)+". "+ getName(i)+" at position:"+ (string)getPosition(i)+" rotation:"+ (string)(llRot2Euler(getOrientation(i))*RAD_TO_DEG)); llOwnerSay(" Parent:"+ (string)getParent(i)+" at "+ (string)getJointToParent(i)); list children = getChildren(i); integer numChildren = getNumChildren(children); integer j; for (j=0; j, 0.0, 10.00, 0.01, // 0.10 1,
< 0.50, 0.2, 0.0 >, < 0.25, 0.1, 0.0 >, color, color, 1.00, 0.00, // 1.0 "",
//Movement PSYS_SRC_BURST_SPEED_MIN, PSYS_SRC_BURST_SPEED_MAX, PSYS_SRC_ACCEL, PSYS_SRC_TARGET_KEY, ]); }
CHAPTER 16
PROJECT 5
* * * * * * * * *
//Flow PSYS_SRC_MAX_AGE, PSYS_PART_MAX_AGE, PSYS_SRC_BURST_RATE, PSYS_SRC_BURST_PART_COUNT, //Appearance PSYS_PART_START_SCALE, PSYS_PART_END_SCALE, PSYS_PART_START_COLOR, PSYS_PART_END_COLOR, PSYS_PART_START_ALPHA, PSYS_PART_END_ALPHA, PSYS_SRC_TEXTURE,
CH12 ADD.
PROJECT 4
llParticleSystem([ PSYS_PART_FLAGS, ( ( EmissiveMask ( BounceMask ( InterpColorMask ( InterpScaleMask ( WindMask ( FollowSrcMask ( FollowVelocityMask ( TargetLinearMask ( TargetPosMask //Placement PSYS_SRC_PATTERN, PSYS_SRC_BURST_RADIUS, PSYS_SRC_ANGLE_BEGIN, PSYS_SRC_ANGLE_END, PSYS_SRC_OMEGA,
CH08 ADD.
1.00, 1.00, < 0.00, 0.00, 0.00 >, llGetKey() // come back to itself
11
ch02 add.
Wacky S eating : M erry-G o Round A uto deploying
Wings
A utohiding : N ow You S ee I t... F ancy F ootwork S ummary
makeFootprint(vector color) { integer attachPoint = llGetAttached(); if (attachPoint == ATTACH_LFOOT || attachPoint == ATTACH_RFOOT) { integer status = llGetAgentInfo(llGetOwner()); if (status & AGENT_WALKING) { particleFootprint(color); } else { llParticleSystem([]); } } } default { land_collision(vector pos) { makeFootprint(RED); } collision(integer num) { makeFootprint(GREEN); } }
Figure 2a.4: Particles make wonderful footprints.
SUMMARY
12
This chapter extended some of the ideas presented in Chapter 2 of Scripting Your World. Each script uses the Chapter 2 ideas as a basis, and augments them with techniques from other chapters: the merry-goround uses spin, the wings adjust the size of the all the prims, and the footprints use particle scripts and sound. As you gain experience with the techniques throughout the book, you will find many more ways to enhance the core ideas presented in the book.
Chapter 6 ADDENDUM
Chapter 6 Addendum: A Water Mill
Chapter 6, "Land Design and Management," presented the intricacies of making a realistic waterfall. It was placed in the context of a water mill, with a working wheel and gears. In this extra material, you'll learn how to make the complete build. You'll use some library functions from other chapters and builds, but combined in a novel way that illustrates the principles of aesthetic practice. Figure 6a.1 shows the completed mill from a long view.
Figure 6a.1: A water mill with working wheel, gears, and turbulent waters
As shown, the build consists of a waterfall powering a water wheel connected to a grist mill. As the water wheel revolves it creates a splashing wave in its wake. It blends with the overall theme of the surrounding area and creates mood and atmosphere. Even so, if a visitor does want to linger near it and reflect, it's sufficiently captivating, owing to its combination of visual and audio effects that provide a meaningful experience. Let's deconstruct the build to understand all its components.
ch06 add.
The Wheels and G ears A ugmenting the Water A nimation S ummary
THE WHEELS AND GEARS First we use a simple rotation script just like the one in Chapter 4's section called "Rotation with Target Omega." Listing 6a.1 causes the wheel to whirl at a leisurely pace of PI/4, which has been carefully matched to the flow of the waterfall tumbling from the cliffs behind.
Listing 6a.1: Millstone and Water Wheel default { state_entry() { llTargetOmega(llVecNorm(), PI/4, 1.0); } }
Figure 6a.2 shows the part of the build that connects the water wheel, linking shaft, and transmission gear. The cogs of the transmission gear and the mated grinding gear both have eight teeth, which mesh evenly with a turning rate of PI/4. Recall that the second parameter of llTargetOmega() is the spin rate; if the vector is normalized, then the spin rate is measured in radians per second.
Figure 6a.2: Working wheel, shaft, and gears
4
CH02 ADD.
AUGMENTING THE WATER ANIMATION New scripters may not realize that water (pools, rivers, rainstorms) is not "real" in the sense of being interactive or reactive to its surrounding environment. Chapter 6 presented both mechanisms for making realistic water: terraforming and texture animation. At the end of the section "A Waterfall," the chapter mentions that to complete the effect of the waterfall, you need to add particle effects and sound. In this section we present these two effects.
ch06 add. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2
Adding Sound Using the animation described in Listing 6.2 of Scripting Your World would be visually interesting enough, and, given the fact that the individual cylinders are flexible (flexiprims), could be visually convincing. Real waterfalls hardly ever drop at constant rates, however, so Listing 6a.2 attempts to add further realism by varying the animation rate for each of the three cylinders over time.
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Listing 6a.2: Improved Water Animation—Extending Listing 6.2 with Sound float randBetween(float min, float max){ return llFrand(max - min) + min; } default { state_entry() { float rand = llFrand(10.0); llSetTimerEvent(rand); } timer() { state fallingWater; } } state fallingWater { state_entry() { float rate = randBetween(0.05, 0.35); if (rate > 0.30) { llTriggerSound("water-flowing3", 0.8); } llSetTextureAnim(ANIM_ON | SMOOTH | LOOP , ALL_SIDES, 1, 1, 1.0, 0.0, rate); llSleep(1.5); state default; } }
The default state sets a random timer event using the llFrand() function to return a random floating-point number between 0.0 and 10.0. When the timer elapses, control transitions immediately to the fallingWater state, and the state_entry() event handler chooses a random number to approximate a reasonable flow rate in harmony with the rotational rate of the water wheel. When the rate is toward the higher end (rate > 0.30) of the animation rate (and hence the waterflow rate), it triggers a gushing water sound, then the animation with its new variable rate is initiated. (Triggering sound is covered in Chapter 11, "Multimedia.")
5
Staging the audio triggering prior to the animation gives the audio enough time to buffer and begin to play so that it's well synchronized to the animation. We added an llSleep() for 1.5 seconds to allow the audio effect to complete before transitioning back to the default state. This value was determined experimentally to be a reasonable interval, and the timing is not crucial here. ch06 add.
The Wheels and G ears A ugmenting the Water A nimation S ummary
Adding Particles Many effects using water as a land feature stretch credibility in a way that makes your subconscious mind scream, "fake!" To illustrate, Figure 6a.3 shows a paddle of the water wheel as it emerges from the water of the catch pool without any water-turbulence effects. The next part of the build is designed to make a splash, literally.
Figure 6a.3: The catch pool sans water turbulence from the wheel.
Notice how static and lifeless the pool looks. That's because objects passing through water don't behave this way. Their intrusion into the water surface creates turbulence, so to add credibility you need to add in this dramatic aesthetic feature as well. The result will more resemble Figure 6.1a (with blue splashing water surrounding the area where the wheel's paddles exit the pool) and give visitors to your build that "Aha!" moment.
6
Listing 6a.3 shows the details for the turbulent waters shown in Figure 6.1a. This effect is a simple particle-system invocation; however, the parameters are carefully considered and set based on the contextual aesthetics. Although particle systems—vital elements for creating spontaneous effects with the appearance of natural randomness—are fully elaborated in Chapter 9, "Special Effects," and Appendix B, "The Particle System," this example explains how to add realism, specifically to a water effect. This particle effect is embodied in a small invisible (colored transparent) prim about one-third of a meter above the surface of the pool itself. If you visit the build and enable Highlight Transparent from the Edit menu, you'll see it; otherwise it's invisible. The script follows the recommended format from Appendix B, showing default values as comments at the end of each line.
Listing 6a.3: Turbulent Waters splash() { integer InterpColorMask = TRUE; integer InterpScaleMask = TRUE; integer EmissiveMask = TRUE; integer FollowSrcMask = FALSE; integer FollowVelocityMask = TRUE; integer WindMask = TRUE; integer BounceMask = TRUE; integer TargetPosMask = FALSE; integer TargetLinearMask = FALSE; llParticleSystem([ PSYS_PART_FLAGS, ( ( EmissiveMask * PSYS_PART_EMISSIVE_MASK ) | ( BounceMask * PSYS_PART_BOUNCE_MASK ) | ( InterpColorMask * PSYS_PART_INTERP_COLOR_MASK ) | ( InterpScaleMask * PSYS_PART_INTERP_SCALE_MASK ) | ( WindMask * PSYS_PART_WIND_MASK ) | ( FollowSrcMask * PSYS_PART_FOLLOW_SRC_MASK ) | ( FollowVelocityMask * PSYS_PART_FOLLOW_VELOCITY_MASK ) | ( TargetLinearMask * PSYS_PART_TARGET_LINEAR_MASK ) | ( TargetPosMask * PSYS_PART_TARGET_POS_MASK ) ), //Appearance PSYS_PART_START_SCALE, < 0.25, 0.25, 0.0>, // PSYS_PART_END_SCALE, < 5.00, 5.00, 0.0>, // PSYS_PART_START_COLOR, < 0.9, 0.9, 1.0 >, // PSYS_PART_END_COLOR, < 1.0, 1.0, 1.0 >, // PSYS_PART_START_ALPHA, 0.5, // 1.0 PSYS_PART_END_ALPHA, 0.0, // 0.0 PSYS_SRC_TEXTURE, "Water Particle 3", // "" //Flow PSYS_SRC_MAX_AGE, 0.0, // 0.0 PSYS_PART_MAX_AGE, 3.00, // 10.0 PSYS_SRC_BURST_RATE, 0.10, // 0.10 PSYS_SRC_BURST_PART_COUNT, 16, // 1 //Placement PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE_CONE, // DROP PSYS_SRC_BURST_RADIUS, 1.00, // 0.0 PSYS_SRC_ANGLE_BEGIN, 0.00, // 0.0 PSYS_SRC_ANGLE_END, 0.65, // 0.0 PSYS_SRC_OMEGA, < 0.00, 0.00, 0.00 >, //Movement PSYS_SRC_BURST_SPEED_MIN, 0.00, // 1.0 PSYS_SRC_BURST_SPEED_MAX, 0.05, // 10.0 PSYS_SRC_ACCEL, < 0.0, 0.0, -1.0 >, // PSYS_SRC_TARGET_KEY, llGetKey() // ignored because TARGET_POS is false ]); } default { state_entry() { splash(); } }
Notice that the parameter llParticleSystem() lists alternating key-value pairs (a strided list where the stride is 2). The first value is PSYS_PART_FLAGS, a bit pattern used to indicate how the particle system should generally behave. Each TRUE bit in the pattern signals the particle system to enable exactly one feature. The following list describes the parameters that are TRUE (that is, on); other parameters are described in Appendix B.
CH02 ADD.
ch06 add. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
7
ch06 add.
The Wheels and G ears A ugmenting the Water A nimation
• The InterpColorMask parameter changes the color of each particle from PSYS_PART_ START_COLOR to PSYS_PART_END_COLOR during the particle's lifespan. If a texture is supplied, as "Water Particle 3" is in this example, the texture's color values are used and the start and end colors are turned into a tint for the texture; otherwise they are applied to a white texture. You will find the texture "Water Particle 3" in your Library. The tinting scheme and the particle texture were carefully chosen to resemble the underlying animated water texture in the catch pool. • The parameter InterpSizeMask does a similar interpolation between a vector of starting- and ending-size parameters, PSYS_PART_START_SCALE and PSYS_PART_END_SCALE. (Note that particles are essentially flat, so the z component is effectively ignored.) • The next parameter you encounter is EmissiveMask. In this case, it is appropriate to have the particles appear both translucent and capturing (and reflecting) the ambient light as they appear to have been drawn up out of the catch pool by each paddle wheel.
S ummary
• The WindMask parameter tells the particle engine to react to the ambient wind conditions generated by the Second Life weather engine. • The last flag, BounceMask, adds the effect of bouncing—falling particles will bounce when they reach the same z value as the emitter. This effect creates the appearance of a bit of a splash. The pattern PSYS_SRC_PATTERN_ANGLE_CONE is one of five particle patterns available in Second Life and most closely approximates the behavior you're looking for in this effect: water droplets energetically rising up from the surface. Starting the effect and having it play repeatedly is accomplished by passing the parameter list to the call to llParticleSystem(). Working with the particle system is as much about understanding the art of what you're trying to accomplish as it is about understanding the parameter values and their effects on the particle system.
SUMMARY Developing a complete build is an important part of scripting in SL. No item should appear on its own, without appropriate context and consideration for where it is placed, how it will be used, and how it will improve the overall user experience. All of the scripts in this addendum add to the user experience, and you should consider how to enhance and complete other builds in your personal suite.
8
More Inventory
Chapter 8 ADDENDUM
This online chapter extends the examples presented in Scripting Your World's Chapter 8, "Inventory," which focused on inventory management—how to give and receive inventory, as well as how to manage inventory permissions. This addendum extends the theme of giving and receiving inventory by showing an example of a simple landmark giver plus augmenting the mailbox of Listing 8.4 in the printed book with the ability to deliver the mail to the mailbox owner.
ch08 add.
S imple A L andmark G iver A Working M ailbox S ummary
A SIMPLE LANDMARK GIVER Listing 8a.1 shows a simple landmark giver. It's embodied in a guard hut that, when residents approach within 7.5 meters, will display the text, "Touch for a landmark," as shown in Figure 8a.1. In contrast with Listing 8.2 of Scripting Your World, which showed a simple notecard giver, this listing uses sensing rather than volume-detecting collisions, and displays a message for one minute when an avatar is sensed; this script does not track the avatars to whom it offered the landmarks.
Figure 8a.1: This guard hut offers a landmark to passersby.
Listing 8a.1: Landmark Giver
4
float SENSOR_INTERVAL = 10.0; float SENSOR_RADIUS = 20.0; float DECAY_INTERVAL = 60.0; // make the text vanish in 1 minute default { state_entry() { llSensorRepeat("", NULL_KEY, AGENT, SENSOR_RADIUS, PI/4, SENSOR_INTERVAL); } touch_start(integer num) { llGiveInventory(llDetectedKey(0), llGetInventoryName(INVENTORY_LANDMARK,0)); llSetText("", , 0.0); } sensor(integer total_number) { llSetText("Touch for a landmark", , 1.0); llSetTimerEvent(DECAY_INTERVAL); } timer() { llSetText("", , 0.0); llSetTimerEvent(0.0); // remove the timer event } }
Most often you'll simply want to display the text at all times, but in this particular sim, having everpresent red text in an elegant Tudor setting would be less aesthetically pleasing. Hence, the "Touch for a landmark" text surfaces only when someone approaches, and then only for 60 seconds through the llSetTimerEvent(DECAY_INTERVAL) function call.
CH02 ADD. CH06 ADD.
Notice that when the timer expires, the timer() event handler resets the text above the hut to blank, and cancels future timer() events until and unless the sensor is tripped again. Also notice that the functional sensor radius, SENSOR_RADIUS, is set to 20.0 meters for an arc of PI/4. When set to an arc value of PI, the sensor defines a notional sphere radiating from the center of the prim in which the script is contained. Values less than PI define a cone around the prim's x-axis; thus, as Figure 8a.2 shows, this sensor radiates out toward the plaza (we carefully placed the x-axis to that orientation). It's important to consider that sensors should be placed to "catch" their targets—in this case, a SENSOR_RADIUS of 20 meters captures the entire plaza and steps leading to the minimall. If this sensor used a value of PI, then the sim would waste computational effort sensing behind the wall.
ch08 add. CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Figure 8a.2: The sensor uses an arc of PI/4, meaning that a cone radiates out 45 degrees from the sensing object’s x-axis.
Note too that a given script can have only one active sensor. This means if you want multiple sensors, you need multiple scripts. It also means you cannot ignore the effects of sensors on performance. Remember that Listing 8.2 of SYW shows a lower-impact alternative for llSensorRepeat(): llVolumeDetect(), and Chapter 5, "Sensing the World," describes the tradeoffs to consider.
5
A WORKING MAILBOX
ch08 add.
S imple A L andmark G iver A Working M ailbox S ummary
Listing 8.4 in Chapter 8 presented a basic mailbox that accepts inventory drops. It would be useful for you (as postmaster of your local post office) to be able to retrieve and read your fan mail (or hate mail; your mileage may vary). You may also want to limit the types of items that can be dropped. Listing 8a.2 augments the mailbox of Listing 8.4 with the ability to retrieve your mail and checks to ensure that only notecards are dropped. There is no way to check whether two items are identical except by comparing the items' keys (and, of course, if you can get the key it's a full-permission object, and probably a freebie).
Listing 8a.2: Delivering Mail to a Mailbox key list key
gOwner; previousInventory; MAILBOXSOUND="2ffb5660-959a-8329-241b-acf2aec8149f";
deliver_mail() { integer i; list mailList; string mailItem; integer invItems = llGetInventoryNumber(INVENTORY_NOTECARD); string mailFolder = "Mail-Folder-"+llGetDate(); for (i = 0; i < invItems; ++i) { mailItem = llGetInventoryName(INVENTORY_NOTECARD, i); mailList += mailItem; } if (llGetListLength(mailList) < 1) { llOwnerSay("No Mail"); } else { llOwnerSay("Your Mail is in Folder "+mailFolder); llGiveInventoryList(gOwner, mailFolder, mailList); } for ( i = 0; i < invItems; ++i){ mailItem = llList2String(mailList,i); llRemoveInventory(mailItem); } } check_inventory() { list removeItems = []; integer i; integer num = llGetInventoryNumber(INVENTORY_ALL); for (i = 0; i < num; ++i) { string name = llGetInventoryName(INVENTORY_ALL,i); if ((llGetInventoryType(name) == INVENTORY_NOTECARD) || (name == llGetScriptName())) { // do nothing } else { removeItems += name; } }
6
integer numNonNotecards = llGetListLength(removeItems); for (i=0; i easy_install feedparser
And that's all there is to it. Now, you are set to explore. To write and install a Python program, you can often sketch out what you plan to do using the Python interactive editor/interpreter and then simply paste the pieces into a CGI script. A CGI script is a program invoked by a web server as a result of someone viewing a web page. Such scripts are often written in a lightweight language like PHP, Python, or Perl and are used to generate the page that is sent back to the browser. Often you have to change the name of the script to end with the file extension .cgi for the site to recognize it as an executable piece of code. Here's an example session where the feedparser library is used in a Python command-line interpreter session to read an RSS feed.
CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
First invoke the Python interpreter in a cmd.exe window in Windows, or a terminal session in Mac OS X or Linux: > python Python 2.4.4 (#1, Oct 18 2006, 10:34:39) [GCC 4.0.1 (Apple Computer, Inc. build 5341)] on darwin Type "help", "copyright", "credits" or "license" for more information.
Try importing the feedparser library you installed. Examine its capabilities. Whenever the interpreter is waiting for inputs from you, it prompts you with triple angle brackets (>>>). >>> import feedparser
As long as Python reported no errors, the import was successful. What can you do with feedparser? It comes with a complete documentation suite in HTML, but be adventurous. Python is very big on key-value pairs, which are members of a class called a dictionary. Dictionaries are conceptually similar to LSL's strided lists, but have a more explicit separation between the types of elements. Any object's functions and attributes are kept inside a dictionary that's part of the object. You can always use the Python built-in dir() function to find out what's inside something's dictionary. Type in the following instruction to the interpreter and observe the output: >>> dir(feedparser) [...'base64', 'binascii', 'cgi', 'chardet', 'copy', 'gzip', 'parse', ...]
The dir() output shows a huge number of attributes and functions in a list, delimited by square brackets. Looking over the list, you see a likely suspect: the parse function. Try it, using the object.function notation, supplying a site you know generates RSS in XML. Stow the results of the feed in a variable (here named data) so you can play with it later: >>> data = feedparser.parse("http://rssnewsapps.ziffdavis.com/audioblogs/ crankygeeks/cg.ipod.xml")
The variable data contains the ouput of the feedparser's parse operation. Again, you can use dir() to poke at the variable's contents:
5
>>> dir(data) [... 'clear', 'copy', 'fromkeys', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keymap', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'] CH12 ADD.
he S erver T Side (P ython): S implifying the XML he C lient T S ide (LSL): R evised N ews Ticker S ummary
It looks like there's a dictionary with keys and values. So far, nothing unexpected. Python is also very big on not doing unexpected things. Try printing out the keys and see what you get: >>> print data.keys() ['feed', 'status', 'updated', 'version', 'encoding', 'bozo', 'headers', 'etag', 'href', 'namespaces', 'entries']
Great; there's a key called feed, and if you look at its contents using dir(data.feed), you'll see it has keys too. By examining the entries attribute you can see the contents of entries, including the channel and each of the stories. >>> print data.entries
This produces significant output; too much to print here (but do try this at home). Inspecting the output shows each entry is a rich trove of information, organized again as a dictionary. How many entries does it have? Using Python's built-in len()function, which gives you the length of a collection of things, you discover that it has five stories: >>> print len(data.entries) 5
Loop through the entries with a for loop, just as you would with LSL. You can use the notation instance['key'], or instance.key and the instance will yield the key's value as a result. Here, item is the instance: >>> for item in data.entries: ... print item.title ... CrankyGeeks 111: Stupid Stories CrankyGeeks 110: The Joke's on You! CrankyGeeks 109: Sarah's All A-Twitter CrankyGeeks 108: The Mothership Returns CrankyGeeks 107: Molly Wood and Jason Cross
This looks promising; so promising, in fact, that you're ready to toss it onto a website and give it a spin. Not bad for 10 minutes' work, including the Web search and the install step. Now that you have the commands you need, you can copy and paste them into a script. Clean it up a little, and you'll have a webserver script (Listing 12a.1) that generates a list of RSS titles, with none of the data you aren't interested in. We won't hook this into the LSL version as we did with the LSL code you produced in Listing 12.6 in the printed book. This version is a quick way to test whether you can put together a working Python script.
Listing 12a.1: RSS Feed for an HTML Page (Python) #!/usr/bin/env python # encoding: utf-8 import feedparser # Substitute your favorite RSS generator here. # Listing 12.6, for example, used cnn.com's RSS provider # If you're looking for tech news, slashdot is one of many fine choices data = feedparser.parse('http://slashdot.org/slashdot.rdf')
6
print("Content-type: text/html")# emits the line plus a newline # triple quote is used in Python for long multi-line strings # note that the """ below emits a newline before the tag # when the HTML interpreter in the browser sees the stream, # it will see Content-type: text/html\n\n page = """ RSS Feed Test """ for item in data.entries: page += "
"+ item.title + "
"
CH02 ADD. CH06 ADD. CH08 ADD.
CH12 ADD. CHAPTER 16 PROJECT 1
page += """ """ print page
PROJECT 2 PROJECT 3 PROJECT 4
The first line in the script,
PROJECT 5
#!/usr/bin/env python
APPENDIX D
tells the web server engine this script is written in Python. The script then parses the RSS-feeder output using feedparser.parse(); the results are returned in the variable data. It then prints a short header to indicate that the output will be a web page: print("Content-type: text/html")
When a CGI script prints something, using print in Python or echo in PHP, the string is printed to the HTTP stream for return to the web page (or to the LSL http_response() function).
NOTE In Python, spaces mean something to the interpreter. Whereas some languages (those like LSL, derived from the C family of computer languages) use punctuation marks such as semicolons and curly brackets to delimit statements made to the interpreter, others, including Python, use an indentation scheme. The creative genius behind Python decided that neat people would want to indent their code to make it readable anyway, so why not get rid of all the extra punctuation marks. Once you embrace tidiness, you begin to appreciate the genius.
The script next uses a for loop to go through each item in data to print the title of each item (item.title). The script is pushed to a convenient website, where it will field queries from the LSL script. We placed the script in Listing 12a.1 at http://ideastax.com/SYW/cgi/HTMLgenerator.cgi; it yields the web page shown in Figure 12a.1. Remember that the difference between this simple version and Listing 12.6 in SYW is that in the book code you are taking the raw output from the RSS feed and parsing it in-world. It would be possible to point the llHttpRequest at this little chunk of Python code and use its output instead, but you'd have to adjust the LSL parsing to find the delimiters that the web page uses. In Listing 12a.1, the item is delimited by a pair of
and
tags. So you'd substitute those for the and tags as you did in Listing 12.6. Additionally, the parse becomes less cumbersome owing to the lack of the interior tags.
7
CH12 ADD.
The S erver Side (P ython): S implifying the XML The C lient S ide (LSL): R evised N ews Ticker S ummary
Figure 12a.1: RSS titles formatted as a web page
Now, with this script installed on the Web, you can use it to mediate an RSS stream. An extremely stripped-down version of an LSL script that uses it is shown in Listing 12a.2. Rez a cube and drop this script into its contents to test your handiwork.
Listing 12a.2: Simplified RSS News Reader string string string integer
RSS_LINK = "http://www.ideastax.com/SYW/cgi/HTMLgenerator.cgi"; XML_TITLE_TAG = "
"; XML_TITLE_TAG_END = "
"; HTTP_OK = 200;
string
gStories = "And Now the News";
list getTitles(string bodyPart) { list rList = []; string title; do { // extract titles bodyPart = chomp(bodyPart, XML_TITLE_TAG); title = extract(bodyPart, XML_TITLE_TAG_END); rList += title+"\n"; bodyPart = chomp(bodyPart, XML_TITLE_TAG); } while (llStringLength(bodyPart) > 0); return rList; } string chomp(string input, string tag) { integer index = llSubStringIndex(input, tag); if (index == -1) return ""; return llDeleteSubString(input, 0, index +llStringLength(tag) - 1); } string extract(string input, string tag) { integer index = llSubStringIndex(input, tag); if (index == -1) return ""; return llGetSubString(input, 0, index - 1); }
8
default { touch_start(integer tnum) { llHTTPRequest(RSS_LINK, [], ""); } http_response(key request_id, integer status, list metadata, string body) { if (status == HTTP_OK) { gStories = "RSS News... " + (string) getTitles(body); } else { llOwnerSay("Unable to fetch "+RSS_LINK); } llOwnerSay(gStories); } }
CH02 ADD. CH06 ADD. CH08 ADD.
CH12 ADD. CHAPTER 16 PROJECT 1
FINDING A WEB SERVICE PROVIDER
PROJECT 2
You need access to a host that lets you deploy CGI scripts in the languages you care about. You’ll probably want a database included. Shared hosting is inexpensive and easy to set up. It’s beyond the scope of this book to recommend a Web-service provider, but a simple search of the topic will reveal many.
PROJECT 4
PROJECT 3
PROJECT 5 APPENDIX D
The script in Listing 12a.1 is useful if you want to produce a normal web page (which you could read with llLoadURL()) but since you want to use it to feed your news ticker, a little more tailoring will improve the output from the llHTTPRequest() even further. Listing 12a.3 shows a version focused on the news ticker's needs. Notice that the first thing printed back to the HTTP stream is print "Content-type: text/plain\n\n"
This command tells the invoking page (or in this case the Second Life object calling llHTTPRequest()) that the result is not fancied-up HTML content, but just plain text. An arcane but important thing to note is that the line must be exactly as shown in the print statement. Listing 12a.3 includes improvements to make the work done by the next version of the news ticker even easier. The first line printed will have the name of the news channel (in this case http://CNN.com) prefaced by the word CHANNEL, and the headlines each prefaced by TITLE and ending with a newline character (\n).
Listing 12a.3: RSS Feed Producing XML (Python) #!/usr/bin/env python # encoding: utf-8 import feedparser # from http://feedparser.org print "Content-type: text/plain\n\n" feed = feedparser.parse('http://rss.cnn.com/rss/cnn_topstories.rss') page = "CHANNEL:"+ (str)(feed.channel.title)+"\n" for item in feed.entries: page += "TITLE:" + item.title + "\n" print page
Figure 12a.2 shows an example result (displayed in a browser).
9
CH12 ADD.
The S erver Side (P ython): S implifying the XML The C lient S ide (LSL): R evised N ews Ticker
Figure 12a.2: RSS titles formatted as plain text
S ummary
If you want to delve deeper into the topic of web programming with Python, there are several resources available, including tutorials at http://python.org.
THE CLIENT SIDE (LSL): REVISED NEWS TICKER After that long excursion into the dark heart of web programming, it's time to turn your attentions back to something a bit more enjoyable: the final version of the news ticker. Revise your version of 12.6 from the printed book to use the parsing strategy as illustrated in Listing 12a.4. The beginning and ending tags are changed from the XML style to the simpler delimiters you used in writing the RSS generator version in Listing 12.9 in SYW. Notice that the http_response() now extracts the channel title, title, and puts that into the string of characters to crawl across the ticker's face.
Listing 12a.4: RSS News Ticker, Python (Improved from Listing 12a.2) string // ... string string string string string
10
RSS_LINK = "http://ideastax.com/SYW/cgi/RSSgenerator.cgi"; CHANNEL_TAG = "CHANNEL:"; XML_TITLE_TAG = "TITLE:"; NEWLINE_CHAR = "\n"; gChannel = ""; gElipses = "...";
string getChannel(string body) { // find the CHANNEL body = chomp(body, CHANNEL_TAG); gChannel = extract(body, NEWLINE_CHAR) + gElipses; body = chomp(body, NEWLINE_CHAR); return body; } list getTitles(string body) { list rList = []; string title;
// find the item tag do { body = chomp(body, XML_TITLE_TAG); title = extract(body, NEWLINE_CHAR); rList += title; body = chomp(body, NEWLINE_CHAR); } while (llStringLength(body) > 0); return rList; } reset(){ llMessageLinked(LINK_ALL_CHILDREN, 0, "", llGetInventoryKey("alphaBlue")); gCurrPos = -gLEDS; gStories = llToUpper(gStories); llSay(0,"Resetting"); llSleep(5.0); llSetTimerEvent(0.5); } default { // new version of http_response. The rest is the same as Listing 12.6 http_response(key request_id, integer status, list metadata, string body) { if (status == HTTP_OK) { body = getChannel(body); list titles = getTitles(body); integer i = 0; gStories = gChannel;
CH02 ADD. CH06 ADD. CH08 ADD.
CH12 ADD. CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
for ( ; i < llGetListLength(titles); i++) { gStories += llList2String(titles, i) + gElipses; } } else { llOwnerSay("unable to fetch "+RSS_LINK+"; status was "+(string)status); } reset(); } }
The parsing done in Listing 12a.4 is much simpler than that shown in Listing 12.6 in SYW. The getChannel() function looks for and extracts the RSS channel and stores it for later use in composing the news ticker. Next the getTitles() function processes the text that appears inside the channel.
SUMMARY In this addendum, you got some insight into how to build traditional web-service programs and use readymade, open-source libraries. You saw methods for creating a simple web page that accomplishes a goal similar to that shown in Chapter 12 of the print version of Scripting Your World, but here you learned how to output to a web page rather than the RSS reader described in SYW. Although the problems are similar, the differences in the approaches reveal much about the architectural approach taken by LSL's implementers versus the architectural approach of writing applications for the web in general. By understanding how to implement some processing out-of-world, you gain a complete and sophisticated method for getting real-world feeds that are much larger than what you'd be able to access easily using LSL and the Linden Lab HTTP interface alone.
11
Programming in the Out-World
CHAPTER 16
Unsurprisingly, the SYW book was almost entirely about writing scripts that act inside the Second Life. Chapter 14, "Dealing with Problems," briefly discussed web resources for getting and giving help. This section serves as an introduction to three major topics about writing Second Life programs. First we'll discuss some of the ways to develop LSL scripts from outside the standard viewer's script-editor window, along with references to some other external tools useful for writing and maintaining large, complex scripting projects. Next, we'll cover alternative ways to interact with Second Life without using the standard viewer, covering both variations on the standard SL client and independently developed but compatible replacements for the traditional viewer. Finally we discuss some of the features available at the Second Life website that can be useful for developing external tools that interface with SL in various ways.
CHAPTER 16
Scripting O utside SL C onnecting to S econd L ife : B eyond the Standard Viewer R esources from S econdlife . com
S ummary
SCRIPTING OUTSIDE SL The script editor built into the Second Life viewer is convenient and perfectly reasonable for simple scripting. It has automatic indenting and code highlighting and some online documentation capability, and it is the only way to create and compile a script. Most serious scripters, however, use external tools to create their scripts. External editors are far more flexible and powerful than the built-in one, and once your scripts are outside the viewer, you can use additional tools to develop more-complex scripts reliably. Many scripters come to LSL from other languages and so are intimately familiar with other programming environments. Many of the more common environments are similar to LSL or can be extended to be compatible with it. Some editors have pretty advanced capabilities including, for instance, offline compiling and debugging, extensive online documentation and coding templates, and integration with revision-control systems. You still usually end up cutting and pasting your sources into the viewer's script window to upload and compile, but you are no longer subject to the limitations of the viewer's editor. Of course, when your scripts are outside SL, you can also use all of the general-purpose programming tools used to support code development in other languages. The most important of these are revisioncontrol systems, which allow you to keep track of all the versions of your scripts, acting as a simple backup mechanism and tracking changes in your sources, especially important when you are planning to support a script-based product that will go though many revisions.
LSL Development Building BlockS Several tools that assist in the development of LSL code are really intended to be used in conjunction with or as part of editors and IDEs. Two of the most prominent ones are covered here.
ESL ESL is the "Extended Second Life Language". Essentially, ESL is LSL code with C-preprocessor directives suitable for running through the cpp program to emit standard LSL code. This trick * allows standard C features such as #include files** (for building libraries of code), compile-time constants, conditionals,
* Note: others have used the same trick to similar effect; most notably Qarl Linden's qLab library (http://qarl. com/qLab/?p=20) has an lslcc script that does almost exactly what ESL does. He uses this to great effect in the rest of his library to assemble complete scripts out of a large number of smaller pieces.
4
** " ESL scripts are basically LSL scripts that are piped through a C preprocessor (Microsoft Visual C++ Toolkit 2003 by default). This allows the use of preprocessor keywords such as #define, #include and #ifdef. SciTE-ez defines a compile (Ctrl+F7) and a go (F5) command. Compile will call the preprocessor (and a whitespace stripper) and produce a file with the .lsl suffix. Go will put the resulting LSL script into the clipboard for easy transfer into SL (it checks if a new compile is required first and does this automatically). Optionally, you can also have it strip comments from the source (if you're hitting the 64kb limit of the SL editor). Set NO_ STRIP_COMMENT to 0 in the file lsl.properties if you wish to enable this." —http://sdfjkl.org/ secondlife/scite/
and simple text macros. The idea originated with Azelda Garcia in the forum thread http://forums. secondlife.com/showthread.php?threadid=18987. Many people have contributed to ESL, including Strife Onizuka (who created cleanups of ESL), Hugh Perkins (who made the original SciTE binding), and Ezhar Fairlight (who introduced the mainline support of ESL in SciTE-ez).
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD.
Installation ESL is little more than an install-specific script that runs the C preprocessor. Only the SciTE editors (discussed later) understand ESL directly, so it is distributed only with SciTE. CHAPTER 16
lslint lslint* is a tool for checking programs for problems without compiling them using the Second Life compiler. It was developed independently, so it doesn't always exactly match the built-in compiler's behavior, but it can be useful if you are working offline. Sometimes lslint's messages are more helpful or specific than the SL compiler's. It is available in source form as well as compiled for Windows, OS X, and Linux. Note that lslint is a command-line program—there is no GUI unless it is used with an editor/IDE, such as one of those mentioned in the following section.
PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
The lslint web page at http://w-hat.com/lslint/ also has a convenient form-based code checker—essentially allowing you to run lslint on your script without installing any software.
Installation Installation is simple: Download the appropriate program from http://w-hat.com/lslint, unpack it if needed (for Linux and OS X), then it's ready to run.
LSL Editors You can use any external editor you like to edit LSL sources—anything that you are comfortable with is fine. In general, however, you cannot use programmers' editors and development environments in modes tuned for other programming languages (Java or C#, for instance). Instead several editors can be customized with LSL-specific capabilities so that they understand LSL syntax. The LSL wiki has a page about Alternative Editors that contains a list of approximately 20 external editors with LSL-specific capabilities. Adam Marker's Shill project site at http://adammarker.org/ shill/ is another good source of editor references. The Shill project is intended to provide up-to-date LSL syntax files for as many of these editors as possible—this site is the official distribution point for all of the Shill syntax files as well as pointers to the editors themselves, and installation instructions. The following editors are some of our favorites—each of them has a significant user base and something relatively unique going for it. They all have LSL-specific native or add-in capabilities, so they'll all serve your scripting needs more directly than a plain-text editor like Notepad.
* "lslint is a tool to check the syntactic and semantic validity of Second Life LSL scripts. It is basically a compiler that produces no output, but has helpful error messages, warnings, and checks for some common problems in LSL. It is mainly for use with an external editor. You can use it online or download it and run it on your own computer." —http://w-hat.com/lslint/
5
SciTE for LSL
CHAPTER 16
Scripting O utside SL C onnecting to S econd L ife : B eyond the Standard Viewer R esources from S econdlife . com
SciTE is text editor based on Scintilla, a free embeddable source-code editor widget available for Windows and Linux-based operating systems. It has extra support for syntax styling and allows for many more styling choices than other editors. There are two common SciTE-based LSL editors: SciTE-ez and SciTE-LSL. SciTE-ez is a customized Windows-only packaging of SciTE that Ezhar Fairlight (Ingmar Hupp in real life) put together. It offers LSL syntax definitions for highlighting, autocompletion, and support for the ESL macro system and lslint. SciTE-ez was a very popular at one point but currently is not maintained. The SciTE-ez home page at http://sdfjkl.org/secondlife/scite/ has the original software, documentation, and links to several other related projects. SciTE-LSL is the successor to SciTE-ez. It's an updated version of the same approach that was originated and is currently maintained by LordJason Kiesler. It should work on any platform supported by SciTE, though it is best supported and easiest to install on Linux.
Installation
S ummary
At the time of this writing, SciTE-LSL does not come prepackaged, so you'll have to build it yourself from the instructions on the main page for SciTE-LSL at http://dimension128.110mb.com/, or try the following sequence: 1. Download SciTE from SourceForge.net: http://scintilla.sourceforge.net/ SciTEDownload.html. 2. Get the SciTE-LSL properties files from Adam Marker's site: http://adammarker.org/ shill/scite/scite_lsl.tar.gz. Unzip and untar the file in the same directory as the SciTE executable. 3. Run SciTE, click Options Open Global Options File, type import lsl, then click File Save.
Emacs Emacs* is another general text editor with a vast library of extensions for customizing the way it behaves and displays text. It is self-documenting, customizable, and extensible. Most Linux distributions have Emacs either installed or as options. For other operating systems, you can get Gnu Emacs from www.gnu.org/software/emacs/ or the alternative XEmacs system from www.xemacs.org/. The original LSL mode for Emacs was posted by Phoenix Linden at the end of 2005 in the SL forums at http://forums.secondlife.com/showthread.php?t=4062. Later follow-ups by Reinhard Neurocam, Turkey Scofield, and Scott Bristol have significantly added to the capabilities. Emacs and the Emacs LSL mode are available for all major operating systems. However, Emacs does not offer direct support for ESL or lslint.
6
* " You are reading about GNU Emacs, the GNU incarnation of the advanced, self-documenting, customizable, extensible editor Emacs. (The 'G' in 'GNU' is not silent.) […] Extensible means that you can go beyond simple customization and write entirely new commands—programs in the Lisp language to be run by Emacs's own Lisp interpreter. Emacs is an "on-line extensible" system, which means that it is divided into many functions that call each other, any of which can be redefined in the middle of an editing session." —www.gnu.org/software/emacs/ manual/html_node/emacs/Intro.html
Installation To install the LSL mode for Emacs, do the following: 1. Download the file lsl-mode.el and lsl-error-mode.el from the forum post number 29, www2.ttcn.ne.jp/tatsu/elisp.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD.
2. Put lsl-mode.el and lsl-error-mode.el in your elisp directory. 3. Add the following lines to your ~/.emacs file: (add-to-list 'load-path "<path-user-elisp>");; t ell .emacs where to find your elisp directory (setq auto-mode-alist (append auto-mode-alist (list '("\\.lsl$" . lsl-mode)))) (autoload 'lsl-mode "lsl-mode" "Major mode for editing LSL traits. " t) (add-hook 'lsl-mode-hook (lambda () (if window-system (turn-on-font-lock))))
CHAPTER 16 PROJECT 1 PROJECT 2
TextMate TextMate* is nice commercial text editor available at http://macromates.com. It is the only application listed here that is Mac OS X-specific. Leon Ash posted a TextMate bundle extending it with LSL support at http://digilander. libero.it/usemac/slweb/textmatebundle.html.
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Installation 1. Download tm.zip from http://digilander.libero.it/usemac/slweb/ textmatebundle.html. 2. Uncompress tm.zip and copy the contents of the TextMate folder to (home)/Library/ Application Support/TextMate. 3. To enable lslint support, type the following in a terminal window: chmod +x ~/Library/Application\ Support/TextMate/lslint
Vim Vim** is a popular text editor favored by many Unix and Linux programmers. It is efficient, customizable, and available for many different operating systems, including Linux, OS X, and Windows.
* "Created by a closet UNIX geek who was lured to the Mac platform by its ease of use and elegance, TextMate has been referred to as the culmination of Emacs and OS X and has resulted in countless requests for both a Windows and Linux port, but TextMate remains exclusive for the Mac, and that is how we like it! "TextMate is not an IDE but by using its powerful snippets, macros, and unique scoping system, it can often provide features that even a language specific IDE lacks. It has enough project management features to keep most users happy, but is otherwise kept lightweight with a clean and minimalistic GUI." —http://macromates.com ** " Vim is a highly configurable text editor built to enable efficient text editing. It is an improved version of the vi editor distributed with most UNIX systems. "Vim is often called a 'programmer's editor,' and so useful for programming that many consider it an entire IDE. It's not just for programmers, though. Vim is perfect for all kinds of text editing, from composing email to editing configuration files." —www.vim.org/about.php
7
Gabriel Spinnaker maintains the LSL syntax extension for Vim at http://geekmafia.net/ ~gabespin/lsl.vim.
CHAPTER 16
Installation 1. Install Vim. 2. Download lsl.vim.
S cripting O utside SL
3. Copy lsl.vim into the appropriate directory
C onnecting to S econd L ife : B eyond the Standard Viewer R esources from S econdlife . com
S ummary
• Windows: c:\vimfiles\after\syntax\ • Unix/Linux/OS X: ~/.vim/after/syntax/
jEdit jEdit* is another popular multiplatform editor—it is flexible, open-source, and because it is implemented in Java, is extremely portable. It is also extremely modular, with over 100 "plugins" available that add more advanced features, including integrated support for CVS and SVN (see the "Source Code Management" section later in this chapter).
Installation 1. Obtain and install jEdit from www.jedit.org/. 2. Run jEdit. 3. Download the jEdit LSL edit mode (syntax highlighter) from www.squeep.com/~trent/lsledit.xml and do the following. (Note that the directories will not exist if you have not already run jEdit.) • Windows: The edit mode is placed in %AppData%\..\.jedit\modes. Edit the %AppData%\..\.jedit\modes\catalog file as in step 4. • Linux: The edit mode is placed in ~/.jedit/modes. Edit the ~/.jedit/modes/ catalog file as in step 4. • Mac OS X: The edit mode is placed in the modes folder (found in the jEdit application folder). Edit the catalog file (also found in the modes folder) as in step 4. 4. Edit the catalog file, adding the following line: <MODE NAME="lsl" FILE="lsl-edit.xml" FILE_NAME_GLOB="*.lsl" />
* "jEdit is a mature programmer's text editor with hundreds (counting the time developing plugins) of person-years of development behind it.... "While jEdit beats many expensive development tools for features and ease of use, it is released as free software with full source code, provided under the terms of the GPL 2.0.
8
"The core of jEdit was originally by Slava Pestov. Now the jEdit core, together with a large collection of plugins is maintained by a world-wide developer team." —www.jedit.org
LSL Integrated Development Environments An integrated development environment (IDE) integrates source code editing, compiling, and usually a debugger. For code editing, the IDE will not only format the code, but also provide tooltips and autocompletion. You can compile and test your code outside the SL grid. IDEs are nice because they are more comprehensive than plain editors, but there are fewer of them, so there is less choice on look and feel. If you use only the editing features, you might be better off with an editor that feels more comfortable.
LSL-Editor
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD.
CHAPTER 16
LSL-Editor* includes some uniquely useful LSL scripting features: an LSL compiler, debugger, and simple runtime environment, a complete language reference, and a range of formatting tools.
PROJECT 1 PROJECT 2
The current release (2.32) includes code highlighting and editing, tooltips, help on every keyword, and an llDialog() input screen. It runs on Windows and requires an installation of the .NET Framework 2.0 (standard on most Windows machines).
PROJECT 3 PROJECT 4 PROJECT 5
Installation
APPENDIX D
1. Download LSLEditor.zip from http://lsleditor.org. 2. Unzip it wherever you want. 3. Run LSLEditor.
ByronStar SL ByronStar SL** is an LSL support plug-in to the widely used Eclipse IDE. Eclipse*** is a full-featured professional development environment that supports a very wide range of languages and programming environments and includes bindings to many development tools, including various revision-control, bug-tracking, and design systems. Eclipse, including the ByronStar SL extension, is available for all major operating systems. *
“ LSL-Editor is a standalone LSL (Second Life) script editor and run-time environment for Windows and compiles and executes LSL scripts. No Second Life viewer is needed nor connection to the Second Life grid. The program is updated on regular intervals. When the project emerges, more and more code can be compiled, debugged, and tested outside Second Life world." —http://lsleditor.org
** "ByronStar SL is a project which—as its ultimate goal—will provide an eclipse based integrated development environment for Second Life. Initially the focus is on text editing for Linden Scripting Language.… This is all based on the eclipse 3.2 text editing framework and implemented as an eclipse plugin in Java. Besides completion and extension of the editor capabilities and introduction of a SL project nature, the manipulation of Second Life 3D graphic objects is an important topic which will be addressed in this context as well." —http://byronstar-sl.sourceforge.net/ *** " Eclipse is an open-source software framework written primarily in Java. In its default form it is an Integrated Development Environment (IDE) for Java developers, consisting of the Java Development Tools (JDT) and the Eclipse Compiler for Java (ECJ). Users can extend its capabilities by installing plug-ins written for the Eclipse software framework, such as development toolkits for other programming languages, and can write and contribute their own plug-in modules. Language packs are available for over a dozen languages." —http://en.wikipedia.org/wiki/Eclipse_(software)
9
Eclipse is a much larger environment than any of the other editors discussed here and it requires more resources to run effectively, but for someone who is already familiar with it or requires a high-end IDE, it is invaluable. ByronStar SL does not currently have any LSL compile or debug capabilities, though it detects some syntactic errors. It also lacks integrated support for ESL or lslint. CHAPTER 16
Scripting O utside SL C onnecting to S econd L ife : B eyond the Standard Viewer R esources from S econdlife . com
ByronStar SL was created by and continues to be developed by ByronStar Comet as a SourceForge.net project.
Installation 1. Download and Install Eclipse by following the directions at www.eclipse.org/. Note that though there are many download options, if you are going to be using it only to develop LSL, you can get by with an Eclipse version packaged for Java or C/C++ development. 2. Create a "remote site" in Eclipse, pointing to the ByronStar SL Eclipse update site as follows: a. Go to Menu Help Software Updates Find and Install... Search for New Features to Install New Remote Site.
S ummary
b. Enter the Name as SF ByronStar SL. c. Enter the URL as http://byronstar-sl.sourceforge.net/update. d. Click OK. 3. You should now be able to check the ByronStar SL check box, click Finish, and be offered the choice to install the LSL extension. 4. As this point, any file you create or edit that ends in .lsl will use the LSL mode.
Source Code Management As you develop your scripts, you may occasionally think, "Gee, I wish I'd kept that version from two days ago." You may also find yourself working with other people to develop code. You may simply want to keep a backup of all your code on a second machine. Source code management, also known as revision control, is the automatic management of multiple revisions of the same unit of information, and is very common practice in software development. The basic idea is that changes to the source files are periodically "checked in" with author comments. Any previous revision can be retrieved or compared to current versions, which can be extremely useful when you discover a new bug or an old bug returns to haunt you.
CVS The Concurrent Versions System (CVS)* is an open-source version-control system that tracks changes in your files. Download it from www.nongnu.org/cvs/. CVS is a collection of files containing some additional information. You can read them on your own machine, and easily figure out the difference between old and new versions. CVS works really well for plain-text files, such as scripts.
10
* "CVS started out as a bunch of shell scripts written by Dick Grune and posted to the newsgroup comp.sources.unix in the volume 6 release of July 1986. While no actual code from these shell scripts is present in the current version of CVS, many of the CVS conflict resolution algorithms come from them." —Version Management with CVS, Per Cederqvist et al (Network Theory, 2002)
SVN
CH02 ADD.
Subversion (SVN)* is a version-control system whose goal was to be a successor to CVS. Download it from http://subversion.tigris.org.
CH08 ADD.
SVN is based on a relational database of binary files. While this settles some problems, data storage is no longer transparent as in CVS; SVN provides utilities for "curing" errors. SVN handles binary files better than CVS, and also supports renaming or moving files more efficiently. More discussion on the difference between SVN and CVS is available at www.pushok.com/soft_svn_vscvs.php. We used SVN to write this book: it allowed the three geographically distributed authors to coordinate code, chapters, and images throughout the many cycles of the editing process.
CH06 ADD.
CH12 ADD.
CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3
CONNECTING TO SECOND LIFE: BEYOND THE STANDARD VIEWER
PROJECT 4 PROJECT 5 APPENDIX D
The official Second Life viewer application is not the only way to interact with SL as an avatar would. The official viewer sources are now easily available, so you can build your own client based on the public sources if you wish. Several people have done exactly that to build their very own version of the SL viewer, tuned and extended to fit their own niches. Going further afield, libsecondlife is a toolkit that you can use to build simple client applications that can connect to the SL grid without using any Linden software on your local computer. Quite a few projects leverage libsecondlife to build lightweight clients.
The Opened Viewer On January 8, 2007, Linden Lab announced the availability of the sources for the official viewer. Even better, the sources were released under the GNU General Public License (GPL)—otherwise known as the opensource license—opening the way for any number of alternative full-featured viewers based on the original sources, and making it far easier for the user community to contribute to the viewer's development by finding and fixing bugs and suggesting improvements.
* " Subversion is well-known in the open source community and is used by many open source projects. Some wellknown projects that use Subversion include: Apache Software Foundation, KDE, GNOME, Free Pascal, FreeBSD, GCC, Python, Django, Ruby, and Mono. SourceForge.net and Tigris.org also provide Subversion hosting for their open source projects. Google Code and BountySource systems use it exclusively. "There is also adoption of Subversion in the corporate world. In a 2007 report by Forrester Research, Subversion was recognized as the sole leader in the Standalone Software Configuration Management (SCM) category and strong performer in the Software Configuration and Change Management (SCCM) category." —http://en.wikipedia.org/wiki/Subversion_(software)
11
CHAPTER 16
Scripting O utside SL C onnecting to S econd L ife : B eyond the Standard Viewer R esources from S econdlife . com
S ummary
We describe some of our favorite alternative versions of the SL viewers in the following sections, but new viewers are appearing and becoming dormant too quickly for any central list to keep up, so the landscape may have changed significantly by the time you read this. Thankfully, Linden Lab maintains a fairly up-to-date list of major variations on the standard viewer at http://wiki.secondlife.com/ wiki/Alternate_viewers.
Extensions to the Standard Viewer Of course, you can always download a variety of beta and "first look" viewers from the main Second Life website, but you can also download the sources from the Second Life source download page at http:// wiki.secondlife.com/wiki/Source_downloads or the public Subversion repositories at https://svn.secondlife.com and compile your own. While this is unlikely to be much of a benefit by itself, it is the first step toward adding your own new capabilities to the viewer. The standard viewer and all derivatives are written in C++, so you'll need a complete C++ development environment on your computer before you do much else. The best place to start is at http://wiki.secondlife.com/wiki/Get_source_and_ compile, where the official download and build instructions are kept, along with details for each of the supported platforms. Be warned that the Second Life viewer is a pretty complex system to build. If you are a novice at this, don't get discouraged, but also don't expect it to be trivial. Once you have it compiling, you'll probably be interested in the official Open Source Portal*, which offers a plethora of valuable information about the design and structure of the viewer and supporting systems. Another resource is Peter Seebach's three-article series for IBM's developerWorks website: "Second Life client: Hacking Second Life" (available at www.ibm.com/developerworks/linux/library/ l-second-life-1.html). The first part discusses how to get all the pieces together to set up a build environment, the second part examines the structure of the viewer and how to find documentation on the internals, and the third part walks you through the implementation of a simple viewer extension. This series is intended for an intermediate programmer on a Linux machine, but much of it is applicable to other environments as well, especially when combined with the Linden Lab resources mentioned earlier. Many suppliers build viewers mainly to better support particular environments. These viewers tend to be less visible than the flashier ones that implement neat new features, but they extremely interesting to people running the targeted environments. Some examples are as follows: Byteme builds for Debian Linux. These are fairly straightforward builds of the standard viewer sources, except that they are packaged as .deb files for trivial installation on Debian Linux and derivatives, including specific support for Ubuntu Linux. They are also remarkable for providing not only the standard 32-bit builds, but also native 64-bit builds for better performance under 64-bit Linux installs. You can find them at www.byteme.org.uk/secondlife-amd64/apt-get-asecondlife.html. Kirstens Viewers, by Kirstenlee Cinquetti, packages a nice set of Second Life viewers that are highly tuned for performance and stability on high-end Intel-based computers running 32-bit Windows operating systems. While not official builds, they are good choices if you have the sort of machine that they target. Information and installation instructions for these viewers are at http://code.google. com/p/kirstens-secondlife-viewers/wiki/AboutKirstensViewer, and downloads are at http://code.google.com/p/kirstens-secondlife-viewers/. Kirsten also builds some prerelease and cutting-edge builds, mainly focused on new and different user interfaces.
12
* http://wiki.secondlife.com/wiki/Open_Source_Portal
Customized Viewers If it is possible to customize the viewer then, of course, people will customize it. The alternate viewer list at http://wiki.secondlife.com/wiki/Alternate_viewers has a good collection of customized viewers. We've selected a few of our favorites because of their unique features, but do check out the rest. The CSI:NY viewer was designed to be a tie-in for the October 24th, 2007 television crime drama CSI: New York episode "Down the Rabbit Hole" in which a large portion of the action took place inside Second Life. The producers decided to allow Second Life residents to explore a CSI:NY environment and to solve virtual crimes. They hired the Electric Sheep Company * to build the environment on several SL sims, and to create a customized tie-in viewer. The viewer included several nonstandard features, such as teleport history buttons and in-viewer web browser. This marked the first time (and so far the only time) that an SL viewer has been built to support a specific real-world product. The CSI:NY virtual-experience website is at www.cbs.com/primetime/csi_ny/second_life/ and you can download the viewer for Windows and OS X at http://viewer.onrez.com/. Dale Glass packages a viewer for Windows and Linux with two unique extra features. First, it incorporates a set of environment-awareness tools: a built-in avatar radar (very much like a more powerful version of the in-world radars), local object monitors, and event logs. These are can be extremely useful both for debugging purposes and for defending against griefers—players who enjoy making Second Life unpleasant for others. Second, it includes support for stereoscopic 3D rendering, using red/cyan glasses, paired monitors, and shutter glasses. Any of these techniques can be used with extra viewing hardware to make Second Life scenes appear fully three-dimensional. Dale's builds are available at http://sl.daleglass.net/.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD.
CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Nicholaz "The Mad Patcher" Beresford maintains a highly patched viewer for Windows. He includes a large set of patches, often experimental and forward-looking. While it is cutting-edge, it is probably also the most well known of the alternative viewers. You can get it at http://nicholazberesford.blogspot.com/. It requires a little bit of configuration, so read the installation instructions carefully. RestrainedLife is a custom viewer maintained by Marine Kelly. This viewer is especially interesting for scripters and viewer developers because it adds scripted control to a variety of avatar actions and viewer functions that are solely under manual control in the standard viewer. Although these concepts are widely applicable, the viewer was specifically targeted at residents who practice BDSM role playing, so examples of typical scripted objects include handcuffs that cannot be removed when locked, blindfolds that actually obscure your view, and gags that prevent chat and IM. Of course, a user can always quit the viewer, so activities remain consensual. Even if you are not interested in these specific extensions, Marine's work serves as a high-quality pattern for implementing other types of script-to-viewer communication channels. The API for developing compatible scripted objects is well documented. The viewer is available for Windows or OS X through http://realrestraint. blogspot.com/. The scripting API is documented on the official Second Life wiki at http:// wiki.secondlife.com/wiki/LSL_Protocol/RestrainedLifeAPI. The Cool SL Viewer by Henri Beauchamp incorporates code from several other custom viewers, including the bulk of the Nickolaz set and the RestrainedLife API. It reverses a number of relatively unpopular user-interface changes that Linden Lab introduced into the standard viewer, and it adds a variety of new user-interface and convenience features, It is available from http://sldev.free.fr/.
* www.electricsheepcompany.com; no relation to our author ElectricSheep Expedition!
13
CHAPTER 16
The other significant use for custom viewers is to add automation and remote-control capabilities. The most common are "landbots," viewers that have been modified with a rule-based automation engine that searches for inexpensive land to be put on the market and sends a robot to purchase it on behalf of the real-estate speculator. You can read more about this use at http://landbot.wordpress.com/ landbot-faq/.
libsecondlife
Scripting O utside SL C onnecting to S econd L ife : B eyond the Standard Viewer R esources from S econdlife . com
S ummary
libsecondlife* is a project started with the intent of understanding the SL network protocols and producing a completely new SL client-side library suitable for both rich, heavyweight clients like the standard viewer and lightweight clients for robotic control of avatars or simple chat-only clients for human users. While the official viewer has since been made available as source code, libsecondlife continues to used and developed for a variety of lightweight purposes. Although most literature and documentation refers to this library as libsecondlife, it is in transition to a new name, libopenmv (for "Open MetaVerse"). libsecondlife is based on the Microsoft .NET 2.0 Framework and is written in C#. You will need .NET 2.0 or Mono** installed to run any libsecondlife programs, and you'll need a C# development environment to build and use it yourself, either in the form of Microsoft Visual C# for Windows or the Mono environment for Linux or OS X. A full discussion of and tutorial for libsecondlife would take up too much room here. Luckily, there is an excellent starting point at www.libsecondlife.org/wiki/Getting_Started, complete with a installation instructions, hints, and an annotated example. To give you a small taste of what a libsecondlife program looks like, Listing 16.1 reprints an edited version of their example.
Listing 16.1: Sample libsecondlife Code // // /* * * * * * * * * * * * * *
example from http://www.libsecondlife.org/wiki/Use_libSL_to_login_to_the_SL_grid Copyright (c) 2007, Second Life Reverse Engineering Team All rights reserved. - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Neither the name of the Second Life Reverse Engineering Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
* "The libsecondlife project is an effort directed at understanding how Second Life works from a technical perspective, and extending and integrating the metaverse with the rest of the web. This includes understanding how the official Second Life client operates and how it communicates with the Second Life simulator servers, as well as development of independent third party clients and tools." —www.libsecondlife.org/wiki/ Main_Page
14
** Yes, the same Mono that runs the new scripting virtual machine in SL as described in SYW Chapter 15. Here, however, Mono is used for its original purpose as a complete portable implementation of the .NET programming environment.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Text; using libsecondlife;
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD.
CHAPTER 16 PROJECT 1 PROJECT 2
namespace MyFirstBot { class MyFirstBot { public static SecondLife client = new SecondLife();
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
private static string first_name = "Minimal"; private static string last_name = "Ballyhoo"; private static string password = "NotMyPassword"; public static void Main() { client.Network.OnConnected += new NetworkManager. ConnectedCallback(Network_OnConnected); if (client.Network.Login(first_name, last_name, password, "Minimal Bot", "Scripting Your World")) { Console.WriteLine("I logged into Second Life!"); } else { Console.WriteLine("I couldn't log in, here is why: " + client.Network.LoginMessage); } } static void Network_OnConnected(object sender) { Console.WriteLine("I'm connected to the simulator, "+ "going to greet everyone around me"); client.Self.Chat("Hello World!", 0, ChatType.Normal); Console.WriteLine("Now I am going to logout of SL. Goodbye!"); client.Network.Logout(); } } }
The structure of this example is pretty clear if you follow the progression through the Main() function: 1. Set up a callback so that once we are connected to the network; the Network_OnConnected method will be invoked. 2. Log in to Second Life as Minimal Ballyhoo. 3. When the connection succeeds, a callback is invoked, where we use standard chat to say, "Hello World!" and then log out of SL.
15
SLeek
CHAPTER 16
Scripting O utside SL C onnecting to S econd L ife : B eyond the Standard Viewer R esources from S econdlife . com
S ummary
SLeek* is a free lightweight viewer by Delta Czukor that is based on libsecondlife. It lets you communicate with other Second Life residents as though you were logged in, but without the bandwidth or 3D rendering performance requirements of the official client. You can chat and IM with people, teleport around, "sense" surrounding objects, and perform some basic inventory-management tasks. Because SLeek doesn't display the virtual world in any way, it can be run on a much less powerful computer than what you need to run the standard SL viewer. You can download SLeek from http://delta.slinked.net/second-life/sleek/ in both Windows executable and source form. Installation is simply a matter of unpacking the zip archive and running the program SLeek.exe.
AjaxLife AjaxLife is another take on a lightweight Second Life client created by Katharine Berry. Here, instead of a libsecondlife-based client that runs on your computer, it is a combination of a web-browser-based AJAX application and a libsecondlife-based proxy that connects to Second Life on your behalf. It has similar functionality to SLeek, but requires only a modern web browser and an Internet connection. To run AjaxLife, just visit http://ajaxlife.net/. Katharine's blog, http://blog. katharineberry.co.uk/, is the best source for information.
RESTbot RESTbot** is a system built by Pleiades Consulting*** that provides a simple web-services interface to libsecondlife. It allows you to use just about any web-services-enabled (it understands XML and HTTP) language to write simple Second Life clients. It does not attempt to implement the complete libsecondlife API as web services, but it has a flexible plug-in architecture that allows you to implement whatever parts of the interface you need. Because RESTbot is based on libsecondlife, plug-ins are C# classes. Listing 16.2 is a RESTbot plug-in that adds a simple one-way chat service.
Listing 16.2: One-way Chat Service Using RESTbot using using using using using using using
System; System.Collections.Generic; System.Text; libsecondlife; libsecondlife.Packets; System.Net; System.Threading;
* "SLeek is an open-source (BSD-style license), relatively lightweight client for Linden Lab's Second Life virtual world. It allows you to perform basic in-world tasks such as chatting, IM, viewing Inventory, viewing Profiles, and more. It can be useful especially for lower-power computers, or if you only need the basic functionality of SL at the moment." —http://delta.slinked.net/second-life/sleek/
16
** "'This is the first and only way for applications written in PHP or Perl to interact with Second Life,' explains Pleiades software developer Andrew Ortman. 'It will let developers like me run web services that can access data Second Life exclusively provides to users of its client software. I think the community will find that very exciting.'" —www.restbot.com *** http://www.pleiades.ca/
namespace RESTbot { public class AvatarChatPlugin : StatefulPlugin { private LLUUID session; public AvatarChatPlugin() { MethodName = "avatar_chat"; } public override void Initialize(RESTbot bot) { session = bot.sessionid; } public override string Process(RESTbot b, Dictionary<string, string> Paramaters) { try { if (Paramaters.ContainsKey("message")) { string msg = Paramaters["message"]. ToString().Replace("+"," "); b.Client.Self.Chat(msg, 0, MainAvatar.ChatType.Normal); return null; } else { return "<error>arguments"; } } catch (Exception e) { DebugUtilities.WriteError(e.Message); return "<error>parsekey"; } }
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD.
CHAPTER 16 PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
} }
Note the line b.Client.Self.Chat(...); this is the same libsecondlife call used earlier to make the avatar say, "Hello world!" The difference is that the AvatarChatPlugin class implements a complete web-service point that can be invoked any number of times. To deploy RESTbot, you edit a small configuration file and run the RESTbot server application. This will fire up a simple RESTful web server on the port you specified suitable for invoking from your client scripts. Clients can be large applications in their own right or extremely simple scripts that implement a single function. For instance, Listing 16.3 contains a small Perl script that can invoke the chat service from your operating system's command line.
Listing 16.3: Invoking the Chat Service from the Command Line #!/usr/bin/perl # botchat.pl use XML::XPath; use HTTP::Request::Common qw(POST); use LWP::UserAgent; $ua = new LWP::UserAgent; if ( ( $#ARGV + 1 ) < 2 ) { print "bad args - url session message\n"; exit; } my $req = POST $ARGV[0] . "/avatar_chat/" . $ARGV[1] . "/", [ "message" => $ARGV[2] ]; $res = $ua->request($req); if ( $res->code == 200 ) { print $res->content . "\n"; }
17
Basically this script sends a simple HTTP POST message to the address of the web service defined in Listing 16.2 (/avatar_chat/) and waits for a response. The following sample session has exactly the same effect as the libsecondlife example in Listing 16.1, using scripts included with RESTbot and Listing 16.3: CHAPTER 16
Scripting O utside SL
% botlogin http://localhost:8888/restbot Minimal Ballyhoo NotMyPassword 1ab2ab3bab4b1b2b34b5b % botchat http://localhost:8888/restbot 1ab2ab3bab4b1b2b34b5b "Hello World!" % botlogout http://localhost:8888/restbot 1ab2ab3bab4b1b2b34b5b
C onnecting to S econd L ife : B eyond the Standard Viewer
The first line lets the user log into SL as the user Minimal Ballyhoo with the dummy password NotMyPassword. RESTbot then prints a session string that is then used to cause Minimal to chat "Hello World" to any avatars who happen to be nearby. The last line then logs Minimal out of SL.
R esources
RESTbot is available only in source form and comes with a copy of libsecondlife. You can download it from http://restbot.com/. Instructions for building and extending RESTbot are contained in the distribution archive, and it should be possible to compile and run the server on any operating system that is supported by libsecondlife itself. The scripts that you use to invoke the web services can, of course, be in any language on any operating system that supports RESTful web-service clients.
from S econdlife . com
S ummary
Be aware that RESTbot is experimental software under heavy development. Check the RESTbot website or SYW Headquarters for updated information if you have any problems.
Open Server One of the exciting recent developments is the push to open up the server side of Second Life. The most visible progress has been the implementation of compatible simulators, reverse-engineering the simulator functions from the network traffic—essentially acting as the mirror of the libsecondlife project. At the time of this writing, OpenSimulator software* is still rather immature, but it is stable and powerful enough already to support a number of public "open" grids that are relatively full-featured. Development on open-source sim project appears to be accelerating. It would not be surprising if there were serious competitors to Linden Lab (in certain categories) for grid servers by the time you read this.
RESOURCES FROM SECONDLIFE.COM The Second Life website provides a number of information services useful to scripting and scripters.
18
* " The OpenSimulator Project is a BSD Licensed Virtual World Server which can be used for creating and deploying 3D Virtual Environments. It has been developed by several developers. Out of the box, the OpenSimulator can be used to create a Second Life like environment, able to run in a standalone mode or connected to other OpenSimulator instances through built in grid technology. It can also easily be extended to produce more specialized 3D interactive applications." —http://opensimulator.org/wiki/Main_Page
Financial Transactions The My Account section of the Second Life website (http://secondlife.com/account) allows you to get a report on all account transactions for the past 30 days. In addition to the usual web-page display, you can have it produce downloads in either Microsoft Excel or XML format. The XML version is quite useful for external scripting, as it represents a way to accomplish (mostly) automated tracking of your Second Life finances. Second Life banks often use these files, uploaded weekly to their web services, to help you track your accounts, making it possible to produce much more comprehensive reports and keep much longer historical records. Unfortunately there is no official way to automate the process of downloading the account-transactions XML files. There have been some attempts, but the introduction of a CAPTCHA* filter in the website login has made it far more difficult to accomplish this task with simple tools.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD.
CHAPTER 16 PROJECT 1 PROJECT 2
Web Services Second Life exposes a set of useful web services explicitly for the use of automated systems. They're documented on the Second Life wiki at http://wiki.secondlife.com/wiki/Web_Services_ Portal. At the moment there are three such services:
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
RegAPI is a service for "creating and managing your community in Second Life.... You can use the Registration API to create Second Life accounts within your own registration process or website." The API lets you to allow new users to register via your own system so that you can keep track of those new users as members of your community. RegAPI is documented at http://wiki. secondlife.com/wiki/RegAPI. MapAPI allows the use of embedded Second Life maps in your own web pages. It extends the Google Maps API and supports pretty much all the same functionality as the familiar real-world Google Maps widgets, except with Second Life geography. Full documentation and examples are available at http://wiki.secondlife.com/wiki/MapAPI. Data feeds are a set of simple services that periodically publish aggregated data about the state of Second Life for informational and statistical use. Documentation is at http://wiki.secondlife. com/wiki/Live_Data_Feeds. There are only two such feeds: Homepage stats feed, which provides information such as how many accounts are open and active, how much money has been spent in the last 24 hours, and how many residents are logged on. LindeX data feeds, (in XML or key-value pairs) which provide up-to-date information on the exchange rates between US$ and L$ as well as some basic information on the market itself.
* C APTCHA, which stands for "Completely Automated Public Turing Test to Tell Computers and Humans Apart," forces you to enter the set of symbols you see onscreen in a difficult-to-read image. The idea is that it is far more difficult for a computer to "read" the symbols than for a human.
19
CHAPTER 16
Scripting O utside SL C onnecting to S econd L ife : B eyond the Standard Viewer R esources from S econdlife . com
S ummary
20
SUMMARY This chapter introduced a variety of tools useful for the practice of programming Second Life scripts, as well as some additional ways of interacting with the SL virtual world in ways other than LSL scripting.
Dance
PROJECT 1
The three most popular virtual activities in SL are shopping, sex, and dancing. The scripted side of shopping is covered in Chapter 13 of SYW, and we wanted this to be a PG-rated book, so that leaves us with dancing. Besides, you could always make your tango horizontal, right? This section starts with basic individual dance and then adds different mechanisms for coordinated multiavatar dance. Spending time dancing in a club is as good a way to meet other people in Second Life, as it is in real life— perhaps even better: you can actually communicate with the people around you and you can probably dance better in Second Life than you can in real life. Of course, how well your avatar dances depends on the quality of the dance animations* and the quality of the scripting that makes it all work.
* C heck out Creating Your World: The Official Guide to Advanced Content Creation in Second Life (Wiley, 2007); Chapter 11, "Animations: Breathing Life into Avatars" offers great information and a tutorial on creating your own animations.
DANCING WITH MYSELF
PROJECT 1
Dancing with M yself
Dance -Hall Days S ummary
This section deals with getting a single avatar to dance in various ways; if you don't have someone to dance with, go dance near somebody else and strike up a conversation. Most of the scripts in this section are really general-purpose animation-control programs. While dancing in various forms is probably the most common use for these techniques, the scripts in this section can also produce scripted furniture with high-quality seating animations and poses for photography, and are similar to scripts for controlling vehicles and other solo activities. Of course, the very simplest way to get yourself dancing is to play a dance animation from your inventory: find the animation you want and activate it by opening it and selecting Play in World. What's the matter with that? Well, nothing, but this method is a little awkward in that everyone needs to own all the dances; it's not very accommodating for club use.
The Ubiquitous Pose Ball The first step beyond just playing your own animation is probably the single most common scripted object in Second Life: the pose ball. Pose balls are usually innocuous-looking balls hovering near furniture, the floor, or even hot tubs. What makes it a pose ball is that the script overrides the default sit animation with another animation, similar to what was shown in Listing 2.10 of SYW. When an avatar sits on a pose ball, the ball will play an appropriate animation. Typically, pose balls are color-coded as pink or blue if the animation or pose is gender-specific, or a neutral color for neutral use. Floating text over a ball often is used to indicate what sort of animation or pose the ball will activate.
BUILD NOTE You can make pose balls look like whatever you want, but most of them in Second Life are simple small spheres, say 0.2m in each dimension. Do remember the spheres are by default rotated by 90 degrees when rezzed: usually you’ll want to reset the rotation of such objects to or risk having your avatars dancing sideways through the floor. Add the script to the object along with a dance animation that loops forever.
The script in Listing P1.1 shows how to build a basic pose ball. The script replaces the sit animation with an appropriate dance animation, shown in Figure P1.1. One script can animate only a single avatar at a time. In the section "Dance-Hall Days" later in this chapter, you'll see how to overcome this limitation by using multiple scripts to coordinate the animation of several avatars at once. This script, however defines constants that indicate how the avatar will be positioned when she sits on the dance ball. We use the same llSitTarget() function that the seating and teleport scripts used in Chapter 2 of SYW. Remember that specifying ZERO_VECTOR for the target will cancel any sit action, so the script must use some other value. The state_entry() event handler uses these constants to activate the sit target.
4
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
PROJECT 1
Figure P1.1: This striped pink pose ball replaces the default "sit" animation with a dance animation from the object's Inventory.
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Listing P1.1: Basic Pose Ball: Simple Single-Dancer Dance Ball vector gSitOffset = ; vector gSitRotation = ; key gDancer = NULL_KEY;
// use Euler degrees // who is dancing?
requestDance() { if (gDancer != llGetPermissionsKey() || (llGetPermissions()&PERMISSION_TRIGGER_ANIMATION) == 0) { llRequestPermissions(gDancer, PERMISSION_TRIGGER_ANIMATION); } else { startDance(); } } startDance() { llStopAnimation("sit"); llStartAnimation(llGetInventoryName(INVENTORY_ANIMATION, 0)); } stopDance() { llStopAnimation(llGetInventoryName(INVENTORY_ANIMATION, 0)); } default { on_rez(integer p) { llResetScript(); } state_entry() { llSitTarget(gSitOffset, llEuler2Rot(gSitRotation * DEG_TO_RAD)); }
5
changed(integer change) { if (change & CHANGED_LINK) { key av = llAvatarOnSitTarget(); if (llAvatarOnSitTarget() != NULL_KEY) { gDancer = av; requestDance(); } else { stopDance(); gDancer = NULL_KEY; } } } run_time_permissions(integer perms) { if (perms & PERMISSION_TRIGGER_ANIMATION) { startDance(); } else { llWhisper(0, llKey2Name(gDancer)+" can't dance!"); gDancer = NULL_KEY; } }
PROJECT 1
Dancing with M yself
Dance -Hall Days S ummary
}
The requestDance() function checks to see if it has permission to trigger animations for the avatar named by the gDancer variable. If not, it requests them with llRequestPermissions() as described in Chapter 2, eventually resulting in a call to the run_time_permissions() event handler in the script's default state. There the code checks to be sure that it actually got the PERMISSION_ TRIGGER_ANIMATION permission and calls startDance(). If the script already has the right permissions, the requestDance() function skips directly to startDance(). It doesn't hurt to ask for permissions even if you already have them: the run_time_permissions() event handler will still get called, but later derivatives of this script will want to take slightly different actions here. Another useful feature here is that when an avatar sits on a scripted object, it will automatically grant animation permissions to the script when asked; it doesn't need approval from the avatar's user, and it and retains permission once the avatar stands up. The script still must make the request, but it assumes that animation permissions will be granted, in which case it can go ahead and start the dance. This code checks to be certain so that it can reuse this code for animation scripts where the avatar will have the opportunity to refuse permission (in Listing P1.9, for instance). The startDance() function first stops the default sit animation and then starts the first animation in the object's inventory. (The section "Animation" in SYW Chapter 2 talks about starting and stopping animations.) Stopping sit may seem odd, but it makes sure your animation is played the way you want. No avatar key is passed into the function, because any script can animate only the single avatar for which permissions have been granted. The stopDance() function simply stops the first animation in inventory. Since the only reason the stopDance() function will be called is if the avatar stood up, there is no need to replace the animation with a neutral one; Second Life will start playing a stand animation automatically when the avatar unsits. This script uses only the default state. You could implement it using multiple states—for instance with separate dancing and idle states—but since the script is never actually doing anything except on user events, there would be very little savings in complexity or efficiency.
6
state_entry() sets the sit target for the prim, just like the first scripts in Chapter 2. You can adjust the seating position and rotation by updating the variables gSitOffset and gSitRotation at the beginning of each script, but remember that you must not use ZERO_VECTOR, or the sit target will be deactivated.
The changed() event handler uses the same logic used in Chapter 2 and elsewhere to detect when an avatar sits on or stands up from an object. When a sit is detected, it starts the process of requesting a dance; standing up stops the dance. The variable gDancer keeps track of the key of the avatar who requested animation: the script may not have received permission to animate the avatar yet. You can use this script as is for dancing, though adding some other features, such as some basic special effects (Listing P1.2) and voice control (Listing P1.3), will make it a little nicer. This script is perfect, however, for adding to a cushion of a chair or couch to play a nice sitting animation, to a pillow for a sleeping animation, or to a trapeze for a circus animation. The possibilities are endless.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
NOTE
PROJECT 1
There are four ways an avatar can interact with a pose ball: by sitting, by touching, by direct chat, or by using a popup dialog. Speaking via direct chat can be a bit awkward as an interface, especially if multiple balls are all listening on the same channel and vying for control of the avatar. Using a dialog, however, provides a great foundation for multiavatar dance balls; in the next section we show how to work around the limitation of each script animating only one avatar.
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
The Ubiquitous Pose Ball Extended If you've tried Listing P1.1, you'll see your avatar moving around your very visible pose ball. That's fine if you don't mind seeing your pose ball while you're dancing, but it can often get in the way. Listing P1.2 adds functions that are called when the dance starts and stops. Here we'll hide the pose ball when starting, and show it again when we're done.
NOTE Many of the scripts in this chapter can get pretty big. To save space and to avoid obscuring the interesting parts, the listings often show only the changes from the previous script. The full scripts are available at SYW HQ.
Listing P1.2: Pose Ball 2 (Auto-Hide) // changes over Listing P1.1 marked in Boldface vector vector key string vector
gSitOffset = ; gSitRotation = ; // we'll use Euler degrees gDancer= NULL_KEY; // who is dancing? gSitText = "Dance"; gSitTextColor = ; // white
requestDance() { if (gDancer != llGetPermissionsKey() || (llGetPermissions()&PERMISSION_TRIGGER_ANIMATION) == 0) { llRequestPermissions(gDancer, PERMISSION_TRIGGER_ANIMATION); } else { startDance(); } }
7
danceStarted() { llSetAlpha(0.0, ALL_SIDES); llSetText("", gSitTextColor, 1.0); } PROJECT 1
danceStopped() { llSetAlpha(1.0, ALL_SIDES); llSetText(gSitText, gSitTextColor, 1.0); }
Dancing with M yself
Dance -Hall Days S ummary
startDance() { danceStarted(); llStopAnimation("sit"); llStartAnimation(llGetInventoryName(INVENTORY_ANIMATION, 0)); } stopDance() { llStopAnimation(llGetInventoryName(INVENTORY_ANIMATION, 0)); danceStopped(); } default { // on_rez(), changed(), run_time_permissions() from Listing P1.1 //... only state_entry() changes for P1.2 state_entry() { danceStopped(); llSitTarget(gSitOffset, llEuler2Rot(gSitRotation * DEG_TO_RAD)); llSetSitText(gSitText); } }
A new global variable, gSitText, defines the text to use for floating text over the pose ball and the text you'll see in place of the word "sit" in the viewer's pie menu. The variable gSitTextColor is used when setting the floating text to the right color. The new danceStarted() function hides the object by setting all sides to full transparency and turning off the floating text. The danceStopped() function does the opposite, setting the object to be fully opaque and restoring the floating text. We add calls to these methods in startDance() and stopDance() as well as in the state_entry() event handler to make certain that the object is visible when first rezzed. For good measure, state_entry() also uses llSetSitText() to change the label on the pie menu of the viewer's user interface from "Sit" to "Dance". The result is a much nicer effect for dancing, where the balls aren't visible when you don't need them to be. If your ball is larger for some aesthetic reason, you may also want to turn it phantom during the dance so avatars don't bump into it; you can do this by using llSetStatus(STATUS_PHANTOM, TRUE).
Pose Ball with Voice Commands It is common to be able to hide and show pose balls using chat commands. Listing P1.3 lets you minimize visual clutter in a scene when you aren't using the poses. Imagine a deserted island paradise . . . littered with dances left over from last night's dance party (Figure P1.2). Chat /99hide to make them all disappear!
8
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
PROJECT 1 PROJECT 2 PROJECT 3
Figure P1.2: Pose-ball litter can be removed with a chat command.
PROJECT 4 PROJECT 5 APPENDIX D
Listing P1.3: Basic Listen Ball // changes over Listing P1.2 marked in Boldface integer vector vector key string vector // // // // //
gControlCh = 99; gSitOffset = ; gSitRotation = ; gDancer= NULL_KEY; gSitText = "Dance"; gSitTextColor = ;
requestDance() danceStarted() danceStopped() startDance() stopDance()
from from from from from
Listing Listing Listing Listing Listing
// Which channel should we listen to? // we'll use Euler degrees // who is dancing? // white
P1.2 P1.2 P1.2 P1.2 P1.2
default { // on_rez(), changed(), run_time_permissions() from Listing P1.1 state_entry() { danceStopped(); llSitTarget(gSitOffset, llEuler2Rot(gSitRotation * DEG_TO_RAD)); llSetSitText(gSitText); llListen(gControlCh, "", NULL_KEY, ""); // ADDED } listen(integer ch, string name, key id, string m) { if (m == "show") { danceStopped(); } else if (m == "hide") { danceStarted(); } else if (m == "reset") { if (id == llGetOwner()) { llResetScript(); } } } }
9
The gControlCh variable indicates which channel to listen for commands on. The state_ entry() event handler adds a broad listener on the control channel. The new listen() event handler responds to the commands show, hide, and (for the owner only) reset. PROJECT 1
A More-Powerful Chattable Pose Ball Dancing with M yself
Dance -Hall Days S ummary
Here's a silly but pedagogically useful extension to Listing P1.3. Listing P1.4 adds dance and stop chat commands. When you are dancing, just type /99stop to stop dancing and be released (unseated) from the dance ball. The intriguing thing about this version is that you don't actually have to sit on the ball to dance: now you can just be standing within 20m of the ball and type /99dance to be animated!
Listing P1.4: Extended Listen Ball // changes over Listing P1.3 marked in Boldface // global variables from Listing P1.3 // requestDance() from Listing P1.2 // danceStarted() from Listing P1.2 // danceStopped() from Listing P1.2 // startDance() from Listing P1.2 stopDance() { llStopAnimation(llGetInventoryName(INVENTORY_ANIMATION, 0)); key seated = llAvatarOnSitTarget(); if (seated == NULL_KEY) { llStartAnimation("stand"); } else { llUnSit(seated); } danceStopped(); } default { // on_rez(), changed(), run_time_permissions() from Listing P1.1 // state_entry() from Listing P1.3 listen(integer ch, string name, key id, string m) { if (m == "show") { danceStopped(); } else if (m == "hide") { danceStarted(); } else if (m == "dance") { if (gDancer == NULL_KEY) { gDancer = id; requestDance(); } } else if (m == "stop") { if (id == gDancer) { stopDance(); gDancer = NULL_KEY; } } else if (m == "reset") { if (id == llGetOwner()) { llResetScript(); } } } }
10
The only tricky part here is deciding how to be certain that the dance actually ends in stopDance(). If there is an avatar seated, the script will unsit her; otherwise it will play the stand animation to make sure the dance actually stops.
CH02 ADD. CH06 ADD. CH08 ADD.
WARNING
CH12 ADD. CHAPTER 16
If you stop all of the animations being played on an avatar without supplying a new one, current viewers will usually just keep playing the last one. Of course, any avatar movement will fix this, but if you really want to be certain that you’ve stopped an avatar’s animation completely, it is safer to stop the animation and start playing whatever the built-in animation should be, such as “sit” or “stand”. PROJECT 1
When you try our this script you may be asked to allow the object to animate your avatar if you use the dance command. Since you might not be sitting on it, you have to manually grant animation permissions. However, the script does retain permissions until someone else uses it or it is reset, so you will not be asked repeatedly if you are the only one using it, even if you use only the voice commands. There are some subtle problems with this approach, however. First, chat commands aren't very specific: although you could rez a dozen of the early dance balls to support a party, you would start having problems when someone tried using /99dance with lots of dance balls around, all vying for control of the speaking avatar. Furthermore, now that your avatar doesn't actually have to be sitting on the object, you not only lose control of where the avatar is getting animated (no sit target in effect!), but the avatar may wander off dancing! We'll address both of these issues when we introduce dance balls that can support many dancers, in the section "Dance-Hall Days."
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Click to Dance Listing P1.5 shows a modification of Listing P1.4 that uses the touch action to turn dancing on and off. The first time you click on the ball, your avatar will start dancing. Click a second time to stop. This is starting to look very similar to the dance balls offered by clubs throughout Second Life; the main difference is that this one supports only a single dancer and a single dance. But don't worry—multiple dancers and dances are added into the mix starting at Listing P1.8.
Listing P1.5: Solo Dance Ball // changes over Listing P1.4 marked in Boldface // global variables from Listing P1.3 // requestDance() from Listing P1.2 danceStarted() { // llSetAlpha(0.0, ALL_SIDES); // DELETED llSetText("Dancing", gSitTextColor, 1.0); } danceStopped() { // llSetAlpha(1.0, ALL_SIDES); // DELETED llSetText(gSitText, gSitTextColor, 1.0); }
11
// startDance()
PROJECT 1
Dancing with M yself
Dance -Hall Days S ummary
from Listing P1.2
stopDance() { llStopAnimation(llGetInventoryName(INVENTORY_ANIMATION, 0)); llStartAnimation("stand"); // ADD danceStopped(); } default { // on_rez(), changed(), run_time_permissions() from Listing P1.1 // state_entry() from Listing P1.3 // listen() from Listing P1.4 touch_end(integer c) { key av = llDetectedKey(0); key oldav = llGetPermissionsKey(); if (gDancer != NULL_KEY && oldav != NULL_KEY) { // if someone was already dancing, stop them stopDance(); gDancer = NULL_KEY; } if (av != gDancer) { // if we weren't dancing already, start us gDancer = av; requestDance(av); } } }
This version changes the danceStarted() and danceStopped() functions to leave the ball visible so that the avatar can find it to stop dancing. As in the chat-controlled dance ball, it also always plays the built-in stand animation after stopping the dance so that the avatar will actually stop. The touch_end() event handler serves the same function as the previous sit detector and chatcommand-listener versions. An avatar who touches the ball will be animated, and if that avatar was already dancing, the dance ball with stop the avatar.
Dance HUD A nice variation on Listing P1.5 makes the dance ball a wearable object instead of one you rez into the world. Listing P1.6 is a version that works as a HUD, though similar scripts would work for any wearable object.
BUILD NOTE Make a cube 0.2m on each side, colored black and 50% transparent. Add the animation you’d like into its inventory, add the script in Listing P1.6, then attach the cube to an unused HUD location.
12
Listing P1.6: Solo Dance HUD
CH02 ADD.
// changes to Listing P1.5 marked in Boldface
CH06 ADD.
vector DANCING_COLOR = ; // green vector IDLE_COLOR = ; // black // global variables from Listing P1.3
CH08 ADD. CH12 ADD. CHAPTER 16
// requestDance() from Listing P1.2 danceStarted() { llSetColor(DANCING_COLOR, ALL_SIDES); } danceStopped() { llSetColor(IDLE_COLOR, ALL_SIDES); } // startDance() from Listing P1.2 // stopDance() from Listing P1.5 // default state from Listing P1.5
PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4
Listing P1.5 was already almost all we needed—all the right behavior was there already. Listing P1.6 simply changed the color of the object to indicate if it is running your dance (green) or not (black). Figure P1.3 shows the HUD in action.
PROJECT 5 APPENDIX D
Figure P1.3: HUDs can control animation, too.
Special Effects Obviously, you can put all sorts of other effects into danceStarted() and danceStopped(). Listing P1.7 shows a variation on the basic pose ball; it fires up an explosion of particles while you're dancing.
13
Listing P1.7: Pose Ball with Special Effects // changes over Listings P1.2, P1.3, P1.4, or P1.5 marked in Boldface
PROJECT 1
Dancing with M yself
Dance -Hall Days S ummary
danceStarted() { llSetAlpha(0.0, ALL_SIDES); llSetText("", gSitTextColor, 1.0); llParticleSystem([PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | PSYS_PART_EMISSIVE_MASK, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE, PSYS_PART_START_COLOR, ]); } danceStopped() { llSetAlpha(1.0, ALL_SIDES); llSetText(gSitText, gSitTextColor, 1.0); llParticleSystem([]); }
The changes are obvious: just start the particle system when the dance starts and cancel it when it ends. This is one place where HUDs are different: particle systems have no effect in HUD-attached objects because the HUD is not visible to anyone except its user. Some fun things to try are to start the pose ball rotating with llTargetOmega(), move the ball around, or trigger some other external actions (Lights! Music!). The triggered events don't have to be visible: a "camping" dance pad is essentially a simple dance ball like any of the ones presented already except that it pays you a bit of money every once in a while when you are actively dancing; see Chapter 13's "Campers" section. Camping objects are commonly used to attract people to artificially increase the popularity of a location, making it seem more interesting than it perhaps actually is.
DANCE-HALL DAYS The scripts so far have talked only about fixtures that animate a single avatar. And while there are certainly uses for that, dancing really calls out for more-flexible solutions: if you are a club owner, you want to provide lots of dances for your clients, even if they are brand-new to Second Life. This section introduces a variety of ways to animate multiple avatars at once, with the goal of making it easy and inexpensive to dance. Carefully coordinated dances need multiple prims, while dances that are approximately coordinated can be handled in a single prim with multiple scripts.
Line Dancing If you rezzed a bunch of single dance balls, you could have a pretty nice party. In real life you dance along with other people—in the real world a beat is provided by the music to keep everyone together. This isn't possible (yet) in Second Life, but it is possible to synchronize the animations of multiple avatars.
14
The biggest single roadblock to synchronized dancing is that a script can animate only a single avatar at a time. Obviously, then, you need one script per dancer. But recall that scripts are, in theory, all running
in parallel—synchronizing inherently asynchronous activities is always problematic, so you need one script to rule them all. It is theoretically possible to use chat to do this sort of coordination between scripted objects, but since chat is much slower than link messaging (see Chapter 3), you should connect objects as linkset if you can. Figure P1.4 shows linked prims keeping all dancers in sync. You can also use link messages to communicate between scripts in the same prim, as we will do to great effect later this chapter.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5
Figure P1.4: Line dancing can be managed with a set of linked pose balls.
APPENDIX D
The next example uses as many dance balls as you would like, under control of a single control script. Each dancer sits on another ball (containing Listing P1.9), and they are kept in sync by the brain script (Listing 1.8) residing in one of the balls.
Listing P1.8: Brain to Support Resync of All Prims in the Linkset string SYNC="sync"; integer gControlCh = 99; sync() { llSay(0, "Synchronizing"); llMessageLinked(LINK_SET, 0, SYNC, NULL_KEY); } default { on_rez(integer p) { llResetScript(); } state_entry() { llListen(gControlCh, "", NULL_KEY, ""); } listen(integer ch, string name, key id, string m) { if (m == SYNC) { sync(); } } link_message(integer linknum, integer num, string m, key id) { if (m == "start") { sync(); } } }
15
PROJECT 1
When the brain script hears someone chat sync on the control channel or when any script in the linkset sends a link message of start, it will call the sync() function. That function simply sends the link message sync to every script in every object in the set—that is, all the dance balls linked together. The heavy lifting is done by the dancer scripts in each ball. This will look very familiar, as it is pretty much the same logic as for the simple balls.
Listing P1.9: Synchronized Dance Ball Dancing with M yself
ance -Hall D Days S ummary
string SYNC="sync"; vector gSitOffset = ; vector gSitRotation = ; // Euler degrees string gSitText = "Dance"; vector gSitTextColor = ; integer gControlCh = 99; key permKey = NULL_KEY; key dancKey = NULL_KEY;
// non-null when we've asked for permission // non-null when we've received permission
danceStarted() { llSetAlpha(0.0, ALL_SIDES); llSetText("", gSitTextColor, 1.0); } danceStopped() { llSetAlpha(1.0, ALL_SIDES); llSetText(gSitText, gSitTextColor, 1.0); } requestDance(key av) { permKey = av; if (av != llGetPermissionsKey() || (llGetPermissions()&PERMISSION_TRIGGER_ANIMATION) == 0) { llRequestPermissions(av, PERMISSION_TRIGGER_ANIMATION); } else { dancKey = av; startDance(); } } startDance() { danceStarted(); llStopAnimation("sit"); //llStartAnimation(llGetInventoryName(INVENTORY_ANIMATION, 0)); llMessageLinked(LINK_ROOT, 0, "start", llAvatarOnSitTarget()); // we just wait for the sync to come back } stopDance() { dancKey = NULL_KEY; permKey = NULL_KEY; llStopAnimation(llGetInventoryName(INVENTORY_ANIMATION, 0)); key seated = llAvatarOnSitTarget(); if (seated == NULL_KEY) { llStartAnimation("stand"); } else { llUnSit(seated); } danceStopped();
16 }
syncDance() { key seated = llAvatarOnSitTarget(); if (seated != NULL_KEY) { if (llGetPermissionsKey() == seated && (llGetPermissions()&PERMISSION_TRIGGER_ANIMATION) != 0) { string anim = llGetInventoryName(INVENTORY_ANIMATION, 0); llStopAnimation(anim); llStartAnimation("stand"); llSleep(0.1); llStopAnimation("stand"); llStartAnimation(anim); } } } default { on_rez(integer p) { llResetScript(); } state_entry() { danceStopped(); llSitTarget(gSitOffset, llEuler2Rot(gSitRotation * DEG_TO_RAD)); llSetSitText(gSitText); } changed(integer change) { if (change & CHANGED_LINK) { key av = llAvatarOnSitTarget(); if (llAvatarOnSitTarget() != NULL_KEY) { requestDance(av); } else { if (permKey != NULL_KEY) { stopDance(); } else { // ignore - probably a real object relink } } } } run_time_permissions(integer perms) { if (perms & PERMISSION_TRIGGER_ANIMATION) { dancKey = permKey; startDance(); } } link_message(integer linknum, integer num, string m, key id) { if (m == SYNC) { syncDance(); } } }
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
This version of the script explicitly tracks the avatar through the stages from sitting and requesting permissions to actually animating; permKey identifies the avatar for whom permissions have been requested, dancKey identifies the avatar actually dancing. The other major difference from previous scripts is that startDance() no longer actually starts the animation! Rather, it prepares the avatar and sends a link message start to the brain. The brain then sends the sync message to all the balls in the linkset. On receipt of the sync link message, the dancer script then calls the syncDance() function. This new function explicitly stops the current dance animation, replaces it with the built-in stand animation, then restarts the dance. This step is necessary because it forces all viewers to restart the dance animation! Recall
17
PROJECT 1
Dancing with M yself
Dance -Hall Days S ummary
that animation is entirely a client-side effect; without an intervening animation, the current viewer will often keep playing the current loop without resetting. Forcing a brief stand animation in the middle makes sure that everyone at least starts out seeing the same thing, subject to the usual network limitations. Even so, different viewers can end up playing animations at different rates and will probably lose synchronization if the user's camera looks away for any length of time.
BUILD NOTE Make a dance ball of your choice that contains the dancer script in Listing P1.9. Rez as many copies as you would like to try out: four or five will do. Add the script from Listing P1.8 to any one of them. Link the balls together. Although not required, it is a good idea to make the ball with the brain script the root prim: select it last when you link.
This solution works well, but still requires one prim per dancer for seating positions. As with individual pose balls, this is the only way to get fine control over dancer positioning. There is one extremely important consideration for synchronized linked-prim animation balls. Couples dance balls are scripted almost exactly like the preceding example. The crucial difference is that couples animations are packages with different animations in each of the two or more linked prims. You can use this technique to animate and precisely position a lead part and a follow part for a tango or a waltz, use scripted prim movement to animate the balls themselves to build a fully choreographed square dance, and, obviously, you use the same technique to arrange for coordinated hugs, kisses, and more-intimate activities. Figure P1.5 shows two avatars dancing together using this technique.
Figure P1.5: Couples dances require carefully coordinated timing and placement that can be achieved only with multiple prims.
18
CH02 ADD.
Many Dancers, One Prim
CH06 ADD.
If you don't need careful positioning you can do away with the separate prims altogether. Permissions are associated with a single script at a time, not the containing prim; that means you can animate the same number of dancers with 10 dancer scripts in one prim as you can with 10 dancer scripts in 10 linked balls. An even bigger benefit is that since all the scripts are in the same prim, they all have access to all the animations in the same inventory! With many new high-quality dances currently costing upward of L$200, this can represent an enormous savings for a dance club over simple dance balls. The big dance ball floating in the middle of the club scene of Figure P1.6 works in exactly this way.
CH08 ADD. CH12 ADD. CHAPTER 16
PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Figure P1.6: One group dance ball can animate multiple avatars.
The brain script in Listing P1.10 needs to be more complex than Listing P1.8. Listing P1.8 needed only to act as a synchronizer, but P1.10 also has to manage requests, keeping track of which avatars are dancing.
Listing P1.10: Brain to Support Multiple Dancers in One Prim list gSlots; integer gSlotCount; integer ALL_SCRIPTS = -1; updateText() { llSetText("Dancing "+ (string) llGetListLength(gSlots) + "/" + (string) gSlotCount, , 1.0); }
19
PROJECT 1
Dancing with M yself
Dance -Hall Days
default { on_rez(integer param) { llResetScript(); } state_entry() { gSlotCount = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; updateText(); } touch_start(integer count) { integer i; for (i = 0; i < count; i++) { key dKey = llDetectedKey(i); integer sloti = llListFindList(gSlots, [dKey]); if (sloti != -1) { // already dancing? // don't reclaim until we get the "reset" message llMessageLinked(LINK_THIS, ALL_SCRIPTS, "stop", dKey); } else { // not dancing, so we start if (llGetListLength(gSlots) == gSlotCount) { llSay(0, "Sorry "+llKey2Name(dKey)+ ", the dance machine is full"); } else { gSlots += [dKey]; // send "start" dKey to dancer 0 llMessageLinked(LINK_THIS, 0, "start", dKey); updateText(); } }
S ummary
} } link_message(integer linknum, integer num, string str, key id) { if (str == "reset") { integer sloti = llListFindList(gSlots, [id]); if (sloti != -1) { // if we know about it… gSlots = llDeleteSubList(gSlots, sloti, sloti); updateText(); } } } }
The gSlots variable is a list of keys of all the avatars that are currently being animated. The gSlotCount variable is the number of dancers the dance ball can support: it is initialized in state_ entry() simply as the number of scripts in inventory minus one, assuming that the prim contains only the (one) brain and a bunch of dancer scripts. The script also defines the ALL_SCRIPTS constant as a linkmessage argument that allows the other scripts to recognize when a message is being sent to all the dancer scripts or is intended to be more personal. There is a really clever trick here: the script does not attempt to keep track of which dancer script is animating which avatar. Instead it keeps a simple list of animated avatars. When it knows that some script already knows about a particular avatar, it can send a link message with ALL_SCRIPTS to ask all the dancer scripts to consider the message. But this approach cannot work to find an open slot. Instead, if the brain knows that there is an unoccupied script, it sends a "start" message with the avatar key to only the first dancer script. If that script is available, it will take responsibility for the avatar. But if it is already busy, it forwards the message to the next script in the chain until an empty slot is found.
20
Most of the tricky logic is in the touch_start() event handler: on a touch event, it checks to see if the avatar is already being animated by checking whether that avatar's key is cached in gSlots. If so, it blasts a link message to all the scripts to stop the identified avatar. It waits until the appropriate dancer script has released the avatar before it reclaims the slot. If the avatar isn't already in the list and there is at least one open slot, it sends a "start" request to the first script in the chain by specifying the 0 as the link message's integer parameter.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
The only time the brain needs to receive a link message is when a dancer notifies the brain that an avatar has left. The script in Listing P1.11 shows how to set up the dancer script so that it reports when an avatar leaves.
Listing P1.11: Dancer in a Multidancer Single Prim integer string integer string
ALL_SCRIPTS = -1; gBaseScriptName = "Dancer "; // name prefix for dancer scripts gDancerX; // which dancer script is this? gTheDance; // name of the dance to use
key permKey = NULL_KEY; key dancKey = NULL_KEY;
// non-null when we've asked for permission // non-null when we've received permission
resetSlot() { llMessageLinked(LINK_THIS, gDancerX, "reset", permKey); llOwnerSay("resetting "+(string)gDancerX); permKey = NULL_KEY; dancKey = NULL_KEY; }
PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
requestDance(key av) { permKey = av; llRequestPermissions(av, PERMISSION_TRIGGER_ANIMATION); } startDance() { llStartAnimation(gTheDance); } stopDance() { llStopAnimation(gTheDance); } default { on_rez(integer param) { llResetScript(); } state_entry() { string sname = llGetScriptName(); list splitname = llParseString2List(sname, [gBaseScriptName], []); gDancerX = llList2Integer(splitname, 0); gTheDance = llGetInventoryName(INVENTORY_ANIMATION, 0); }
21
link_message(integer linknum, integer num, string str, key id) { if (num == ALL_SCRIPTS) { // message to everyone? if (str == "stop") { if (id == permKey) { // we've got the right av? if (dancKey == id) { // running the av? stopDance(); // stop it } resetSlot(); // reclaim slot } } else if (str == "allstop") { if (dancKey != NULL_KEY) { // running the av? stopDance(); } resetSlot(); // reclaim slot } } else { // message to the chain of dancer scripts if (num == gDancerX) { // are we the right script? if (str == "start") { if (permKey == NULL_KEY) { // we're empty? requestDance(id); // start the dance! } else { // busy? // try next script llMessageLinked(linknum, num+1, str, id); } } } } } run_time_permissions(integer perms) { if (perms & PERMISSION_TRIGGER_ANIMATION) { dancKey = permKey; startDance(); } else { resetSlot(); // if we didn't get the permissions, bail } }
PROJECT 1
Dancing with M yself
Dance -Hall Days S ummary
}
This script needs to keep track of exactly which dancer script it is so that it can be precisely addressed using link messages. The state_entry() event handler calculates the new variable gDancerX by assuming that the dancer scripts are sequentially numbered starting with 0 (or no number), looking at its own name using llGetScriptName(), and parsing an integer out of the second component of the name. For instance, if the name of the script is Dancer 5, then gDancerX will be set to 5.
DEFINITION string llGetScriptName() Returns the name of the script in inventory.
The script calls the new resetSlot() function when the script has stopped animating a particular avatar. In addition to forgetting about the avatar it had been controlling, it also sends a "reset" link message with the key of the avatar just released so that the brain can forget about the avatar as well.
22
The other half of the rather tricky link-messaging logic is here in the link_message() event handler. First, the num parameter is used to determine if the message is intended for all dancer scripts or a particular one. If it is directed to ALL_SCRIPTS, then there are several possibilities: if the message is "stop", the script has the identified avatar, and the avatar is currently dancing, then the script sends a message back to the brain to indicate the slot can be reclaimed. If the message is "allstop" and the script is running any avatar, then the script stops it just as though it had been specifically addressed with a "stop" message. If the message was directed specifically to the current script, then it looks at the message. If the message is "start" and the script is not currently running an avatar, then it starts up the identified avatar using requestDance(key). If the script is already busy, then it resends the message directed to the next script number down the chain.
BUILD NOTE Make a single object. Drop in one copy of the script from Listing P1.10 and any number of copies of P1.11. Important: all copies of Listing P1.11 must be named to match the string defined as gBaseScriptName in the script. This will happen automatically if you name the script "Dancer" and keep dragging copies in, as Second Life will tack on an increasing number on the end—for instance "Dancer", then "Dancer 1", "Dancer 2", etc. It is OK if the first one is just "Dancer"; it is critical that the object contain only one brain and sequentially numbered dancer scripts, along with the dance animation you want to use.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
Error Checking What happens if an avatar clicks on the dance ball and decides not to give the script permission to animate? What happens if a dancing avatar leaves the area by wandering off, teleporting away, or even crashing? The next version of the dancer script, shown in Listing P1.12, detects these cases for a much more robust dance ball.
Listing P1.12: Dancer with Error Checking // changes over Listing P1.11 marked in Boldface // Constants and variables // New constants float TIMEOUT = 10.0; // float MAX_DIST = 50.0; // float SENSE_FREQ = 30.0;//
from Listing P1.11 ADD: how long should we wait for problems? ADD: how far away may the av get? ADD: how often should we look for the av?
resetSlot() { llMessageLinked(LINK_THIS, gDancerX, "reset", permKey); llOwnerSay("resetting "+(string)gDancerX); llSetTimerEvent(0.0); // ADD: cancel outstanding timer events llSensorRemove(); // ADD: Stop the sensor permKey = NULL_KEY; dancKey = NULL_KEY; }
23
requestDance(key av) { permKey = av; llRequestPermissions(av, PERMISSION_TRIGGER_ANIMATION); llSetTimerEvent(TIMEOUT); // ADD } PROJECT 1
Dancing with M yself
Dance -Hall Days
// startDance() from Listing P1.11 // stopDance() from Listing P1.11 default { // on_rez() from Listing P1.11 // state_entry() from Listing P1.11 // link_message() from Listing P1.11
S ummary
run_time_permissions(integer perms) { if (perms & PERMISSION_TRIGGER_ANIMATION) { dancKey = permKey; llSetTimerEvent(0.0); // ADD: cancel timer llSensorRepeat("", dancKey, AGENT, MAX_DIST, PI, SENSE_FREQ); startDance(); } else { resetSlot(); } } timer() { // ADD: if we TIMEOUT, bail resetSlot(); } no_sensor() { // ADD: if we don't see the av, reset! stopDance(); resetSlot(); } }
First the script detects when it has asked for permission to animate the avatar but didn't get it in a reasonable amount of time. The new constant TIMEOUT indicates how long the script is willing to wait. The script sets a timer for TIMEOUT seconds in requestDance() just after requesting animation permissions. If the timer expires, the timer() event handler gets called and the script bails out and tells the brain to reclaim the slot. If it gets the permissions successfully, then the timer gets canceled to prevent the event from firing. The other new code looks to see if the dancing avatar is still nearby. As soon as permissions are granted, the script sets up a repeating sensor to look in the surrounding MAX_DIST meters every SENSE_FREQ seconds for the controlled avatar. (Sensors are described in Chapter 5 of SYW.) This does not create much lag because the filter is so specific—it looks for the avatar with only the specific key dancKey. It would be a bad idea to do the scanning in the brain script, not only because it would have to use a much broader and thus more costly filter, but also because sensor scans return at most the 16 objects closest to the sensor that pass the filter criteria—insufficient for a crowded environment that could easily have more than 16 avatars present, all dancing. If the script ever gets a no_sensor() event, then the avatar has left the vicinity and the script will attempt* to stop them dancing and ask the brain to reclaim the dance slot.
24
* A s of this writing, once animation permission has been granted, a script is able to animate or stop the animation of its avatar as long as the avatar is on the same sim as the script. However, the results when the avatar leaves the original sim are unpredictable, even when the avatar stays nearby: sometimes the avatar will stop by itself when it gets out of range, and sometimes it will get stuck in its animation.
A Complete Dance-Hall Animator: Now Synchronized! Listing P1.13 is a multiavatar pose-ball variation that will keep all your guests dancing in sync, rotating through a selection of animations in the ball. Add this script to the root prim, as a second script with the one from Listing P1.10.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
Listing P1.13: Multidance Brain float CHANGE_FREQ = 15.0; // ADD: how often to change the dance? integer currentDance = 0; // ADD: which dance is running now? updateText() { string dancename = llGetInventoryName(INVENTORY_ANIMATION, currentDance); llSetText("Dancing "+ dancename + ", "+ (string) llGetListLength(gSlots) + "/" + (string) gSlotCount, , 1.0); }
PROJECT 1 PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
default { state_entry() { gSlotCount = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; dancecount = llGetInventoryNumber(INVENTORY_ANIMATION); updateText(); llSetTimerEvent(CHANGE_FREQ); // ADD: wake every x seconds } timer() { currentDance++; if (currentDance>=dancecount) { currentDance = 0; } llMessageLinked(LINK_THIS, ALL_SCRIPTS, "change", llGetInventoryName(INVENTORY_ANIMATION, currentDance)); updateText(); } }
This version of the brain adds a timer that goes off every CHANGE_FREQ seconds. With each call, the timer() event handler chooses the next dance and then sends a "change" message to all the dancer scripts with the name of the chosen dance passed as the key. This implicit casting is slightly tricky because the name will not be a valid key, but LSL doesn't care. For good measure, it also updates the floating text to indicate which dance is playing as well as how many gSlots are occupied. The only change required in the dance script (Listing P1.14) is to respond to the "change" link message by stopping the current dance and then starting the new one. As a pleasant side effect, as long as there are at least two dances in inventory, this script will resynchronize all the dancing avatars each time the dance switches.
25
Listing P1.14: Dancer Control // changes over Listing P1.12 marked in Boldface
PROJECT 1
Dancing with M yself
Dance -Hall Days S ummary
default { link_message(integer linknum, integer num, string str, key id) { if (num == -1) { if (str == "change") { // ADD: handle dance cycle if (dancKey != NULL_KEY) { stopDance(); } gTheDance = (string) id; // recast if (dancKey != NULL_KEY) { startDance(); } } else if (str == "stop") { If (id == permKey) { if (dancKey == id) { stopDance(); } resetSlot(); } } else if (str == "allstop") { if (dancKey != NULL_KEY) { stopDance(); } resetSlot(); } } else { // ... same as Listing P1.12 } } }
What Else? In Second Life parlance, a dance chimera, or chim (see Figure P1.7) is a multiavatar dance ball that you wear. Just build a dance ball like in Listings P1.13 and P1.14 except make it a bit smaller and make it nearly invisible (alpha close to 0.0). Wear it when you go dancing, and use it instead of the club's dances. Tell your friends to click on your right shoulder (or wherever you attached it) and you'll instantly look like a troupe of pro dancers! An alternative to click-and-dance is for the owner of the chim to invite nearby avatars, either by name or with a sensor and menu-based select (see the section "Land Security" in Chapter 6 for a start on this). A common alternative to offering synchronized dances is to offer the dancers a menu-based selection of dances. Instead of having the touch event start them dancing right away, pop up a menu of dance choices and "stop" using the same sort of system as in Listings P1.3 and P1.4, where you use chatted instructions instead of touches. You'll remember that the start and stop commands were problematic back then, but here you've got only a single object listening, so you don't have the same crosstalk issues. If, for some reason, you really need multiple dance balls of this sort, just use different chat channels. Combine these two ideas with a HUD-based control system, and you'll have something that looks like high-end commercial dance-chim products.
26
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
PROJECT 1 PROJECT 2
Figure P1.7: Dancing on a chim—one avatar’s HUD is animating five nearby avatars in synchrony.
PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
People have extended and combined these techniques in lots of other creative ways . One of the most common scripted animation systems is Miffy Fluffy's Multi-Love-Pose (or MLP) product*. It rezzes and positions simple pose balls that are used to position seated avatars precisely for each animation set, and then animates each avatar using separate scripts in the main prim. It has an extensible and customizable menu system for control and includes security and customization options. This package is used in a variety of furniture and settings—anywhere that that people would like to be able to choose their own poses and animations without having to "pose-ball hop" to transition.
SUMMARY What else would complete a scripted evening of romantic dancing? Add candles from Chapter 9, "Special Effects;" a radio playing soft music from Chapter 11, "Multimedia;" and maybe assure your privacy with a security system from Chapter 6, "Land Design and Management." Scripting an entire dance club? Set up some lighting effects and fog from Chapter 9, add a scripted tip jar from Chapter 13, "Money Makes the World Go 'Round," and include a welcome mat from Chapter 5, "Sensing the World."
* Y ou can get a free copy by searching for Miffy Fluffy's profile using the viewer search tool, opening the "picks" tab, and teleporting to the location associated with the "~ MLP ~" pick. There you can purchase an MLP device for L$0.
27
Bowling
PROJECT 2
Second Life supports a wide variety of sports, including soccer, football, fishing, scuba diving, sky diving, minigolf, sumo wrestling, bowling, fencing, darts, and billiards. Indoor and outdoor, single- and multiparticipant, the possibilities are endless. With gravity being something you can choose to ignore, and no worries about injury, the possibilities are even broader than in real life. Many of the sports require interactions with the physics engine. A soccer game, for example, would need a physical ball to kick around. A HUD would control the direction of the kick. The goal would need a thin, rectangular "screen" in front of it that uses llVolumeDetect() to detect when the ball scores (see Chapter 5, "Sensing the World" for more details). A pool game would need a set of 16 textured physical balls, six volume-detecting pockets in the table, two cues, and two HUDs. Notice the similarities? Rather than sampling a little bit from many different sports, this section goes into detail about structuring a (mostly) complete bowling game. It shows the ball, the pins, the glove (to hold the ball), and the HUD. It also presents a short inventory-giver script that gives the glove and the HUD to visitors who want to play.
THE PHYSICS OF BOWLING
PROJECT 2
The P hysics of B owling Complete A B owling S uite
The basic idea is simply a ball that rolls toward a set of physical pins, as shown in Figure P2.1. How hard can it be? Take the cannon-ball script from Chapter 7, "Physics and Vehicles," change the impulse vector to point toward the pins, and you're done, right?
S ummary
Figure P2.1: The bowling ball rolls toward the sensed pins.
Unfortunately it isn't that simple. How does the ball know where to aim? And how do the pins know when to pick themselves up? And what if you want the avatar to look like she's throwing the ball? In this section you'll see how to handle the first two issues. The last step requires a lot of (nonphysical) infrastructure, which is covered in the section "A Complete Bowling Suite."
The Bowling Ball Listing P2.1 shows a basic bowling-ball script. Because the ball and the pins are not linked, it uses a COMMS_ CHANNEL to communicate: the ball will tell the pins when it is rolling (so the pins can turn on physics) and when it is waiting for an avatar to touch it (so the pins can pick themselves up).
Listing P2.1: Basic Bowling Ball
4
integer string string string float float
COMMS_CHANNEL = 73509; BALL_ROLLING = "BallRolling"; BALL_WAITING = "BallWaitingForPickup"; DELIM = "|"; TIMER_RESET = 10.0; RADIUS = 10.0;
vector gPrevPos; integer gThrowStrength = 20; // Listing 2.4, moveTo(origin, destination) moveTo(vector origin, vector destination) { // removed jumpdist float dist = llVecDist(origin, destination); integer passes = llCeil( llLog(dist/10.0) / llLog(2.0) ); integer i; list params = [PRIM_POSITION, destination]; for (i=0; i<passes; i++) { params = (params=[]) + params + params; } llSetPrimitiveParams(params); }
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1
release_ball() // Release straight ahead { float mass = llGetMass(); vector throwDirection = ; llApplyImpulse(throwDirection, TRUE); } default { on_rez(integer _startParam) { llResetScript(); } touch_start(integer n) { gPrevPos = llGetPos(); llSetStatus(STATUS_PHYSICS, TRUE); llSensor("Bowling Pin", NULL_KEY, ACTIVE|PASSIVE, RADIUS, PI ); } timer() { llSetStatus(STATUS_PHYSICS, FALSE); llSleep(0.2); moveTo(llGetPos(), gPrevPos); llSay(COMMS_CHANNEL, BALL_WAITING); llSetTimerEvent(0); } sensor(integer numDetected) { llSay(COMMS_CHANNEL, BALL_ROLLING); llLookAt(llDetectedPos(0), 0.05, 0.2); // 0 is closest llSleep(0.5); release_ball(); // STRAIGHT AHEAD, because llLookAt handled llSetTimerEvent(TIMER_RESET); } }
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
BUILD NOTE The ball needs to be heavy enough to roll the distance and knock over the pins. The pins need to be named "Bowling Pin" so the ball can find them. They also need to be light enough to fall over, so make them hollow.
When the avatar touches the bowling ball, it records its current location, turns on physics, and makes a single sensing action to look for a "Bowling Pin". Sensors are covered in Chapter 5, but briefly, llSensor() looks for either a named object or specified key within a certain RADIUS in a specified
5
PROJECT 2
The P hysics of B owling Complete A B owling S uite S ummary
arc around the x-axis (PI means a sphere). If a bowling pin is sensed, the sensor() event will trigger, indicating how many pins were detected. You can ask for details with a suite of llDetected*() functions or llGetObjectDetails(). If there are several detections inside the region they will be sorted from closest (0) to farthest (numDetected-1). Here, the script checks only the position of the closest pin, using llDetectedPos(0). It then turns to face that position by calling llLookAt(). The script can then use a really straightforward call to llApplyImpulse() to release the ball—it doesn't need to do any quaternion math to rotate the throwDirection. (Because the bowling ball in the complete bowling suite needs to take into account the avatar's orientation, it can't get away with that trick: somewhere it needs to do some math.) A simple timer() returns the ball to its original location.
The Pins The script for the pins, shown in Listing P2.2, listens for the ball's status. If the ball is rolling (BALL_ ROLLING), it turns on physics, which allows the pins to fall over when the ball strikes. When the ball returns to its original position and announces BALL_WAITING, the pins turn off physics and return to their original positions.
Listing P2.2: Bowling Pins integer string string string
COMMS_CHANNEL = 73509; BALL_ROLLING = "BallRolling"; BALL_WAITING = "BallWaitingForPickup"; DELIM = "|";
vector gOrigin; rotation gOriginRot;
6
init() { gOrigin = llGetPos(); gOriginRot = llGetRot(); llSetStatus(STATUS_PHYSICS,FALSE); llListen(COMMS_CHANNEL, "", NULL_KEY, ""); } default { state_entry() { init(); } on_rez(integer _startParam) { llResetScript(); } listen(integer channel, string name, key id, string msg) { list components = llParseString2List(msg, [DELIM], []); string command = llList2String(components, 0); if (command == BALL_ROLLING) { vector currPos = llGetPos(); llSetStatus(STATUS_PHYSICS,TRUE); //llMoveToTarget(currPos, 0.1); //llSetTimerEvent(0.2); } else if (command == BALL_WAITING) { llSetStatus(STATUS_PHYSICS, FALSE); llSleep(0.1); llSetPos(gOrigin); //10 m max!!! llSetRot(gOriginRot); } }
//timer() { // llStopMoveToTarget(); // llSetTimerEvent(0); //}
CH02 ADD. CH06 ADD.
}
CH08 ADD.
Notice the six commented lines with a timer that controls a very short-duration llMoveToTarget(). Because SL considers interactions closer than 10cm together to be a collision, you can get strange interactions when you turn on physics. For example, pins close to the ground can "pop up" to 10cm above the ground and then fall over. Not exactly the behavior you were after! The likelihood of falling over seems to be worse when the pins are above a prim bowling alley than when they're on the true ground. They are commented out here because you may or may not need them, depending on your exact bowling setup. The call to llMoveToTarget() is just enough to stabilize the pins while physics is engaging. Just be sure to call llStopMoveToTarget() before the ball arrives!
CH12 ADD. CHAPTER 16 PROJECT 1
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
A COMPLETE BOWLING SUITE The preceding section showed the basic underpinnings of a bowling game—the physics required for the ball and the pins. However, putting together a complete bowling game requires a lot more. What if you want it to look like your avatar is holding and throwing the ball? As soon as you do that you have to deal with several complications. First you need to think about the avatar's position and rotation. Because adjusting rotation with the keyboard isn't very fine-grained, a good way to give the avatar fine-grained control over throw direction is by developing a HUD. The second complication is that you can't have your avatar actually pick up the ball and throw it. While the pickup is possible with llAttachToAvatar(), there is no way to detach the ball into the world; llDetachFromAvatar() puts the object in inventory. Another approach you might try is to wear a glove and use llCreateLink() to pick up the ball and llBreakLink() to release it. Unfortunately that doesn't work either: you can't create or break links in an attached object (just as you can't create or break links in an object the avatar is sitting on). A third thing you might try is to have a glove that broadcasts its position and have the ball move to "follow" the glove. Again, this approach doesn't work because the granularity of the glove's position is nowhere near good enough for the ball. The solution, therefore, is to have two balls: one that rolls along the ground and a second that is permanently attached to a glove. They each turn visible and invisible at the right moments. To make the bowling alley shown in Figure P2.2, you'll need a suite of six scripts in four objects: • The glove with two prims: the glove and the ball (Figure P2.2) • The ball on the ground • The pins (each identical but independent objects) • The HUD with three prims: the release button, the moving texture marking (where the fine aim is), and a third unscripted prim with the background behind the fine aim marker
7
PROJECT 2
The P hysics of B owling Complete A B owling S uite S ummary
Figure P2.2: A functioning bowling alley needs a lot of coordinated scripts.
If you want other people to play your game, you'll almost definitely want to add an inventory giver that hands out copies of the HUD and the glove, along with a notecard describing how to play the game. You could add scoring, a fancy ball-return, mechanisms to pick up the pins, and so forth. If you've got improvements or additions for this suite, please drop by SYW HQ and share! When the avatar attaches the HUD and wears the glove, the main behavior loop of the algorithm goes like this: 1. Touch the ball on the ground to pick it up. 2. Adjust your avatar's position and tweak the HUD's fine-aim control using Shift+left arrow and Shift+right arrow. 3. Click the HUD to release the ball. 4. The pins self-right when the ball returns. The scripts coordinate with communications using llSay() on a private channel. There's a lot of communication going on, and Figure P2.3 attempts to explain it all. As you read through the scripts, come back to this figure to see where a message might have come from or might be going. The ball on the ground has three states: default, ball_in_hand, and ball_rolling. The avatar directly interacts with the scripts using touch (to pick up the ball on the ground and release the ball on the HUD) and a keyboard control to adjust the fine aim. When touched, the ball on the ground announces it has been TOUCHED; the glove tells both balls and the HUD that it has successfully ATTACHED. Adjustments in the avatar's position are monitored by the glove and announced with AVATAR_DIRECTION, while fine-aim control is monitored by the HUD and announced with AIM_ OFFSET. When the avatar touches the HUD to throw the ball, the HUD announces RELEASED. When the ball starts rolling, it announces ROLLING, which tells the pins to get ready. When the ball is ready to be picked up again, it announces WAITING.
8
CH02 ADD.
Ball On Ground Touch Event
ATTACHED
default
CH06 ADD.
Pins
WAITING
CH08 ADD.
Cleanup
TOUCHED
CH12 ADD.
Set Physics
CHAPTER 16 PROJECT 1
AVATAR DIRECTION
Touch Event
HUD Aim
AIM_OFFSET
Glove Ball
ball_in_hand
Invisible Visible
RELEASE
Key Control
HUD Release
PROJECT 2 PROJECT 3
ball_rolling
ROLLING
PROJECT 4
ATTACHED
Timer
PROJECT 5
ATTACHED
Glove
APPENDIX D
AVATAR_DIRECTION
Figure P2.3: There are lots of different messages that travel between the scripts for coordination.
To support communications, each of the six scripts has the same set of declarations, shown in Listing P2.3. Please pick your own COMMS_CHANNEL; there are 4,294,967,294 you can choose from.
Listing P2.3: Declarations for All Bowling Scripts integer COMMS_CHANNEL
= 513761; // PICK YOUR OWN BIG NUMBER HERE!
string string string string string string string string string string
= = = = = = = = = =
REPORT_AIM_OFFSET AIM_OFFSET AVATAR_DIRECTION BALL_TOUCHED RELEASE_BALL BALL_ROLLING BALL_ATTACHED BALL_WAITING KILL_ALL DELIM
"ReportAimOffset"; "AimOffset"; "AvatarDirection"; "BallTouched"; "ReleaseBall"; "BallRolling"; "BallAttached"; "BallWaitingForPickup"; "KillAllObjects"; "|";
Bowling Pins Except for making sure that the declarations from Listing P2.3 are properly incorporated, the pins don't need to change from what you saw in Listing P2.2.
9
The Ball on the Ground PROJECT 2
The P hysics of B owling Complete A B owling S uite S ummary
The longest and most complex of the scripts is for the ball on the ground. It needs to do different things when it is waiting to be picked up, when it is in the hand, and when it's rolling down the lane. The script, therefore, has three explicit states and transitions between them at appropriate moments. For presentation, the script has been broken into three parts: one for each of the states (plus the declarations from Listing P2.3). You'll have to stitch them together or look for a complete copy on the website and at SYW HQ.
The Default State (Ball Waiting on the Ground) The default state models all the things the ball should do when it's sitting on the ground waiting to be picked up. Listing P2.4 shows this part of the complete script. Thus the first thing it does in the init() function is use BALL_WAITING to announce on the COMMS_CHANNEL that the ball is waiting. Figure P2.3 showed that when the Pins hear the message they clean themselves up, and when the Glove Ball hears the message it makes itself invisible.
Listing P2.4: Ball on the Ground—default State and Supporting Functions // Insert Declarations from Listing P2.3 integer integer float float vector integer vector integer
gUpdatedAimOffset; gUpdatedAvatarDirection; gAimOffset; gAvatarDirection; gAvatarPosition; gAttachPoint; gPrevPos; gThrowStrength = 20; // could augment HUD to collect
init() { llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 1]); llListen(COMMS_CHANNEL, "", NULL_KEY, ""); llSay(COMMS_CHANNEL, BALL_WAITING); llSetTimerEvent(5); }
10
default // ball waiting { on_rez(integer _startParam) { llResetScript(); init(); } state_entry() { init(); } touch_start(integer n) { llSay(COMMS_CHANNEL, BALL_TOUCHED); // wait for glove to say attached because avatar may // not have worn glove yet llSetTimerEvent(2); } timer() { // if no BALL_ATTACHED, tell them to wear glove llWhisper(0, "Do you have the glove?"); llSetTimerEvent(0); }
listen(integer channel, string name, key id, string msg ) { list components = llParseString2List(msg, [DELIM], []); string command = llList2String(components, 0);; if (command == BALL_ATTACHED) { llSetTimerEvent(0); state ball_in_hand; } else if (command == KILL_ALL) { // ball on ground becomes visible llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 1]); } }
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1
}
When a touch() event is triggered, it announces BALL_TOUCHED on the COMMS_CHANNEL; all the other objects will hear the message, but only the glove will do something. The script does not however, directly transition to the ball_in_hand state, because the avatar may not be wearing the glove. You don't want the script to get itself into the wrong state with no easy way to get out of it! The script, therefore, waits for the glove to acknowledge and announce that the ball is now attached with a BALL_ ATTACHED message. When the ball on the ground hears the message (in the listen() event handler), it then transitions to the ball_in_hand state. If on the other hand, the BALL_ATTACHED message never arrives, the script has a backup timer() event trigger to remind the user to wear the glove. Being a polite script, it also turns the reminder off right away.
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
This script also supports a KILL_ALL command (not mentioned in Figure P2.3 because the figure was already complex enough). When the avatar detaches the glove, the glove sends the KILL_ALL command, allowing all the other scripts to put themselves in to safe states, ready for the next time someone wants to play.
The ball_in_hand State Listing P2.5 shows the code for the ball_in_hand state. As soon as the avatar picks up the ball, the ball on the ground has to turn invisible. It does this in the state_entry() event handler with a call to llSetPrimitiveParams(). Note also that it calls llListen() again. The script needs to reset its listeners when it transitions from state to state because all of the old listen triggers are removed.
Listing P2.5: Ball on the Ground—ball_in_hand State state ball_in_hand { state_entry() { gUpdatedAimOffset = FALSE; gUpdatedAvatarDirection = FALSE; gAimOffset = 0.0; gAvatarDirection = 0.0; llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 0]); llListen(COMMS_CHANNEL, "", NULL_KEY, ""); llSay(COMMS_CHANNEL, REPORT_AIM_OFFSET); } on_rez(integer _startParam) { //llOwnerSay("Weird case."); state default; }
11
listen(integer channel, string name, key id, string msg ) { list components = llParseString2List(msg, [DELIM], []); string command = llList2String(components, 0);; if (command == RELEASE_BALL) { if (gUpdatedAimOffset == TRUE && gUpdatedAvatarDirection == TRUE) { state ball_rolling; } } else if (command == AIM_OFFSET) { gUpdatedAimOffset = TRUE; gAimOffset = (float)llList2String(components, 1); } else if (command == AVATAR_DIRECTION) { // direction is in radians gAvatarDirection = (float)llList2String(components, 1); gAvatarPosition = (vector)llList2String(components, 2); gUpdatedAvatarDirection = TRUE; } else if (command == KILL_ALL) { state default; } }
PROJECT 2
The P hysics of B owling Complete A B owling S uite S ummary
}
This state is responsible for keeping track of whether the ball knows what direction to roll. Since it's not directly attached to either the HUD or the avatar, it has to be told. Therefore, when it starts it tells other objects to REPORT_AIM_OFFSET, and every time it hears the messages AIM_OFFSET or AVATAR_DIRECTION, it updates the information. When it hears the message RELEASE_BALL (from the HUD) it checks to make sure it has recent aim data from the avatar and the HUD, then transitions to the ball_rolling state. Note that if the aim data has not been updated, the user may have to click Release Ball on the HUD more than once. You could add a READY_TO_ROLL message from the ball to the HUD that would tell the HUD when to turn green. Alternatively, put in a timer() that waits until both values are true. You'll also notice a handler for the on_rez() event. Even though this script does its best to return to the default state when it can, it is quite possible that it got put away in the wrong state. Therefore, if it is rezzed into the wrong state it immediately transitions back to default.
The ball_rolling State The ball_rolling state really has only one job: to roll the ball. The section of the script shown in Listing P2.6 shows the ball_rolling state and its two support functions. It first makes sure the ball is visible—would you like to be hit by a 14-pound invisible bowling ball? Then, if the avatar is close enough, it announces BALL_ROLLING to the other objects and releases the ball using release_ball(). Note that this function has changed slightly from Listing P2.1; it uses the avatar's direction plus the HUD's fineaim control that were collected in the ball_in_hand state.
Listing P2.6: Ball on the Ground—ball_rolling State and Supporting Functions
12
// moveTo was from Listing 2.4 moveTo(vector origin, vector destination) { float dist = llVecDist(origin, destination); integer passes = llCeil( llLog(dist/10.0) / llLog(2.0) ); integer i; list params = [PRIM_POSITION, destination]; for (i=0; i<passes; i++) { params = (params=[]) + params + params; } llSetPrimitiveParams(params); }
release_ball( float direction ) { float mass = llGetMass(); gPrevPos = llGetPos(); llSetStatus(STATUS_PHYSICS, TRUE); llSleep(0.2); rotation aimDir = llEuler2Rot(); vector throwDirection = <mass*gThrowStrength,0,0> * aimDir; llPushObject(llGetKey(), throwDirection, ZERO_VECTOR, TRUE);
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1
} state ball_rolling { state_entry() { llListen(COMMS_CHANNEL, "", NULL_KEY, "" ); llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 1]); vector currPos = llGetPos(); float avDist = llVecDist(currPos, gAvatarPosition); if (avDist < 2) { llSay(COMMS_CHANNEL, BALL_ROLLING); float aimOffsetRadians; aimOffsetRadians = 10 * (gAimOffset - 0.5) * DEG_TO_RAD; release_ball(aimOffsetRadians + gAvatarDirection); llSetTimerEvent(10.0); } else { llSay(0, "Not close enough to bowling lane!"); state default; } } timer() { llSetStatus(STATUS_PHYSICS, FALSE); llSleep(0.2); moveTo(llGetPos(), gPrevPos); llSetRot(ZERO_ROTATION); state default; } on_rez(integer _startParam) { //llOwnerSay("Weird case."); state default; } listen(integer channel, string name, key id, string msg ) { list components = llParseString2List(msg, [DELIM], []); string command = llList2String(components, 0); if (command == KILL_ALL) { state default; } } }
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
The HUD's texture offset ranges from 0.0 to 1.0 (left to right), so 0.5 is the middle. These values are mapped to a range of –5 to +5 degrees with this call: aimOffsetRadians = 10 * (gAimOffset - 0.5) * DEG_TO_RAD;
It then sets a timer for the ball to return to its original position, gPrevPos. Note that you could use a sensor instead if you really trust the physics engine and are sure your ball will actually end up at the end of the alley. Recall that your ball had better be relatively heavy; otherwise it won't knock over the pins. If the throw is too light, the ball won't make it to the end of the alley. Too strong a throw, and your ball will fly over the end of the alley.
13
The Bowling Glove PROJECT 2
The glove object has two prims: the glove itself (as root) and the permanently attached ball (as child), as shown in Figure P2.4.
The P hysics of B owling Complete A B owling S uite S ummary
Figure P2.4: The bowling glove has a permanently attached ball.
BUILD NOTE Make a simple glove appropriately sized for a hand; insert Listing P2.7 into its Content folder. Make a ball about 20–25cm diameter and set its texture; insert Listing P2.8 into its Content folder. Don't worry about the ball's visibility—that is controlled by the script. Link the two prims together, with the glove as the root prim. Finally, wear it on your hand and make sure its rotation and orientation look right.
The Glove The glove's main task is to make sure it can "catch" the ball when it is picked up. It therefore needs to make sure it's attached to the avatar. It has two other tasks: reporting the avatar's position and orientation so the ball rolls in the right direction when it is released, and sending a message to the other objects to clean up after themselves when the avatar detaches the glove.
14
The first thing the script in Listing P2.7 does is check whether the glove is attached. It uses the function llGetAttached(), described in Chapter 2 of SYW, to find the attachPoint. While this isn't strictly necessary to attach the glove to the hand (ATTACH_LHAND or ATTACH_RHAND), it does look rather odd for a bowling ball to appear on a foot. If you're making a soccer game, however, you might check for ATTACH_LFOOT, ATTACH_RFOOT, ATTACH_HEAD, or anywhere else you explicitly want to show the ball attached to. Note that the script will still work wherever the glove is attached—even as a HUD! If you don't want this behavior, you could put the call to llListen() inside the test where it is attached, and it will simply ignore any effort to pick up the ball.
When the attachPoint is zero, the avatar has detached the glove and the script might only have the smallest moment to react before the script is completely deactivated, so it immediately sends KILL_ ALL to the other objects so they can clean up appropriately.
Listing P2.7: The Glove // Insert Declarations from Listing P2.3 init() { llListen(COMMS_CHANNEL, "", NULL_KEY, ""); } catch_attach() { integer attachPoint = llGetAttached(); if ( attachPoint == 0 ) { llSay(COMMS_CHANNEL,KILL_ALL); } else if ( attachPoint == ATTACH_LHAND || attachPoint == ATTACH_RHAND ) { // OK. Do nothing. Could put the llListen() here. } else { llWhisper(0,"Not attached to a hand"); } }
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
report_avatar_direction() { vector avatarPos = llGetRootPosition(); vector avatarRotEuler = llRot2Euler(llGetRootRotation()); float zAxis = avatarRotEuler.z; llSay(COMMS_CHANNEL, AVATAR_DIRECTION+DELIM+ (string)zAxis+DELIM+(string)avatarPos); } default { state_entry() { init(); catch_attach(); } on_rez(integer n) { llResetScript(); } attach(key k) { llResetScript(); } timer() { report_avatar_direction(); } listen(integer channel, string name, key id, string msg ) { list components = llParseString2List(msg, [DELIM], []); string command = llList2String(components, 0); if (command == BALL_TOUCHED) { llSay(COMMS_CHANNEL, BALL_ATTACHED); report_avatar_direction(); llSetTimerEvent(1); } else if (msg == REPORT_AIM_OFFSET) { report_avatar_direction(); } else if (command == BALL_ROLLING) { llSetTimerEvent(0); } } }
15
When the glove knows it is attached, it waits for the BALL_TOUCHED message from the ball on the ground. It promptly reports that it "caught" the ball by announcing BALL_ATTACHED on the COMMS_CHANNEL, then starts reporting the avatar's position and orientation in the report_avatar_ direction() function. Only the z-axis matters, because bowling balls are usually sent horizontally. PROJECT 2
The P hysics of B owling Complete A B owling S uite S ummary
When the ball on the ground announces it is rolling (with BALL_ROLLING), the only thing the glove needs to do is turn off the timer.
The Ball Hiding in the Glove The ball hiding in the glove is a remarkably simple script, shown in Listing P2.8, which makes the ball visible or invisible. When it hears the message BALL_ATTACHED from the glove, it makes the ball visible using an alpha of 1.0. When it hears BALL_ROLLING or BALL_WAITING, it hides itself. Note that this script doesn't need to catch the KILL_ALL message, because it is part of the glove and will therefore detach at the same time as Listing P2.7.
Listing P2.8: The Ball Hiding in the Glove // Insert Declarations from Listing P2.3 init() { llListen(COMMS_CHANNEL, "", NULL_KEY, ""); llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 0]); } default { state_entry() { init(); } on_rez(integer n) { llResetScript(); // will trigger state_entry() } listen(integer channel, string name, key id, string msg ) { list components = llParseString2List(msg, [DELIM], []); string command = llList2String(components, 0); if (command == BALL_ROLLING) { llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 0]); } else if (command == BALL_WAITING) { llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 0]); } else if (command == BALL_ATTACHED) { // only glove sends this command llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, , 1]); } } }
Bowling HUD The Bowling HUD described here provides two functions: allowing the avatar to release the ball and adjusting the aim of the throw. The HUD in Figure P2.5 has three prims: one for the red (or green) button, one for the thin red line that marks the fine aim, and one for the background image of the bowling pins.
16
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1
Figure P2.5: Three prims make up this HUD: the button, the red marker, and the bowling-pin picture background.
PROJECT 2 PROJECT 3 PROJECT 4 PROJECT 5 APPENDIX D
BUILD NOTE Make three boxes. Call one the "HUD release", sized to . Add two textures with display text, one called "PickUpBall" and one called "ReleaseBall". The script will switch both color and display text. Call the next the "HUD marker", sized to . Texture it with a mostly transparent texture that has a thin red line. Call the last one the "HUD background", sized the same as the marker, and texture it with a picture of the bowling pins. Place the marker in front of the background, and both of them above the release. Link them together so the release is the root prim. Finally, attach to your HUD at the bottom of your screen, adjust its position, make sure its size is right, and check that the marker is facing you rather than hiding behind the background.
HUD Ball-Release Button The script for the HUD ball-release button, shown in Listing P2.9, acts a lot like the pins and the ball in the glove. It listens for messages about what the ball is doing and changes the color of the button: BALL_ ATTACHED turns the HUD green and BALL_ROLLING or BALL_WAITING turns the HUD red. The script also switches the textures between ReleaseBall and PickUpBall.
Listing P2.9: The HUD Ball-Release Button // Insert Declarations from Listing P2.3 integer gEnabled = TRUE;
// is the ball in hand?
colourHUD() { // very similar to Listing 2.12, but adds llSetTexture if (gEnabled) { llSetColor(, ALL_SIDES); llSetTexture("ReleaseBall", ALL_SIDES); } else { llSetColor(, ALL_SIDES); llSetTexture("PickUpBall", ALL_SIDES); } }
17
PROJECT 2
The P hysics of B owling Complete A B owling S uite S ummary
init() { if (llGetAttached() > 30) { llListen(COMMS_CHANNEL, "", "", ""); } gEnabled = FALSE; colourHUD(); llRequestPermissions(llGetOwner(), PERMISSION_ATTACH); } default { on_rez(integer _startParam) { llResetScript(); } state_entry() { init(); if (llGetAttached() > 0) { colourHUD(); } } attach(key k) { llResetScript(); } touch_end(integer count) { if (gEnabled) { // tell ball to roll llSay(COMMS_CHANNEL, RELEASE_BALL); } } run_time_permissions(integer perms) { if (perms & PERMISSION_ATTACH) llAttachToAvatar(ATTACH_HUD_BOTTOM); } listen(integer channel, string name, key id, string msg ) { if (msg == BALL_ATTACHED) { gEnabled = TRUE; } else if (msg == BALL_ROLLING || msg == BALL_WAITING) { gEnabled = FALSE; } else if (msg == KILL_ALL) { llDetachFromAvatar(); } colourHUD(); } }
When the user clicks the button, the script checks to see that the button is enabled (gEnabled), and if so, tells the ball to roll using RELEASE_BALL. The key difference between this HUD button and the other objects is that the script actively manages its attachment point (the other bowling scripts just checked if they were attached using llGetAttached()). To change attachment points, the script must request permissions to attach and detach from the user using this call: llRequestPermissions(llGetOwner(), PERMISSION_ATTACH);
If the user has already attached the HUD, permissions are implicitly granted and no dialog box appears. Otherwise a dialog box pops up, as shown in Figure P2.6. When the run_time_ permissions event handler fires, it checks that permissions were granted; if so, it attaches to ATTACH_ HUD_BOTTOM, in the bottom center of the screen.
18
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16 PROJECT 1
PROJECT 2 PROJECT 3
Figure P2.6: When rezzed to the world, the HUD requests permissions to attach.
PROJECT 4 PROJECT 5 APPENDIX D
The HUD will not listen to the COMMS_CHANNEL until it is attached to a HUD point; namely when llGetAttached() returns an integer of 31 or higher. When the script hears the KILL_ALL message, it self-detaches from the screen using llDetachFromAvatar(). This function also requires PERMISSION_ATTACH permissions. If the avatar manually attached the HUD, permissions are implicitly granted; the script still has to ask, however.
DEFINITION llAttachToAvatar(integer attachPoint) Attaches the object to the owner after the owner has granted PERMISSION_ATTACH to the script. The object is taken into the user's inventory and attached to the specified attach point. attachPoint — Indicates the attachment point using one of the ATTACH_* constants described in Chapter 2, or a valid integer value.
DEFINITION llDetachFromAvatar() Detaches the object from the avatar and places it in inventory. There is no way to place the object down in-world—for example, on the ground.
19
HUD Fine-Aiming Control
PROJECT 2
The P hysics of B owling Complete A B owling S uite S ummary
Avatars can control direction up to roughly 5 degrees of granularity. When throwing a ball over a long distance, 5 degrees adds up; for bowling pins 10m away, the avatar could be off by as much as 0.87m. The HUD's fine-aim control allows the avatar to "tweak" the direction of the throw. The script in Listing P2.10 captures the user's Shift+left arrow and Shift+right arrow keystrokes and moves a texture left and right across the prim. In this script, each keystroke is equivalent to 1/20th of the width of the prim and is mapped to a total of 10 degrees by the ball on the ground (in its ball_rolling state), meaning each key stroke maps to 8cm when pins are 10m away.
Listing P2.10: HUD Fine-Aiming Control // Insert Declarations from Listing P2.3 init() { llOffsetTexture( 0.5, 0, ALL_SIDES ); // set to middle llListen(COMMS_CHANNEL, "", NULL_KEY, ""); } default { on_rez( integer _code ) { llResetScript(); } state_entry() { init(); llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS); } attach(key k) { llResetScript(); } run_time_permissions(integer parm) { if( parm & PERMISSION_TAKE_CONTROLS ) { llTakeControls(CONTROL_LEFT|CONTROL_RIGHT, TRUE, FALSE); } } control(key id, integer held, integer recentChange) { vector offset = llGetTextureOffset(ALL_SIDES); if (held & CONTROL_RIGHT) { offset.x -= 0.05; } if (held & CONTROL_LEFT) { offset.x += 0.05; } if (offset.x < 0.04) offset.x = 1 - offset.x; if (offset.x > 1.04) offset.x = offset.x - 1; llOffsetTexture(offset.x, offset.y, ALL_SIDES); llSay(COMMS_CHANNEL, AIM_OFFSET+DELIM+(string)offset.x); } listen(integer channel, string name, key id, string msg ) { //Reporting on BALL_DETACHED: delay is too much if (msg == REPORT_AIM_OFFSET) { vector offset = llGetTextureOffset(ALL_SIDES); llSay(COMMS_CHANNEL, AIM_OFFSET+DELIM+(string)offset.x); } } }
To capture the keystrokes, the script requests PERMISSION_TAKE_CONTROLS; when granted, it uses llTakeControls() to grab CONTROL_LEFT and CONTROL_RIGHT (Shift+left arrow and Shift+right arrow). The avatar can still use the regular arrow keys for the large-scale adjustment.
20
In the control() event handler, the script requests the current offset of the texture using llGetTextureOffset(). It then calculates the new offset based on which key was input,
and adjusts the offset.x by 5% in one direction or the other. It then moves the texture using llOffsetTexture() and announces the new value on the COMMS_CHANNEL. The texture is shown in Figure P2.7, where you'll see that the red line is at the far left; thus an offset of 50% (0.5) indicates no change to the throwing direction.
CH02 ADD. CH06 ADD. CH08 ADD. CH12 ADD. CHAPTER 16
Figure P2.7: The texture applied to the fine-aim prim is mostly transparent, with a thin red line. Only 32-bit TGA files capture transparency.
PROJECT 1
PROJECT 2
DEFINITION
PROJECT 3 PROJECT 4
vector llGetTextureOffset(integer face)
PROJECT 5 APPENDIX D
Returns a vector that describes the texture's current offset. The z value is always zero. face — Number of the face, or ALL_SIDES.
DEFINITION llOffsetTexture(float x, float y, integer face) Sets the texture offset. x — Offset in the x direction, range 0.0 to 1.0. Note that in the object-editor GUI, this is the u value. y — Offset in the y direction, range 0.0 to 1.0. In the object-editor GUI, this is the v value. face — Number of the face, or ALL_SIDES.
This script also politely reports the offset whenever asked (REPORT_AIM_OFFSET) by the ball on the ground as it enters the ball_in_hand state.
Inventory Giver The inventory-giver script shown in Listing P2.11 is one of many "finishing touches" you can put on your bowling game. Using the techniques described in Chapter 8, "Inventory," this script offers a notecard describing how to play the game, the glove, and the HUD to anyone who touches it. It also keeps track of who touched the object. Finally, it changes color on a regular basis to attract visitors.
21
Listing P2.11: Inventory Giver string gRecentVisitors; integer gNumRecentVisitors; integer gClearList = TRUE; PROJECT 2
The P hysics of B owling Complete A B owling S uite S ummary
default { state_entry() { llSetText("Touch me to get notecard,\n"+ "bowling glove & HUD.",,1); llSetTimerEvent(5); } timer() { vector color = ; llSetColor(color, ALL_SIDES); } touch_start(integer total_number) { key avatar = llDetectedKey(0); if (llDetectedKey(0) == llGetOwner()) { llOwnerSay((string)gNumRecentVisitors+" visitors since last time."); if (gNumRecentVisitors>0) { llOwnerSay("Visited by:\n"+gRecentVisitors); } if (gClearList) { gRecentVisitors = ""; gNumRecentVisitors =0; } } else { string name = llKey2Name(avatar)+"\n"; if (llSubStringIndex(gRecentVisitors, name) < 0) { gRecentVisitors += name; } } llGiveInventory(avatar, "Bowling notecard (SYW)"); llGiveInventory(avatar, "Bowling Glove"); llGiveInventory(avatar, "HUD for bowling"); } }
Finishing Touches There are lots of other finishing touches you can add: a fancy ball return, a moving mechanism that appears to pick up the balls, a scoreboard, realistic animations. And after all that work, a script that automatically generates the entire bowling alley and all the accoutrements wherever you place it, and for whomever has bought your system.
SUMMARY 22
Hopefully this chapter has whetted your appetite to go out and script some more-detailed and -interactive objects. There is plenty left to be done, so go build the next big thing!
ReturnType Code STATUS_DIE_AT_EDGE (0x80) STATUS_PHANTOM (0x10) STATUS_PHYSICS (0x1)
Page(s) 158, 170, 238 158 5, 158, 165, 168, 177, 178, 185, 238 158 158 158 158 158 9, 14 14 14 14
STATUS_RETURN_AT_EDGE (0x100) STATUS_ROTATE_X (0x2) STATUS_ROTATE_Y (0x4) STATUS_ROTATE_Z (0x8) STATUS_SANDBOX (0x20) string STRING_TRIM (3) STRING_TRIM_HEAD (0x01) STRING_TRIM_TAIL (0x02) TEXTURE_BLANK (5748decc-f629-461c9a36-a35a221fe21) TEXTURE_DEFAULT (8b5fec65-8d8d9dc5-cda8-8fdf2716e361) TEXTURE_PLYWOOD (89556747-24cb43ed-920b-47caed15465) TEXTURE_TRANSPARENT (59facb664a72-40a2-815c-7d9b42c56f60) timer() event 7, 30, 52, 60, 63, 79, 93, 110, 122, 152, 160, 172, 173, 219, 226, 230, 246, 271 touch() event 7, 45, 52, 135, 262, 264, 338, 339 touch_end() event 7, 45, 51, 52, 79, 293, 321, 338 touch_start() event 4, 7, 30, 45, 52, 93, 108, 122, 160, 252, 269, 286, 338 TRUE (1) 5, 9, 17, 21, 63, 73, 86, 87 12, 210, TWO_PI (6.28318530717958647692➥ 217 5286766559) TYPE_FLOAT (2) 10, 15 TYPE_INTEGER (1) 15 TYPE_INVALID (0) 15 TYPE_KEY (4) 15 TYPE_ROTATION (6) 15 TYPE_STRING (3) 15 TYPE_VECTOR (5) 15 vector 10-12 VEHICLE_ANGULAR_DEFLECTION_ 180 EFFICIENCY (32) VEHICLE_ANGULAR_DEFLECTION_ 180 TIMESCALE (33) VEHICLE_ANGULAR_FRICTION_TIMESCALE 180 (17) VEHICLE_ANGULAR_MOTOR_DECAY_ 180 TIMESCALE (35) VEHICLE_ANGULAR_MOTOR_DIRECTION 177, 179 (19)
ReturnType Code VEHICLE_ANGULAR_MOTOR_TIMESCALE (34) VEHICLE_BANKING_EFFICIENCY (38) VEHICLE_BANKING_MIX (39) VEHICLE_BANKING_TIMESCALE (40) VEHICLE_BUOYANCY (27) VEHICLE_FLAG_CAMERA_DECOUPLED (0x200) VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT (0x10) VEHICLE_FLAG_HOVER_TERRAIN_ONLY (0x8) VEHICLE_FLAG_HOVER_UP_ONLY (0x20) VEHICLE_FLAG_HOVER_WATER_ONLY (0x4) VEHICLE_FLAG_LIMIT_MOTOR_UP (0x40) VEHICLE_FLAG_LIMIT_ROLL_ONLY (0x2) VEHICLE_FLAG_MOUSELOOK_BANK (0x100) VEHICLE_FLAG_MOUSELOOK_STEER (0x80) VEHICLE_FLAG_NO_DEFLECTION_UP (0x1) VEHICLE_HOVER_EFFICIENCY (25) VEHICLE_HOVER_HEIGHT (24) VEHICLE_HOVER_TIMESCALE (26) VEHICLE_LINEAR_DEFLECTION_ EFFICIENCY (28) VEHICLE_LINEAR_DEFLECTION_ TIMESCALE (29) VEHICLE_LINEAR_FRICTION_TIMESCALE (16) VEHICLE_LINEAR_MOTOR_DECAY_ TIMESCALE (31) VEHICLE_LINEAR_MOTOR_DIRECTION (18) VEHICLE_LINEAR_MOTOR_OFFSET (20) VEHICLE_LINEAR_MOTOR_TIMESCALE (30) VEHICLE_REFERENCE_FRAME (44) VEHICLE_TYPE_AIRPLANE (4) VEHICLE_TYPE_BALLOON (5) VEHICLE_TYPE_BOAT (3) VEHICLE_TYPE_CAR (2) VEHICLE_TYPE_NONE (0) VEHICLE_TYPE_SLED (1) VEHICLE_VERTICAL_ATTRACTION_ EFFICIENCY (36) VEHICLE_VERTICAL_ATTRACTION_ TIMESCALE (37) while ZERO_ROTATION ()
ZERO_VECTOR ()
Page(s) 176, 180 180 181 176, 180 176, 181 181, 183 181 181 181 181 179, 181 176, 181 181 181 181 180 181 180 180 180 179, 180 179, 180 177, 179 179 180 179 176 176 176 176 176 176 180 180 20-21 12-13, 15, 40, 42, 107, 210, 292 11, 15, 40, 42, 107, 210, 292
385
370-386 Index and BoB Ad.indd 385
8/8/08 2:08:10 AM
Your second life is how you script it
Moore Thome Haigh
Scripting is the spice of Second Life, and this official guide to the Linden Scripting Language (LSL) is the complete scripting resource for making your Second Life creations come to life, interacting with players, who cover every single aspect of LSL, from the basics of avatar movement and communication, to physics and special effects, to interacting with the world outside of Second Life. This thorough reference and tutorial features detailed walk-throughs and step-by-steps that teach the “why” of scripting while explaining core LSL conventions. It includes more than 50 exclusive, brand-new custom scripts that you can use and modify right now. Make doors open automatically, put a message in a bottle and watch it float out to sea, create a tip jar, and start a storm complete with loud thunderclaps and flashing lightning. • Understand the Linden Scripting Language: Master every aspect of Second Life’s programming language • Automate with scripts: Greet visitors by name, teleport around the world, and dance with a partner • Learn physics and vehicles: Create a jet pack, a magic carpet, and a machine gun • Use environmental controls: Create moving shadows, make water splash, and have bees flit from flower to flower • Integrate multimedia: Dazzle residents with streaming TV and ambient soundscapes • Incorporate the outside world: Build a live news ticker and send e-mail
Learn LSL so you can make your world move and grow
Create stunning special effects, including rainbows and fireworks
Choreograph everything from dancing to swordplay
Companion Website and Resource Center developers at http://syw.fabulo.us. Further resources can be found in-world at Scripting Your World Headquarters, Hennepin .
About the Authors
scripting your world ®
the official guide to second life scripting
®
Find additional in-depth projects, scripts, and code, as well as forums, book updates, and a community of LSL
the official guide to scripting your world second life scripting
creating dazzling special effects, and adding realism to the virtual world. The authors are an expert team of programmers
Dana Moore is ElectricSheep Expedition in Second Life, where he runs a shop, creates clothes, and writes tons of scripts. Michael Thome (Vex Streeter) is a scripter-for-hire who develops everything from games to virtual toys. Dr. Karen Haigh (Karzita Zabaleta) enjoys writing scripts that push the boundaries of what is possible in SL. The authors have a combined 50 years of experience as enterprise software developers and are computer scientists for Internet pioneer BBN Technologies.
ISBN: 978-0-470-33983-1
$39.99 US $43.99 CAN
Category COMPUTERS/Internet/General
By Dana Moore, Michael Thome, and Dr. Karen Zita Haigh Foreword by Joe Miller, Linden Lab Vice President of Platform and Technology Development