implement realtime communication with mmo server

-also added messages for anyone on wojs or earlier for ribbons
-mmo communication occurs in the ``MessagingService`` and ``BuddyService``
-for now this requires a DLL from the game client as we should not distribute the SmartFox client openly here due to it being closed sourced
This commit is contained in:
Alan Moon 2025-03-17 14:05:43 -07:00
parent 0a97ff8d42
commit 6fab853d5f
8 changed files with 153 additions and 6 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ __pycache__/
sodoff.db sodoff.db
sodoff.db-shm sodoff.db-shm
sodoff.db-wal sodoff.db-wal
src/Dlls/SmartFox2X.dll

View File

@ -1754,6 +1754,7 @@ public class ContentController : Controller {
{ {
existingScene.XmlData = contentXml; existingScene.XmlData = contentXml;
ctx.SaveChanges(); ctx.SaveChanges();
return Ok(true); return Ok(true);
} }
else else
@ -1765,6 +1766,7 @@ public class ContentController : Controller {
}; };
viking.SceneData.Add(sceneData); viking.SceneData.Add(sceneData);
ctx.SaveChanges(); ctx.SaveChanges();
return Ok(true); return Ok(true);
} }
} }

View File

@ -30,6 +30,7 @@ builder.Services.AddSingleton<ItemService>();
builder.Services.AddSingleton<StoreService>(); builder.Services.AddSingleton<StoreService>();
builder.Services.AddSingleton<DisplayNamesService>(); builder.Services.AddSingleton<DisplayNamesService>();
builder.Services.AddSingleton<MMOConfigService>(); builder.Services.AddSingleton<MMOConfigService>();
builder.Services.AddSingleton<MMOClientService>();
builder.Services.AddSingleton<WorldIdService>(); builder.Services.AddSingleton<WorldIdService>();
builder.Services.AddScoped<KeyValueService>(); builder.Services.AddScoped<KeyValueService>();
@ -44,6 +45,8 @@ builder.Services.AddScoped<ModerationService>();
builder.Services.AddScoped<MessagingService>(); builder.Services.AddScoped<MessagingService>();
builder.Services.AddScoped<BuddyService>(); builder.Services.AddScoped<BuddyService>();
builder.Services.AddHostedService<MMOClientService>(provider => provider.GetService<MMOClientService>());
bool assetServer = builder.Configuration.GetSection("AssetServer").GetValue<bool>("Enabled"); bool assetServer = builder.Configuration.GetSection("AssetServer").GetValue<bool>("Enabled");
string assetIP = builder.Configuration.GetSection("AssetServer").GetValue<string>("ListenIP"); string assetIP = builder.Configuration.GetSection("AssetServer").GetValue<string>("ListenIP");
int assetPort = builder.Configuration.GetSection("AssetServer").GetValue<int>("Port"); int assetPort = builder.Configuration.GetSection("AssetServer").GetValue<int>("Port");

View File

