Skip to content

Conversation

MorquinDevlar
Copy link
Contributor

This PR transforms the GMCP system from monolithic structures that sent complete state updates to a granular architecture with individual "lowest level node" structures. Instead of sending entire Char, Room, or Party objects, the system now sends only the specific nodes that changed (e.g., Char.Vitals, Room.Info.Exits, Party.Vitals). This enables real-time updates without the overhead of transmitting complete state objects.

Added

Granular Node Structure:

  • Room nodes: Info.Basic, Info.Exits, Info.Contents.Players, Info.Contents.Npcs, Info.Contents.Items, Info.Contents.Containers, Add.Player/Npc/Item, Remove.Player/Npc/Item
  • Character nodes: Individual nodes for Info, Status, Stats, Vitals, Worth, Affects, Inventory.Worn, Inventory.Backpack.Items, Inventory.Backpack.Summary, Quests, Pets
  • Party nodes: Separated Party.Info and Party.Vitals for efficient updates
  • Game nodes: Enhanced Game.Who.Players with real-time updates

GMCP Combat Modules:

  • Individual combat tracking modules: Char.Combat.Cooldown, Char.Combat.Damage, Char.Combat.Enemies, Char.Combat.Events, Char.Combat.Status, Char.Combat.Target
  • Real-time combat cooldown updates (200ms intervals)
  • Combat participant tracking with automatic cleanup
  • Target HP monitoring and updates
  • Combat state management with deduplication
  • PlayerDespawn cleanup handlers for all tracking data
  • Combat event tests with comprehensive coverage

GMCP Request Options:

  • SendFullPayload support for requesting complete state updates
  • Individual node requests via Send commands (e.g., SendCharVitals, SendRoomInfoExits)
  • Clients can request full payloads via Core.Options with {"SendFullPayload": true}

Enhanced Room Features:

  • Exit details with type, state, hasKey, hasPicked fields
  • NPC threat levels (peaceful/hostile/aggressive/fighting) and targeting arrays
  • Quest flags on items and NPCs
  • Individual add/remove events for room contents

New Events for Tracking:

  • CharacterAlignmentChanged - Tracks alignment shifts
  • CombatStarted/CombatEnded - Combat state changes
  • DamageDealt/HealingReceived - Combat damage tracking
  • TargetChanged - Target switching
  • AggroGained/AggroLost - Enemy attention tracking
  • MobVitalsChanged/MobStatusChanged - Mob state updates
  • CombatActionStarted/Completed/Interrupted - Action tracking
  • AttackAvoided - Dodges, parries, blocks
  • CombatEffectTriggered - Special combat effects
  • CombatantFled - Flee attempts
  • ExitLockChanged - Exit state changes

Other Improvements:

  • Exit state change notifications via GMCP
  • Combat event logging hooks
  • Helper function validateUserForGMCP() for consistent validation
  • Rate limiting for vitals updates (100ms minimum interval)
  • Item command field indicating primary action
  • Unix epoch timestamp in Game.Info.login_time_epoch

Removed

  • Monolithic GMCP update structures
  • Redundant whitespace from multiple files

Breaking Changes (Only for the Mudlet UI - new UI fixed and ready to deploy)

  • Room.Info is now split into Room.Info.Basic, Room.Info.Exits, and Room.Info.Contents subnodes
  • Character data split into individual nodes instead of single Char object
  • Party structure separated into Party.Info and Party.Vitals
  • JSON field naming: num → id, coords → coordinates

@MorquinDevlar MorquinDevlar requested a review from Volte6 as a code owner August 8, 2025 07:05
@MorquinDevlar MorquinDevlar self-assigned this Aug 8, 2025
@MorquinDevlar MorquinDevlar added the enhancement New feature or request label Aug 8, 2025
@MorquinDevlar MorquinDevlar marked this pull request as draft August 8, 2025 21:43
@MorquinDevlar MorquinDevlar marked this pull request as ready for review August 9, 2025 21:32
@MorquinDevlar
Copy link
Contributor Author

MorquinDevlar commented Aug 9, 2025

The latest commit fixes the bug/issue I encountered while testing:

Problem

The GMCP module's plugin configuration system was overwriting user-defined settings. When users added custom values to their config-overrides.yaml file under the Modules.gmcp namespace, these values were being replaced by the plugin's default configuration on server startup.

Root Cause

The plugin system used AddOverlayOverrides() which unconditionally overwrote configuration values, even when users had explicitly set different values in their override files. Additionally, the GMCP Mudlet module cached configuration values at startup, preventing runtime configuration changes from taking effect.

Solution

Configuration Preservation

  • Added AddOverlayDefaults() function in internal/configs/configs.go that only sets configuration values if they don't already exist
  • Changed plugin loading in internal/plugins/plugins.go to use AddOverlayDefaults() instead of AddOverlayOverrides()
  • This ensures user-defined values in config-overrides.yaml take precedence over plugin defaults

