using System;
using System.Collections.Generic;
using Server.Accounting;
using Server.Mobiles;
using Server.Multis;
namespace Server.Custom.Bridge
{
///
/// Inbound request/response. The sidecar asks; the shard answers. Every handler runs on the
/// Core thread (BridgeBoot dispatches inbound lines through Timer.DelayCall first), so all of
/// these may read live world state freely.
///
/// A request carries an optional "reqId" the shard echoes back, so the sidecar can correlate
/// the reply with the request it sent. A malformed or unresolvable request gets a
/// "bridge.error" reply rather than silence, so the website can show a real failure instead
/// of hanging.
///
public static class BridgeRequests
{
public static void Initialize()
{
if (!BridgeConfig.Enabled)
return;
BridgeBoot.RegisterHandler("char.request", OnCharRequest);
BridgeBoot.RegisterHandler("account.roster", OnRosterRequest);
BridgeBoot.RegisterHandler("vendor.snapshot", OnVendorSnapshotRequest);
}
private static void Fail(string reqId, string reason)
{
var sb = BridgeJson.Begin("bridge.error");
if (reqId != null)
sb.Str("reqId", reqId);
sb.Str("reason", reason);
BridgeLink.Emit(sb.End());
}
// ---- char.request ----
///
/// Resolve a character by serial, or by account + slot, and reply with a full profile.
/// Works for offline characters too: a logged-off mobile is still resident.
///
private static void OnCharRequest(Dictionary o)
{
var reqId = BridgeJson.GetString(o, "reqId");
PlayerMobile pm = null;
var serialStr = BridgeJson.GetString(o, "serial");
if (serialStr != null)
{
pm = ResolveSerial(serialStr) as PlayerMobile;
if (pm == null)
{
Fail(reqId, "no player with serial " + serialStr);
return;
}
}
else
{
var acctName = BridgeJson.GetString(o, "account");
var acct = acctName == null ? null : Accounting.Accounts.GetAccount(acctName) as Account;
if (acct == null)
{
Fail(reqId, "unknown account");
return;
}
int slot = BridgeJson.GetInt(o, "slot", 0);
if (slot < 0 || slot >= acct.Length)
{
Fail(reqId, "slot out of range");
return;
}
pm = acct[slot] as PlayerMobile;
if (pm == null)
{
Fail(reqId, "no character in slot " + slot);
return;
}
}
BridgeLink.Emit(BridgeProfile.BuildProfile(pm, reqId));
}
// ---- account.roster ----
private static void OnRosterRequest(Dictionary o)
{
var reqId = BridgeJson.GetString(o, "reqId");
var acctName = BridgeJson.GetString(o, "account");
var acct = acctName == null ? null : Accounting.Accounts.GetAccount(acctName) as Account;
if (acct == null)
{
Fail(reqId, "unknown account");
return;
}
BridgeLink.Emit(BridgeProfile.BuildRoster(acct, reqId));
}
// ---- vendor.snapshot ----
///
/// Every player vendor owned by any character on an account, with its held gold and
/// priced listings. Enumerates PlayerVendor.PlayerVendors and matches by owner account.
///
private static void OnVendorSnapshotRequest(Dictionary o)
{
var reqId = BridgeJson.GetString(o, "reqId");
var acctName = BridgeJson.GetString(o, "account");
var acct = acctName == null ? null : Accounting.Accounts.GetAccount(acctName) as Account;
if (acct == null)
{
Fail(reqId, "unknown account");
return;
}
var sb = BridgeJson.Begin("vendor.snapshot");
if (reqId != null)
sb.Str("reqId", reqId);
sb.Str("acct", acct.Username);
sb.Append(",\"vendors\":[");
bool firstVendor = true;
var all = PlayerVendor.PlayerVendors;
if (all != null)
{
foreach (var v in all)
{
if (v == null || v.Deleted || v.Owner == null)
continue;
if (!(v.Owner.Account is Account ownerAcct) || ownerAcct != acct)
continue;
if (!firstVendor) sb.Append(',');
firstVendor = false;
sb.Append("{\"serial\":\"0x").Append(v.Serial.Value.ToString("X")).Append('"');
sb.Append(",\"shopName\":");
BridgeJson.Escape(sb, v.ShopName ?? "");
sb.Append(",\"holdGold\":").Append(v.HoldGold);
sb.Append(",\"ownerSerial\":\"0x").Append(v.Owner.Serial.Value.ToString("X")).Append('"');
var house = v.Map;
sb.Append(",\"map\":");
BridgeJson.Escape(sb, v.Map == null ? "" : v.Map.Name);
sb.Append(",\"x\":").Append(v.X).Append(",\"y\":").Append(v.Y);
sb.Append(",\"listings\":[");
bool firstItem = true;
var pack = v.Backpack;
if (pack != null)
{
foreach (var item in pack.Items)
{
var vi = v.GetVendorItem(item);
if (vi == null)
continue;
if (!firstItem) sb.Append(',');
firstItem = false;
sb.Append("{\"serial\":\"0x").Append(item.Serial.Value.ToString("X")).Append('"');
sb.Append(",\"itemId\":").Append(item.ItemID);
sb.Append(",\"amount\":").Append(item.Amount);
sb.Append(",\"price\":").Append(vi.Price);
sb.Append(",\"forSale\":").Append(vi.IsForSale ? "true" : "false").Append('}');
}
}
sb.Append("]}");
}
}
sb.Append(']');
BridgeLink.Emit(sb.End());
}
// ---- helpers ----
private static Mobile ResolveSerial(string serialStr)
{
try
{
var s = serialStr.Trim();
int value;
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
value = Convert.ToInt32(s.Substring(2), 16);
else
value = Convert.ToInt32(s, 10);
return World.FindMobile(value);
}
catch
{
return null;
}
}
}
}