@ -12,7 +12,8 @@ namespace sodoff.Services {
private MessagingService messagingService; private MessagingService messagingService;
public readonly DBContext ctx; public readonly DBContext ctx;
public AchievementService(AchievementStoreSingleton achievementStore, InventoryService inventoryService, MessagingService messagingService, DBContext ctx) { public AchievementService(AchievementStoreSingleton achievementStore, InventoryService inventoryService, MessagingService messagingService, DBContext ctx)
{
this.achievementStore = achievementStore; this.achievementStore = achievementStore;
this.inventoryService = inventoryService; this.inventoryService = inventoryService;
this.messagingService = messagingService; this.messagingService = messagingService;
@ -149,6 +150,9 @@ namespace sodoff.Services {
// TODO: check trophies, etc criteria and id need apply and add to results extra reward here // TODO: check trophies, etc criteria and id need apply and add to results extra reward here
if (viking.GameVersion <= ClientVersion.WoJS)
messagingService.AddMessageToViking(null, viking, MessageType.Data, MessageTypeID.Achievement, MessageLevel.WhiteList, "[[Line1]]=[[Great Job! You've Earned A Ribbon, JumpStars, And Coins!]][[SubType]]=[[Ribbon]]", "[[Line1]]=[[Great Job! You've Earned A Ribbon, JumpStars, And Coins!]][[SubType]]=[[Ribbon]]", "[[Line1]]=[[Great Job! You've Earned A Ribbon, JumpStars, And Coins!]][[SubType]]=[[Ribbon]]", isPrivate: true);
return grantedRewards.ToArray(); return grantedRewards.ToArray();
} }

View File

@ -14,11 +14,13 @@ public class BuddyService
private readonly DBContext ctx; private readonly DBContext ctx;
private readonly MessagingService messagingService; private readonly MessagingService messagingService;
private readonly MMOClientService mMOClientService;
public BuddyService(DBContext ctx, MessagingService messagingService) public BuddyService(DBContext ctx, MessagingService messagingService, MMOClientService mMOClientService)
{ {
this.ctx = ctx; this.ctx = ctx;
this.messagingService = messagingService; this.messagingService = messagingService;
this.mMOClientService = mMOClientService;
} }
public BuddyActionResult CreateBuddyRelation(Viking viking1, Viking viking2, BuddyStatus buddyStatus1 = BuddyStatus.PendingApprovalFromOther, BuddyStatus buddyStatus2 = BuddyStatus.PendingApprovalFromSelf) public BuddyActionResult CreateBuddyRelation(Viking viking1, Viking viking2, BuddyStatus buddyStatus1 = BuddyStatus.PendingApprovalFromOther, BuddyStatus buddyStatus2 = BuddyStatus.PendingApprovalFromSelf)
@ -53,7 +55,10 @@ public class BuddyService
// if this is a buddy relationship, use MessagingService to send buddy request message to viking2 // if this is a buddy relationship, use MessagingService to send buddy request message to viking2
if (buddyStatus1 == BuddyStatus.PendingApprovalFromOther && buddyStatus2 == BuddyStatus.PendingApprovalFromSelf) if (buddyStatus1 == BuddyStatus.PendingApprovalFromOther && buddyStatus2 == BuddyStatus.PendingApprovalFromSelf)
{
messagingService.AddMessageToViking(viking1, viking2, MessageType.Data, MessageTypeID.BuddyList, MessageLevel.WhiteList, "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]", "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]", "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]"); messagingService.AddMessageToViking(viking1, viking2, MessageType.Data, MessageTypeID.BuddyList, MessageLevel.WhiteList, "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]", "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]", "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]");
mMOClientService.SendCommandToUser(viking2.Uid.ToString(), "SBE", new string[] { "SBE", "-1", viking1.Uid.ToString(), viking2.Uid.ToString(), "1" });
}
// return result // return result
return new BuddyActionResult return new BuddyActionResult
@ -77,6 +82,9 @@ public class BuddyService
buddy.BuddyStatus2 = buddyStatus2; buddy.BuddyStatus2 = buddyStatus2;
ctx.SaveChanges(); ctx.SaveChanges();
if ((buddyStatus1 & buddyStatus2) == BuddyStatus.Approved)
mMOClientService.SendCommandToUser(viking.Uid.ToString(), "SBE", new string[] { "SBE", "-1", buddyViking.Uid.ToString(), viking.Uid.ToString(), "4" });
// return result // return result
return true; return true;
} else return false; } else return false;
@ -161,6 +169,8 @@ public class BuddyService
ctx.Buddies.Remove(buddy); ctx.Buddies.Remove(buddy);
ctx.SaveChanges(); ctx.SaveChanges();
mMOClientService.SendCommandToUser(buddyViking.Uid.ToString(), "SBE", new string[] { "SBE", "-1", viking.Uid.ToString(), buddyViking.Uid.ToString(), "2" });
return true; return true;
} }
else return false; else return false;

View File