Dynamic Configuration Reading

  • Removed cached MudletConfig struct from the GMCP Mudlet handler
  • Configuration values are now read on-demand using getConfigString()
  • Each GMCP message that needs configuration data reads the current value from the config system
  • This allows configuration changes to take effect without server restart

YAML Structure Preservation

  • Added YAML v3 support to maintain file structure, comments, and field order when saving configuration
  • Implemented marshalConfigWithOrder() and related functions to update YAML files without reorganizing them
  • This prevents the config-overrides.yaml file from being reformatted when the server saves changes

Type System Improvements

  • Updated StringToConfigValue() to accept simplified type names ("int", "string", "bool") in addition to full type names
  • Changed default behavior for unknown types from ConfigSliceString to ConfigString
  • This provides better compatibility with module configurations which typically use simple string values

How It Works Now

  1. Plugin default configurations are loaded from embedded files (e.g., modules/gmcp/files/data-overlays/config.yaml)
  2. These defaults are applied using AddOverlayDefaults() which checks if values already exist
  3. User overrides in config-overrides.yaml are preserved and take precedence
  4. When GMCP needs configuration (e.g., sending Mudlet map URL), it reads the current value from the config system
  5. Configuration changes can be made at runtime and will be used immediately without requiring a server restart

This architecture supports multiple GMCP sub-modules sharing configuration while respecting user customization.

@MorquinDevlar
Copy link
Contributor Author

If the mob-equipment-template PR gets approved first, I will have to make a minor change to this one, to use the new UserInterface structure, as this one still uses the old TextFormats setup. Just FYI.

@MorquinDevlar
Copy link
Contributor Author

Latest commit also addresses the WebClient to be able to use the moduler GMCP setup.

I've also added a minor fix to enable the WebClient to be used across HTTPS.

- Split monolithic GMCP updates into focused, event-driven modules
  - Added real-time combat tracking with individual update streams:
    - Combat status and cooldown timers (250ms updates)
    - Damage/healing event notifications
    - Enemy tracking with target priorities
    - Current target health monitoring
  - Added client-configurable GMCP subscriptions for selective data
streaming
  - Extended event system with granular combat and game state events
  - Improved room GMCP with enhanced player/mob tracking
  - Added party member health and position updates
  - Enhanced game GMCP with server time and player count updates
  - Fixed combat cleanup when mobs are removed from rooms
  - Optimized GMCP updates to send only changed data

New Events Added

  Event Constants:
  - CmdNoRoomGMCP - Skip GMCP room updates (prevents duplicate sends)

  Combat Events:
  - CombatStarted - Combat begins
  - CombatEnded - Combat ends (not death)
  - DamageDealt - Damage calculated and applied
  - HealingReceived - Health restored
  - TargetChanged - Primary target changes
  - AggroGained - Mob becomes hostile
  - AggroLost - Mob stops being hostile
  - MobVitalsChanged - Mob health/mana changes
  - MobStatusChanged - Mob status effects change
  - CombatActionStarted - Actions begin (casting, channeling)
  - CombatActionCompleted - Actions finish
  - CombatActionInterrupted - Actions interrupted
  - AttackAvoided - Attack fails to connect
  - CombatEffectTriggered - DoTs/bleeds/effects tick
  - CombatantFled - Flee attempt

  These events enable the granular GMCP combat tracking system.

This refactoring enables clients to subscribe to specific GMCP modules
and receive targeted updates instead of full state dumps, significantly
reducing bandwidth and improving responsiveness during combat and
exploration.
Added:
- PlayerDespawn cleanup handlers to all combat modules
- User validation with validateUserForGMCP helper function
- ExitLockChanged event for exit state notifications
- GMCP handler to send Room.Info.Exits updates on lock changes
- Exit details map with type, state, name, hasKey, and hasPicked fields
- Package documentation explaining design patterns for each module

Fixed:
- Memory leaks from uncleaned tracking maps
- Function naming inconsistencies in Status and Events modules
- Redundant "exits" wrapper in Room.Info.Exits output

Changed:
- Cooldown timer interval from 250ms to 200ms
- Mutex usage to RLock for read-only operations
- Exit state values to "locked" and "open"

Removed:
- Unused imports from gmcp.go and combat modules
- Unused gmcp_batcher.go file
- Rate limiting code from damage module
Added:
- Centralized combat tracking system with shared combatUsers map
- GetLowestRoomId() method to mapper for stable map identifiers
- Map_id field to GMCP room data based on lowest connected room ID
- Exit details for cross-map navigation (leads_to_map, leads_to_area)
- ANSI code stripping for all GMCP text fields
- CleanupUser() function for unified combat state cleanup
- Support for non-compass direction exits in GMCP

Fixed:
- Race conditions in combat module cleanup by centralizing PlayerDespawn handling
- Synchronous cooldown updates to prevent state inconsistencies
- Combat round handlers to only process users currently in combat

