feat: Add metro transit, gadgets, shop, social apps, and traffic models checkpoint
This commit is contained in:
1 parent
caa42b84ed
commit
74e2963a12
79 files changed
+527962
-496
No files matched your search
@@ -0,0 +1,173 @@
|
||||
using Ananta.SDK.Network;
|
||||
using Ananta.SDK.Rpc;
|
||||
using Ananta.SDK.Serialization;
|
||||
using Ananta.Server.ClientData.Client4229938;
|
||||
using Ananta.Server.Protocol.Client4229938;
|
||||
using Auto = Ananta.Server.RpcTypes.Client4229938.Auto;
|
||||
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
|
||||
|
||||
namespace Ananta.Server.Handlers.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Phone contacts app ("NETWORK OF CONTACTS"). The list is push-only: the login snapshot
|
||||
/// seeds the two summon numbers, and AddSpiritPhoneInfos refreshes each character's book.
|
||||
/// This ports PrivateServer1's handbook push (GameRouter.Baseline.cs PushSpiritHandbookAsync)
|
||||
/// and adds the C2S add/edit/delete actions as a sandbox (session-side list, re-pushed).
|
||||
/// City contacts bind REAL agent-catalog NPC names so the app lists actual city residents.
|
||||
/// </summary>
|
||||
internal sealed partial class GameRouter
|
||||
{
|
||||
private const string PhoneContactsKey = "phoneContacts";
|
||||
|
||||
private static (string Remark, string Number)[] SummonContacts { get; } =
|
||||
[
|
||||
(string.Empty, "142857"), // "Call vehicle" quick summon
|
||||
(string.Empty, "206337"), // "Express Depot"
|
||||
];
|
||||
|
||||
/// <summary>Stable per-session contact book, seeded with summon + city NPC contacts.</summary>
|
||||
private static Dictionary<uint, List<Auto.PhoneContact>> ContactsOf(TcpSession session)
|
||||
{
|
||||
if (session.Items.TryGetValue(PhoneContactsKey, out var raw) && raw is Dictionary<uint, List<Auto.PhoneContact>> dict)
|
||||
return dict;
|
||||
dict = [];
|
||||
var city = CityContacts();
|
||||
foreach (var character in ClientConfigRepository.Characters())
|
||||
dict[character.TemplateId] = SummonContacts
|
||||
.Select(c => new Auto.PhoneContact { Remark = c.Remark, PhoneNumber = c.Number })
|
||||
.Concat(city)
|
||||
.ToList();
|
||||
session.Items[PhoneContactsKey] = dict;
|
||||
return dict;
|
||||
}
|
||||
|
||||
/// <summary>Real NPC names from the AgentConfig catalog become callable city contacts.</summary>
|
||||
private static List<Auto.PhoneContact> CityContacts()
|
||||
{
|
||||
var result = new List<Auto.PhoneContact>();
|
||||
var picked = 0;
|
||||
foreach (var npc in NpcCatalog4229938.All)
|
||||
{
|
||||
if (npc.Category != "citizen" || npc.GeneralModelId == 0 || string.IsNullOrWhiteSpace(npc.Name))
|
||||
continue;
|
||||
var name = NameTranslator.Agent(npc.Name);
|
||||
if (string.IsNullOrWhiteSpace(name) || name == npc.Name && ContainsCjk(name))
|
||||
continue; // keep untranslated CN names out of the English book
|
||||
result.Add(new Auto.PhoneContact { Remark = name, PhoneNumber = $"110{npc.Id % 100000000:D8}" });
|
||||
if (++picked >= 24)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool ContainsCjk(string text)
|
||||
=> text.Any(c => c is >= '\u4E00' and <= '\u9FFF');
|
||||
|
||||
/// <summary>Pushes every character's contact book (call after SyncPlayerInfo at world entry).</summary>
|
||||
internal static async Task PushSpiritHandbookAsync(TcpSession session, CancellationToken token = default)
|
||||
{
|
||||
var contacts = ContactsOf(session);
|
||||
foreach (var character in ClientConfigRepository.Characters())
|
||||
{
|
||||
var list = contacts.TryGetValue(character.TemplateId, out var l) ? l : [];
|
||||
await session.NotifyAsync(MethodId.AddSpiritPhoneInfos, UxSerializer.Serialize(
|
||||
new GameMethods.AddSpiritPhoneInfos4229938
|
||||
{
|
||||
spiritId = character.TemplateId,
|
||||
phoneInfos = new Auto.PhoneInfos
|
||||
{
|
||||
ContactList = list,
|
||||
ContactGroupList = [],
|
||||
CallRecordList = [],
|
||||
ContactOutgoingCallTimesDict = [],
|
||||
},
|
||||
}), token);
|
||||
}
|
||||
session.Log.Info($"[HANDBOOK] contacts pushed spirits={contacts.Count} city={contacts.Values.FirstOrDefault()?.Count ?? 0}");
|
||||
}
|
||||
|
||||
// ---------------- client-driven phone contact actions (sandbox) ----------------
|
||||
|
||||
[Handler(MethodId.AskPhoneAddContact, HandlerPacketKind.Invoke)]
|
||||
private async Task PhoneAddContact(Connection conn, UxRpcMessage msg)
|
||||
{
|
||||
var args = msg.GetArgs<GameMethods.AskPhoneAddContact4229938>();
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
var book = ContactsOf(conn.Session);
|
||||
if (!book.TryGetValue(args.spiritid, out var list))
|
||||
book[args.spiritid] = list = [];
|
||||
if (list.All(c => c.PhoneNumber != args.phonenumber))
|
||||
{
|
||||
list.Add(new Auto.PhoneContact { Remark = args.contactname, PhoneNumber = args.phonenumber });
|
||||
conn.Log.Info($"[PHONE] add contact spirit={args.spiritid} '{args.contactname}' {args.phonenumber}");
|
||||
await PushSpiritBookAsync(conn.Session, args.spiritid);
|
||||
}
|
||||
}
|
||||
|
||||
[Handler(MethodId.AskPhoneEditContact, HandlerPacketKind.Invoke)]
|
||||
private async Task PhoneEditContact(Connection conn, UxRpcMessage msg)
|
||||
{
|
||||
var args = msg.GetArgs<GameMethods.AskPhoneEditContact4229938>();
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
var book = ContactsOf(conn.Session);
|
||||
if (book.TryGetValue(args.spiritid, out var list))
|
||||
{
|
||||
var contact = list.FirstOrDefault(c => c.PhoneNumber == args.oldphonenumber);
|
||||
if (contact is not null)
|
||||
{
|
||||
contact.PhoneNumber = args.newphonenumber;
|
||||
contact.Remark = args.contactname;
|
||||
conn.Log.Info($"[PHONE] edit contact spirit={args.spiritid} '{args.contactname}' {args.oldphonenumber}->{args.newphonenumber}");
|
||||
await PushSpiritBookAsync(conn.Session, args.spiritid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Handler(MethodId.AskPhoneDeleteContact, HandlerPacketKind.Invoke)]
|
||||
private async Task PhoneDeleteContact(Connection conn, UxRpcMessage msg)
|
||||
{
|
||||
var args = msg.GetArgs<GameMethods.AskPhoneDeleteContact4229938>();
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
var book = ContactsOf(conn.Session);
|
||||
if (book.TryGetValue(args.spiritid, out var list) && list.RemoveAll(c => c.PhoneNumber == args.phonenumber) > 0)
|
||||
{
|
||||
conn.Log.Info($"[PHONE] delete contact spirit={args.spiritid} {args.phonenumber}");
|
||||
await PushSpiritBookAsync(conn.Session, args.spiritid);
|
||||
}
|
||||
}
|
||||
|
||||
// Group management and contact options stay accepted-but-static in the sandbox: the
|
||||
// client keeps its own optimistic UI state and the server list stays the source of truth.
|
||||
[Handler(MethodId.AskPhoneAddContactGroup, HandlerPacketKind.Invoke)]
|
||||
private Task PhoneAddContactGroup(Connection conn, UxRpcMessage msg) => conn.ReturnEmptyOkAsync(msg);
|
||||
|
||||
[Handler(MethodId.AskPhoneEditContactGroup, HandlerPacketKind.Invoke)]
|
||||
private Task PhoneEditContactGroup(Connection conn, UxRpcMessage msg) => conn.ReturnEmptyOkAsync(msg);
|
||||
|
||||
[Handler(MethodId.AskPhoneDeleteContactGroup, HandlerPacketKind.Invoke)]
|
||||
private Task PhoneDeleteContactGroup(Connection conn, UxRpcMessage msg) => conn.ReturnEmptyOkAsync(msg);
|
||||
|
||||
[Handler(MethodId.AskPhoneAddContactToGroup, HandlerPacketKind.Invoke)]
|
||||
private Task PhoneAddContactToGroup(Connection conn, UxRpcMessage msg) => conn.ReturnEmptyOkAsync(msg);
|
||||
|
||||
[Handler(MethodId.AskPhoneContactOptionAction, HandlerPacketKind.Invoke)]
|
||||
private Task PhoneContactOptionAction(Connection conn, UxRpcMessage msg) => conn.ReturnEmptyOkAsync(msg);
|
||||
|
||||
private async Task PushSpiritBookAsync(TcpSession session, uint spiritId, CancellationToken token = default)
|
||||
{
|
||||
var contacts = ContactsOf(session);
|
||||
var list = contacts.TryGetValue(spiritId, out var l) ? l : [];
|
||||
await session.NotifyAsync(MethodId.AddSpiritPhoneInfos, UxSerializer.Serialize(
|
||||
new GameMethods.AddSpiritPhoneInfos4229938
|
||||
{
|
||||
spiritId = spiritId,
|
||||
phoneInfos = new Auto.PhoneInfos
|
||||
{
|
||||
ContactList = list,
|
||||
ContactGroupList = [],
|
||||
CallRecordList = [],
|
||||
ContactOutgoingCallTimesDict = [],
|
||||
},
|
||||
}), token);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user