Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
74e8d17
Add Character Token system and column to Group Manager with image cac…
Sunnyl75 Jun 18, 2025
e2fd035
GroupManager: add showTokens preference to toggle icon column
Sunnyl75 Jun 24, 2025
6e3b121
GroupManager: add tokenIconSize preference (16???64 px live-resize)
Sunnyl75 Jun 24, 2025
341ccb4
GroupManager: add per-row icon picker and default-icon reset
Sunnyl75 Jun 24, 2025
d3771e5
feat(display): character & NPC map tokens
Sunnyl75 Jul 10, 2025
8923b94
WIP: Preserve widget edits before switching branch
nschimme Jun 15, 2025
edc5aaa
groupwidget: refresh token column after preference change
Sunnyl75 Jul 10, 2025
df58c62
Update map immediately after token change
Sunnyl75 Jul 11, 2025
69f5461
grouppage: tidy header before PR
Sunnyl75 Jul 11, 2025
b4abdd7
style: whitespace and clang-format clean-up
Sunnyl75 Jul 11, 2025
0fdd110
style: remove two redundant blank lines (CodeFactor)
Sunnyl75 Jul 11, 2025
2520f7d
refactor: simplify getToken() and extract helper funcs
Sunnyl75 Jul 11, 2025
0e9b534
refactor: simplify dataForCharacter() and remove duplicate ToolTipRole
Sunnyl75 Jul 11, 2025
0f61d96
refactor: shorten setCharacters() and move context-menu to helper slots
Sunnyl75 Jul 11, 2025
a43fe41
fix: rename remaining insertNewChars call to insertNewCharactersInto
Sunnyl75 Jul 11, 2025
e8da0a5
Create build-tokens
Sunnyl75 Jul 21, 2025
1ce824c
Create build-map-tokens-clean
Sunnyl75 Jul 21, 2025
d89263d
Update build-map-tokens-clean
Sunnyl75 Jul 21, 2025
50ed40b
chore: add GhostRegistry skeleton and global instance
Sunnyl75 Jul 19, 2025
d335d1b
feat: add provisional isMount() helper (currently == isNpc())
Sunnyl75 Jul 19, 2025
3971bf8
feat: add provisional isMount() helper and capture GhostInfo on mount…
Sunnyl75 Jul 19, 2025
c815ab4
feat: clear ghost entry when mount is visible again
Sunnyl75 Jul 19, 2025
5e32b76
feat: add last-seen mount ghost token
Sunnyl75 Jul 19, 2025
040c0fc
fix: token quad now uses caller color, enabling 70% ghost opacity
Sunnyl75 Jul 19, 2025
dc91030
fix: token quad now uses caller color, enabling 50% ghost opacity
Sunnyl75 Jul 19, 2025
18cc2f3
fix: purge stale ghost using roomSid while keeping string-keyed registry
Sunnyl75 Jul 19, 2025
40cc594
fix: use room-id map key in ghost draw loop (struct no longer stores …
Sunnyl75 Jul 19, 2025
78c3c28
config: define KEY_GROUP_SHOW_NPC_GHOSTS constant
Sunnyl75 Jul 19, 2025
0ea3400
config: add showNpcGhosts flag (default true)
Sunnyl75 Jul 19, 2025
3675c6b
feat: honour 'Show last-seen NPC icon' preference in ghost logic
Sunnyl75 Jul 20, 2025
6714486
feat: preferences checkbox & build tweaks for NPC ghost feature
Sunnyl75 Jul 21, 2025
d0f56ac
ci: trigger map-tokens-clean build
Sunnyl75 Jul 21, 2025
2f1692b
tokens: draw map tokens at 85% of room size, centered (no asset changes)
Sunnyl75 Aug 5, 2025
4f3de6d
group: keep row height; render token icons at ~85% of row (no centering)
Sunnyl75 Aug 5, 2025
a1bde36
Group Manager: center character token icons and size column to scaled…
Sunnyl75 Aug 11, 2025
bf166d2
Group Manager: token column — paint background then centered icon; si…
Sunnyl75 Aug 11, 2025
44f359c
CI: add Qt6 tripack workflow (Linux/macOS/Windows) and package as Mma…
Sunnyl75 Aug 11, 2025
cbbe149
CI: align workflows with upstream; remove custom workflows
Sunnyl75 Aug 14, 2025
b380534
Merge feature/map-tokens-clean into master
Sunnyl75 Aug 14, 2025
212ae10
CI: fix macOS env echo typo; clang: make row*0.85 cast explicit to av…
Sunnyl75 Aug 21, 2025
1347195
CI(macOS): correctly insert Deploy Qt step and set DMG format (UDZO)
Sunnyl75 Aug 22, 2025
430359b
CI(macOS): build both Intel (macos-13) and Apple Silicon (macos-lates…
Sunnyl75 Aug 22, 2025
d456e72
CI(macOS): add arch-vars + mac packages (UDZO + arch) + hardened macd…
Sunnyl75 Aug 22, 2025
c3ec16c
ci: trigger full build matrix (mac included)
Sunnyl75 Aug 26, 2025
94238bb
CI: replace corrupted workflow; enable mac Intel+ARM; fix mac env and…
Sunnyl75 Aug 26, 2025
101a5c9
CI(macOS): use clang_64 for aqt; force UDZO at cpack
Sunnyl75 Aug 26, 2025
36b6ea3
CI: sanitize GIT_TAG for DMG-safe filenames
Sunnyl75 Aug 27, 2025
878d533
CI(macOS): sanitize GIT_TAG; force simple CPACK_PACKAGE_FILE_NAME; en…
Sunnyl75 Aug 27, 2025
50d85d0
CI(macOS): Release build, enable websockets+qtkeychain, add ad-hoc co…
Sunnyl75 Aug 27, 2025
f206832
Qt6: replace QLatin1String uses with QString/QStringLiteral for porta…
Sunnyl75 Aug 28, 2025
7f2942c
CI: macos-13 – unlink/remove Homebrew Qt headers to avoid QLatin1Stri…
Sunnyl75 Aug 28, 2025
fcb3953
CI: fix YAML; insert macOS-13 Qt unlink step after checkout block
Sunnyl75 Aug 29, 2025
ab6a735
CI: macos-13 — debug & hard-remove Homebrew Qt headers before build
Sunnyl75 Aug 29, 2025
0936178
CI: macos-13 — minimal first-step cleanup of Homebrew Qt headers
Sunnyl75 Aug 29, 2025
1b21fa2
CI: fix indentation of macOS-13 Qt unlink step
Sunnyl75 Aug 29, 2025
1c6a0b1
CI: macos-13 debug pre/post and unlink after checkout
Sunnyl75 Aug 29, 2025
2e36974
CI: fix YAML indentation for macos-13 cleanup step
Sunnyl75 Aug 29, 2025
f864176
CI: remove duplicate macos-13 Qt unlink step
Sunnyl75 Aug 29, 2025
db2ef3a
CI: drop post-checkout duplicate macos-13 unlink step
Sunnyl75 Aug 29, 2025
56493df
CI: reset build-test.yml to match master
Sunnyl75 Aug 29, 2025
3f91631
CI: skip cpack on macOS (avoid DMG failure); keep build+tests
Sunnyl75 Aug 29, 2025
845899a
CI: reset workflow from master; skip macOS cpack
Sunnyl75 Aug 29, 2025
091ee2e
CI: skip cpack on macOS (avoid DMG failure); keep build+tests
Sunnyl75 Sep 1, 2025
4b7031f
CI(macOS): stop installing cmake via Homebrew; use preinstalled CMake
Sunnyl75 Sep 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ jobs:
- if: runner.os == 'macOS'
name: Install Packages for Mac
run: |
brew install cmake ninja lcov
echo "MMAPPER_CMAKE_EXTRA=-DCMAKE_PREFIX_PATH=$env:QT_ROOT_DIR -DUSE_CODE_COVERAGE=true" >> $GITHUB_ENV
brew install ninja lcov
echo "$QT_ROOT_DIR/bin" >> $GITHUB_PATH
echo "MMAPPER_CMAKE_EXTRA=-DCMAKE_PREFIX_PATH=$QT_ROOT_DIR -DUSE_CODE_COVERAGE=true" >> $GITHUB_ENV
echo "COVERAGE=false" >> $GITHUB_ENV

#
Expand Down Expand Up @@ -191,6 +192,7 @@ jobs:
# Package
#
- name: Package MMapper
if: runner.os != 'macOS'
run: cd build && cpack
- if: runner.os == 'Linux'
name: Get Linux package file base name
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ MMapper
[![Code Coverage](https://codecov.io/gh/MUME/MMapper/branch/master/graph/badge.svg)](https://codecov.io/gh/MUME/MMapper)
[![GitHub](https://img.shields.io/github/license/MUME/MMapper.svg)](https://github.com/MUME/MMapper/blob/master/COPYING.txt)
[![snapcraft](https://snapcraft.io/mmapper/badge.svg)](https://snapcraft.io/mmapper)
[![Flathub Downloads](https://img.shields.io/flathub/downloads/org.mume.MMapper)](https://flathub.org/apps/org.mume.MMapper)

[![MMapper Screenshot](/../master/appdata/screenshot1.png?raw=true "MMapper")<br>Download the latest version of MMapper](https://github.com/MUME/MMapper/releases)

Expand All @@ -28,4 +27,5 @@ MMapper is a graphical mapping tool for the MUD (Multi-User Dungeon) game MUME (
To get started with MMapper, follow the [setup instructions](https://github.com/MUME/MMapper/wiki) in our wiki. It includes detailed steps for configuring MMapper with your MUME client and installing it on Linux, Windows, and macOS. Once installed, you'll be ready to start your mapping with ease.

## Contributing
We welcome contributions to MMapper! If you're interested in improving the tool, simply submit a pull request on GitHub. Your contributions will help improve the experience for all MUME players. You can also read the [BUILD.md](BUILD.md) to get started with building MMapper from source.
We welcome contributions to MMapper! If you're interested in improving the tool, simply submit a pull request on GitHub. Your contributions will help improve the experience for all MUME players. You can also check out the [build instructions](https://github.com/MUME/MMapper/wiki/Build) on the wiki to get started with building MMapper from source.
# trigger CI
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ set(mmapper_SRCS
group/mmapper2character.h
group/mmapper2group.cpp
group/mmapper2group.h
group/tokenmanager.cpp
group/tokenmanager.h
logger/autologger.cpp
logger/autologger.h
mainwindow/DescriptionWidget.cpp
Expand Down Expand Up @@ -648,6 +650,7 @@ add_executable(mmapper WIN32 MACOSX_BUNDLE
${mmapper_RCS}
${mmapper_QRC}
${mmapper_DATA}
display/GhostRegistry.h
)

set(mm_libs mm_map mm_global)
Expand Down
4 changes: 2 additions & 2 deletions src/configuration/PasswordConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
#include "../global/macros.h"

#ifndef MMAPPER_NO_QTKEYCHAIN
static const QLatin1String PASSWORD_KEY("password");
static const QLatin1String APP_NAME("org.mume.mmapper");
static const QString PASSWORD_KEY = QStringLiteral("password");
static const QString APP_NAME = QStringLiteral("org.mume.mmapper");
#endif

PasswordConfig::PasswordConfig(QObject *const parent)
Expand Down
27 changes: 27 additions & 0 deletions src/configuration/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ ConstString KEY_GROUP_NPC_COLOR = "npc color";
ConstString KEY_GROUP_NPC_COLOR_OVERRIDE = "npc color override";
ConstString KEY_GROUP_NPC_SORT_BOTTOM = "npc sort bottom";
ConstString KEY_GROUP_NPC_HIDE = "npc hide";
ConstString KEY_GROUP_SHOW_MAP_TOKENS = "show map tokens";
ConstString KEY_GROUP_SHOW_NPC_GHOSTS = "show npc ghosts";
ConstString KEY_GROUP_SHOW_TOKENS = "show tokens";
ConstString KEY_GROUP_TOKEN_ICON_SIZE = "token icon size";
ConstString KEY_GROUP_TOKEN_OVERRIDES = "token overrides";
ConstString KEY_AUTO_LOG = "Auto log";
ConstString KEY_AUTO_LOG_ASK_DELETE = "Auto log ask before deleting";
ConstString KEY_AUTO_LOG_CLEANUP_STRATEGY = "Auto log cleanup strategy";
Expand Down Expand Up @@ -689,6 +694,18 @@ void Configuration::GroupManagerSettings::read(const QSettings &conf)
npcColorOverride = conf.value(KEY_GROUP_NPC_COLOR_OVERRIDE, false).toBool();
npcHide = conf.value(KEY_GROUP_NPC_HIDE, false).toBool();
npcSortBottom = conf.value(KEY_GROUP_NPC_SORT_BOTTOM, false).toBool();
showTokens = conf.value(KEY_GROUP_SHOW_TOKENS, true).toBool();
showMapTokens = conf.value(KEY_GROUP_SHOW_MAP_TOKENS, true).toBool();
tokenIconSize = conf.value(KEY_GROUP_TOKEN_ICON_SIZE, 32).toInt();
showNpcGhosts = conf.value(KEY_GROUP_SHOW_NPC_GHOSTS, true).toBool();

tokenOverrides.clear();
QSettings &rw = const_cast<QSettings &>(conf);
rw.beginGroup(KEY_GROUP_TOKEN_OVERRIDES);
const QStringList keys = rw.childKeys();
for (const QString &k : keys)
tokenOverrides.insert(k, rw.value(k).toString());
rw.endGroup();
}

void Configuration::MumeClockSettings::read(const QSettings &conf)
Expand Down Expand Up @@ -855,6 +872,16 @@ void Configuration::GroupManagerSettings::write(QSettings &conf) const
conf.setValue(KEY_GROUP_NPC_COLOR_OVERRIDE, npcColorOverride);
conf.setValue(KEY_GROUP_NPC_HIDE, npcHide);
conf.setValue(KEY_GROUP_NPC_SORT_BOTTOM, npcSortBottom);
conf.setValue(KEY_GROUP_SHOW_TOKENS, showTokens);
conf.setValue(KEY_GROUP_SHOW_MAP_TOKENS, showMapTokens);
conf.setValue(KEY_GROUP_TOKEN_ICON_SIZE, tokenIconSize);
conf.setValue(KEY_GROUP_SHOW_NPC_GHOSTS, showNpcGhosts);

conf.beginGroup(KEY_GROUP_TOKEN_OVERRIDES);
conf.remove(""); // wipe old map entries
for (auto it = tokenOverrides.cbegin(); it != tokenOverrides.cend(); ++it)
conf.setValue(it.key(), it.value());
conf.endGroup();
}

void Configuration::MumeClockSettings::write(QSettings &conf) const
Expand Down
5 changes: 5 additions & 0 deletions src/configuration/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,11 @@ class NODISCARD Configuration final
bool npcColorOverride = false;
bool npcSortBottom = false;
bool npcHide = false;
bool showTokens = true;
bool showMapTokens = true;
int tokenIconSize = 32;
bool showNpcGhosts = true;
QMap<QString, QString> tokenOverrides;

private:
SUBGROUP();
Expand Down
174 changes: 146 additions & 28 deletions src/display/Characters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "../configuration/configuration.h"
#include "../group/CGroupChar.h"
#include "../group/mmapper2group.h"
#include "../group/tokenmanager.h"
#include "../map/room.h"
#include "../map/roomid.h"
#include "../mapdata/mapdata.h"
Expand All @@ -16,6 +17,7 @@
#include "Textures.h"
#include "mapcanvas.h"
#include "prespammedpath.h"
#include "GhostRegistry.h"

#include <array>
#include <cmath>
Expand All @@ -25,6 +27,9 @@
#include <glm/glm.hpp>

#include <QtCore>
#include <QColor>

std::unordered_map<ServerRoomId, GhostInfo> g_ghosts;

static constexpr float CHAR_ARROW_LINE_WIDTH = 2.f;
static constexpr float PATH_LINE_WIDTH = 4.f;
Expand All @@ -48,7 +53,7 @@ bool CharacterBatch::isVisible(const Coordinate &c, float margin) const
return m_mapScreen.isRoomVisible(c, margin);
}

void CharacterBatch::drawCharacter(const Coordinate &c, const Color &color, bool fill)
void CharacterBatch::drawCharacter(const Coordinate &c, const Color &color, bool fill,const QString &dispName)
{
const Configuration::CanvasSettings &settings = getConfig().canvas;

Expand Down Expand Up @@ -99,8 +104,7 @@ void CharacterBatch::drawCharacter(const Coordinate &c, const Color &color, bool
}

const bool beacon = visible && !differentLayer && wantBeacons;
gl.drawBox(c, fill, beacon, isFar);
}
gl.drawBox(c, fill, beacon, isFar, dispName);}

void CharacterBatch::drawPreSpammedPath(const Coordinate &c1,
const std::vector<Coordinate> &path,
Expand Down Expand Up @@ -214,7 +218,8 @@ void CharacterBatch::CharFakeGL::drawQuadCommon(const glm::vec2 &in_a,
void CharacterBatch::CharFakeGL::drawBox(const Coordinate &coord,
bool fill,
bool beacon,
const bool isFar)
const bool isFar,
const QString &dispName)
{
const bool dontFillRotatedQuads = true;
const bool shrinkRotatedQuads = false; // REVISIT: make this a user option?
Expand Down Expand Up @@ -274,6 +279,41 @@ void CharacterBatch::CharFakeGL::drawBox(const Coordinate &coord,
addTransformed(c);
addTransformed(d);

// ── NEW: also queue a token quad (drawn under coloured overlay) ──
if (!dispName.isEmpty() && getConfig().groupManager.showMapTokens)
{
const Color &tokenColor = color; // inherits alpha from caller
const auto &mtx = m_stack.top().modelView;

auto pushVert = [this, &tokenColor, &mtx](const glm::vec2 &roomPos,
const glm::vec2 &uv) {
const auto tmp = mtx * glm::vec4(roomPos, 0.f, 1.f);
m_charTokenQuads.emplace_back(tokenColor, uv, glm::vec3{tmp / tmp.w});
};

// Scale the room quad around its center to 85%
static constexpr float kTokenScale = 0.85f;
const glm::vec2 center = 0.5f * (a + c); // midpoint of opposite corners
const auto scaleAround = [&](const glm::vec2 &p) {
return center + (p - center) * kTokenScale;
};

const glm::vec2 sa = scaleAround(a);
const glm::vec2 sb = scaleAround(b);
const glm::vec2 sc = scaleAround(c);
const glm::vec2 sd = scaleAround(d);

// Keep full UVs so the whole texture shows on the smaller quad
pushVert(sa, {0.f, 0.f}); // lower-left
pushVert(sb, {1.f, 0.f}); // lower-right
pushVert(sc, {1.f, 1.f}); // upper-right
pushVert(sd, {0.f, 1.f}); // upper-left

QString key = TokenManager::overrideFor(dispName);
key = key.isEmpty() ? canonicalTokenKey(dispName) : canonicalTokenKey(key);
m_charTokenKeys.emplace_back(key);
}

if (beacon) {
drawQuadCommon(a, b, c, d, QuadOptsEnum::BEACON);
}
Expand Down Expand Up @@ -312,6 +352,38 @@ void CharacterBatch::CharFakeGL::reallyDrawCharacters(OpenGL &gl, const MapCanva
gl.renderColoredQuads(m_charBeaconQuads, blended_noDepth.withCulling(CullingEnum::FRONT));
}

// ── draw map-tokens underneath the coloured overlay ──
for (size_t q = 0; q < m_charTokenKeys.size(); ++q) {
const size_t base = q * 4;
if (base + 3 >= m_charTokenQuads.size())
break;

const QString &key = m_charTokenKeys[q];
MMTextureId id = tokenManager().textureIdFor(key);

if (id == INVALID_MM_TEXTURE_ID) {
QPixmap px = tokenManager().getToken(key);
id = tokenManager().uploadNow(key, px);
}

if (id == INVALID_MM_TEXTURE_ID)
continue;

SharedMMTexture tex = tokenManager().textureById(id);
if (tex)
gl.setTextureLookup(id, tex);

if (id == INVALID_MM_TEXTURE_ID)
continue;

gl.renderColoredTexturedQuads(
{m_charTokenQuads[base + 0],
m_charTokenQuads[base + 1],
m_charTokenQuads[base + 2],
m_charTokenQuads[base + 3]},
blended_noDepth.withTexture0(id));
}

if (!m_charRoomQuads.empty()) {
gl.renderColoredTexturedQuads(m_charRoomQuads,
blended_noDepth.withTexture0(textures.char_room_sel->getId()));
Expand All @@ -335,6 +407,9 @@ void CharacterBatch::CharFakeGL::reallyDrawCharacters(OpenGL &gl, const MapCanva
gl.renderFont3d(textures.char_arrows, m_screenSpaceArrows);
m_screenSpaceArrows.clear();
}

m_charTokenQuads.clear();
m_charTokenKeys.clear();
}

void CharacterBatch::CharFakeGL::reallyDrawPaths(OpenGL &gl)
Expand Down Expand Up @@ -383,9 +458,16 @@ void MapCanvas::paintCharacters()
}

CharacterBatch characterBatch{m_mapScreen, m_currentLayer, getTotalScaleFactor()};
const CGroupChar *playerChar = nullptr;
for (const auto &pCharacter : m_groupManager.selectAll()) {
if (pCharacter->isYou()) { // ← ‘isYou()’ marks the local player
playerChar = pCharacter.get();
break;
}
}

// IIFE to abuse return to avoid duplicate else branches
[this, &characterBatch]() {
[this, &characterBatch, playerChar]() {
if (const std::optional<RoomId> opt_pos = m_data.getCurrentRoomId()) {
const auto &id = opt_pos.value();
if (const auto room = m_data.findRoomHandle(id)) {
Expand All @@ -397,7 +479,11 @@ void MapCanvas::paintCharacters()

// paint char current position
const Color color{getConfig().groupManager.color};
characterBatch.drawCharacter(pos, color);
characterBatch.drawCharacter(pos, color,
/*fill =*/ true,
/*name =*/ playerChar
? playerChar->getDisplayName()
: QString());

// paint prespam
const auto prespam = m_data.getPath(id, m_prespammedPath.getQueue());
Expand All @@ -417,40 +503,72 @@ void MapCanvas::paintCharacters()

void MapCanvas::drawGroupCharacters(CharacterBatch &batch)
{
if (m_data.isEmpty()) {
if (m_data.isEmpty())
return;

const Map &map = m_data.getCurrentMap();

/* Find the player's room once */
const CGroupChar *playerChar = nullptr;
ServerRoomId playerRoomSid = INVALID_SERVER_ROOMID;
RoomId playerRoomId = INVALID_ROOMID;
for (const auto &p : m_groupManager.selectAll()) {
if (p->isYou()) {
playerChar = p.get();
playerRoomSid = p->getServerId();
if (const auto r = map.findRoomHandle(p->getServerId()))
playerRoomId = r.getId();
break;
}
}

RoomIdSet drawnRoomIds;
const Map &map = m_data.getCurrentMap();

for (const auto &pCharacter : m_groupManager.selectAll()) {
// Omit player so that they know group members are below them
if (pCharacter->isYou())
const CGroupChar &character = *pCharacter;
if (character.isYou())
continue;
const auto r = map.findRoomHandle(character.getServerId());
if (!r) continue; // skip Unknown rooms

const CGroupChar &character = deref(pCharacter);
const RoomId id = r.getId();
const Coordinate & pos = r.getPosition();
const Color col = Color{character.getColor()};
const bool fill = !drawnRoomIds.contains(id);

const auto &r = [&character, &map]() -> RoomHandle {
const ServerRoomId srvId = character.getServerId();
if (srvId != INVALID_SERVER_ROOMID) {
if (const auto &room = map.findRoomHandle(srvId)) {
return room;
}
}
return RoomHandle{};
}();
const bool showToken = (id != playerRoomId);

const QString tokenKey = showToken ? character.getDisplayName()
: QString(); // empty → no token

// Do not draw the character if they're in an "Unknown" room
if (!r) {
batch.drawCharacter(pos, col, fill, tokenKey);
drawnRoomIds.insert(id);
}
/* ---------- draw (and purge) ghost tokens ------------------------------ */
if (!getConfig().groupManager.showNpcGhosts) {
g_ghosts.clear(); // purge any stale registry entries
} else
for (auto it = g_ghosts.begin(); it != g_ghosts.end(); /* ++ in body */) {
ServerRoomId ghostSid = it->first; // map key is the room id
const QString tokenKey = it->second.tokenKey;

if (ghostSid == playerRoomSid) { // player in same room → purge
it = g_ghosts.erase(it);
continue;
}

const RoomId id = r.getId();
const auto &pos = r.getPosition();
const auto color = Color{character.getColor()};
const bool fill = !drawnRoomIds.contains(id);
/* use ghostSid here ▾ instead of ghostInfo.roomSid */
if (const auto h = map.findRoomHandle(ghostSid)) {
const Coordinate &pos = h.getPosition();

batch.drawCharacter(pos, color, fill);
drawnRoomIds.insert(id);
QColor tint(Qt::white); tint.setAlphaF(0.50f);
Color col(tint);

const bool fill = !drawnRoomIds.contains(h.getId());
batch.drawCharacter(pos, col, fill, tokenKey /*, 0.9f */);
drawnRoomIds.insert(h.getId());
}
++it;
}
}

Loading