package main // Cross-machine vital sharing (share_*), a faithful port of main.py:3658-3703 + // _update_vital_sharing_peer_state / _broadcast_share_to_plugin_clients. // Memory-only: subscriber set + last-known peer snapshot, fanned out to other // opted-in plugin clients and to browsers. In shadow mode there are no plugin // connections, so the fan-out is a no-op; the peer state still drives // /vital-sharing/peers. func (i *Ingestor) handleShareSubscribe(data map[string]any) { char := toStr(data["character_name"]) if char == "" { return } i.mu.Lock() i.vitalSubscribers[char] = true entry := i.vitalPeerEntry(char) if tags, ok := data["tags"].([]any); ok { entry["tags"] = tags } entry["connected"] = true i.mu.Unlock() } func (i *Ingestor) handleShareUnsubscribe(data map[string]any) { char := toStr(data["character_name"]) if char == "" { return } i.mu.Lock() delete(i.vitalSubscribers, char) delete(i.vitalPeerState, char) i.mu.Unlock() if i.broadcast != nil { i.broadcast(map[string]any{"type": "share_peer_removed", "character_name": char}) } } func (i *Ingestor) handleShareUpdate(msgType string, data map[string]any) { origin := toStr(data["character_name"]) i.mu.Lock() i.updateVitalPeerState(msgType, data) // Snapshot subscribers for the fan-out. subs := make(map[string]bool, len(i.vitalSubscribers)) for k := range i.vitalSubscribers { subs[k] = true } i.mu.Unlock() // Fan out to other opted-in plugin clients (no-op when no plugins connected). if i.plugins != nil && len(subs) > 0 { i.plugins.fanoutShare(data, origin, subs) } } // vitalPeerEntry returns (creating if needed) the peer snapshot for char. Caller // holds i.mu. func (i *Ingestor) vitalPeerEntry(char string) map[string]any { entry, ok := i.vitalPeerState[char] if !ok { entry = map[string]any{ "character_name": char, "tags": []any{}, "vitals": nil, "position": nil, "items": nil, "connected": true, "last_update": nil, } i.vitalPeerState[char] = entry } return entry } // updateVitalPeerState mirrors _update_vital_sharing_peer_state. Caller holds i.mu. func (i *Ingestor) updateVitalPeerState(msgType string, data map[string]any) { char := toStr(data["character_name"]) if char == "" { return } entry := i.vitalPeerEntry(char) entry["last_update"] = data["timestamp"] if tags, ok := data["tags"].([]any); ok { entry["tags"] = tags } switch msgType { case "share_vital_update": entry["vitals"] = map[string]any{ "current_health": data["current_health"], "max_health": data["max_health"], "current_stamina": data["current_stamina"], "max_stamina": data["max_stamina"], "current_mana": data["current_mana"], "max_mana": data["max_mana"], } case "share_position_update": entry["position"] = map[string]any{ "ew": data["ew"], "ns": data["ns"], "z": data["z"], "heading": data["heading"], } case "share_item_update": entry["items"] = data["items"] } } // vitalSharingPeers returns the peer list for /vital-sharing/peers (main.py:1800). func (i *Ingestor) vitalSharingPeers() ([]map[string]any, int) { i.mu.RLock() defer i.mu.RUnlock() peers := make([]map[string]any, 0, len(i.vitalPeerState)) for char, entry := range i.vitalPeerState { p := make(map[string]any, len(entry)+2) for k, v := range entry { p[k] = v } p["subscribed"] = i.vitalSubscribers[char] p["plugin_connected"] = i.plugins != nil && i.plugins.isConnected(char) peers = append(peers, p) } return peers, len(i.vitalSubscribers) }