Changed:
- Cooldown timer interval from 250ms to 200ms
- NPC targeting from boolean to array of player names
- Room.Info.Exits structure to remove redundant wrapper
- GMCP module handling to always send all modules

Removed:
- Individual PlayerDespawn handlers from each combat module
- GMCPUserCleanup event type and related infrastructure
- Core.Supports.Set/Remove module tracking
- Mob health tracking from Target module
- Restriction on non-compass direction exits

Breaking Changes:
- Room.Info.Exits now exist as a primary node instead of Room.Info.Exits.exits
- NPC targeting_you changed from boolean to targeting array
Added:
  - CharacterAlignmentChanged event for dedicated alignment change
notifications
  - IsUserInCombat() centralized function for combat state detection
  - Proper PlayerDespawn cleanup calling CleanupUser() for all GMCP maps
  - CombatStarted events in attack commands for immediate GMCP updates
  - CombatEnded event when breaking combat
  - Immediate CharacterVitalsChanged events on all damage applications

Fixed:
  - Cooldown timer only runs when player has aggro (actively fighting)
  - Combat status properly detects both attacking and being attacked
states
  - Alignment changes now trigger GMCP Char.Info updates via dedicated
event
  - Enemy list maintains consistency when being attacked without
fighting back
  - Type mismatches in alignment event parameters (int8 to int
conversion)

Changed:
  - Consolidated duplicate combat detection logic into single source of
truth
  - Replaced CharacterVitalsChanged with CharacterAlignmentChanged for
alignment updates
  - Refactored combat event firing to use priority queue for immediate
processing

Removed:
  - Debug logging from GMCP and combat modules for production readiness
  - Unused imports from multiple files
  - Old backup files (.go.old, eventtypes.go.backup)
  - Incorrect code comments about CombatStarted events not firing
  - TODO comments and excessive documentation
Added:
  - AddOverlayDefaults function to preserve user configuration overrides
  - YAML v3 support for maintaining file structure when updating configs
  - Handler for Client.Map GMCP requests from Mudlet clients
  - Dynamic configuration reading for GMCP Mudlet settings

Fixed:
  - SendClientMap GMCP command now properly responds with Client.Map
structure
  - Plugin configuration now respects user overrides from
config-overrides.yaml

Changed:
  - Simplified config type inference with direct type names ("int",
"string")
  - Converted GMCPMudletModule to GMCPMudletHandler pattern
  - Mudlet functionality consolidated into main GMCP module
  - Plugin overlays now use AddOverlayDefaults instead of
AddOverlayOverrides

Removed:
  - Empty load/save functions that were no longer needed
  - Duplicate plugin registration for Mudlet module
Added:
  - sendMudletMapperInstall function to handle mapper package
installation via Client.GUI

Changed:
  - Reordered GMCP messages to send Client.Map before Client.GUI for
proper Mudlet client
  handling
  - Refactored sendMudletConfig to use dedicated functions instead of
inline payload
  creation

Removed:
  - Unnecessary user record fetch in sendMudletConfig function
  - Duplicate payload construction for mapper installation
Added:
  - Field mapping from new GMCP structure to expected flat format for
Room data
  - Room.Info.Basic fields (id, coordinates, area) mapped to Info level
  - Room.Info.Exits converted to exitsv2 format with dx/dy/dz delta
fields
  - Comments explaining why mapping is necessary for Room handler
compatibility

Changed:
  - Room.Info.Basic and Room.Info.Exits handlers to create flat data
structure
  - Room handler triggers to check for mapped fields instead of nested
objects
  - Vitals handler to use new field names directly (health,
spell_points)
  - WebSocket connection to detect https protocol for wss://
Added:
  - Package documentation for GMCP module explaining protocol
implementation

  Changed:
  - Removed redundant "Fire X event" comments from combat event handling
  - Removed obvious comments that restated code behavior
  - Removed visual noise comments (section dividers, unnecessary
headers)
  - Added context for magic numbers and non-obvious algorithms
  - Focused comments on explaining WHY rather than WHAT

Removed:
  - 18 redundant event firing comments in combat.go
  - Section divider comments throughout GMCP modules
  - Debug comments and commented-out code
@MorquinDevlar MorquinDevlar force-pushed the gmcp-individual-nodes branch from 288d1af to b300210 Compare August 15, 2025 10:10
Updated all references from GetTextFormatsConfig() to
GetUserInterfaceConfig().Formats to maintain compatibility with the
UserInterface configuration structure introduced in mob-equipment-templates.

Changed:
- Config struct to use UserInterface instead of TextFormats
- gmcp.Game.go to use c.UserInterface.Formats.Time
- All usercommands and mobcommands to use GetUserInterfaceConfig().Formats
- inbox.go DateString() to use UserInterface.Formats.Time
- userrecord.prompt.go to use GetUserInterfaceConfig().Formats.Prompt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant