Commit graph

170 commits

Author SHA1 Message Date
Erik
0b1abf69f9 fix(combat): remove plugin-side lifetime — backend accumulates now
The plugin's _lifetimeState was always identical to _sessionState
because both started fresh on every load/login and accumulated the
same events. Lifetime needs persistence across sessions.

Fix: plugin now only maintains _sessionState and sends lifetime=null.
The backend computes deltas between consecutive session snapshots and
accumulates them into a persisted lifetime in the combat_stats DB table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:58:21 +02:00
Erik
b48af81a13 fix(combat): wire combat tracker in InitializeForHotReload
The combat stats tracker was only started in LoginComplete — but
auto-updates go through InitializeForHotReload() which skipped it
entirely. After every hot reload, _combatStatsTracker was created but
never had SetCombatTracker/RestartSession/Start called.

Also adds vital sharing re-init for the same hot reload path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:02:38 +02:00
Erik
11969fc590 debug(combat): explicit error catch + null-check logging
If the CombatStatsTracker constructor throws (e.g. TypeInitializationException
from static field initializers), we now catch and print the exact exception
type + message + inner exception to the DECAL chat window.

Also logs "tracker=OK" or "tracker=NULL" in LoginComplete so we can see
if the object was created at all.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:00:36 +02:00
Erik
77e87484e8 fix(combat): replace reflection with direct kill patterns array
The static constructor used reflection to read KillTracker.KillPatterns
which caused a TypeInitializationException — making _combatStatsTracker
null and silently disabling the entire combat tracking pipeline.

Replaced with a direct copy of the 35 kill regex patterns. No reflection,
no static constructor, no silent failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 09:58:33 +02:00
Erik
00d713a134 debug(combat): add diagnostic logging to trace parser pipeline
Temporary verbose logs:
- CombatStatsTracker.Start() logs when timer starts
- ProcessChatLine logs first 3 invocations (text preview)
- OnSendTick logs first 3 sends (kill/damage/monster counts)

Will remove once we confirm the pipeline is working.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 09:55:49 +02:00
Erik
f71ae72935 fix(combat): strip HTML tags + newlines before regex matching
DECAL's ChatBoxMessage delivers raw text with HTML tags and trailing
\r\n that break the ^...$ regex anchors in every combat pattern.
ProcessChatLine now strips tags and trims before matching, same as
ChatEventRouter.NormalizeChatLine does for the WebSocket chat stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 09:48:11 +02:00
Erik
38a14c5894 feat: Mag-Tools style combat stats tracking and streaming
Adds a full combat event parser to the plugin that replicates every
stat Mag-Tools' Combat Tracker shows:

- CombatInfo.cs: DamageElement/AttackType enums, DamageStats class
  (TotalAttacks, FailedAttacks, Crits, Normal/Crit damage + max),
  MonsterCombatRecord with nested offense/defense dictionaries
  (AttackType → DamageElement → DamageStats), CombatSessionState.
- CombatStatsTracker.cs: parses combat chat using the same regex
  patterns as Mag-Tools' StandardTracker + AetheriaTracker +
  CloakTracker. Detects melee/missile/magic hits (given+received),
  evades, resists, kill messages, aetheria surges, cloak surges.
  Element detection via verb keyword matching. Maintains session
  (cleared on login) and lifetime (never cleared) state. Sends a
  combat_stats snapshot to Overlord every 10 seconds.
- ChatEventRouter: wired to call ProcessChatLine on every chat event
- PluginCore: instantiates tracker, starts on login, disposes on shutdown
- WebSocket: added SendCombatStatsAsync + made SessionId internal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 09:41:55 +02:00
Erik
e59e2cdfc3 feat(vitalsharing): close button on overlay + rename settings button
- Settings tab button renamed from "Open Overlay" to "Toggle Overlay"
  since the same button now closes the overlay if it's already open
  (it already toggled, the old label was misleading).
- Overlay now draws a red X close button in the top-right corner while
  Ctrl is held, matching UB's NetworkUI affordance. Ctrl+left-click on
  the X hides the overlay. Same Ctrl-modifier gate as the drag
  operation so the normal game UI is not affected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 15:16:14 +02:00
