From 6878fbb80a230f91297ad9fcfca5d0594361dac0 Mon Sep 17 00:00:00 2001 From: plenarius Date: Sat, 18 Apr 2026 04:57:38 -0400 Subject: [PATCH 1/3] feat: detect road tiles via crosser metadata for surface speed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of repainting every tileset's walkmesh to use DIRTPATH/STONEPATH, detect road tiles at runtime by checking tile edge (crosser) metadata. When a player steps on DIRT or STONE, the plugin checks if the tile has crosser edges — if any edge is not in an exclusion list, the surface material is promoted to DIRTPATH/STONEPATH for the speed lookup. Exclusion list is loaded from a config file at startup, configurable via NWNX_LAYONARA_CROSSER_EXCLUSIONS_FILE env var. --- Plugins/Layonara/Layonara.cpp | 65 +++++++++++++++++++ Plugins/Layonara/Layonara.hpp | 5 ++ Plugins/Layonara/surface_speed_exclusions.txt | 14 ++++ 3 files changed, 84 insertions(+) create mode 100644 Plugins/Layonara/surface_speed_exclusions.txt diff --git a/Plugins/Layonara/Layonara.cpp b/Plugins/Layonara/Layonara.cpp index 7c652a89a1..1c743b7f40 100644 --- a/Plugins/Layonara/Layonara.cpp +++ b/Plugins/Layonara/Layonara.cpp @@ -18,6 +18,7 @@ #include "API/CNWSInventory.hpp" #include "API/CNWSModule.hpp" #include "API/CNWSPlaceable.hpp" +#include "API/CNWTileData.hpp" #include "API/CNWSPlayer.hpp" #include "API/CNWSTrigger.hpp" #include "API/CNWSJournal.hpp" @@ -119,12 +120,70 @@ Layonara::Layonara(Services::ProxyServiceList* services) m_SurfaceMaterialSpeeds[SurfaceMaterials::SWAMP] = -20; m_SurfaceMaterialSpeeds[SurfaceMaterials::SNOW] = -5; m_SurfaceMaterialSpeeds[SurfaceMaterials::SAND] = -5; + + LoadCrosserExclusions(); } Layonara::~Layonara() { } +void Layonara::LoadCrosserExclusions() +{ + auto path = Config::Get("CROSSER_EXCLUSIONS_FILE", + "/data/surface_speed_exclusions.txt"); + + std::ifstream file(path); + if (!file.is_open()) + { + LOG_WARNING("No crosser exclusions file at %s, all crossers treated as roads", path); + return; + } + + std::string line; + while (std::getline(file, line)) + { + auto start = line.find_first_not_of(" \t"); + if (start == std::string::npos || line[start] == '#') + continue; + auto end = line.find_last_not_of(" \t\r\n"); + m_CrosserExclusions.insert(line.substr(start, end - start + 1)); + } + + LOG_INFO("Loaded %d crosser exclusions", m_CrosserExclusions.size()); +} + +bool Layonara::IsTileOnRoad(CNWSArea* pArea, Vector vPos, uint8_t iMat) +{ + if (iMat != SurfaceMaterials::DIRT && iMat != SurfaceMaterials::STONE) + return false; + + int tileX = (int)(vPos.x / 10.0f); + int tileY = (int)(vPos.y / 10.0f); + + if (tileX < 0 || tileX >= pArea->m_nWidth || + tileY < 0 || tileY >= pArea->m_nHeight) + return false; + + int tileIdx = tileY * pArea->m_nWidth + tileX; + auto* pTileData = pArea->m_pTile[tileIdx].m_pTileData; + if (!pTileData) + return false; + + for (uint8_t e = 0; e < 4; e++) + { + CExoString edge = pTileData->GetEdgeType(e); + if (!edge.IsEmpty()) + { + std::string edgeStr(edge.CStr()); + if (m_CrosserExclusions.find(edgeStr) == m_CrosserExclusions.end()) + return true; + } + } + + return false; +} + CNWSItem *Layonara::GetItemInSlotHook(CNWSInventory *pThis, uint32_t nSlot) { // Arrow @@ -936,6 +995,12 @@ void Layonara::SetPositionHook(CNWSObject* thisPtr, Vector vPos, int32_t bDoingC return; } auto iMat = pArea->GetSurfaceMaterial(vPos); + if (g_plugin->IsTileOnRoad(pArea, vPos, iMat)) + { + iMat = (iMat == SurfaceMaterials::DIRT) + ? SurfaceMaterials::DIRTPATH + : SurfaceMaterials::STONEPATH; + } if (!g_plugin->m_objectCurrentMaterial.count(thisPtr->m_idSelf) || g_plugin->m_objectCurrentMaterial[thisPtr->m_idSelf] != iMat) { diff --git a/Plugins/Layonara/Layonara.hpp b/Plugins/Layonara/Layonara.hpp index 2397321d85..1702834de2 100644 --- a/Plugins/Layonara/Layonara.hpp +++ b/Plugins/Layonara/Layonara.hpp @@ -1,8 +1,10 @@ #pragma once #include "nwnx.hpp" +#include #include #include +#include using namespace std; @@ -90,6 +92,9 @@ class Layonara : public NWNXLib::Plugin unordered_map> m_GemBonuses; unordered_map m_SurfaceMaterialSpeeds; unordered_map m_objectCurrentMaterial; + unordered_set m_CrosserExclusions; + void LoadCrosserExclusions(); + bool IsTileOnRoad(CNWSArea* pArea, Vector vPos, uint8_t iMat); std::list m_ElementalDamageTypes; std::list m_PhysicalDamageTypes; diff --git a/Plugins/Layonara/surface_speed_exclusions.txt b/Plugins/Layonara/surface_speed_exclusions.txt new file mode 100644 index 0000000000..983c8e8d7f --- /dev/null +++ b/Plugins/Layonara/surface_speed_exclusions.txt @@ -0,0 +1,14 @@ +# Crosser edge names that are NOT roads. +# Any crosser not listed here is treated as a road for speed purposes. +# One edge name per line. Lines starting with # are comments. + +# Streams and water +Stream_Summer +stream-deep +Water_Bridge + +# Terrain features (not roads) +Cliffside_Summer +Wall_Cobble +smallwall +bridge From e2d2e8d74a821526c58e5080717dbddd82f0ccbc Mon Sep 17 00:00:00 2001 From: plenarius Date: Sat, 18 Apr 2026 05:01:53 -0400 Subject: [PATCH 2/3] fix: include CNWSTile.hpp to resolve incomplete type error --- Plugins/Layonara/Layonara.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Layonara/Layonara.cpp b/Plugins/Layonara/Layonara.cpp index 1c743b7f40..0a05f7da65 100644 --- a/Plugins/Layonara/Layonara.cpp +++ b/Plugins/Layonara/Layonara.cpp @@ -18,6 +18,7 @@ #include "API/CNWSInventory.hpp" #include "API/CNWSModule.hpp" #include "API/CNWSPlaceable.hpp" +#include "API/CNWSTile.hpp" #include "API/CNWTileData.hpp" #include "API/CNWSPlayer.hpp" #include "API/CNWSTrigger.hpp" From d3184ba9c90768acb313658efd113bf20cadaf71 Mon Sep 17 00:00:00 2001 From: plenarius Date: Sat, 18 Apr 2026 05:06:39 -0400 Subject: [PATCH 3/3] chore: remove exclusions file, now tracked in nwn-module --- Plugins/Layonara/surface_speed_exclusions.txt | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 Plugins/Layonara/surface_speed_exclusions.txt diff --git a/Plugins/Layonara/surface_speed_exclusions.txt b/Plugins/Layonara/surface_speed_exclusions.txt deleted file mode 100644 index 983c8e8d7f..0000000000 --- a/Plugins/Layonara/surface_speed_exclusions.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Crosser edge names that are NOT roads. -# Any crosser not listed here is treated as a road for speed purposes. -# One edge name per line. Lines starting with # are comments. - -# Streams and water -Stream_Summer -stream-deep -Water_Bridge - -# Terrain features (not roads) -Cliffside_Summer -Wall_Cobble -smallwall -bridge