furware/FURWARE text.lsl
Ochi Wolfe 4ec4e6683a - Fixed an issue with displaying unknown characters (accidentally introduced in v2.0).
- Removed llGetFreeMemory() on start (was a debug leftover).
- The set pointer is now part of a box's status integer (bit-packed). This reduces the length of a box data list stride by 1.
- Removed the getNumberOfPrims() function.
- Moved the check whether we have any sets at all when receiving a link message to the beginning of the link_message handler.
2013-07-20 13:20:07 +02:00

1045 lines
42 KiB
Text

////////////////////////////////////////////////////////////
// //
// _cg####ggi_ //
// _g00000000000o_ //
// ,0000^.omggc.000¡ //
// 000 ,F ¯°0#¡000L //
// FURWARE text #00 ¡ ¡0 000#00 ^ //
// #0 ]O 00 #0 00 #L //
// Version 2.0.1 0 #0 0O J0 #0 0O //
// Open Source v #00#0¡ #0 0 ]0O //
// J000000c_ J0 c00^ //
// 0000c^00@NN ,#000 //
// `0000@ggg#0000^ //
// ^°0000000^^ //
// //
////////////////////////////////////////////////////////////
////////// DOCUMENTATION ///////////////////////////////////
/*
A user's manual as well as documentation for developers
is available on the Second Life (R) Wiki at
http://wiki.secondlife.com/wiki/FURWARE_text
*/
////////// LICENSE /////////////////////////////////////////
/*
MIT License
Copyright (c) 2010-2013 Ochi Wolfe, FURWARE, the contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
////////// CONTRIBUTORS ////////////////////////////////////
/*
Ochi Wolfe - Initial development from 2010 to 2013.
*/
////////// CONSTANTS ///////////////////////////////////////
// Index offsets and stride size of data in "boxDataList".
integer BOX_DATA = 0; // The referenced data has type "string".
integer BOX_CONF = 1; // The referenced data has type "string".
integer BOX_STATUS = 2; // The referenced data has type "integer".
integer BOX_GEOM = 3; // The referenced data has type "rotation".
integer BOX_STRIDE = 4;
// Tags used to remember the kind of the last performed action.
integer ACTION_CONTENT = 1; // Text or style may have been modified.
integer ACTION_ADD_BOX = 2; // A virtual text box was added.
integer ACTION_DEL_BOX = 3; // A virtual text box was deleted.
// String of displayable characters, in the order of the font texture.
string CHARS = ""; // Populated in state_entry.
////////// VARIABLES ///////////////////////////////////////
// Global status
integer primCount; // Used to check for prim count changes.
integer lastAction; // The kind of the last performed action.
// Per-box data (box = virtual text box)
integer boxDataLength; // Length of the "boxDataList".
list boxNameList; // List of virtual text boxes' names.
list boxDataList; // Strided list of virtual text box data.
// Per-set data (set = physical display)
integer setCount; // Number of display sets.
list setDataList; // Strided list of display set data.
// Per-prim data
list primLinkList; // List of the prims' link indices
list primFillList; // Tells us which faces are showing non-blanks.
list primLayerList; // Contains layer assignments for each face.
// Memory for templates
list tmplNameList; // Names of stored templates.
list tmplDataList; // Contents of stored templates.
// Global configuration
integer gNotify; // Whether notifications shall be sent when done.
string gConfAll; // Base configuration for all boxes everywhere.
string gConfRoot; // Base configuration for root boxes.
string gConfNonRoot; // Base configuration for non-root boxes.
// Default configuration
string dAlign; // Text alignment.
string dTrim; // Whitespace trimming at begin/end.
string dWrap; // Text wrapping mode.
rotation dColor; // Text color.
string dFont; // Text font.
string dBorder; // Border type around virtual text box.
string dTags; // Use of inline style tags (<!...>).
integer dForce; // Force refresh, overriding cached face-filled state.
// Current configuration
string cAlign; // See above.
string cTrim;
string cWrap;
rotation cColor;
string cFont;
string cBorder;
string cTags;
integer cForce;
// Prim data cache
integer cacheIndex = -1; // Index of currently cached prim data.
integer cacheDirty; // Bit 0 set = layer dirty, bit 1 set = stat dirty.
integer cacheLink; // Link number cache.
integer cacheFill; // Face-filled state cache.
integer cacheLayer; // Layer cache.
// Shared data between refresh() and draw(),
// just to avoid lots of parameter passing
integer setAuxIndex;
integer setColCount;
integer setFaceCount;
integer boxStatus;
////////// FUNCTIONS ///////////////////////////////////////
refresh() {
llSetTimerEvent(0.0);
integer boxDataIndex;
integer step = BOX_STRIDE;
integer end = boxDataLength;
if (lastAction != ACTION_ADD_BOX) { // Last action is NOT adding a box -> iterate backwards
boxDataIndex = boxDataLength - BOX_STRIDE;
step = end = -BOX_STRIDE;
}
while (boxDataIndex != end) {
boxStatus = llList2Integer(boxDataList, boxDataIndex + BOX_STATUS);
if (boxStatus & 0x1000) { // Dirty
// Set the factory default settings.
cAlign = dAlign = "left";
cTrim = dTrim = "on";
cWrap = dWrap = "word";
cColor = dColor = <1.0, 1.0, 1.0, 1.0>;
cFont = dFont = "f974fdfc-8fbd-2f29-2b38-3c34411c1fcc";
cBorder = dBorder = "";
cTags = dTags = "on";
cForce = dForce = FALSE;
integer setIndex = (boxStatus >> 4) & 0xFF;
rotation boxGeometry = llList2Rot(boxDataList, boxDataIndex + BOX_GEOM);
// Load set data.
vector setGeometry = llList2Vector(setDataList, 2*setIndex);
setAuxIndex = llList2Integer(setDataList, 2*setIndex+1);
// Load box config.
set(gConfAll, TRUE, TRUE);
if (boxDataIndex / BOX_STRIDE < setCount) {
set(gConfRoot, TRUE, TRUE);
} else {
set(gConfNonRoot, TRUE, TRUE);
}
set(llList2String(boxDataList, boxDataIndex + BOX_CONF), TRUE, TRUE);
integer boxX = (integer)boxGeometry.x;
integer boxY = (integer)boxGeometry.y;
integer boxW = (integer)boxGeometry.z;
integer boxH = (integer)boxGeometry.s;
integer boxR = boxX+boxW-1;
integer boxB = boxY+boxH-1;
setColCount = (integer)setGeometry.x;
setFaceCount = (integer)setGeometry.z;
integer borderTT = !!(~llSubStringIndex(cBorder, "T"));
integer borderRR = !!(~llSubStringIndex(cBorder, "R"));
integer borderBB = !!(~llSubStringIndex(cBorder, "B"));
integer borderLL = !!(~llSubStringIndex(cBorder, "L"));
integer borderT = borderTT || (~llSubStringIndex(cBorder, "t"));
integer borderR = borderRR || (~llSubStringIndex(cBorder, "r"));
integer borderB = borderBB || (~llSubStringIndex(cBorder, "b"));
integer borderL = borderLL || (~llSubStringIndex(cBorder, "l"));
integer borderSt;
if (~llSubStringIndex(cBorder, "1")) borderSt = 1;
else if (~llSubStringIndex(cBorder, "2")) borderSt = 2;
if (boxStatus & 0x2000) { // Potentially need to refresh borders?
integer i;
if (borderT) {
for (i = boxX+borderL; i <= boxR-borderR; ++i) draw(235 + 25*borderSt, i, boxY);
}
if (borderR) {
if (borderT) draw(227 - borderRR + 25*borderTT + 3*borderSt, boxR, boxY);
for (i = boxY+borderT; i <= boxB-borderB; ++i) draw(262 + borderSt, boxR, i);
if (borderB) draw(277 - borderRR - 25*borderBB + 3*borderSt, boxR, boxB);
}
if (borderB) {
for (i = boxR-borderR; i >= boxX+borderL; --i) draw(235 + 25*borderSt, i, boxB);
}
if (borderL) {
if (borderB) draw(275 + borderLL - 25*borderBB + 3*borderSt, boxX, boxB);
for (i = boxB-borderB; i >= boxY+borderT; --i) draw(262 + borderSt, boxX, i);
if (borderT) draw(225 + borderLL + 25*borderTT + 3*borderSt, boxX, boxY);
}
}
boxX += borderL;
boxY += borderT;
boxR -= borderR;
boxB -= borderB;
boxW -= borderL + borderR;
boxH -= borderT + borderB;
if (boxW > 0 && boxH > 0) {
// Prepare data.
string text = llList2String(boxDataList, boxDataIndex + BOX_DATA);
integer textLength = llStringLength(text);
integer textIndex;
list part;
integer partLength;
integer partIndex;
integer dataNewLine = TRUE;
string token;
integer tokenLength;
integer boxRow;
while (boxRow < boxH) {
list line;
integer lineLength;
list tagPosList;
list tagCmdList;
integer tagCmdListLength;
string tagCommand;
integer spacesTail;
integer textMode = TRUE;
integer rowDone;
integer skipRestOfLine;
integer lastTokenWasSpace;
// Parsing loop.
while ((partIndex < partLength || textIndex < textLength) && !rowDone) {
if (partIndex >= (partLength-1) && textIndex < textLength) {
part = llParseString2List(
llGetSubString(text, textIndex, textIndex+boxW),
[], [" ", "\n", "<!", ">"]
);
partLength = llGetListLength(part);
partIndex = 0;
tokenLength = 0;
}
if (!tokenLength) {
token = llList2String(part, partIndex);
tokenLength = llStringLength(token);
}
integer dataAdvance = TRUE;
integer tokenLengthPrev = tokenLength;
// Text mode takes care of all tokens that are not enclosed by <! ... >.
if (textMode) {
if ((cTags == "on") && (token == "<!")) {
textMode = FALSE;
} else if (token == "\n") {
dataNewLine = TRUE;
rowDone = TRUE;
} else if (!skipRestOfLine) {
dataNewLine = FALSE;
integer tokenIsSpace = (token == " ");
if ((cTrim != "off") && tokenIsSpace) {
if (lineLength) {
++spacesTail;
line += [0];
}
} else {
integer spaceLeft = boxW - lineLength - spacesTail;
integer toAppend;
if (tokenLength <= spaceLeft) {
toAppend = tokenLength;
lineLength += spacesTail;
spacesTail = 0;
} else {
if ((cWrap != "word") || !lastTokenWasSpace) {
if (spaceLeft > 0) {
toAppend = spaceLeft;
lineLength += spacesTail;
spacesTail = 0;
}
}
if (cWrap != "none") {
rowDone = TRUE;
dataAdvance = FALSE;
} else {
skipRestOfLine = TRUE;
}
}
integer i;
for (i = 0; i < toAppend; ++i, --tokenLength, ++lineLength) {
integer charPos = llSubStringIndex(CHARS, llGetSubString(token, i, i));
if (~charPos) line += [charPos]; else line += [68]; // 68 = "?"
}
if ((cWrap != "none") && tokenLength) {
token = llGetSubString(token, -tokenLength, -1);
}
}
lastTokenWasSpace = tokenIsSpace;
}
// Tag mode takes care of all tokens within <! ... >.
} else {
if (token == ">") {
if (dataNewLine) {
set(tagCommand, TRUE, FALSE);
} else {
tagPosList += [lineLength+spacesTail];
tagCmdList += [tagCommand];
++tagCmdListLength;
}
tagCommand = "";
textMode = TRUE;
} else {
tagCommand += token;
}
}
if (dataAdvance) {
++partIndex;
tokenLength = 0;
}
textIndex += (tokenLengthPrev - tokenLength);
}
integer nextTagListIndex;
integer nextTagCharIndex = -1;
if (tagCmdListLength) {
nextTagCharIndex = llList2Integer(tagPosList, 0);
}
integer lineIndex;
if (cAlign != "left") {
integer delta = boxW - lineLength;
if (cAlign == "center") delta /= 2;
lineIndex -= delta;
}
integer x;
for (x = boxX; x <= boxR; ++x, ++lineIndex) {
integer pos;
if (lineIndex >= 0 && lineIndex < lineLength) {
while (nextTagListIndex < tagCmdListLength && lineIndex >= nextTagCharIndex) {
set(llList2String(tagCmdList, nextTagListIndex++), FALSE, FALSE);
nextTagCharIndex = llList2Integer(tagPosList, nextTagListIndex);
}
pos = llList2Integer(line, lineIndex);
}
draw(pos, x, boxY + boxRow);
}
while (nextTagListIndex < tagCmdListLength) {
set(llList2String(tagCmdList, nextTagListIndex++), FALSE, FALSE);
}
++boxRow;
}
}
// Mark box as clean.
boxDataList = llListReplaceList(
boxDataList, [boxStatus & 0xFFF], boxDataIndex + BOX_STATUS, boxDataIndex + BOX_STATUS
);
}
boxDataIndex += step;
}
draw(-1, 0, 0); // Flush cache.
lastAction = 0;
if (gNotify) {
llMessageLinked(LINK_SET, 0, "", "fw_done");
}
}
draw(integer char, integer x, integer y) {
integer newCacheIndex = -1;
if (~char) newCacheIndex = setAuxIndex + y*setColCount + x/setFaceCount;
if (newCacheIndex != cacheIndex) {
if (~cacheIndex) {
// Stat cache dirty
if (cacheDirty & 2) primFillList = llListReplaceList(
primFillList, [cacheFill], cacheIndex, cacheIndex
);
// Layer cache dirty
if (cacheDirty & 1) primLayerList = llListReplaceList(
primLayerList, [cacheLayer], cacheIndex, cacheIndex
);
cacheDirty = 0;
}
cacheIndex = newCacheIndex;
if (~cacheIndex) {
cacheLink = llList2Integer(primLinkList, cacheIndex);
cacheFill = llList2Integer(primFillList, cacheIndex);
cacheLayer = llList2Integer(primLayerList, cacheIndex);
}
}
if (!~char) return;
integer face = x % setFaceCount;
integer layer = (cacheLayer >> 4*face) & 0xF;
integer boxLayer = (boxStatus & 0xF);
if ((0x10000 << layer) & boxStatus) { // Layer override
cacheLayer = (cacheLayer & ~(0xF << 4*face)) | (boxLayer << 4*face);
cacheDirty = cacheDirty | 1;
} else if (layer != boxLayer) {
return;
}
integer filled = cForce || char;
integer statDiffers = (filled ^ ((cacheFill >> face) & 1));
if (filled || statDiffers) {
if (statDiffers) {
cacheFill = (cacheFill & ~(1 << face)) | (filled << face);
cacheDirty = cacheDirty | 2;
}
llSetLinkPrimitiveParamsFast(cacheLink, [
PRIM_TEXTURE, face, cFont, <0.03125, 0.0625, 0.0>,
<0.0390625 * (char % 25), -0.0703125 * (char / 25), 0.0>, 0.0,
PRIM_COLOR, face, <cColor.x, cColor.y, cColor.z>, filled * cColor.s
]);
}
}
set(string data, integer startOfLine, integer setDefaults) {
if (data == "") return;
list parts = llParseString2List(data, [";"], []);
integer partCount = llGetListLength(parts);
integer part;
for (part = 0; part < partCount; ++part) {
list tokens = llParseString2List(llList2String(parts, part), ["="], []);
if (llGetListLength(tokens) > 1) {
string tag = llStringTrim(llList2String(tokens, 0), STRING_TRIM);
string valUpper = llStringTrim(llList2String(tokens, 1), STRING_TRIM);
string valLower = llToLower(valUpper);
integer valIsDef = (valLower == "def");
if (tag == "c") {
if (valIsDef) cColor = dColor;
else if (valLower == "rand") cColor = <llFrand(1.0), llFrand(1.0), llFrand(1.0), 1.0>;
else if (valLower == "white") cColor = <1.0, 1.0, 1.0, 1.0>;
else if (valLower == "black") cColor = <0.0, 0.0, 0.0, 1.0>;
else if (valLower == "darkred") cColor = <0.5, 0.0, 0.0, 1.0>;
else if (valLower == "darkgreen") cColor = <0.0, 0.5, 0.0, 1.0>;
else if (valLower == "darkblue") cColor = <0.0, 0.0, 0.5, 1.0>;
else if (valLower == "darkcyan") cColor = <0.0, 0.5, 0.5, 1.0>;
else if (valLower == "darkmagenta") cColor = <0.5, 0.0, 0.5, 1.0>;
else if (valLower == "darkyellow") cColor = <0.5, 0.5, 0.0, 1.0>;
else if (valLower == "gray") cColor = <0.5, 0.5, 0.5, 1.0>;
else if (valLower == "red") cColor = <1.0, 0.0, 0.0, 1.0>;
else if (valLower == "green") cColor = <0.0, 1.0, 0.0, 1.0>;
else if (valLower == "blue") cColor = <0.0, 0.0, 1.0, 1.0>;
else if (valLower == "cyan") cColor = <0.0, 1.0, 1.0, 1.0>;
else if (valLower == "magenta") cColor = <1.0, 0.0, 1.0, 1.0>;
else if (valLower == "yellow") cColor = <1.0, 1.0, 0.0, 1.0>;
else if (valLower == "silver") cColor = <0.75, 0.75, 0.75, 1.0>;
else {
valLower = "<" + valLower + ">";
cColor = (rotation)valLower;
if (cColor == ZERO_ROTATION) {
vector tmp = (vector)valLower;
cColor = <tmp.x, tmp.y, tmp.z, 1.0>;
}
}
}
else if (tag == "f") {
if (valIsDef) cFont = dFont; else cFont = valUpper;
}
else if (tag == "style") {
integer tmplIndex = llListFindList(tmplNameList, [valUpper]);
if (~tmplIndex) {
set(llList2String(tmplDataList, tmplIndex), startOfLine, setDefaults);
} else {
llOwnerSay("FW text: Style var \"" + valUpper + "\" not found.");
}
}
else if (startOfLine) {
if (tag == "w") {
if (valIsDef) cWrap = dWrap; else cWrap = valLower;
} else if (tag == "t") {
if (valIsDef) cTrim = dTrim; else cTrim = valLower;
} else if (tag == "a") {
if (valIsDef) cAlign = dAlign; else cAlign = valLower;
} else if (tag == "border") {
if (valIsDef) cBorder = dBorder; else cBorder = valUpper;
} else if (tag == "tags") {
if (valIsDef) cTags = dTags; else cTags = valLower;
} else if (tag == "force") {
if (valIsDef) cForce = dForce; else cForce = (valLower == "on");
}
}
}
}
if (setDefaults) {
dAlign = cAlign;
dTrim = cTrim;
dWrap = cWrap;
dColor = cColor;
dFont = cFont;
dBorder = cBorder;
dTags = cTags;
dForce = cForce;
}
}
setDirty(integer action, integer first, integer last, integer isConf,
integer newLayerOverrideBits, integer setIndex, integer withData, string data) {
integer i;
for (i = first; i <= last; i += BOX_STRIDE) {
integer setMatches = TRUE;
if (~setIndex) {
setMatches = (((llList2Integer(boxDataList, i + BOX_STATUS) >> 4) & 0xFF) == setIndex);
}
if (setMatches) {
integer j;
if (withData) {
j = i + isConf;
boxDataList = llListReplaceList(boxDataList, [data], j, j);
}
j = i + BOX_STATUS;
boxDataList = llListReplaceList(
boxDataList, [
llList2Integer(boxDataList, j) | 0x1000 | (isConf * 0x2000) | (newLayerOverrideBits << 16)
], j, j
);
}
}
if (!lastAction) {
llSetTimerEvent(0.05);
lastAction = action;
}
}
////////// STATES //////////////////////////////////////////
default {
state_entry() {
llOwnerSay("FURWARE text is starting...");
CHARS = llBase64ToString(
"IGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFS" +
"U1RVVldYWVowMTIzNDU2Nzg5Liw6OyE/IifCtGBefistKi9cfCgpW117fTw+" +
"PUAkJSYjX8Ogw6HDosOjw6TDpcOmwqrDp8Oww6jDqcOqw6vDrMOtw67Dr8Ox" +
"w7LDs8O0w7XDtsO4xZPCusOexaHDn8O5w7rDu8O8w73Dv8W+w4DDgcOCw4PD" +
"hMOFw4bDh8OQw4jDicOKw4vDjMONw47Dj8ORw5LDk8OUw5XDlsOYxZLDvsWg" +
"w5nDmsObw5zDncW4xb3CosKj4oKswqXCp8K1wqHCv8Kpwq7CscOXw7fCt8Kw" +
"wrnCssKzwqvCu8Ks4oCm4oC54oC64oCTwrzCvcK+4oSi4oCiICAgICAgICAg" +
"ICAgICAgICAgICAgICAgICAgIOKUjOKUrOKUkOKUj+KUs+KUk+KVlOKVpuKV" +
"l+KVtuKUgOKVtOKVt+KVuyDilK/ilrLil4Dilrzilrbil4vil5Til5Hil5Xi" +
"l4/ilJzilLzilKTilKPilYvilKvilaDilazilaPilbrilIHilbjilILilIPi" +
"lZHilL/ihpHihpDihpPihpLihrrihrvimJDimJHimJLilJTilLTilJjilJfi" +
"lLvilJvilZrilanilZ0g4pWQIOKVteKVuSDilLfilKDilYLilKjihpXihpTi" +
"mYDimYLimqDihLninYzijJbiiKHijJvijJrimarimavimaDimaPimaXimabi" +
"moDimoHimoLimoPimoTimoXinJTinJjimLrimLnilqDil77ilqzilq7ilog="
);
// Remember prim count to detect changes later on.
primCount = llGetObjectPrimCount(llGetKey())+llGetNumberOfPrims()*!!llGetAttached();
// Determine which link IDs to iterate.
integer linkMin;
integer linkMax;
if (primCount > 1) {
linkMin = 1;
linkMax = primCount;
}
// Fetch data from prims.
list sets;
integer dataLength;
while (linkMin <= linkMax) {
list tokens = llParseStringKeepNulls(llGetLinkName(linkMin), [":"], []);
if (llList2String(tokens, 0) == "FURWARE text mesh") {
string setStr = llList2String(tokens, 1);
if (setStr != "") {
integer set = llListFindList(sets, [setStr]);
if (!~set) {
set = llGetListLength(sets);
sets += [setStr];
}
primLinkList += [
(set << 20) |
((llList2Integer(tokens, 2) & 0x3FF) << 10) |
(llList2Integer(tokens, 3) & 0x3FF),
linkMin
];
++dataLength;
}
}
++linkMin;
}
// Abort if not a single element was found.
if (!dataLength) {
llOwnerSay("FW text: No text 2.x prims found.");
return;
}
// Sort the data according to their set-row-col values.
primLinkList = llListSort(primLinkList, 2, TRUE);
// Parse the gathered data.
integer dataIndex;
integer setrowcol = llList2Integer(primLinkList, dataIndex);
integer set = (setrowcol >> 20) & 0x3FF;
integer row = (setrowcol >> 10) & 0x3FF;
integer nextSet;
integer nextRow;
do { // Set
integer rowCount;
integer colCount;
integer faceCount;
integer setAuxPtr = dataIndex;
do { // Row
++rowCount;
integer newColCount;
do { // Col
++newColCount;
integer link = llList2Integer(primLinkList, 2*dataIndex+1);
integer newFaceCount = llGetLinkNumberOfSides(link);
if (faceCount) {
if (newFaceCount != faceCount) {
llOwnerSay("FW text: All prims within a set need to have the same number of faces.");
setCount = 0;
return;
}
} else {
faceCount = newFaceCount;
}
llSetLinkPrimitiveParamsFast(link, [
PRIM_TEXTURE, ALL_SIDES, TEXTURE_TRANSPARENT,
<1.0, 1.0, 0.0>, ZERO_VECTOR, 0.0
]);
setrowcol = llList2Integer(primLinkList, 2*(++dataIndex));
nextSet = (setrowcol >> 20) & 0x3FF;
nextRow = (setrowcol >> 10) & 0x3FF;
} while (dataIndex < dataLength && set == nextSet && row == nextRow);
if (colCount) {
if (newColCount != colCount) {
llOwnerSay("FW text: All rows within a set need to have the same number of prims.");
setCount = 0;
return;
}
} else {
colCount = newColCount;
}
row = nextRow;
} while (dataIndex < dataLength && set == nextSet);
setDataList += [<colCount, rowCount, faceCount>, setAuxPtr];
boxNameList += [llList2String(sets, set)];
boxDataList += ["", "", setCount << 4, <0, 0, colCount*faceCount, rowCount>];
boxDataLength += BOX_STRIDE;
++setCount;
set = nextSet;
} while (dataIndex < dataLength);
primLinkList = llDeleteSubList(primLinkList, 0, 0);
primLinkList = llList2ListStrided(primLinkList, 0, -1, 2);
while (dataIndex--) primFillList += [0];
primLayerList = primFillList;
llOwnerSay("FURWARE text started with " + (string)setCount + " set(s).");
llMessageLinked(LINK_SET, 0, "", "fw_ready");
}
link_message(integer sender, integer num, string str, key id) {
if (!setCount) return;
if (llGetSubString(id, 0, 2) != "fw_") return;
list tokens = llParseStringKeepNulls(id, [":"], []);
string token0 = llStringTrim(llList2String(tokens, 0), STRING_TRIM);
integer isConf = (token0 == "fw_conf");
if (token0 == "fw_data" || isConf) {
if (lastAction && lastAction != ACTION_CONTENT) refresh();
integer tokenCount = llGetListLength(tokens);
if (tokenCount < 2) tokenCount = 2;
integer t;
for (t = 1; t < tokenCount; ++t) {
list boxTokens = llParseStringKeepNulls(llList2String(tokens, t), [";"], []);
integer boxTokenCount = llGetListLength(boxTokens);
string boxToken0 = llStringTrim(llList2String(boxTokens, 0), STRING_TRIM);
string boxToken1 = llStringTrim(llList2String(boxTokens, 1), STRING_TRIM);
integer first = 0;
integer last = boxDataLength - BOX_STRIDE;
integer setIndex = -1;
if (boxToken0 != "") {
first = llListFindList(boxNameList, [boxToken0]);
if (!~first) {
llOwnerSay(token0 + ": Box \"" + boxToken0 + "\" not found.");
jump SkipBox;
}
first *= BOX_STRIDE;
setIndex = (llList2Integer(boxDataList, first + BOX_STATUS) >> 4) & 0xFF;
}
if (boxToken1 != "") {
last = llListFindList(boxNameList, [boxToken1]);
if (!~last) {
llOwnerSay(token0 + ": Box \"" + boxToken1 + "\" not found.");
jump SkipBox;
}
last *= BOX_STRIDE;
integer secondSetIndex = (llList2Integer(boxDataList, last + BOX_STATUS) >> 4) & 0xFF;
if (~setIndex && setIndex != secondSetIndex) {
llOwnerSay(token0 + ": Box sets must match when specifying a range.");
jump SkipBox;
}
setIndex = secondSetIndex;
} else if (boxTokenCount == 1 && ~setIndex) {
last = first;
}
setDirty(ACTION_CONTENT, first, last, isConf, 0, setIndex, TRUE, str);
@SkipBox;
}
return;
}
string token1 = llStringTrim(llList2String(tokens, 1), STRING_TRIM);
if (token0 == "fw_var") {
if (lastAction && lastAction != ACTION_CONTENT) refresh();
if (token1 == "") {
llOwnerSay("fw_var: No variable name given.");
return;
}
integer tmplIndex = llListFindList(tmplNameList, [token1]);
if (~tmplIndex) {
tmplNameList = llDeleteSubList(tmplNameList, tmplIndex, tmplIndex);
tmplDataList = llDeleteSubList(tmplDataList, tmplIndex, tmplIndex);
}
if (str != "") {
tmplNameList += [token1];
tmplDataList += [str];
}
setDirty(ACTION_CONTENT, 0, boxDataLength - BOX_STRIDE, TRUE, 0, -1, FALSE, "");
return;
}
if (token0 == "fw_defaultconf") {
if (lastAction && lastAction != ACTION_CONTENT) refresh();
integer first = -1;
integer last = boxDataLength - BOX_STRIDE;
if (token1 == "") {
gConfAll = str;
first = 0;
} else if (token1 == "root") {
gConfRoot = str;
first = 0;
last = setCount * BOX_STRIDE - BOX_STRIDE;
} else if (token1 == "nonroot") {
gConfNonRoot = str;
first = setCount * BOX_STRIDE;
}
if (~first) {
setDirty(ACTION_CONTENT, first, last, TRUE, 0, -1, FALSE, "");
}
return;
}
string token2 = llStringTrim(llList2String(tokens, 2), STRING_TRIM);
string token3 = llStringTrim(llList2String(tokens, 3), STRING_TRIM);
string token4 = llStringTrim(llList2String(tokens, 4), STRING_TRIM);
if (token0 == "fw_addbox") {
if (token1 == "") {
llOwnerSay("fw_addbox: Box name cannot be empty.");
return;
}
if (lastAction && lastAction != ACTION_ADD_BOX) refresh();
integer boxNameIndex = llListFindList(boxNameList, [token1]);
if (~boxNameIndex) {
llOwnerSay("fw_addbox: Box \"" + token1 + "\" already exists.");
return;
}
integer parNameIndex = llListFindList(boxNameList, [token2]);
if (!~parNameIndex) {
llOwnerSay("fw_addbox: No parent box \"" + token2 + "\".");
return;
}
integer boxDataIndex = BOX_STRIDE * boxNameIndex;
integer parDataIndex = BOX_STRIDE * parNameIndex;
integer setIndex = (llList2Integer(boxDataList, parDataIndex + BOX_STATUS) >> 4) & 0xFF;
integer layersUsed;
integer b;
for (b = 0; b < boxDataLength; b += BOX_STRIDE) {
if (setIndex == ((llList2Integer(boxDataList, b + BOX_STATUS) >> 4) & 0xFF)) {
layersUsed = layersUsed | (1 << (llList2Integer(boxDataList, b + BOX_STATUS) & 0xF));
}
}
if (layersUsed == 0xFFFF) {
llOwnerSay("fw_addbox: No layer available.");
return;
}
integer boxLayer;
while (layersUsed & (1 << boxLayer)) ++boxLayer;
rotation boxGeom = (rotation)("<" + token3 + ">");
rotation parGeom = llList2Rot(boxDataList, parDataIndex + BOX_GEOM);
vector setGeom = llList2Vector(setDataList, 2*setIndex);
boxGeom.x += parGeom.x;
boxGeom.y += parGeom.y;
if (boxGeom.x < 0 || boxGeom.y < 0 ||
boxGeom.z < 1 || boxGeom.s < 1 ||
(boxGeom.x + boxGeom.z) > (setGeom.x * setGeom.z) ||
(boxGeom.y + boxGeom.s) > setGeom.y)
{
llOwnerSay("fw_addbox: Invalid box geometry.");
return;
}
boxNameList += [token1];
boxDataList += [str, token4, (setIndex << 4) | boxLayer, boxGeom];
setDirty(ACTION_ADD_BOX, boxDataLength, boxDataLength, TRUE, 0xFFFF, -1, FALSE, "");
boxDataLength += BOX_STRIDE;
return;
}
if (token0 == "fw_delbox") {
if (lastAction && lastAction != ACTION_DEL_BOX) refresh();
integer tokenCount = llGetListLength(tokens);
integer t;
for (t = 1; t < tokenCount; ++t) {
string boxName = llStringTrim(llList2String(tokens, t), STRING_TRIM);
integer boxNameIndex = llListFindList(boxNameList, [boxName]);
if (!~boxNameIndex || (boxNameIndex < setCount)) {
llOwnerSay("fw_delbox: Box \"" + boxName + "\" doesn't exist.");
jump SkipDelBox;
}
integer boxDataIndex = BOX_STRIDE * boxNameIndex;
integer boxStatus = llList2Integer(boxDataList, boxDataIndex + BOX_STATUS);
setDirty(ACTION_DEL_BOX, 0, boxDataIndex - BOX_STRIDE, TRUE,
1 << (boxStatus & 0xF), (boxStatus >> 4) & 0xFF, FALSE, "");
boxNameList = llDeleteSubList(boxNameList, boxNameIndex, boxNameIndex);
boxDataList = llDeleteSubList(boxDataList, boxDataIndex, boxDataIndex + BOX_STRIDE - 1);
boxDataLength -= BOX_STRIDE;
@SkipDelBox;
}
return;
}
if (token0 == "fw_touchquery") {
// Need to flush first so that BOX_STATUS only contains the set index and box layer.
if (lastAction) refresh();
string reply = "::::::" + str;
integer link = (integer)token1;
integer face = (integer)token2;
list primNameTokens = llParseStringKeepNulls(llGetLinkName(link), [":"], []);
if (llList2String(primNameTokens, 0) == "FURWARE text mesh") {
integer rootIndex = llListFindList(boxNameList, [llList2String(primNameTokens, 1)]);
if (~rootIndex) {
integer auxIndex = llListFindList(primLinkList, [link]);
if (~auxIndex) {
integer layer = (llList2Integer(primLayerList, auxIndex) >> (4*face)) & 0xF;
integer boxIndex = llListFindList(boxDataList, [(rootIndex << 4) | layer]);
if (~boxIndex) {
boxIndex -= BOX_STATUS;
vector setGeom = llList2Vector(setDataList, 2*rootIndex);
rotation boxGeom = llList2Rot(boxDataList, boxIndex + BOX_GEOM);
integer x = llList2Integer(primNameTokens, 3) * (integer)setGeom.z + face;
integer y = llList2Integer(primNameTokens, 2);
reply = llList2String(boxNameList, boxIndex/BOX_STRIDE) + ":" +
(string)(x - (integer)boxGeom.x) + ":" +
(string)(y - (integer)boxGeom.y) + ":" +
llList2String(boxNameList, rootIndex) + ":" +
(string)x + ":" +
(string)y + ":" + str;
}
}
}
}
llMessageLinked(sender, 0, reply, "fw_touchreply");
return;
}
if (token0 == "fw_notify") {
gNotify = (str == "on");
return;
}
if (token0 == "fw_memory") {
llOwnerSay((string)llGetFreeMemory() + " bytes free");
return;
}
if (token0 == "fw_reset") {
llResetScript();
}
}
timer() {
refresh();
}
changed(integer change) {
if (change & CHANGED_LINK) {
if (llGetObjectPrimCount(llGetKey())+llGetNumberOfPrims()*!!llGetAttached() != primCount) {
llResetScript();
}
}
}
}