Erik
49ec534514 fix(vitalsharing): UI sync + re-subscribe on reconnect
Two bugs fixed:

1. /mm vitalsharing on|off did not update the checkbox on the Settings
   tab, leaving the UI out of sync with the actual state. Toggling via
   PluginCore.SetVitalSharingEnabled now calls
   Views.VVSTabbedMainView.RefreshSettingsFromConfig so the checkbox
   reflects the new state regardless of whether the toggle came from
   chat, the settings checkbox, or the settings overlay.

2. After a WebSocket reconnect, the plugin was silently dropping out of
   the backend's vital sharing subscriber set. The receive loop's
   disconnect cleanup removes the character from _vital_sharing_subscribers,
   but the plugin only sent share_subscribe once at tracker.Start() — so
   after any reconnect, no future share_* messages would be forwarded to
   that client. Fix: when the WebSocket (re)connects and registers, if
   PluginSettings.VitalSharingEnabled is true, re-send share_subscribe
   immediately after the register envelope.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 15:14:30 +02:00
Erik
2b6e3cc4fc feat(vitalsharing): verbose logging toggle for VTank feed tracing
Adds a Verbose Logging checkbox on the Settings tab (backed by the
existing PluginSettings.VerboseLogging flag) plus trace lines in
VitalSharingTracker so you can verify cross-PC coordination is actually
reaching VTank.