@ -0,0 +1,111 @@
using System;
using Microsoft.Extensions.Options;
using Sfs2X;
using Sfs2X.Core;
using Sfs2X.Entities;
using Sfs2X.Entities.Data;
using Sfs2X.Entities.Variables;
using Sfs2X.Requests;
using Sfs2X.Util;
using sodoff.Configuration;
namespace sodoff.Services;
public class MMOClientService : IHostedService
{
private readonly IOptions<ApiServerConfig> Config;
private ConfigData SFSConfig;
public SmartFox SFSClient { get; private set; }
public bool IsLoggedIn { get; private set; }
public User? CurrentUser { get; private set; }
public MMOClientService(IOptions<ApiServerConfig> config)
{
Config = config;
// set SFSConfig
SFSConfig = new ConfigData();
SFSConfig.Host = Config.Value.MMOAdress;
SFSConfig.Port = Config.Value.MMOPort;
SFSConfig.Zone = "JumpStart";
// set SFSClient
SFSClient = new SmartFox();
SFSClient.ThreadSafeMode = false; // this is set to true by default which requires Unity's thread processing stuff
}
public Task StartAsync(CancellationToken cancellationToken)
{
// connect and login to mmo server using SFSConfig
Console.WriteLine("Starting Connection To MMO Server...");
SFSClient.Connect(SFSConfig);
SFSClient.AddEventListener(SFSEvent.CONNECTION, OnConnectionEstablished);
SFSClient.AddEventListener(SFSEvent.LOGIN, OnLogin);
// return completed task (SmartFox seems to be completely synchronous)
return Task.CompletedTask;
}
public Task SetRoomVarForRoom(int roomId, RoomVariable var)
{
if (IsLoggedIn)
{
Room room = SFSClient.GetRoomById(roomId);
if (room != null) room.SetVariable(var);
else return Task.CompletedTask;
}
else throw new InvalidOperationException("MMO Client Was Not Ready");
return Task.CompletedTask;
}
public Task SendCommandToUser(string userId, string cmd, string[] parameters)
{
// SFSClient doesn't seem to have a method to send commands to specific users, so we'll make a custom extension endpoint that will handle it
ISFSObject obj = SFSObject.NewInstance();
obj.PutUtfString("UID", userId);
obj.PutUtfString("CMD", cmd);
obj.PutUtfStringArray("ARR", parameters);
SFSClient.Send(new ExtensionRequest("SUE", obj));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Stopping Connection To MMO Server...");
if(SFSClient.IsConnected)
{
if(IsLoggedIn) SFSClient.Send(new LogoutRequest());
SFSClient.Disconnect();
}
return Task.CompletedTask;
}
private void OnConnectionEstablished(BaseEvent evt)
{
bool success = (bool)evt.Params["success"];
if (success)
{
Console.WriteLine("Connection Established. Sending Login Request...");
SFSClient.Send(new LoginRequest("API"));
}
}
private void OnLogin(BaseEvent evt)
{
CurrentUser = (User)evt.Params["user"];
IsLoggedIn = CurrentUser != null;
Console.WriteLine($"Logged In? - {IsLoggedIn}");
if (IsLoggedIn) Console.WriteLine("MMO Service Ready.");
else Console.WriteLine("MMO Service Not Ready.");
}
}

View File

@ -11,9 +11,11 @@ namespace sodoff.Services;
public class MessagingService public class MessagingService
{ {
private readonly DBContext ctx; private readonly DBContext ctx;
public MessagingService(DBContext ctx) private readonly MMOClientService mMOClientService;
public MessagingService(DBContext ctx, MMOClientService mMOClientService)
{ {
this.ctx = ctx; this.ctx = ctx;
this.mMOClientService = mMOClientService;
} }
public Model.Message AddMessageToViking(Viking? viking, Viking toViking, MessageType messageType, MessageTypeID messageTypeID, MessageLevel messageLevel, string data, string memberMessage = "", string nonMemberMessage = "", bool IsNew = true, bool IsDeleted = false, bool isReply = false, bool isPrivate = false, int parentMessageId = 0) public Model.Message AddMessageToViking(Viking? viking, Viking toViking, MessageType messageType, MessageTypeID messageTypeID, MessageLevel messageLevel, string data, string memberMessage = "", string nonMemberMessage = "", bool IsNew = true, bool IsDeleted = false, bool isReply = false, bool isPrivate = false, int parentMessageId = 0)
@ -74,6 +76,10 @@ public class MessagingService
ctx.Messages.Add(message); ctx.Messages.Add(message);
ctx.SaveChanges(); ctx.SaveChanges();
// update receiving users messages
mMOClientService.SendCommandToUser(toViking.Uid.ToString(), "SPMN", new string[] { "SPMN" });
mMOClientService.SendCommandToUser(toViking.Uid.ToString(), "NMP", new string[] { "NMP", "-1", "1", "1" });
// return constructed message // return constructed message
return message; return message;
} }

View File

@ -159,4 +159,14 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="SmartFox2X">
<HintPath>Dlls\SmartFox2X.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Dlls\" />
</ItemGroup>
</Project> </Project>