From 33e8c0643df0dbaa4ba451a067fa63d5321746bf Mon Sep 17 00:00:00 2001 From: fgenesis Date: Fri, 5 May 2023 04:15:41 +0200 Subject: [PATCH] Implement element tags A map element ("tile") can now be tagged with an int. In the editor: Ctrl+Numpad to quickly set tag 0-9 Ctrl+T to input tag number for selection This tag can be used by scripts for more advanced tile manipulation compared to the old node_setElementsInLayerActive(). This function now takes a 4th parameter, the tag. If 0 or omitted, the behavior is unchanged. Otherwise, only affect tiles with that tag. Add Lua func refreshElementsWithTagCallback(tag, callback): Call callback(templateIdx:int, texture:str, active:bool, layer:int, tag:int), and the tile is set to active when the return value is true. refreshElementsOnLayerCallback(tag, callback) is also changed to use the same callback. Also fix UB (dangling pointer)in Element::render() that never even caused a single problem. --- Aquaria/Element.cpp | 58 +++++++++++++++++++++++++++++-------- Aquaria/Element.h | 4 ++- Aquaria/Game.cpp | 32 +++++++++++++++++--- Aquaria/SceneEditor.cpp | 42 +++++++++++++++++++++++---- Aquaria/ScriptInterface.cpp | 51 +++++++++++++++++++++++++++----- 5 files changed, 156 insertions(+), 31 deletions(-) diff --git a/Aquaria/Element.cpp b/Aquaria/Element.cpp index 258eda9..b83b4c7 100644 --- a/Aquaria/Element.cpp +++ b/Aquaria/Element.cpp @@ -42,6 +42,7 @@ Element::Element() : Quad() elementFlag = EF_NONE; elementActive = true; bgLayer = 0; + tag = 0; templateIdx = -1; eff = NULL; } @@ -280,37 +281,66 @@ void Element::setElementEffectByIndex(int eidx) } } +// shamelessly ripped from paint.net default palette +static const Vector s_tagColors[] = +{ + /* 0 */ Vector(0.5f, 0.5f, 0.5f), + /* 1 */ Vector(1,0,0), + /* 2 */ Vector(1, 0.415686f, 0), + /* 3 */ Vector(1,0.847059f, 0), + /* 4 */ Vector(0.298039f,1,0), + /* 5 */ Vector(0,1,1), + /* 6 */ Vector(0,0.580392,1), + /* 7 */ Vector(0,0.149020f,1), + /* 8 */ Vector(0.282353f,0,1), + /* 9 */ Vector(0.698039f,0,1), + + /* 10 */ Vector(1,0,1), // anything outside of the pretty range +}; + +static inline const Vector& getTagColor(int tag) +{ + const unsigned idx = std::min(unsigned(tag), Countof(s_tagColors)-1); + return s_tagColors[idx]; + +} + void Element::render(const RenderState& rs) const { if (!elementActive) return; - const RenderState *rsp = &rs; if (dsq->game->isSceneEditorActive() && this->bgLayer == dsq->game->sceneEditor.bgLayer && dsq->game->sceneEditor.editType == ET_ELEMENTS) { - RenderState rs2(rs); - rsp = &rs2; - rs2.forceRenderBorder = true; - rs2.forceRenderCenter = true; - rs2.renderBorderColor = Vector(0.5f, 0.5f, 0.5f); - + Vector tagColor = getTagColor(tag); + bool hl = false; if (!dsq->game->sceneEditor.selectedElements.empty()) { for (size_t i = 0; i < dsq->game->sceneEditor.selectedElements.size(); i++) { if (this == dsq->game->sceneEditor.selectedElements[i]) - rs2.renderBorderColor = Vector(1,1,1); + { + hl = true; + break; + } } } else { - if (dsq->game->sceneEditor.editingElement == this) - rs2.renderBorderColor = Vector(1,1,1); + hl = dsq->game->sceneEditor.editingElement == this; } - } + if(hl) + tagColor += Vector(0.5f, 0.5f, 0.5f); - Quad::render(*rsp); + RenderState rs2(rs); + rs2.forceRenderBorder = true; + rs2.forceRenderCenter = true; + rs2.renderBorderColor = tagColor; + Quad::render(rs2); + } + else // render normally + Quad::render(rs); } void Element::fillGrid() @@ -336,3 +366,7 @@ void Element::fillGrid() } } +void Element::setTag(int tag) +{ + this->tag = tag; +} diff --git a/Aquaria/Element.h b/Aquaria/Element.h index 5d01ab5..f362f9d 100644 --- a/Aquaria/Element.h +++ b/Aquaria/Element.h @@ -65,15 +65,17 @@ public: void update(float dt); size_t templateIdx; int bgLayer; + int tag; Element *bgLayerNext; void render(const RenderState& rs) const OVERRIDE; ElementFlag elementFlag; void fillGrid(); - bool isElementActive() { return elementActive; } + bool isElementActive() const { return elementActive; } int getElementEffectIndex(); void setElementEffectByIndex(int e); void setElementActive(bool v) { elementActive = v; } void doInteraction(Entity *ent, float mult, float touchWidth); + void setTag(int tag); protected: void ensureEffectData(); void freeEffectData(); diff --git a/Aquaria/Game.cpp b/Aquaria/Game.cpp index d41037c..91e47b5 100644 --- a/Aquaria/Game.cpp +++ b/Aquaria/Game.cpp @@ -1889,7 +1889,7 @@ bool Game::loadSceneXML(std::string scene) } if (simpleElements->Attribute("repeatScale")) { - SimpleIStringStream is2(simpleElements->Attribute("repeatScale")); + SimpleIStringStream is2(simpleElements->Attribute("repeatScale"), SimpleIStringStream::REUSE); for(size_t i = 0; i < loadedElements.size(); ++i) { Element *e = loadedElements[i]; @@ -1904,6 +1904,18 @@ bool Game::loadSceneXML(std::string scene) } } } + if (simpleElements->Attribute("tag")) + { + SimpleIStringStream is2(simpleElements->Attribute("tag"), SimpleIStringStream::REUSE); + for(size_t i = 0; i < loadedElements.size(); ++i) + { + Element *e = loadedElements[i]; + int tag = 0; + if(!(is2 >> tag)) + break; + e->setTag(tag); + } + } simpleElements = simpleElements->NextSiblingElement("SE"); } @@ -2130,6 +2142,8 @@ bool Game::saveScene(std::string scene) std::ostringstream simpleElements[LR_MAX]; std::ostringstream simpleElements_repeatScale[LR_MAX]; + std::ostringstream simpleElements_tag[LR_MAX]; + unsigned tagBitsUsed[LR_MAX] = { 0 }; for (size_t i = 0; i < dsq->getNumElements(); i++) { @@ -2153,6 +2167,9 @@ bool Game::saveScene(std::string scene) SE_rs << e->repeatToFillScale.x << " " << e->repeatToFillScale.y << " "; } + + simpleElements_tag[e->bgLayer] << e->tag << " "; + tagBitsUsed[e->bgLayer] |= e->tag; } if (dsq->game->entitySaveData.size() > 0) @@ -2187,9 +2204,16 @@ bool Game::saveScene(std::string scene) XMLElement *simpleElementsXML = saveFile.NewElement("SE"); simpleElementsXML->SetAttribute("k", s.c_str()); simpleElementsXML->SetAttribute("l", i); - std::string repeatScaleStr = simpleElements_repeatScale[i].str(); - if(!repeatScaleStr.empty()) - simpleElementsXML->SetAttribute("repeatScale", repeatScaleStr.c_str()); + std::string str = simpleElements_repeatScale[i].str(); + if(!str.empty()) + simpleElementsXML->SetAttribute("repeatScale", str.c_str()); + if(tagBitsUsed[i]) // skip writing tags on layers where it's all zero (mainly to avoid putting a long string of 0's for border elements) + { + str = simpleElements_tag[i].str(); + if(!str.empty()) + simpleElementsXML->SetAttribute("tag", str.c_str()); + } + saveFile.InsertEndChild(simpleElementsXML); } } diff --git a/Aquaria/SceneEditor.cpp b/Aquaria/SceneEditor.cpp index 0cf4451..67a01d7 100644 --- a/Aquaria/SceneEditor.cpp +++ b/Aquaria/SceneEditor.cpp @@ -787,11 +787,23 @@ void SceneEditor::reversePath() void SceneEditor::setGridPattern(int gi) { - if (selectedElements.size()) - for (size_t i = 0; i < selectedElements.size(); ++i) - selectedElements[i]->setElementEffectByIndex(gi); - else if (editingElement) - editingElement->setElementEffectByIndex(gi); + if(core->getCtrlState()) + { + const int tag = gi + 1; // key 0 is tag 0... otherwise it's all off by one + if (selectedElements.size()) + for (size_t i = 0; i < selectedElements.size(); ++i) + selectedElements[i]->setTag(tag); + else if (editingElement) + editingElement->setTag(tag); + } + else + { + if (selectedElements.size()) + for (size_t i = 0; i < selectedElements.size(); ++i) + selectedElements[i]->setElementEffectByIndex(gi); + else if (editingElement) + editingElement->setElementEffectByIndex(gi); + } } void SceneEditor::setGridPattern0() @@ -1755,6 +1767,23 @@ void SceneEditor::flipElementHorz() if (editType != ET_ELEMENTS) return; + if(core->getCtrlState()) + { + if(!selectedElements.empty() || editingElement) + { + std::string inp = dsq->getUserInputString("Enter tag (int):"); + if(inp.empty()) + return; + int tag = atoi(inp.c_str()); + if(!selectedElements.empty()) + for(size_t i = 0; i < selectedElements.size(); ++i) + selectedElements[i]->setTag(tag); + else + editingElement->setTag(tag); + } + return; + } + if (editingElement) editingElement->flipHorizontal(); else @@ -2607,10 +2636,11 @@ void SceneEditor::updateText() if (e) { os << " id: " << e->templateIdx; + os << " efx: " << (e->getElementEffectIndex() + 1); // +1 so that it resembles the layout on numpad + os << " tag: " << e->tag; ElementTemplate *et = game->getElementTemplateByIdx(e->templateIdx); if (et) os << " gfx: " << et->gfx; - os << " efx: " << (e->getElementEffectIndex() + 1); // +1 so that it resembles the layout on numpad } } break; diff --git a/Aquaria/ScriptInterface.cpp b/Aquaria/ScriptInterface.cpp index 8a629ce..2473e3e 100644 --- a/Aquaria/ScriptInterface.cpp +++ b/Aquaria/ScriptInterface.cpp @@ -7672,9 +7672,10 @@ luaFunc(node_setElementsInLayerActive) { int l = lua_tointeger(L, 2); bool v = getBool(L, 3); + int tag = lua_tointeger(L, 3); for (Element *e = dsq->getFirstElementOnLayer(l); e; e = e->bgLayerNext) { - if (e && p->isCoordinateInside(e->position)) + if (e && (!tag || e->tag == tag) && p->isCoordinateInside(e->position)) { e->setElementActive(v); } @@ -7683,22 +7684,55 @@ luaFunc(node_setElementsInLayerActive) luaReturnNil(); } +static int pushElementData(lua_State *L, const Element *e) +{ + lua_pushinteger(L, e->templateIdx); + lua_pushstring(L, e->texture->name.c_str()); + lua_pushboolean(L, e->isElementActive()); + lua_pushinteger(L, e->bgLayer); + lua_pushinteger(L, e->tag); + return 5; +} + +// (layer, func) luaFunc(refreshElementsOnLayerCallback) { - int layer = lua_tointeger(L, 1); + const int layer = lua_tointeger(L, 1); + size_t done = 0; for (Element *e = dsq->getFirstElementOnLayer(layer); e; e = e->bgLayerNext) { - bool on = e->isElementActive(); lua_pushvalue(L, 2); // the callback - lua_pushinteger(L, e->templateIdx); - lua_pushstring(L, e->texture->name.c_str()); - lua_pushboolean(L, e->isElementActive()); - lua_call(L, 3, 1); + int args = pushElementData(L, e); + lua_call(L, args, 1); bool newon = lua_toboolean(L, -1); lua_pop(L, 1); e->setElementActive(newon); + ++done; } - luaReturnNil(); + luaReturnInt(done); +} + +// (tag, func) +luaFunc(refreshElementsWithTagCallback) +{ + const int tag = lua_tointeger(L, 1); + const size_t N = dsq->getNumElements(); + size_t done = 0; + for(size_t i = 0; i < N; ++i) + { + Element *e = dsq->getElement(i); + if(e->tag == tag) + { + lua_pushvalue(L, 2); // the callback + int args = pushElementData(L, e); + lua_call(L, args, 1); + bool newon = lua_toboolean(L, -1); + lua_pop(L, 1); + e->setElementActive(newon); + ++done; + } + } + luaReturnInt(done); } @@ -10747,6 +10781,7 @@ static const struct { luaRegister(node_setElementsInLayerActive), luaRegister(refreshElementsOnLayerCallback), + luaRegister(refreshElementsWithTagCallback), luaRegister(entity_setHealth),