When enabled, chat logs will include:
  [VitalShare] SEND share_cast_attempt ...
  [VitalShare] SEND share_cast_success ...
  [VitalShare→VTank] HelperPlayerUpdate <name> HP=.../... S=.../... M=.../...
  [VitalShare→VTank] LogCastAttempt from <name>: spell=X target=0xY
  [VitalShare→VTank] LogSpellCast from <name>: spell=X target=0xY duration=Zms
  [VitalShare→VTank] SKIP ... (with reason when vTank.Instance is null
  or the target isn't a valid local object)

No overhead when the toggle is off — VerboseLog() short-circuits before
formatting the message arguments, matching the existing log-gating
pattern in the codebase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 15:09:55 +02:00
Erik
62a9067125 feat(vitalsharing): direction arrow, range text, Ctrl+drag overlay
Mirrors UtilityBelt NetworkUI behaviour for the cross-PC vital sharing
overlay:

- Each non-self row now draws a small triangle pointing toward the peer
  (relative to the local character's facing) and a range label in meters,
  both tinted red as distance grows. Uses share_position_update data
  that was already being streamed but previously ignored on receive.
- VitalSharingTracker caches peer positions from share_position_update
  into the same PeerSnapshot used by the overlay.
- Hold Ctrl and left-drag the overlay to reposition it. A yellow border
  highlights the drag bounds while Ctrl is held (matches UB).
  Position is persisted to PluginSettings.VitalSharingOverlayX/Y.
- Input handled via CoreManager.WindowMessage, eating the events so the
  game doesn't also react to the drag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 14:49:54 +02:00
Erik
d220970130 feat(vitalsharing): UB-style DxHud overlay + self in peer list
- Rewrite VitalSharingOverlayView as a DxHud drawn directly onto the
  game surface with the same row layout UB NetworkUI uses: full-width
  HP bar on top, Stamina + Mana bars side-by-side on the bottom, plus
  per-row border and name/percentage text. Colours use the sidebar
  palette (#ff4444 / #ffaa00 / #4488ff).
- Always include the local character as the first row so the overlay
  shows something even before any peer traffic arrives. Previously
  the window said "(no peers)" until another subscribed client
  streamed vitals in.
- VitalSharingTracker.GetSelfSnapshot() reads current vitals via
  CoreManager.Actions.Vital.
- Drop the old HudList-based XML view (no longer used).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 14:40:34 +02:00
Erik
29a54b720f feat(vitalsharing): settings UI, in-game overlay, reuse CharTag
- Settings tab: new "Vital Sharing (cross-PC)" checkbox + "Open Overlay"
  button, plus help text clarifying CharTag is used as the sharing group.
- Drop separate VitalSharingTags list — reuse PluginSettings.CharTag as
  the single tag value sent with every share_* payload.
- New VitalSharingOverlayView (HudList of peers with coloured HP/STA/MANA
  percentages) modeled after UB's NetworkUI. Colours match the web
  sidebar palette (#ff4444 / #ffaa00 / #4488ff).
- VitalSharingTracker now caches a peer snapshot on every inbound
  share_vital_update so the overlay can render without re-polling.
- PluginCore.SetVitalSharingEnabled static helper so the settings
  checkbox can toggle without poking at private fields.
- /mm vitalsharing command simplified to on|off|status|overlay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 14:33:17 +02:00
Erik
b87412e8f9 feat: cross-machine vital/debuff sharing via MosswartOverlord
Reimplements UtilityBelt VTankFellowHeals + Networking using Overlord's
WebSocket pipeline so debuff coordination and vital sharing work across
multiple PCs instead of only localhost.

- VitalSharingTracker: packet hook (0xF7B1/0x004A) for cast attempts,
  chat regex for cast success (handles proc debuffs), 150ms vital timer,
  300ms position timer, 5s item timer. Inbound share_* queued to main
  thread via Core.RenderFrame and routed to UBHelper.vTank.Instance
  (LogCastAttempt / LogSpellCast / HelperPlayerUpdate).
- WebSocket: new OnServerMessage event, share_* routing in receive loop,
  SendVitalShareAsync helper.
- PluginSettings: VitalSharingEnabled (default off) + VitalSharingTags.
- PluginCore: /mm vitalsharing on|off|status|tag add/remove|tags.
- csproj: reference UtilityBelt.Helper.dll (sPlayerInfoUpdate actually
  lives in utank2-i.dll under the uTank2 namespace).

Running UB local sharing and MM cross-PC sharing simultaneously is safe
since VTank's Log* methods are idempotent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 14:19:32 +02:00
Erik
a40974ea67 feat: increase portal detection range from 60m to 240m
60m was too narrow (~half a house). 240m matches the typical in-game
view distance for terrain features (1 AC coordinate unit = 240 game units).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:24:30 +02:00
Erik
7d657c6af4 feat: increase portal detection range from 12m to 60m
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 19:40:59 +02:00
Erik
a4db8e08a5 feat: /mm sendinventory now scans, identifies all items, then sends
Previously sent inventory immediately with potentially unidentified items.
Now uses the same scan-wait-send pattern as first-login:
1. Requests ID for all items needing identification
2. Waits for all identifications to complete (ChangeObject handler)
3. Only sends complete, fully-identified inventory when ready

Ensures clean inventory data for the backend, eliminates stale/partial items.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:52:18 +02:00
Erik
08f2d57e08 fix: add debug logging for dungeon_map sends, guard COM exceptions
- Log dungeon_map payload size and send success/failure
- Guard early COM calls (CharacterFilter, Actions.Heading) with
  try/catch to prevent RPC_E_SERVERFAULT during state transitions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:38:40 +02:00
Erik
8402d7aa5b feat: add dungeon map streaming for radar
New DungeonMapReader reads dungeon cell geometry from game dat files
via DECAL FileService. NearbyObjectsTracker detects dungeon entry
via landblock change, reads cells once, and streams dungeon_map event.
nearby_objects payload now includes is_dungeon flag, landblock ID,
and raw physics coordinates for accurate dungeon positioning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:16:45 +02:00
Erik
5f20d395a6 feat: add on-demand nearby objects radar streaming
New NearbyObjectsTracker polls WorldFilter.GetLandscape() every 1s
when activated by start_radar/stop_radar commands from the browser.
Streams all visible objects (monsters, players, NPCs, portals, etc.)
with coordinates to the backend for real-time radar display.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:10:42 +02:00
Erik
98c43c4c61 feat: only stream portals within 12 meters of the player 2026-03-30 22:26:19 +02:00
Erik
269964e3d3 feat: rename Pull Outdated button and sort new/updated files first
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:33:46 +01:00
Erik
3bcd7d9e49 fix: stream burden stats to overlord 2026-03-13 11:10:04 +01:00
Erik
0fbd100b7b feat: stream live equipment cantrip states 2026-03-13 09:02:44 +01:00
Erik
e20f9df256 feat: add searchable metas sync tab and configurable auto-updates
Add a Metas tab that lists remote .met/.nav files, checks update status, and downloads with .bak backups on overwrite. Add an Auto Install Updates setting (default on) and guard settings usage during early startup to avoid initialization errors.
2026-03-09 11:36:47 +01:00
erik
278802c0af fix: update auto-updater URL from spawn-detection to master branch 2026-03-04 17:55:23 +00:00
erik
cac8e96656 chore: include Release DLL with appraisal and settings fixes
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-02 21:06:09 +00:00
erik
7610ad9029 fix: ignore unmatched YAML properties in PluginSettings
Prevents deserialization crash when config file contains keys from newer plugin versions.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-02 21:06:03 +00:00
erik
ac691d3140 feat: add item appraisal requests to LiveInventoryTracker
RequestId() is called for armor, weapons, jewelry, and other items that need full ID data when picked up via OnCreateObject or OnChangeObject. Adds ObjectClassNeedsIdent helper matching MossyInventory's logic.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-02 21:05:46 +00:00
erik
97ace3375a chore: include Release DLL for live inventory delta feature 2026-03-02 19:50:13 +00:00
erik
afa85ef80d feat: add live inventory delta tracking via WebSocket
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:38:05 +00:00
erik
ce0fae7d10 feat: add ContainerId to MyWorldObject for inventory tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:34:16 +00:00
erik
725bbf473f feat: display CalVer version in main window only
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:26:21 +00:00
erik
c702263770 feat: auto-generate CalVer version from build date
Replace hardcoded AssemblyVersion/AssemblyFileVersion with a CalVer
scheme (YYYY.M.D.HHmm) generated at build time via an MSBuild target
that writes a CalVer.cs file into obj/ before compilation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:24:07 +00:00
erik
a13d30f0b2 feat: auto-check for updates 30s after startup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:13:35 +00:00
erik
57a6946e6b feat: add CheckAndInstallAsync convenience method to UpdateManager
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:11:27 +00:00
erik
aed74984c6 Fix hot reload: prevent duplicate event handlers and timer leaks
Unsubscribe all event handlers and stop/dispose timers at the start of
Startup() before re-creating objects. On first load the -= calls are
no-ops; on hot reload they remove stale handlers that would otherwise
compound with each reload. Also adds LoginComplete unsubscription to
Shutdown() for completeness.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:55:58 +00:00
erik
1ffa163501 Fix hot reload: restore window position and add character stats timer
InitializeForHotReload was missing window position restore (settings
loaded after ViewInit, but position never re-applied) and character
stats streaming timer (10-min interval + initial send).

Added VVSTabbedMainView.RestorePosition() static wrapper and wired
it into the hot reload path after settings load. Added character
stats timer initialization as step 10.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:05:21 +00:00
erik
361c2012da Fix hot reload init ordering: move after core objects are created
InitializeForHotReload() was called at the top of Startup() before
_killTracker, _chatEventRouter, and _inventoryMonitor were created,
causing NullReferenceException. Move the hot reload block to after
all core objects are initialized.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:55:26 +00:00
erik
1fff36e3f7 Fix rare detection broken on hot reload / DLL update
InitializeForHotReload() never created a RareTracker — it only ran
if one already existed. When the new DLL is loaded on an already-
logged-in character, LoginComplete doesn't fire, so _rareTracker
stayed null and the null-check in ChatEventRouter.OnChatText silently
skipped all rare detection.

Now InitializeForHotReload creates and wires the RareTracker if it
hasn't been set yet, matching what LoginComplete does.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:52:19 +00:00
erik
a446158f63 Add DWORD properties and titles list to character stats payload
Parse augmentation, rating, mastery, society, and general DWORD properties
from the 0x0013 login message. Capture the full titles list from 0x0029.
Both are included in the character_stats WebSocket payload for the new
TreeStats-style character window in the frontend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:39:08 +00:00
erik
a4d2108b3a Clean up /mm help commands
- Remove dead no-op commands (harmonyraw, debugtaper)
- Remove duplicate command (initgui, same as gui)
- Hide debug/test commands from help output (vtanktest, decalstatus,
  decaldebug, testprismatic, testdeath, testtaper, debugupdate)
- Clean up descriptions for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:17:55 +00:00
erik
c30704aaa7 Remove files that should not be in repo
- Remove .claude/ settings (local Claude Code config)
- Remove CLAUDE.md files (local project instructions)
- Remove PluginCore.backup.cs (pre-refactoring backup, no longer needed)
- Remove FINDINGS.md (scratch analysis file)
- Add .claude/ and CLAUDE.md to .gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:10:39 +00:00
erik
64e690f625 Phase 6: Fix swallowed exceptions and cleanup unused usings
- Add debug logging to all empty catch blocks in DecalHarmonyClean.cs setup methods
  (prefix method catches intentionally stay silent to never break other plugins)
- Add error logging to VtankControl.VtSetSetting catch
- Add logging to DecalPatchMethods.ProcessInterceptedMessage catch
- Remove unused usings from PluginCore.cs (System.Diagnostics, System.Drawing,
  System.Text, System.Text.RegularExpressions)
- Flatten redundant nested try/catch in PatchPluginHost

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:59:34 +00:00
erik
0713e96a99 Phase 5: Extract QuestStreamingService and introduce IGameStats
- Extract QuestStreamingService.cs from PluginCore (timer, IsHighPriorityQuest, FormatCountdown)
- Create IGameStats interface for WebSocket telemetry decoupling
- PluginCore implements IGameStats, WebSocket.BuildPayloadJson reads from IGameStats
- WebSocket.cs no longer references PluginCore directly
- Update queststatus command to use QuestStreamingService
- Static bridge properties remain for VVSTabbedMainView compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:56:13 +00:00
erik
f9264f2767 Phase 4: Extract ChatEventRouter and GameEventRouter
- ChatEventRouter.cs: routes chat events to KillTracker, RareTracker, handles
  allegiance report trigger and WebSocket chat streaming
- GameEventRouter.cs: routes ServerDispatch messages (0xF7B0, 0x02CF) to CharacterStats
- PluginCore no longer contains OnChatText, AllChatText, NormalizeChatLine,
  or EchoFilter_ServerDispatch methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:50:41 +00:00
erik
c90e888d32 Phase 3: Extract RareTracker and InventoryMonitor
- RareTracker.cs: owns rare discovery detection, meta state toggle, WebSocket/allegiance notifications
- InventoryMonitor.cs: owns Prismatic Taper tracking with event-driven delta math
- PluginCore no longer contains inventory event handlers or rare detection logic
- Bridge properties maintain backward compat for WebSocket telemetry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:33:44 +00:00
erik
366cca8cb6 Phase 2: Extract IPluginLogger and KillTracker
- Create IPluginLogger interface, PluginCore implements it
- CharacterStats.cs and WebSocket.cs now use IPluginLogger instead of PluginCore.WriteToChat
- Extract KillTracker.cs: owns kill detection (all 36 regex patterns), death tracking,
  rate calculation, and the 1-sec stats update timer
- Bridge properties on PluginCore maintain backward compat for WebSocket telemetry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:29:49 +00:00
erik
4845a67c1f Phase 1: Extract Constants.cs and CommandRouter.cs
- Extract magic numbers (timer intervals, message type IDs, property keys) into Constants.cs
- Replace ~600-line HandleMmCommand switch with dictionary-based CommandRouter
- All /mm commands preserved with same behavior, now registered via lambdas
- PluginCore.cs and CharacterStats.cs updated to use named constants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:24:43 +00:00
erik
9e9a94f159 Remove unused features: HTTP server, old telemetry, !do/!dot chat commands
- Delete HttpCommandServer.cs (localhost:8085 HTTP listener)
- Delete Telemetry.cs (old HTTP POST to mosswart.snakedesert.se/position/)
- Remove !do/!dot allegiance chat regex matching from OnChatText
- Remove RemoteCommandsEnabled, HttpServerEnabled, TelemetryEnabled settings
- Remove corresponding UI checkboxes, /mm command handlers, and wiring
- Keep: WebSocket command receive, ClientTelemetry.cs (used by WS streaming)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:02:07 +00:00