Commit graph

9 commits

Author SHA1 Message Date
Vadim Peretokin
69cd06b1c2
add: starter interface with health bars, map and chat for new players (#9454)
#### Brief overview of PR changes/additions
- New "Mudlet base UI" package auto-installed for new players on every
game: adjustable right-side dock with map (placeholder until the game is
mapped), tabbed chat (All/Tells/Channels with unread badges) and themed
HP/MP/MV/XP gauges - only ever built from data the game actually
provides
- Chat capture: GMCP Comm.Channel.Text when available, otherwise a
generic additive trigger layer (line shapes distilled from an audit of
all 216 packages in the Mudlet package repository); nothing is ever
gagged, and the trigger layer retires itself the moment GMCP chat
appears
- Vitals ladder for the gauges: GMCP (several dialect spellings) > MSDP
(negotiated automatically) > self-sufficient prompts (labeled cur/max or
percents) > score-screen harvesting; when a prompt only shows bare
currents, Mudlet sends `score` once - visibly, with an announcement - to
learn the maxima. Maxima are never guessed. An enemy-health bar appears
during fights on games that report it
- `baseui hide` / `baseui show` opt-out persists across restarts;
first-run announcement defers to the UI tour; experienced players never
get the package
- Plays nice with games that provide their own interface: profiles for
games whose bundled loader installs the game's official UI (flagged in
TGameDetails: Carrion Fields, Icesus, MorgenGrauen, Medievia) skip the
starter UI entirely, and when a game pushes a `Client.GUI` package the
starter UI quietly stands aside (via the new `sysServerGuiInstalled`
event) - `baseui show` brings it back for players who prefer it
- `docs/package-capture-audits/` records the chat + vitals capture
audits (with provenance) behind the design

#### Motivation for adding to Mudlet
New players currently get a bare text screen; this gives every game -
GMCP-rich, MSDP-only or plain telnet - an immediate, honest starter UI
out of the box.

#### Other info (issues closed, discussion etc)
Progresses #3071 and #3070 (phase 1 - not closing them).

**Test case:** Fresh install → create a profile for any game → connect.
On GMCP/MSDP games the dock builds with ticking gauges and tabbed chat;
on a game whose prompt carries cur/max values, gauges appear once the
prompt repeats; on a bare-number prompt, `score` is sent once
(announced) to size the gauges; on pure chat games, tells/says/channels
are captured with no gauges invented. `baseui hide` removes it and
survives restart.



https://github.com/user-attachments/assets/56d207cc-5bad-4464-867c-1a810d41cf2f

---------

Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-07-26 08:43:21 +02:00
Vadim Peretokin
3a9c6f2615
fix: profile reset no longer forgets which variables to save (#9484)
#### Brief overview of PR changes/additions
- Carry the Variables view's "save this variable" and "hidden by user"
sets across resetProfile(), instead of losing them with the rebuilt Lua
state
- Re-hide Mudlet's internal Lua API after reset (same call the normal
profile load uses), so the Variables view shows user variables only
- 3 regression tests including an end-to-end check that a saved variable
still lands in the profile XML after a reset

#### Motivation for adding to Mudlet
Since the #9430 crash fix, the first profile save after resetProfile()
silently stripped all user-saved variables from the profile, and the
Variables view flooded with hundreds of internal entries.

#### Other info (issues closed, discussion etc)
Follow-up to #9430; found by a post-merge QA audit and confirmed with a
failing test before the fix.

**Test case:** Mark a variable as saved in the Variables view, run `lua
resetProfile()`, save the profile - the variable survives a restart, and
the Variables view lists only user variables.

Assisted-by: Claude:claude-fable-5
2026-07-25 20:25:26 +02:00
Vadim Peretokin
cb402cc3e6
infrastructure: use std::chrono literals for time durations (#9493)
#### Brief overview of PR changes/additions

Convert raw millisecond integer literals at time-duration call sites to
`std::chrono` literals, and add `#include <chrono>` to each touched
translation unit. Examples:

- `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)`
- `mpTimerReplay->setInterval(1000)` → `setInterval(1s)`
- `mPendingTimer.start(60000)` → `start(1min)`
- `QObject::startTimer(50)` → `startTimer(50ms)`
- `QTest::qWait(100)` → `QTest::qWait(100ms)`
- `QThread::msleep(10)` → `QThread::sleep(10ms)`

This is a semantics-preserving refactor - every duration is kept exactly
equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes
`1min`). No behavioural change.

