diff --git a/code/nel/include/nel/gui/event_descriptor.h b/code/nel/include/nel/gui/event_descriptor.h index 276979fdd..95bdad212 100644 --- a/code/nel/include/nel/gui/event_descriptor.h +++ b/code/nel/include/nel/gui/event_descriptor.h @@ -63,7 +63,7 @@ public: keystring, // a string has been sent. The string is a ucstring unknown, // uninitialized event }; - CEventDescriptorKey() : _KeyEvent(unknown) + CEventDescriptorKey() : _KeyEvent(unknown), _CtrlState(false), _ShiftState(false), _AltState(false), _Char(0) { _EventType = key; } @@ -105,6 +105,31 @@ public: { return _AltState; } + + // return true if key was pressed or held down at a time of this event + bool isShiftDown() + { + return (_KeyEvent == CEventDescriptorKey::keydown && (_Key == NLMISC::KeySHIFT || _ShiftState)) + || (_KeyEvent == CEventDescriptorKey::keyup && (_Key != NLMISC::KeySHIFT && _ShiftState)) + || (_KeyEvent == CEventDescriptorKey::keychar && _ShiftState); + } + + // return true if key was pressed or held down at a time of this event + bool isCtrlDown() + { + return (_KeyEvent == CEventDescriptorKey::keydown && (_Key == NLMISC::KeyCONTROL || _CtrlState)) + || (_KeyEvent == CEventDescriptorKey::keyup && (_Key != NLMISC::KeyCONTROL && _CtrlState)) + || (_KeyEvent == CEventDescriptorKey::keychar && _CtrlState); + } + + // return true if key was pressed or held down at a time of this event + bool isAltDown() + { + return (_KeyEvent == CEventDescriptorKey::keydown && (_Key == NLMISC::KeyMENU || _AltState)) + || (_KeyEvent == CEventDescriptorKey::keyup && (_Key != NLMISC::KeyMENU && _AltState)) + || (_KeyEvent == CEventDescriptorKey::keychar && _AltState); + } + // init from a CEventKey obj void init(const NLMISC::CEventKey &ev); diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index 74c5a0145..590c43a89 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -360,6 +360,10 @@ namespace NLGUI // Delete page content and prepare next page void removeContent (); + // Counter to number html elements without id attribute + uint32 getNextAutoIdSeq() { return _AutoIdSeq++; } + uint32 _AutoIdSeq; + // Current URL for relative links in page std::string _URL; // Current URL @@ -679,6 +683,8 @@ namespace NLGUI std::vector Entries; }; std::vector _Forms; + // if
element has been closed or not + bool _FormOpen; // submit buttons added to from struct SFormSubmitButton @@ -949,6 +955,7 @@ namespace NLGUI //void htmlEM(const CHtmlElement &elm); void htmlFONT(const CHtmlElement &elm); void htmlFORM(const CHtmlElement &elm); + void htmlFORMend(const CHtmlElement &elm); void htmlH(const CHtmlElement &elm); void htmlHend(const CHtmlElement &elm); void htmlHEAD(const CHtmlElement &elm); diff --git a/code/nel/include/nel/gui/view_renderer.h b/code/nel/include/nel/gui/view_renderer.h index 4194a59fa..aefbb315f 100644 --- a/code/nel/include/nel/gui/view_renderer.h +++ b/code/nel/include/nel/gui/view_renderer.h @@ -267,6 +267,8 @@ namespace NLGUI bool bReleasable=true ); + // Create texture from dataURL "data:image/png;base64," string + sint32 createTextureFromDataURL(const std::string &data, bool uploadDXTC=true, bool bReleasable=true); // change position of a sub-texture (inside its big texture) from the sub-texture filename void updateTexturePos(const std::string &texturefileName, diff --git a/code/nel/include/nel/gui/widget_manager.h b/code/nel/include/nel/gui/widget_manager.h index 4d46c72ff..d0c65356b 100644 --- a/code/nel/include/nel/gui/widget_manager.h +++ b/code/nel/include/nel/gui/widget_manager.h @@ -269,6 +269,14 @@ namespace NLGUI CViewPointerBase* getPointer(){ return _Pointer; } void setPointer( CViewPointerBase *pointer ){ _Pointer = pointer; } + // If > 0, snap window to others closer than distance + void setWindowSnapDistance(uint32 d) { _WindowSnapDistance = d; } + uint32 getWindowSnapDistance() const { return _WindowSnapDistance; } + + // If true, only snap when shift is held down + void setWindowSnapInvert(bool b) { _WindowSnapInvert = b; } + bool getWindowSnapInvert() const { return _WindowSnapInvert; } + /** * get the window under a spot * \param : X coord of the spot @@ -310,6 +318,9 @@ namespace NLGUI void drawOverExtendViewText(); + // Snap to closest visible window border if snapping is enabled + void snapIfClose(CInterfaceGroup *group); + // Internal : adjust a tooltip with respect to its parent. Returns the number of coordinate that were clamped // against the screen border uint adjustTooltipPosition( CCtrlBase *newCtrl, CInterfaceGroup *win, THotSpot ttParentRef, @@ -624,6 +635,9 @@ namespace NLGUI CEventDescriptorKey lastKeyEvent; + uint32 _WindowSnapDistance; + bool _WindowSnapInvert; + uint32 _ScreenH; uint32 _ScreenW; float _InterfaceScale; diff --git a/code/nel/include/nel/misc/base64.h b/code/nel/include/nel/misc/base64.h new file mode 100644 index 000000000..4c5c2babd --- /dev/null +++ b/code/nel/include/nel/misc/base64.h @@ -0,0 +1,154 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#ifndef NL_BASE64_H +#define NL_BASE64_h + +namespace NLMISC +{ + +/** + * \brief base64 encode/decode + * \date 2020-01-23 12:39GMT + * \author Meelis Mägi (Nimetu) + */ + +struct base64 { + static std::string encode(const std::string &data) + { + /* Conversion table. for base 64 */ + static const char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + static const char padChar = '='; + + size_t inLength = data.size(); + size_t outLength = 4 * ((inLength + 2) / 3); + + std::string out; + out.resize(outLength); + + size_t iRead=0, oWrite=0; + for (size_t iLoop = 0; iLoop < inLength/3; iLoop++) + { + out[oWrite+0] = tbl[ (data[iRead+0] >> 2) & 0x3F]; + out[oWrite+1] = tbl[((data[iRead+0] << 4) & 0x30) + ((data[iRead+1] >> 4) & 0x0F)]; + out[oWrite+2] = tbl[((data[iRead+1] << 2) & 0x3C) + ((data[iRead+2] >> 6) & 0x03)]; + out[oWrite+3] = tbl[ data[iRead+2] & 0x3F]; + iRead += 3; + oWrite += 4; + } + + // remaining bytes + switch(inLength % 3) + { + case 2: + out[oWrite+0] = tbl[ (data[iRead+0] >> 2) & 0x3F]; + out[oWrite+1] = tbl[((data[iRead+0] << 4) & 0x30) + ((data[iRead+1] >> 4) & 0x0F)]; + out[oWrite+2] = tbl[((data[iRead+1] << 2) & 0x3C)]; + out[oWrite+3] = padChar; + break; + case 1: + out[oWrite+0] = tbl[ (data[iRead+0] >> 2) & 0x3F]; + out[oWrite+1] = tbl[((data[iRead+0] << 4) & 0x30)]; + out[oWrite+2] = padChar; + out[oWrite+3] = padChar; + break; + default: + break; + } + + return out; + } + + static std::string decode(const std::string &in) + { + static sint8 tbl[] = { + // 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 20 + / + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, // 30 0..9 = + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40 A.. + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 50 ..Z + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60 a.. + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 70 ..z + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // B0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // D0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // F0 + }; + static char padChar = '='; + + size_t inLength = in.size(); + // add optional padding if its missing from input + size_t outLength = (inLength + inLength % 4) / 4 * 3; + + std::string out; + if (inLength > 0) + { + uint8 buf[4]; + size_t iBuf = 0; + size_t iRead = 0; + size_t oWrite = 0; + + out.resize(outLength); + while(iRead < inLength && in[iRead] != padChar) + { + buf[iBuf] = (uint8)tbl[in[iRead]]; + // invalid byte in input + if (buf[iBuf] == 0xFF) + break; + + iRead++; + iBuf++; + if (iBuf == 4) + { + out[oWrite+0] = ((buf[0] << 2) & 0xFC) + ((buf[1] >> 4) & 0x0F); + out[oWrite+1] = ((buf[1] << 4) & 0xF0) + ((buf[2] >> 2) & 0x0F); + out[oWrite+2] = ((buf[2] << 6) & 0xC0) + (buf[3] & 0x3F); + oWrite += 3; + iBuf = 0; + } + } + + if (iBuf > 0) + { + uint8 tmp[3]; + tmp[0] = ((buf[0] << 2) & 0xFC) + ((buf[1] >> 4) & 0x0F); + tmp[1] = ((buf[1] << 4) & 0xF0) + ((buf[2] >> 2) & 0x0F); + tmp[2] = ((buf[2] << 6) & 0xC0) + (buf[3] & 0x3F); + for(uint i = 0; i < iBuf-1; i++, oWrite++) + out[oWrite] = tmp[i]; + } + + if (out.size() != oWrite) + out.resize(oWrite); + } + + return out; + } +}; + +}//namespace NLMISC + +#endif // NL_BASE64_H + diff --git a/code/nel/include/nel/misc/common.h b/code/nel/include/nel/misc/common.h index 2b6f85f84..f5f99ea6a 100644 --- a/code/nel/include/nel/misc/common.h +++ b/code/nel/include/nel/misc/common.h @@ -307,6 +307,10 @@ template T trimQuotes (const T &str) return str.substr(1, size - 2); } +// encode/decode uri component using %AB hex encoding +std::string encodeURIComponent(const std::string &in); +std::string decodeURIComponent(const std::string &in); + ////////////////////////////////////////////////////////////////////////// // **** DEPRECATED *****: PLEASE DON'T USE THESE METHODS BUT FUNCTIONS ABOVE toLower() and toUpper() ////////////////////////////////////////////////////////////////////////// diff --git a/code/nel/src/gui/group_container.cpp b/code/nel/src/gui/group_container.cpp index b642a50b5..87fb59c38 100644 --- a/code/nel/src/gui/group_container.cpp +++ b/code/nel/src/gui/group_container.cpp @@ -819,6 +819,8 @@ namespace NLGUI _Parent->setX(x); _Parent->setY(y); + CWidgetManager::getInstance()->snapIfClose(_Parent); + // if some action handler to call when moving if(gc->getAHOnMovePtr()) { diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 2e74182c3..8009c5ac8 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -718,6 +718,14 @@ namespace NLGUI std::string finalUrl; img->setModulateGlobalColor(style.GlobalColor); + // data:image/png;base64,AA...== + if (startsWith(url, "data:image/")) + { + setImage(img, decodeURIComponent(url), type); + setImageSize(img, style); + return; + } + // load the image from local files/bnp std::string image = CFile::getPath(url) + CFile::getFilenameWithoutExtension(url) + ".tga"; if (lookupLocalFile(finalUrl, image.c_str(), false)) @@ -1193,7 +1201,7 @@ namespace NLGUI case HTML_DT: htmlDTend(elm); break; case HTML_EM: renderPseudoElement(":after", elm);break; case HTML_FONT: break; - case HTML_FORM: renderPseudoElement(":after", elm);break; + case HTML_FORM: htmlFORMend(elm); break; case HTML_H1://no-break case HTML_H2://no-break case HTML_H3://no-break @@ -1447,6 +1455,8 @@ namespace NLGUI _LastRefreshTime = 0.0; _RenderNextTime = false; _WaitingForStylesheet = false; + _AutoIdSeq = 0; + _FormOpen = false; // Register CWidgetManager::getInstance()->registerClockMsgTarget(this); @@ -2535,6 +2545,7 @@ namespace NLGUI { // Add a new paragraph CGroupParagraph *newParagraph = new CGroupParagraph(CViewBase::TCtorParam()); + newParagraph->setId(getCurrentGroup()->getId() + ":PARAGRAPH" + toString(getNextAutoIdSeq())); newParagraph->setResizeFromChildH(true); newParagraph->setMarginLeft(getIndent()); @@ -3137,45 +3148,67 @@ namespace NLGUI ctrlButton->setId(name); } - // Load only tga files.. (conversion in dds filename is done in the lookup procedure) - string normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga"; - - // if the image doesn't exist on local, we check in the cache - // if(!CFile::fileExists(normal)) - if(!CPath::exists(normal)) + std::string normal; + if (startsWith(normalBitmap, "data:image/")) { - // search in the compressed texture - CViewRenderer &rVR = *CViewRenderer::getInstance(); - sint32 id = rVR.getTextureIdFromName(normal); - if(id == -1) + normal = decodeURIComponent(normalBitmap); + } + else + { + // Load only tga files.. (conversion in dds filename is done in the lookup procedure) + normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga"; + + // if the image doesn't exist on local, we check in the cache + if(!CPath::exists(normal)) { - normal = localImageName(normalBitmap); - addImageDownload(normalBitmap, ctrlButton, style); + // search in the compressed texture + CViewRenderer &rVR = *CViewRenderer::getInstance(); + sint32 id = rVR.getTextureIdFromName(normal); + if(id == -1) + { + normal = localImageName(normalBitmap); + addImageDownload(normalBitmap, ctrlButton, style); + } } } - string pushed = pushedBitmap.empty()?"":CFile::getPath(pushedBitmap) + CFile::getFilenameWithoutExtension(pushedBitmap) + ".tga"; - // if the image doesn't exist on local, we check in the cache, don't download it because the "normal" will already setuped it - // if(!CFile::fileExists(pushed)) - if(!CPath::exists(pushed)) + std::string pushed; + if (startsWith(pushedBitmap, "data:image/")) + { + pushed = decodeURIComponent(pushedBitmap); + } + else { - // search in the compressed texture - CViewRenderer &rVR = *CViewRenderer::getInstance(); - sint32 id = rVR.getTextureIdFromName(pushed); - if(id == -1) + pushed = pushedBitmap.empty()?"":CFile::getPath(pushedBitmap) + CFile::getFilenameWithoutExtension(pushedBitmap) + ".tga"; + // if the image doesn't exist on local, we check in the cache, don't download it because the "normal" will already setuped it + if(!CPath::exists(pushed)) { - pushed = localImageName(pushedBitmap); + // search in the compressed texture + CViewRenderer &rVR = *CViewRenderer::getInstance(); + sint32 id = rVR.getTextureIdFromName(pushed); + if(id == -1) + { + pushed = localImageName(pushedBitmap); + } } } - string over = overBitmap.empty()?"":CFile::getPath(overBitmap) + CFile::getFilenameWithoutExtension(overBitmap) + ".tga"; - // schedule mouseover bitmap for download if its different from normal - if (!over.empty() && !CPath::exists(over)) + std::string over; + if (startsWith(overBitmap, "data:image/")) { - if (overBitmap != normalBitmap) + over = decodeURIComponent(overBitmap); + } + else + { + over = overBitmap.empty()?"":CFile::getPath(overBitmap) + CFile::getFilenameWithoutExtension(overBitmap) + ".tga"; + // schedule mouseover bitmap for download if its different from normal + if (!over.empty() && !CPath::exists(over)) { - over = localImageName(overBitmap); - addImageDownload(overBitmap, ctrlButton, style, OverImage); + if (overBitmap != normalBitmap) + { + over = localImageName(overBitmap); + addImageDownload(overBitmap, ctrlButton, style, OverImage); + } } } @@ -3242,6 +3275,7 @@ namespace NLGUI _Cells.clear(); _TR.clear(); _Forms.clear(); + _FormOpen = false; _FormSubmit.clear(); _Groups.clear(); _Divs.clear(); @@ -3255,6 +3289,7 @@ namespace NLGUI _ReadingHeadTag = false; _IgnoreHeadTag = false; _IgnoreBaseUrlTag = false; + _AutoIdSeq = 0; paragraphChange (); @@ -4331,6 +4366,7 @@ namespace NLGUI if (!_GroupListAdaptor) { _GroupListAdaptor = new CGroupListAdaptor(CViewBase::TCtorParam()); // deleted by the list + _GroupListAdaptor->setId(getList()->getId() + ":GLA"); _GroupListAdaptor->setResizeFromChildH(true); getList()->addChild (_GroupListAdaptor, true); } @@ -5545,6 +5581,11 @@ namespace NLGUI std::string tooltip = elm.getAttribute("tooltip"); bool disabled = elm.hasAttribute("disabled"); + if (formId.empty() && _FormOpen) + { + formId = _Forms.back().id; + } + if (!formAction.empty()) { formAction = getAbsoluteUrl(formAction); @@ -5647,6 +5688,8 @@ namespace NLGUI { string style = elm.getAttribute("style"); string id = elm.getAttribute("id"); + if (id.empty()) + id = "DIV" + toString(getNextAutoIdSeq()); typedef pair TTmplParam; vector tmplParams; @@ -5679,10 +5722,10 @@ namespace NLGUI parentId = _Paragraph->getId(); } - CInterfaceGroup *inst = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName, this->_Id+":"+id, tmplParams); + CInterfaceGroup *inst = CWidgetManager::getInstance()->getParser()->createGroupInstance(templateName, parentId, tmplParams); if (inst) { - inst->setId(this->_Id+":"+id); + inst->setId(parentId+":"+id); inst->updateCoords(); if (haveParentDiv) { @@ -5815,6 +5858,8 @@ namespace NLGUI // *************************************************************************** void CGroupHTML::htmlFORM(const CHtmlElement &elm) { + _FormOpen = true; + // Build the form CGroupHTML::CForm form; // id check is case sensitive and auto id's are uppercase @@ -5839,6 +5884,12 @@ namespace NLGUI renderPseudoElement(":before", elm); } + void CGroupHTML::htmlFORMend(const CHtmlElement &elm) + { + _FormOpen = false; + renderPseudoElement(":after", elm); + } + // *************************************************************************** void CGroupHTML::htmlH(const CHtmlElement &elm) { @@ -6374,6 +6425,12 @@ namespace NLGUI if (_Forms.empty() || _Forms.back().Entries.empty()) return; + // use option text as value + if (!elm.hasAttribute("value")) + { + _Forms.back().Entries.back().SelectValues.back() = _SelectOptionStr.toUtf8(); + } + // insert the parsed text into the select control CDBGroupComboBox *cb = _Forms.back().Entries.back().ComboBox; if (cb) @@ -6611,6 +6668,10 @@ namespace NLGUI CGroupTable *table = new CGroupTable(TCtorParam()); table->BgColor = _CellParams.back().BgColor; + if (elm.hasNonEmptyAttribute("id")) + table->setId(getCurrentGroup()->getId() + ":" + elm.getAttribute("id")); + else + table->setId(getCurrentGroup()->getId() + ":TABLE" + toString(getNextAutoIdSeq())); // TODO: border-spacing: 2em; { @@ -6771,6 +6832,12 @@ namespace NLGUI } _Cells.back() = new CGroupCell(CViewBase::TCtorParam()); + if (elm.hasNonEmptyAttribute("id")) + _Cells.back()->setId(table->getId() + ":" + elm.getAttribute("id")); + else + _Cells.back()->setId(table->getId() + ":TD" + toString(getNextAutoIdSeq())); + // inner cell content + _Cells.back()->Group->setId(_Cells.back()->getId() + ":CELL"); if (_Style.checkStyle("background-repeat", "repeat")) _Cells.back()->setTextureTile(true); diff --git a/code/nel/src/gui/group_table.cpp b/code/nel/src/gui/group_table.cpp index 4411be1dd..4b63903e4 100644 --- a/code/nel/src/gui/group_table.cpp +++ b/code/nel/src/gui/group_table.cpp @@ -610,16 +610,12 @@ namespace NLGUI // ---------------------------------------------------------------------------- void CGroupCell::setTextureTile(bool tiled) { - if (tiled) - nlinfo("Set texture is Tiled"); _TextureTiled = tiled; } // ---------------------------------------------------------------------------- void CGroupCell::setTextureScale(bool scaled) { - if (scaled) - nlinfo("Set texture is Scaled : %s"); _TextureScaled = scaled; } diff --git a/code/nel/src/gui/view_bitmap.cpp b/code/nel/src/gui/view_bitmap.cpp index 633a8856b..8922d5c4e 100644 --- a/code/nel/src/gui/view_bitmap.cpp +++ b/code/nel/src/gui/view_bitmap.cpp @@ -481,7 +481,7 @@ namespace NLGUI } } else - _TextureId.setTexture (toLower(TxName).c_str (), _TxtOffsetX, _TxtOffsetY, _TxtWidth, _TxtHeight, false); + _TextureId.setTexture (TxName.c_str (), _TxtOffsetX, _TxtOffsetY, _TxtWidth, _TxtHeight, false); } // ---------------------------------------------------------------------------- diff --git a/code/nel/src/gui/view_renderer.cpp b/code/nel/src/gui/view_renderer.cpp index 5dabede88..4c2db062a 100644 --- a/code/nel/src/gui/view_renderer.cpp +++ b/code/nel/src/gui/view_renderer.cpp @@ -24,6 +24,8 @@ #include "nel/misc/file.h" #include "nel/misc/uv.h" #include "nel/misc/hierarchical_timer.h" +#include "nel/misc/base64.h" +#include "nel/misc/md5.h" using namespace NLMISC; using namespace std; @@ -998,6 +1000,10 @@ namespace NLGUI ) { if (sGlobalTextureName.empty()) return -1; + + if (startsWith(sGlobalTextureName, "data:image/")) + return createTextureFromDataURL(sGlobalTextureName, uploadDXTC, bReleasable); + // Look if already existing string sLwrGTName = toLower(sGlobalTextureName); TGlobalTextureList::iterator ite = _GlobalTextures.begin(); @@ -1062,6 +1068,93 @@ namespace NLGUI return TextID; } + sint32 CViewRenderer::createTextureFromDataURL(const std::string &data, bool uploadDXTC, bool bReleasable) + { + if (!startsWith(data, "data:image/")) + return -1; + + size_t pos = data.find(";base64,"); + if (pos == std::string::npos) + { + nlwarning("Failed to parse dataURL (not base64?) '%s'", data.c_str()); + return -1; + } + + std::string md5hash = getMD5((uint8 *)data.c_str(), (uint32)data.size()).toString(); + + TGlobalTextureList::iterator ite = _GlobalTextures.begin(); + while (ite != _GlobalTextures.end()) + { + if (md5hash == ite->Name) + break; + ite++; + } + + // If global texture not exists create it + if (ite == _GlobalTextures.end()) + { + std::string decoded = base64::decode(data.substr(pos + 8)); + if (decoded.empty()) + { + nlwarning("base64 decode failed '%s'", data.substr(pos + 8).c_str()); + return -1; + } + + // + CMemStream buf; + if (buf.isReading()) buf.invert(); + buf.serialBuffer((uint8 *)(decoded.data()), decoded.size()); + buf.invert(); + + CBitmap btm; + btm.load(buf); + + SGlobalTexture gtTmp; + gtTmp.FromGlobaleTexture = false; + + gtTmp.Width = gtTmp.DefaultWidth = btm.getWidth();; + gtTmp.Height = gtTmp.DefaultHeight = btm.getHeight(); + + if (gtTmp.Width == 0 || gtTmp.Height == 0) + { + nlwarning("Failed to load the texture '%s', please check image format", data.c_str()); + return -1; + } + + UTextureMem *texture = driver->createTextureMem(btm.getWidth(), btm.getHeight(), CBitmap::RGBA); + if (!texture) + { + nlwarning("Failed to create mem texture (%d,%d)", btm.getWidth(), btm.getHeight()); + return -1; + } + + memcpy(texture->getPointer(), btm.getPixels().getPtr(), btm.getSize() * 4); + + gtTmp.Texture = texture; + gtTmp.Name = md5hash; + gtTmp.Texture->setFilterMode(UTexture::Nearest, UTexture::NearestMipMapOff); + gtTmp.Texture->setReleasable(bReleasable); + if(uploadDXTC) + gtTmp.Texture->setUploadFormat(UTexture::DXTC5); + + _GlobalTextures.push_back(gtTmp); + ite = _GlobalTextures.end(); + ite--; + } + + // Add a texture with reference to the i th global texture + SImage iTmp; + + // Set default parameters + iTmp.Name = data; + iTmp.GlobalTexturePtr = &(*ite); + iTmp.UVMin = CUV(0.f , 0.f); + iTmp.UVMax = CUV(1.f , 1.f); + sint32 TextID = addSImage(iTmp); + + return TextID; + } + void CViewRenderer::updateTexturePos(const std::string &texturefileName, sint32 offsetX /*=0*/, sint32 offsetY /*=0*/, sint32 width /*=-1*/, sint32 height /*=-1*/) { sint32 id = getTextureIdFromName (texturefileName); @@ -1159,6 +1252,11 @@ namespace NLGUI { driver->deleteTextureFile (tf); } + else + { + UTextureMem *tf = dynamic_cast(iteGT->Texture); + if (tf) driver->deleteTextureMem(tf); + } _GlobalTextures.erase (iteGT); return; } diff --git a/code/nel/src/gui/widget_manager.cpp b/code/nel/src/gui/widget_manager.cpp index 3e324b4de..e74867961 100644 --- a/code/nel/src/gui/widget_manager.cpp +++ b/code/nel/src/gui/widget_manager.cpp @@ -1205,6 +1205,10 @@ namespace NLGUI CViewText *vtDst = dynamic_cast(groupOver->getView("text")); if (vtDst != NULL) { + groupOver->setParentPos(vtSrc); + + sint32 backupX = groupOver->getX(); + // Copy all aspects to the view vtDst->setText (vtSrc->getText()); vtDst->setFontSize (vtSrc->getFontSize()); @@ -1227,42 +1231,36 @@ namespace NLGUI pOutline->setModulateGlobalColor(vtSrc->getModulateGlobalColor()); } - // the group is the position of the overed text, but apply the delta of borders (vtDst X/Y) - sint32 x = vtSrc->getXReal() - vtDst->getX(); - sint32 y = vtSrc->getYReal() - vtDst->getY(); - // update one time only to get correct W/H groupOver->updateCoords (); - if(!vtSrc->isClampRight()) + // align and clamp to screen coords + sint32 x = -backupX; + if (vtSrc->isClampRight()) + { + x += std::max(0, (groupOver->getXReal() + groupOver->getWReal()) - (groupOver->getParent()->getXReal() + groupOver->getParent()->getWReal())); + } + else { - // clamped from the left part - x += vtSrc->getWReal() - vtDst->getWReal(); + x += vtDst->getWReal() - vtSrc->getWReal(); + if ( x > (groupOver->getXReal() - groupOver->getParent()->getXReal()) ) + { + x -= x - (groupOver->getXReal() - groupOver->getParent()->getXReal()); + } } + if (x != 0) groupOver->setX(-x); - // clamp to screen coords, and set - if ((x+groupOver->getW()) > groupOver->getParent()->getWReal()) - x = groupOver->getParent()->getWReal() - groupOver->getW(); - if (x < 0) - x = 0; - if ((y+groupOver->getH()) > groupOver->getParent()->getHReal()) - y = groupOver->getParent()->getHReal() - groupOver->getH(); - if (y < 0) - y = 0; - - // set pos - groupOver->setX (x); - groupOver->setY (y); - - // update coords 3 times is required - groupOver->updateCoords (); - groupOver->updateCoords (); - groupOver->updateCoords (); + // TODO: there should be no overflow on y, unless barely visible and next to screen border + + groupOver->updateCoords(); // draw groupOver->draw (); // flush layers CViewRenderer::getInstance()->flush(); + + // restore backup values + if (x != 0) groupOver->setX(backupX); } } @@ -1271,7 +1269,125 @@ namespace NLGUI } } + // ---------------------------------------------------------------------------- + void CWidgetManager::snapIfClose(CInterfaceGroup *group) + { + if (!group || _WindowSnapDistance == 0 || _WindowSnapInvert != lastKeyEvent.isShiftDown()) + return; + + uint hsnap = _WindowSnapDistance; + uint vsnap = _WindowSnapDistance; + + sint32 newX = group->getX(); + sint32 newY = group->getY(); + + // new coords for window without snap + // used to calculate distance from target + sint gLeft = newX; + sint gRight = newX + group->getWReal(); + sint gTop = newY; + sint gBottom = newY - group->getHReal(); + + // current window coords as if already snaped + // used to calculate target for snap + sint gLeftR = group->getXReal(); + sint gRightR = gLeftR + group->getWReal(); + sint gBottomR = group->getYReal(); + sint gTopR = gBottomR + group->getHReal(); + + for (uint32 nMasterGroup = 0; nMasterGroup < _MasterGroups.size(); nMasterGroup++) + { + CWidgetManager::SMasterGroup &rMG = _MasterGroups[nMasterGroup]; + if (!rMG.Group->getActive()) continue; + + for (uint8 nPriority = WIN_PRIORITY_MAX; nPriority > 0 ; nPriority--) + { + const std::list &rList = rMG.PrioritizedWindows[nPriority-1]; + std::list::const_reverse_iterator itw; + for (itw = rList.rbegin(); itw != rList.rend(); itw++) + { + CInterfaceGroup *pIG = *itw; + // do not snap to self, inactive, or not using mouse interaction + if (group == pIG || !(pIG->getActive() && pIG->getUseCursor())) + continue; + + // target + sint wLeft = pIG->getXReal(); + sint wRight = pIG->getXReal() + pIG->getWReal(); + sint wTop = pIG->getYReal() + pIG->getHReal(); + sint wBottom = pIG->getYReal(); + sint delta; + + if (gTopR >= wBottom && gBottomR <= wTop) + { + delta = abs(gRight - wLeft); + if (delta <= hsnap) + { + hsnap = delta; + newX = wLeft - group->getWReal(); + } + + delta = abs(gLeft - wRight); + if (delta <= hsnap) + { + hsnap = delta; + newX = wRight; + } + + delta = abs(gLeft - wLeft); + if (delta <= hsnap) + { + hsnap = delta; + newX = wLeft; + } + + delta = abs(gRight - wRight); + if (delta <= hsnap) + { + hsnap = delta; + newX = wRight - group->getWReal(); + } + } + if (gLeftR <= wRight && gRightR >= wLeft) + { + delta = abs(gTop - wBottom); + if (delta <= vsnap) + { + vsnap = delta; + newY = wBottom; + } + + delta = abs(gBottom - wTop); + if (delta <= vsnap) + { + vsnap = delta; + newY = wTop + group->getHReal(); + } + + delta = abs(gTop - wTop); + if (delta <= vsnap) + { + vsnap = delta; + newY = wTop; + } + + delta = abs(gBottom - wBottom); + if (delta <= vsnap) + { + vsnap = delta; + newY = wBottom + group->getHReal(); + } + } + }//windows + }//priority + }//master group + + group->setX(newX); + group->setY(newY); + } + + // ---------------------------------------------------------------------------- uint CWidgetManager::adjustTooltipPosition( CCtrlBase *newCtrl, CInterfaceGroup *win, THotSpot ttParentRef, THotSpot ttPosRef, sint32 xParent, sint32 yParent, sint32 wParent, sint32 hParent ) @@ -3788,6 +3904,9 @@ namespace NLGUI setScreenWH(0, 0); _InterfaceScale = 1.0f; + _WindowSnapDistance = 10; + _WindowSnapInvert = false; + _GroupSelection = false; multiSelection = false; _WidgetCount = 0; diff --git a/code/nel/src/misc/common.cpp b/code/nel/src/misc/common.cpp index 31a11b792..b9e9a7d85 100644 --- a/code/nel/src/misc/common.cpp +++ b/code/nel/src/misc/common.cpp @@ -784,6 +784,94 @@ bool fromHexa(const char hexa, uint8 &b) return false; } +static std::vector makeCharLookupTable(const std::string &chars) +{ + std::vector out(256, -1); + for(uint i = 0; i< chars.size(); i++) + out[chars[i]] = i; + + return out; +} + +std::string encodeURIComponent(const std::string &in) +{ + static const char hexLookup[] = "0123456789ABCDEF"; + static const std::vector notEscaped(makeCharLookupTable( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_.!~*'()" + )); + + if (in.empty()) + return std::string(); + + std::string out; + + size_t inSize = in.size(); + size_t outSize = in.size(); + + // resize to worst case for smaller strings, + // give some replacements for free for larger strings + if (in.size() < 100) + out.reserve(in.size() * 3); + else + out.reserve(in.size() + 200); + + for(size_t i = 0; i < inSize; i++) + { + char ch = in[i]; + if (notEscaped[(uint8)ch] == -1) + { + out += '%'; + out += hexLookup[(ch>>4)& 0x0F]; + out += hexLookup[ch & 0x0F]; + outSize += 2; + } + else + { + out += ch; + } + } + // resize back to correct size + out.resize(outSize); + + return out; +} + +std::string decodeURIComponent(const std::string &in) +{ + if (in.find("%") == std::string::npos) + return in; + + std::string out; + out.resize(in.size()); + + size_t outIndex = 0, inSize = in.size(); + for(size_t i = 0; i < inSize; i++, outIndex++) + { + if (in[i] == '%' && (i+2 < inSize)) + { + uint8 a; + uint8 b; + if (fromHexa(in[i+1], a) && fromHexa(in[i+2], b)) + { + out[outIndex] = (a << 4) | b; + i += 2; + } else { + // not hex chars + out[outIndex] = in[i]; + } + } + else + { + out[outIndex] = in[i]; + } + } + out.resize(outIndex); + return out; +} + std::string formatThousands(const std::string& s) { sint i, k; diff --git a/code/nel/tools/nel_unit_test/ut_misc.h b/code/nel/tools/nel_unit_test/ut_misc.h index 7f125f2f3..aef1e4c90 100644 --- a/code/nel/tools/nel_unit_test/ut_misc.h +++ b/code/nel/tools/nel_unit_test/ut_misc.h @@ -31,6 +31,7 @@ #include "ut_misc_variable.h" #include "ut_misc_types.h" #include "ut_misc_string_common.h" +#include "ut_misc_base64.h" // Add a line here when adding a new test CLASS struct CUTMisc : public Test::Suite @@ -51,6 +52,7 @@ struct CUTMisc : public Test::Suite add(std::auto_ptr(new CUTMiscVariable)); add(std::auto_ptr(new CUTMiscTypes)); add(std::auto_ptr(new CUTMiscStringCommon)); + add(std::auto_ptr(new CUTMiscBase64)); // Add a line here when adding a new test CLASS } }; diff --git a/code/nel/tools/nel_unit_test/ut_misc_base64.h b/code/nel/tools/nel_unit_test/ut_misc_base64.h new file mode 100644 index 000000000..84ddfcefc --- /dev/null +++ b/code/nel/tools/nel_unit_test/ut_misc_base64.h @@ -0,0 +1,82 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef UT_MISC_BASE64 +#define UT_MISC_BASE64 + +#include + +struct CUTMiscBase64 : public Test::Suite +{ + CUTMiscBase64() + { + TEST_ADD(CUTMiscBase64::testEncode); + TEST_ADD(CUTMiscBase64::testDecode); + TEST_ADD(CUTMiscBase64::testDecodeNoPadding); + TEST_ADD(CUTMiscBase64::testDecodeInvalid); + } + + void testEncode() + { + TEST_ASSERT("" == NLMISC::base64::encode("")); + + TEST_ASSERT("AA==" == NLMISC::base64::encode(std::string(1, '\0'))); + TEST_ASSERT("YQ==" == NLMISC::base64::encode("a")); + TEST_ASSERT("YWI=" == NLMISC::base64::encode("ab")); + TEST_ASSERT("YWJj" == NLMISC::base64::encode("abc")); + + std::string expect = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODkhQCMwXiYqKCk7Ojw+LC4gW117fQ=="; + std::string encoded = NLMISC::base64::encode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#0^&*();:<>,. []{}"); + TEST_ASSERT(expect == encoded); + } + + void testDecode() + { + TEST_ASSERT("" == NLMISC::base64::decode("")); + TEST_ASSERT("" == NLMISC::base64::decode("=")); + TEST_ASSERT("" == NLMISC::base64::decode("==")); + TEST_ASSERT("" == NLMISC::base64::decode("===")); + TEST_ASSERT("" == NLMISC::base64::decode("====")); + + TEST_ASSERT(std::string(1, '\0') == NLMISC::base64::decode("AA==")); + TEST_ASSERT("a" == NLMISC::base64::decode("YQ==")); + TEST_ASSERT("ab" == NLMISC::base64::decode("YWI=")); + TEST_ASSERT("abc" == NLMISC::base64::decode("YWJj")); + + std::string expect = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#0^&*();:<>,. []{}"; + std::string decoded = NLMISC::base64::decode("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODkhQCMwXiYqKCk7Ojw+LC4gW117fQ=="); + TEST_ASSERT(expect == decoded); + } + + void testDecodeNoPadding() + { + TEST_ASSERT(std::string(1, '\0') == NLMISC::base64::decode("AA")); + TEST_ASSERT("a" == NLMISC::base64::decode("YQ")); + TEST_ASSERT("ab" == NLMISC::base64::decode("YWI")); + + std::string expect = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#0^&*();:<>,. []{}"; + std::string decoded = NLMISC::base64::decode("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODkhQCMwXiYqKCk7Ojw+LC4gW117fQ"); + TEST_ASSERT(expect == decoded); + } + + void testDecodeInvalid() + { + TEST_ASSERT("" == NLMISC::base64::decode("A")); + TEST_ASSERT("" == NLMISC::base64::decode("A===")); + } +}; + +#endif diff --git a/code/nel/tools/nel_unit_test/ut_misc_common.h b/code/nel/tools/nel_unit_test/ut_misc_common.h index d33d65064..8a7aab150 100644 --- a/code/nel/tools/nel_unit_test/ut_misc_common.h +++ b/code/nel/tools/nel_unit_test/ut_misc_common.h @@ -26,6 +26,8 @@ struct CUTMiscCommon : public Test::Suite { TEST_ADD(CUTMiscCommon::bytesToHumanReadableUnits); TEST_ADD(CUTMiscCommon::humanReadableToBytes); + TEST_ADD(CUTMiscCommon::encodeURIComponent); + TEST_ADD(CUTMiscCommon::decodeURIComponent); // Add a line here when adding a new test METHOD } @@ -166,6 +168,27 @@ struct CUTMiscCommon : public Test::Suite bytes = NLMISC::humanReadableToBytes("-1 B"); TEST_ASSERT(bytes == 0); } + + void encodeURIComponent() + { + TEST_ASSERT("%00" == NLMISC::encodeURIComponent(std::string("\x00", 1))); + TEST_ASSERT("%0A" == NLMISC::encodeURIComponent(std::string("\x0A", 1))); + TEST_ASSERT("%A0" == NLMISC::encodeURIComponent(std::string("\xA0", 1))); + TEST_ASSERT("a%20b" == NLMISC::encodeURIComponent("a b")); + TEST_ASSERT("a%2Bb" == NLMISC::encodeURIComponent("a+b")); + } + + void decodeURIComponent() + { + TEST_ASSERT(std::string("\x00", 1) == NLMISC::decodeURIComponent(std::string("\x00", 1))); + TEST_ASSERT(std::string("\x0A", 1) == NLMISC::decodeURIComponent(std::string("\x0A", 1))); + TEST_ASSERT(std::string("\xA0", 1) == NLMISC::decodeURIComponent(std::string("\xA0", 1))); + TEST_ASSERT("a b" == NLMISC::decodeURIComponent("a%20b")); + TEST_ASSERT("a+b" == NLMISC::decodeURIComponent("a%2Bb")); + + TEST_ASSERT("a%A" == NLMISC::decodeURIComponent("a%A")); + TEST_ASSERT("a%AX" == NLMISC::decodeURIComponent("a%AX")); + } }; #endif diff --git a/code/ryzom/client/client_default.cfg b/code/ryzom/client/client_default.cfg index 293f0875d..1c8a4e226 100644 --- a/code/ryzom/client/client_default.cfg +++ b/code/ryzom/client/client_default.cfg @@ -338,6 +338,9 @@ BilinearUI = 1; MaxMapScale = 2.0; R2EDMaxMapScale = 8.0; +WindowSnapInvert = 0; +WindowSnapDistance = 10; + ////////////////// // SOUND CONFIG // ////////////////// diff --git a/code/ryzom/client/src/client_cfg.cpp b/code/ryzom/client/src/client_cfg.cpp index b147226ef..812a146d2 100644 --- a/code/ryzom/client/src/client_cfg.cpp +++ b/code/ryzom/client/src/client_cfg.cpp @@ -316,6 +316,9 @@ CClientConfig::CClientConfig() InterfaceScale_step = 0.05; BilinearUI = true; + WindowSnapInvert = false; + WindowSnapDistance = 10; + VREnable = false; VRDisplayDevice = "Auto"; VRDisplayDeviceId = ""; @@ -859,6 +862,8 @@ void CClientConfig::setValues() READ_FLOAT_FV(InterfaceScale_step); clamp(ClientCfg.InterfaceScale, ClientCfg.InterfaceScale_min, ClientCfg.InterfaceScale_max); READ_BOOL_FV(BilinearUI); + READ_BOOL_FV(WindowSnapInvert); + READ_INT_FV(WindowSnapDistance); // 3D Driver varPtr = ClientCfg.ConfigFile.getVarPtr ("Driver3D"); if (varPtr) diff --git a/code/ryzom/client/src/client_cfg.h b/code/ryzom/client/src/client_cfg.h index 9a64fb4bd..49bcf7495 100644 --- a/code/ryzom/client/src/client_cfg.h +++ b/code/ryzom/client/src/client_cfg.h @@ -157,6 +157,10 @@ struct CClientConfig float InterfaceScale_step; bool BilinearUI; + // Window snap + bool WindowSnapInvert; + uint32 WindowSnapDistance; + // VR bool VREnable; std::string VRDisplayDevice; diff --git a/code/ryzom/client/src/init.cpp b/code/ryzom/client/src/init.cpp index 5007a0ea1..2bbfc3c93 100644 --- a/code/ryzom/client/src/init.cpp +++ b/code/ryzom/client/src/init.cpp @@ -1353,6 +1353,9 @@ void prelogInit() CViewRenderer::getInstance()->setInterfaceScale(1.0f, 1024, 768); CViewRenderer::getInstance()->setBilinearFiltering(ClientCfg.BilinearUI); + CWidgetManager::getInstance()->setWindowSnapInvert(ClientCfg.WindowSnapInvert); + CWidgetManager::getInstance()->setWindowSnapDistance(ClientCfg.WindowSnapDistance); + // Yoyo: initialize NOW the InputHandler for Event filtering. CInputHandlerManager *InputHandlerManager = CInputHandlerManager::getInstance(); InputHandlerManager->addToServer (&Driver->EventServer); diff --git a/code/ryzom/client/src/interface_v3/action_handler_help.cpp b/code/ryzom/client/src/interface_v3/action_handler_help.cpp index 94754432d..77f5f5552 100644 --- a/code/ryzom/client/src/interface_v3/action_handler_help.cpp +++ b/code/ryzom/client/src/interface_v3/action_handler_help.cpp @@ -1120,8 +1120,8 @@ class CHandlerHTMLSubmitForm : public IActionHandler return; } - sint32 x = pCaller->getEventX(); - sint32 y = pCaller->getEventY(); + sint32 x = pCaller ? pCaller->getEventX() : 0; + sint32 y = pCaller ? pCaller->getEventY() : 0; CInterfaceElement *element = CWidgetManager::getInstance()->getElementFromId(container); { @@ -1131,6 +1131,10 @@ class CHandlerHTMLSubmitForm : public IActionHandler { groupHtml->submitForm(button, x, y); } + else + { + nlwarning("CGroupHTML with id '%s' not found.", container.c_str()); + } } } }; diff --git a/code/ryzom/common/src/game_share/effect_families.cpp b/code/ryzom/common/src/game_share/effect_families.cpp index 8d00cf200..391626393 100644 --- a/code/ryzom/common/src/game_share/effect_families.cpp +++ b/code/ryzom/common/src/game_share/effect_families.cpp @@ -323,6 +323,19 @@ namespace EFFECT_FAMILIES { "thorn_wall_aura.sbrick", PowerThornWall }, { "water_wall_aura.sbrick", PowerWaterWall }, { "lightning_wall_aura.sbrick", PowerLightningWall }, + + { "life_aura.sbrick", PowerRootLifeAura }, + { "stamina_aura.sbrick", PowerRootStaminaAura }, + { "sap_aura.sbrick", PowerRootSapAura }, + { "umbrella_aura.sbrick", PowerRootUmbrella }, + { "melee_protection_aura.sbrick", PowerRootProtection }, + { "anti_magic_shield_aura.sbrick", PowerRootAntiMagicShield }, + { "war_cry_aura.sbrick", PowerRootWarCry }, + { "fire_wall_aura.sbrick", PowerRootFireWall }, + { "thorn_wall_aura.sbrick", PowerRootThornWall }, + { "water_wall_aura.sbrick", PowerRootWaterWall }, + { "lightning_wall_aura.sbrick", PowerRootLightningWall }, + { "chg_charac.sbrick", PowerChgCharac }, { "mod_defense.sbrick", PowerModDefenseSkill }, { "mod_dodge.sbrick", PowerModDodgeSkill },