#### Motivation for adding to Mudlet

Chrono literals make time durations self-documenting and type-safe. `1s`
/ `100ms` read unambiguously where a bare `1000` / `100` forces the
reader to remember each API's unit, and the compiler now rejects unit
mismatches. Only genuine duration arguments were converted - loop
counts, scroll-line counts, sizes, ports and the like were deliberately
left as plain integers.

All targeted APIs provide `std::chrono` overloads in the minimum
supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8),
`QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)`
(6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7).

#### Other info (issues closed, discussion etc)

Test case: the full application builds cleanly and the entire functional
`ctest` suite passes. The only failing test is the known, pre-existing
`PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is
unrelated to this change.

Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
Vadim Peretokin
094022f47c
infrastructure: test suites no longer clash when run at the same time (#9483)
#### Brief overview of PR changes/additions
- TelnetServerStub now binds an OS-assigned (ephemeral) port; its log
reports the actual bound port
- All 11 functional tests that hardcoded listen ports (three shared port
4000, two pairs shared 4003/4004) now read the real port back via
serverPort()

#### Motivation for adding to Mudlet
Concurrent test runs (parallel CI jobs, multiple checkouts on one
machine) collided on the fixed ports, causing flaky bind failures and
tests connecting to the wrong run's server.

#### Other info (issues closed, discussion etc)
Follows the pattern GMCPCharLoginTest already used. Verified by running
two copies of TelnetTextDisplayedTest simultaneously - both passed on
distinct ports (37503/42303), impossible before.

**Test case:** Run the functional suite twice in parallel (two build
dirs or ctest -j2 repeated); no "address already in use" failures.

Assisted-by: Claude:claude-opus-4-8
2026-07-20 09:01:51 +02:00
John McKisson
a4f7829226
Fix: Regression of resetProfile handling of labels (#9255)
#### Brief overview of PR changes/additions

Fixes issue https://github.com/Mudlet/Mudlet/issues/9254 where a
mouseover event in labels placed in geyser containers throw errors after
a resetProfile due to lua_unref in the wrong lua state during the reset
process.

Unit test and fix researched and applied by by Claude Opus.

Draft to see if a unit test catches the error first before I add the
fix.

#### Motivation for adding to Mudlet

#### Other info (issues closed, discussion etc)
2026-05-08 06:29:22 +02:00
Stephen Lyons
113218930f
Infrastructure: Swap out QtConcurrent module header for sub-module ones (#9246)
#### Brief overview of PR changes/additions
The Qt documentation for `QtConcurrent` points out:
> If you include the `<QtConcurrent>` header, the entire Qt Concurrent
module with the entire Qt Core module will be included, which may
increase compilation times and binary sizes. To use individual functions
from the QtConcurrent namespace, you can include more specific headers.
>
> The table below lists the functions in the QtConcurrent namespace and
their corresponding headers:

|Function|Header|
|--------|------|
|`QtConcurrent::run()`|`<QtConcurrentRun>`|
|`QtConcurrent::task()`| `<QtConcurrentTask>`|

|`QtConcurrent::filter()`,<br>`QtConcurrent::filtered()`,<br>`QtConcurrent::filteredReduced()`|`<QtConcurrentFilter>`|
|`QtConcurrent::map()`,<br>`QtConcurrent::mapped()`,<br>`QtConcurrent::mappedReduced()`|`<QtConcurrentMap>`|

#### Motivation for adding to Mudlet
To speed up the build a little by removing stuff that isn't needed.

#### Other info (issues closed, discussion etc)
In doing this I happened to start cleaning up a couple of header files
`T2DMap.h` and then `mudlet.h`, I then got into converting some
`#include`s into forward declarations in a "include-what-you-use" move.
This then rippled through into a (more than 10!) number of files but
should "improve" things.

Note that the ordering of `#include` in many files seems to be rather
haphazard and is due for some serious overhaul - I suggest that we
should actually declare an "official" style for this project so that
everyone knows what it is.

**During the CI/CB process I discovered that Linux and then MacOS builds
were failing because the file referred to by the `#include
<QtConcurrentTask>` header file was missing, yet was present on my local
PC when I was using the Qt framework from the On-line installer.
Initially I suspected a Debian (and then Devuan - as the packaged
version on my own machine also had this defect AND Ubuntu) package
problem; however it now seems to be an upstream Qt issue as the various
Qt versions & OS combinations suggest that Qt themselves fixed it for Qt
6.10:**
| OS     | QtVersion             | Missing header |
|--------|-----------------------|----------------|
| Windows| 6.11.0 package        |       No       |
| Devuan | 6.8.2 package         |      Yes       |
| Devuan | 6.10.0 online install |       No       |
| Ubuntu | 6.9.0 package         |      Yes       |
| Debian | 6.8.2 package         |      Yes       |
| MacOS  | 6.9.0 package         |      Yes       |

**To fix this I reverted to an `#include <qtconcurrenttask.h>` for Linux
and MacOS builds - although it would probably have been better to make
it conditional on the Qt Version instead...**

*I have reported this upstream to Debian - see:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1135197*

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2026-04-29 13:35:29 +01:00
Vadim Peretokin
d3396ee5bb
Fix: several resetProfile() bugs (#9083)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
1. Double reset guard (Host.cpp) — Added early return in
resetProfile_phase1() if mResetProfile is already true.
Without this, calling resetProfile() twice before the event loop
processes would schedule two phase2s, and the
second one would leak the Lua state (since initLuaGlobals() overwrites
pGlobalLua without closing the old one).
2. Anonymous event handlers cleared (Host.cpp) — Added
mAnonymousEventHandlerFunctions.clear() to
resetProfile_phase2(). This map was the only event-related map not
cleared during reset. After reset, it
contained stale function names pointing to functions that no longer
existed in the new Lua state, causing error
  spam when events were raised.
3. Media stopped on reset (Host.cpp + TMedia.h) — Added
mpMedia->stopAllMediaPlayers() call to
resetProfile_phase2(). Audio/music continued playing through profile
resets. Also moved stopAllMediaPlayers()
  from private to public in TMedia.h so Host can call it.

#### Motivation for adding to Mudlet
Bug fixes.
#### Other info (issues closed, discussion etc)
Builds on https://github.com/Mudlet/Mudlet/pull/9082, requires that to
be merged first.

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-13 18:55:09 +02:00
Vadim Peretokin
62e7daaa63
Fix: crash when resetProfile() is called while downloads are in-progress (#9138)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
esetProfile() now properly destoys the old Lua state, but it never
cancelled in-flight downloads. When a download was finished, it would
try and use the old Lua state that wasn't there anymore. Fix - cancel
downloads on resetProfile()


#### Motivation for adding to Mudlet
Fix crash introduced into development.
#### Other info (issues closed, discussion etc)
The new tests genuinely found a crashing issue!
2026-04-03 18:50:08 +02:00
Vadim Peretokin
b69283b106
Infrastructure: add comprehensive resetProfile() test suite (#9082)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Add 29 functional tests covering all aspects of Host::resetProfile():
temp item removal, permanent item survival, stopwatches, event system
cleanup, Lua state reinitialization, sysLoadEvent, flag lifecycle, UI
cleanup, two-phase correctness, ANSI colors, map survival, telnet
connection, Geyser/gmcp reload, double reset safety, and script event
handler re-registration.

Also fix qtkeychain include visibility (PRIVATE -> PUBLIC) so functional
tests can build from scratch.
#### Motivation for adding to Mudlet
resetProfile() seems simple, but is quite a complicated function to pull
off, and we haven't had any tests covering the functionality.
#### Other info (issues closed, discussion etc)
Also allows Geyser to be loaded in C++ tests, which it was not being
done before!
2026-04-02 11:33:38 +02:00