Buddy System #3

Merged
Moonbase merged 18 commits from buddies into master 2025-03-07 18:20:40 -08:00
31 changed files with 11467 additions and 34 deletions

View File

@ -24,6 +24,7 @@ public class ContentController : Controller {
private DisplayNamesService displayNamesService;
private NeighborhoodService neighborhoodService;
private WorldIdService worldIdService;
private BuddyService buddyService;
private Random random = new Random();
private readonly IOptions<ApiServerConfig> config;
@ -40,6 +41,7 @@ public class ContentController : Controller {
DisplayNamesService displayNamesService,
NeighborhoodService neighborhoodService,
WorldIdService worldIdService,
BuddyService buddyService,
IOptions<ApiServerConfig> config
) {
this.ctx = ctx;
@ -54,6 +56,7 @@ public class ContentController : Controller {
this.displayNamesService = displayNamesService;
this.neighborhoodService = neighborhoodService;
this.worldIdService = worldIdService;
this.buddyService = buddyService;
this.config = config;
}
@ -1142,12 +1145,104 @@ public class ContentController : Controller {
return Ok(taskResult);
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/AddBuddy")]
[VikingSession]
public IActionResult AddBuddy(Viking viking, [FromForm] Guid buddyUserID)
{
// get buddy
Viking? buddyViking = ctx.Vikings.FirstOrDefault(e => e.Uid == buddyUserID);
if (buddyViking != null)
return Ok(buddyService.CreateBuddyRelation(viking, buddyViking));
else return Ok(new BuddyActionResult{ Result = BuddyActionResultType.InvalidFriendCode });
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/AddBuddyByFriendCode")]
[VikingSession]
public IActionResult AddBuddyByFriendCode(Viking viking, [FromForm] string friendCode, [FromForm] string apiKey)
{
// make sure viking doesn't add self as buddy
if (viking.BuddyCode != null && viking.BuddyCode == friendCode) return Ok(new BuddyActionResult{ Result = BuddyActionResultType.CannotAddSelf });
// find viking
Viking? buddyViking = ctx.Vikings.FirstOrDefault(e => e.BuddyCode == friendCode);
if (buddyViking != null)
{
uint gameVersion = ClientVersion.GetVersion(apiKey);
// check if game clients are different
if (
(buddyViking.GameVersion != gameVersion) &&
!(buddyViking.GameVersion >= ClientVersion.Min_SoD && gameVersion >= ClientVersion.Min_SoD) &&
!(buddyViking.GameVersion >= ClientVersion.WoJS && gameVersion >= ClientVersion.WoJS && buddyViking.GameVersion < ClientVersion.WoJS_NewAvatar && gameVersion < ClientVersion.WoJS_NewAvatar)
) return Ok(new BuddyActionResult { Result = BuddyActionResultType.InvalidFriendCode });
else return Ok(buddyService.CreateBuddyRelation(viking, buddyViking));
} else return Ok(new BuddyActionResult{ Result = BuddyActionResultType.InvalidFriendCode });
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/RemoveBuddy")]
[VikingSession]
public IActionResult RemoveBuddy(Viking viking, [FromForm] Guid buddyUserId)
{
// find buddy viking
Viking? buddyViking = ctx.Vikings.FirstOrDefault(e => e.Uid == buddyUserId);
if (buddyViking != null)
return Ok(buddyService.RemoveBuddy(viking, buddyViking));
else return Ok(false);
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/ApproveBuddy")]
[VikingSession]
public IActionResult ApproveBuddy(Viking viking, [FromForm] Guid buddyUserID)
{
// get buddy
Viking? buddyViking = ctx.Vikings.FirstOrDefault(e => e.Uid == buddyUserID);
if (buddyViking != null)
return Ok(buddyService.UpdateBuddyRelation(viking, buddyViking, BuddyStatus.Approved, BuddyStatus.Approved));
else return Ok(false);
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/UpdateBestBuddy")]
[VikingSession]
public IActionResult SetBestBuddy(Viking viking, [FromForm] Guid buddyUserID, [FromForm] bool bestBuddy)
{
// get buddy
Viking? buddyViking = ctx.Vikings.FirstOrDefault(e => e.Uid == buddyUserID);
if (buddyViking != null)
return Ok(buddyService.UpdateBestBuddy(viking, buddyViking, bestBuddy));
else return Ok(false);
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetBuddyList")]
public IActionResult GetBuddyList() {
// TODO: this is a placeholder
return Ok(new BuddyList { Buddy = new Buddy[0] });
[VikingSession]
public IActionResult GetBuddyList(Viking viking) {
return Ok(buddyService.ConstructBuddyList(viking));
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetFriendCode")]
public IActionResult GetFriendCode([FromForm] Guid userId)
{
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
if (viking == null) return NotFound();
else return Ok(buddyService.GetOrSetBuddyCode(viking));
}
[HttpPost]

View File

@ -37,19 +37,20 @@ public class MessagingController : Controller {
ArrayOfKeyValuePairOfStringString arrayOfKeyValuePairOfStringString = XmlUtil.DeserializeXml<ArrayOfKeyValuePairOfStringString>(data);
MessageTypeID typeID = MessageTypeID.Unknown;
string typeText = "";
switch(arrayOfKeyValuePairOfStringString.KeyValuePairOfStringString[0]?.Value)
switch(arrayOfKeyValuePairOfStringString.KeyValuePairOfStringString![0].Value)
{
case "Photo":
typeID = MessageTypeID.Photo;
typeText = "Photo";
break;
case "Drawing":
typeID = MessageTypeID.GreetingCard;
typeText = "Card";
break;
case "Photo":
typeID = MessageTypeID.Photo;
typeText = "PhotoBomb";
break;
}
var msg = messagingService.AddMessageToViking(viking, toViking, MessageType.Data, typeID, MessageLevel.WhiteList, data, "[[Line1]]=[[{{BuddyUserName}}]] has sent you a " + typeText + "!", "[[Line1]]=[[{{BuddyUserName}}]] has sent you a " + typeText + "!");
var msg = messagingService.AddMessageToViking(viking, toViking, MessageType.Data, typeID, MessageLevel.WhiteList, data, "[[Line1]]=[[{{BuddyUserName}} has sent you a " + typeText + "!]]", "[[Line1]]=[[{{BuddyUserName}} has sent you a " + typeText + "!]]");
if (msg != null) return Ok(true);
else return Ok(false);
}
@ -66,12 +67,13 @@ public class MessagingController : Controller {
[HttpPost]
[Produces("application/xml")]
[Route("MessageWebService.asmx/GetCombinedListMessage")]
public ArrayOfCombinedListMessage? GetCombinedListMessage([FromForm] Guid userId)
[VikingSession(UseLock = false)]
public ArrayOfCombinedListMessage? GetCombinedListMessage(Viking viking, [FromForm] Guid userId)
{
// find viking
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
Viking? uidViking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
if (viking != null) return messagingService.ConstructCombinedMessageArray(viking);
if (viking != null && uidViking != null) return messagingService.ConstructCombinedMessageArray(viking, uidViking);
else return new ArrayOfCombinedListMessage();
}
@ -83,4 +85,25 @@ public class MessagingController : Controller {
messagingService.RemoveMessage(messageID);
return Ok(true);
}
[HttpPost]
[Route("Messaging/PostTextMessage")]
public IActionResult PostTextMessage([FromForm] Guid token, [FromForm] Guid userId, [FromForm] string data, [FromForm] int messageLevel, [FromForm] int replyMessageId)
{
// check if session is valid
Session? session = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token);
// find viking
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
if (session != null && viking != null)
{
// post text message
var message = messagingService.AddMessageToViking(session.Viking, viking, MessageType.Post, MessageTypeID.Messaging, (MessageLevel)messageLevel, data, isReply: replyMessageId > 0, parentMessageId: replyMessageId);
if (message != null) return Ok(true);
}
return Ok(false);
}
}

View File

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using sodoff.Attributes;
using sodoff.Model;
namespace sodoff.Controllers.Common
{
[ApiController]
public class PrecenseController : Controller
{
private readonly DBContext ctx;
public PrecenseController(DBContext ctx) => this.ctx = ctx;
[HttpPost]
[Produces("application/json")]
[Route("Precense/SetVikingOnline")]
public IActionResult SetVikingOnline([FromForm] Guid token, [FromForm] bool online)
{
// get viking from session
Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token)?.Viking;
if (viking != null)
{
viking.Online = online;
ctx.SaveChanges();
return Ok(viking.Online);
} else return Ok(false);
}
}
}

View File

@ -177,7 +177,7 @@ public class ProfileController : Controller {
GameCurrency = currency.GameCurrency,
CashCurrency = currency.CashCurrency,
ActivityCount = 0,
BuddyCount = 0,
BuddyCount = viking.BuddyList.Count + viking.BuddiesMade.Count, // relations are hard
UserGradeData = new UserGrade { UserGradeID = 0 },
UserProfileTag = new UserProfileTag() {
CreateDate = new DateTime(DateTime.Now.Ticks),

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace sodoff.Migrations
{
/// <inheritdoc />
public partial class Buddies : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Buddy",
columns: table => new
{
VikingId = table.Column<int>(type: "INTEGER", nullable: false),
BuddyVikingId = table.Column<int>(type: "INTEGER", nullable: false),
BuddyStatus1 = table.Column<int>(type: "INTEGER", nullable: false),
BuddyStatus2 = table.Column<int>(type: "INTEGER", nullable: false),
IsBestFriend1 = table.Column<bool>(type: "INTEGER", nullable: false),
IsBestFriend2 = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Buddy", x => x.VikingId);
table.ForeignKey(
name: "FK_Buddy_Vikings_BuddyVikingId",
column: x => x.BuddyVikingId,
principalTable: "Vikings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Buddy_Vikings_VikingId",
column: x => x.VikingId,
principalTable: "Vikings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Buddy_BuddyVikingId",
table: "Buddy",
column: "BuddyVikingId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Buddy");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace sodoff.Migrations
{
/// <inheritdoc />
public partial class Buddies_DBSetFix : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,114 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace sodoff.Migrations
{
/// <inheritdoc />
public partial class Buddies_CreateDate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Buddy_Vikings_BuddyVikingId",
table: "Buddy");
migrationBuilder.DropForeignKey(
name: "FK_Buddy_Vikings_VikingId",
table: "Buddy");
migrationBuilder.DropPrimaryKey(
name: "PK_Buddy",
table: "Buddy");
migrationBuilder.RenameTable(
name: "Buddy",
newName: "Buddies");
migrationBuilder.RenameIndex(
name: "IX_Buddy_BuddyVikingId",
table: "Buddies",
newName: "IX_Buddies_BuddyVikingId");
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Buddies",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddPrimaryKey(
name: "PK_Buddies",
table: "Buddies",
column: "VikingId");
migrationBuilder.AddForeignKey(
name: "FK_Buddies_Vikings_BuddyVikingId",
table: "Buddies",
column: "BuddyVikingId",
principalTable: "Vikings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Buddies_Vikings_VikingId",
table: "Buddies",
column: "VikingId",
principalTable: "Vikings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Buddies_Vikings_BuddyVikingId",
table: "Buddies");
migrationBuilder.DropForeignKey(
name: "FK_Buddies_Vikings_VikingId",
table: "Buddies");
migrationBuilder.DropPrimaryKey(
name: "PK_Buddies",
table: "Buddies");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Buddies");
migrationBuilder.RenameTable(
name: "Buddies",
newName: "Buddy");
migrationBuilder.RenameIndex(
name: "IX_Buddies_BuddyVikingId",
table: "Buddy",
newName: "IX_Buddy_BuddyVikingId");
migrationBuilder.AddPrimaryKey(
name: "PK_Buddy",
table: "Buddy",
column: "VikingId");
migrationBuilder.AddForeignKey(
name: "FK_Buddy_Vikings_BuddyVikingId",
table: "Buddy",
column: "BuddyVikingId",
principalTable: "Vikings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Buddy_Vikings_VikingId",
table: "Buddy",
column: "VikingId",
principalTable: "Vikings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace sodoff.Migrations
{
/// <inheritdoc />
public partial class Buddies_Id : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_Buddies",
table: "Buddies");
migrationBuilder.AddColumn<int>(
name: "Id",
table: "Buddies",
type: "INTEGER",
nullable: false,
defaultValue: 0)
.Annotation("Sqlite:Autoincrement", true);
migrationBuilder.AddPrimaryKey(
name: "PK_Buddies",
table: "Buddies",
column: "Id");
migrationBuilder.CreateIndex(
name: "IX_Buddies_VikingId",
table: "Buddies",
column: "VikingId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_Buddies",
table: "Buddies");
migrationBuilder.DropIndex(
name: "IX_Buddies_VikingId",
table: "Buddies");
migrationBuilder.DropColumn(
name: "Id",
table: "Buddies");
migrationBuilder.AddPrimaryKey(
name: "PK_Buddies",
table: "Buddies",
column: "VikingId");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace sodoff.Migrations
{
/// <inheritdoc />
public partial class Messaging_IsPrivate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsPrivate",
table: "Messages",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsPrivate",
table: "Messages");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace sodoff.Migrations
{
/// <inheritdoc />
public partial class Messaging_VikingIdOptional : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Messages_Vikings_VikingId",
table: "Messages");
migrationBuilder.AlterColumn<int>(
name: "VikingId",
table: "Messages",
type: "INTEGER",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER");
migrationBuilder.AddForeignKey(
name: "FK_Messages_Vikings_VikingId",
table: "Messages",
column: "VikingId",
principalTable: "Vikings",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Messages_Vikings_VikingId",
table: "Messages");
migrationBuilder.AlterColumn<int>(
name: "VikingId",
table: "Messages",
type: "INTEGER",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "INTEGER",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Messages_Vikings_VikingId",
table: "Messages",
column: "VikingId",
principalTable: "Vikings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace sodoff.Migrations
{
/// <inheritdoc />
public partial class Viking_BuddyCode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "BuddyCode",
table: "Vikings",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BuddyCode",
table: "Vikings");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace sodoff.Migrations
{
/// <inheritdoc />
public partial class Viking_Online : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Online",
table: "Vikings",
type: "INTEGER",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Online",
table: "Vikings");
}
}
}

View File

@ -70,6 +70,42 @@ namespace sodoff.Migrations
b.ToTable("AchievementTaskState");
});
modelBuilder.Entity("sodoff.Model.Buddy", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("BuddyStatus1")
.HasColumnType("INTEGER");
b.Property<int>("BuddyStatus2")
.HasColumnType("INTEGER");
b.Property<int>("BuddyVikingId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<bool>("IsBestFriend1")
.HasColumnType("INTEGER");
b.Property<bool>("IsBestFriend2")
.HasColumnType("INTEGER");
b.Property<int>("VikingId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("BuddyVikingId");
b.HasIndex("VikingId");
b.ToTable("Buddies");
});
modelBuilder.Entity("sodoff.Model.Dragon", b =>
{
b.Property<int>("Id")
@ -280,6 +316,9 @@ namespace sodoff.Migrations
b.Property<bool>("IsNew")
.HasColumnType("INTEGER");
b.Property<bool>("IsPrivate")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastUpdatedAt")
.HasColumnType("TEXT");
@ -307,7 +346,7 @@ namespace sodoff.Migrations
b.Property<int>("ToVikingId")
.HasColumnType("INTEGER");
b.Property<int>("VikingId")
b.Property<int?>("VikingId")
.HasColumnType("INTEGER");
b.HasKey("Id");
@ -778,6 +817,9 @@ namespace sodoff.Migrations
b.Property<DateTime?>("BirthDate")
.HasColumnType("TEXT");
b.Property<string>("BuddyCode")
.HasColumnType("TEXT");
b.Property<DateTime?>("CreationDate")
.HasColumnType("TEXT");
@ -791,6 +833,9 @@ namespace sodoff.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool?>("Online")
.HasColumnType("INTEGER");
b.Property<int?>("SelectedDragonId")
.HasColumnType("INTEGER");
@ -849,6 +894,25 @@ namespace sodoff.Migrations
b.Navigation("Viking");
});
modelBuilder.Entity("sodoff.Model.Buddy", b =>
{
b.HasOne("sodoff.Model.Viking", "BuddyViking")
.WithMany("BuddyList")
.HasForeignKey("BuddyVikingId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("sodoff.Model.Viking", "Viking")
.WithMany("BuddiesMade")
.HasForeignKey("VikingId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BuddyViking");
b.Navigation("Viking");
});
modelBuilder.Entity("sodoff.Model.Dragon", b =>
{
b.HasOne("sodoff.Model.Viking", "Viking")
@ -930,9 +994,7 @@ namespace sodoff.Migrations
b.HasOne("sodoff.Model.Viking", "Viking")
.WithMany("MessagesMade")
.HasForeignKey("VikingId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.HasForeignKey("VikingId");
b.Navigation("ParentMessage");
@ -1206,6 +1268,10 @@ namespace sodoff.Migrations
b.Navigation("AchievementTaskStates");
b.Navigation("BuddiesMade");
b.Navigation("BuddyList");
b.Navigation("Dragons");
b.Navigation("GameData");

25
src/Model/Buddy.cs Normal file
View File

@ -0,0 +1,25 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using sodoff.Schema;
namespace sodoff.Model;
public class Buddy
{
[Key]
public int Id { get; set; }
public int VikingId { get; set; }
public int BuddyVikingId { get; set; }
public BuddyStatus BuddyStatus1 { get; set; }
public BuddyStatus BuddyStatus2 { get; set; }
public bool IsBestFriend1 { get; set; }
public bool IsBestFriend2 { get; set; }
public DateTime CreatedAt { get; set; }
public virtual Viking? Viking { get; set; }
public virtual Viking? BuddyViking { get; set; }
}

View File

@ -32,6 +32,7 @@ public class DBContext : DbContext {
public DbSet<UserBadgeCompleteData> UserBadgesCompleted { get; set; } = null!;
public DbSet<UserBan> Bans { get; set; } = null!;
public DbSet<Message> Messages { get; set; } = null!;
public DbSet<Buddy> Buddies { get; set; } = null!;
private readonly IOptions<ApiServerConfig> config;
@ -167,6 +168,12 @@ public class DBContext : DbContext {
builder.Entity<Viking>().HasMany(v => v.MessagesMade)
.WithOne(r => r.Viking);
builder.Entity<Viking>().HasMany(v => v.BuddyList)
.WithOne(r => r.BuddyViking);
builder.Entity<Viking>().HasMany(v => v.BuddiesMade)
.WithOne(r => r.Viking);
// Dragons
builder.Entity<Dragon>().HasOne(d => d.Viking)
.WithMany(e => e.Dragons)
@ -321,7 +328,8 @@ public class DBContext : DbContext {
// Messages
builder.Entity<Message>().HasOne(r => r.Viking)
.WithMany(e => e.MessagesMade)
.HasForeignKey(e => e.VikingId);
.HasForeignKey(e => e.VikingId)
.IsRequired(false); // system messages usually don't have am author
builder.Entity<Message>().HasOne(r => r.ToViking)
.WithMany(e => e.MessageBoard)
@ -331,5 +339,14 @@ public class DBContext : DbContext {
.WithOne(e => e.ParentMessage)
.HasForeignKey(e => e.ParentMessageId)
.OnDelete(DeleteBehavior.Cascade);
// Buddies
builder.Entity<Buddy>().HasOne(r => r.BuddyViking)
.WithMany(e => e.BuddyList)
.HasForeignKey(e => e.BuddyVikingId);
builder.Entity<Buddy>().HasOne(r => r.Viking)
.WithMany(e => e.BuddiesMade)
.HasForeignKey(e => e.VikingId);
}
}

View File

@ -9,7 +9,7 @@ public class Message
[Key]
public int Id { get; set; }
public int VikingId { get; set; }
public int? VikingId { get; set; }
public int ToVikingId { get; set; }
public int QueueID { get; set; }
@ -29,6 +29,7 @@ public class Message
public bool IsDeleted { get; set; }
public bool IsNew { get; set; }
public bool IsPrivate { get; set; }
public virtual Viking? Viking { get; set; }
public virtual Viking? ToViking { get; set; }

View File

@ -19,8 +19,12 @@ public class Viking {
public string? AvatarSerialized { get; set; }
public string? BuddyCode { get; set; }
public int? SelectedDragonId { get; set; }
public bool? Online { get; set; }
public virtual ICollection<Session> Sessions { get; set; } = null!;
public virtual User User { get; set; } = null!;
public virtual ICollection<Dragon> Dragons { get; set; } = null!;
@ -47,6 +51,8 @@ public class Viking {
public virtual ICollection<UserBan> UserBans { get; set; } = null!;
public virtual ICollection<Message> MessageBoard { get; set; } = null!;
public virtual ICollection<Message> MessagesMade { get; set; } = null!;
public virtual ICollection<Buddy> BuddyList { get; set; } = null!;
public virtual ICollection<Buddy> BuddiesMade { get; set; } = null!;
public DateTime? CreationDate { get; set; }
public DateTime? BirthDate { get; set; }

View File

@ -42,6 +42,7 @@ builder.Services.AddScoped<ProfileService>();
builder.Services.AddScoped<NeighborhoodService>();
builder.Services.AddScoped<ModerationService>();
builder.Services.AddScoped<MessagingService>();
builder.Services.AddScoped<BuddyService>();
bool assetServer = builder.Configuration.GetSection("AssetServer").GetValue<bool>("Enabled");
string assetIP = builder.Configuration.GetSection("AssetServer").GetValue<string>("ListenIP");

View File

@ -0,0 +1,22 @@
using sodoff.Schema;
using System.Xml.Serialization;
namespace sodoff.Schema
{
[XmlRoot(ElementName = "BuddyActionResult", Namespace = "")]
[Serializable]
public class BuddyActionResult
{
// Token: 0x04000203 RID: 515
[XmlElement(ElementName = "Result")]
public BuddyActionResultType Result;
// Token: 0x04000204 RID: 516
[XmlElement(ElementName = "Status")]
public BuddyStatus Status;
// Token: 0x04000205 RID: 517
[XmlElement(ElementName = "BuddyUserID")]
public string BuddyUserID;
}
}

View File

@ -0,0 +1,30 @@
using System.Xml.Serialization;
namespace sodoff.Schema
{
[Flags]
public enum BuddyActionResultType
{
// Token: 0x04000217 RID: 535
[XmlEnum("0")]
Unknown = 0,
// Token: 0x04000218 RID: 536
[XmlEnum("1")]
Success = 1,
// Token: 0x04000219 RID: 537
[XmlEnum("2")]
BuddyListFull = 2,
// Token: 0x0400021A RID: 538
[XmlEnum("3")]
FriendBuddyListFull = 3,
// Token: 0x0400021B RID: 539
[XmlEnum("4")]
AlreadyInList = 4,
// Token: 0x0400021C RID: 540
[XmlEnum("5")]
InvalidFriendCode = 5,
// Token: 0x0400021D RID: 541
[XmlEnum("6")]
CannotAddSelf = 6
}
}

View File

@ -9,11 +9,13 @@ namespace sodoff.Services {
public class AchievementService {
private AchievementStoreSingleton achievementStore;
private InventoryService inventoryService;
private MessagingService messagingService;
public readonly DBContext ctx;
public AchievementService(AchievementStoreSingleton achievementStore, InventoryService inventoryService, DBContext ctx) {
public AchievementService(AchievementStoreSingleton achievementStore, InventoryService inventoryService, MessagingService messagingService, DBContext ctx) {
this.achievementStore = achievementStore;
this.inventoryService = inventoryService;
this.messagingService = messagingService;
this.ctx = ctx;
}

View File

@ -0,0 +1,181 @@
using System;
using System.Data;
using System.Security;
using sodoff.Model;
using sodoff.Schema;
using sodoff.Util;
namespace sodoff.Services;
public class BuddyService
{
private char[] BuddyCodeCharacterList = new char[]
{ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','1','2','3','4','5','6','7','8','9','0' };
private readonly DBContext ctx;
private readonly MessagingService messagingService;
public BuddyService(DBContext ctx, MessagingService messagingService)
{
this.ctx = ctx;
this.messagingService = messagingService;
}
public BuddyActionResult CreateBuddyRelation(Viking viking1, Viking viking2, BuddyStatus buddyStatus1 = BuddyStatus.PendingApprovalFromOther, BuddyStatus buddyStatus2 = BuddyStatus.PendingApprovalFromSelf)
{
// get execution UTC timestamp
DateTime now = DateTime.UtcNow;
// construct buddy
Model.Buddy buddy = new Model.Buddy
{
Viking = viking1,
VikingId = viking1.Id,
BuddyViking = viking2,
BuddyVikingId = viking2.Id,
BuddyStatus1 = buddyStatus1,
BuddyStatus2 = buddyStatus2,
IsBestFriend1 = false,
IsBestFriend2 = false,
CreatedAt = now
};
// do not add if viking1 is on a different game version than viking2
if (viking1.GameVersion < viking2.GameVersion) return new BuddyActionResult { Result = BuddyActionResultType.InvalidFriendCode };
// do not add if relationship already exists
Model.Buddy? existingBuddy = ctx.Buddies.FirstOrDefault(e => e.VikingId == buddy.VikingId && e.BuddyVikingId == buddy.BuddyVikingId);
if (existingBuddy != null) return new BuddyActionResult { Result = BuddyActionResultType.AlreadyInList };
// add relationship to database
ctx.Buddies.Add(buddy);
ctx.SaveChanges();
// if this is a buddy relationship, use MessagingService to send buddy request message to viking2
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!]]");
// return result
return new BuddyActionResult
{
Result = BuddyActionResultType.Success,
Status = buddy.BuddyStatus1,
BuddyUserID = viking2.Uid.ToString()
};
}
public bool UpdateBuddyRelation(Viking viking, Viking buddyViking, BuddyStatus buddyStatus1, BuddyStatus buddyStatus2)
{
// find relation
Model.Buddy? buddy = ctx.Buddies.Where(e => e.VikingId == viking.Id || e.BuddyVikingId == viking.Id)
.FirstOrDefault(e => e.BuddyVikingId == buddyViking.Id || e.VikingId == buddyViking.Id);
if (buddy != null)
{
// update it
buddy.BuddyStatus1 = buddyStatus1;
buddy.BuddyStatus2 = buddyStatus2;
ctx.SaveChanges();
// return result
return true;
} else return false;
}
public bool UpdateBestBuddy(Viking viking, Viking buddyViking, bool bestBuddy)
{
// find relation
Model.Buddy? buddy = ctx.Buddies.Where(e => e.VikingId == viking.Id || e.BuddyVikingId == viking.Id)
.FirstOrDefault(e => e.BuddyVikingId == buddyViking.Id || e.VikingId == buddyViking.Id);
if (buddy != null)
{
if (buddy.VikingId == viking.Id)
buddy.IsBestFriend2 = bestBuddy;
else buddy.IsBestFriend1 = bestBuddy;
ctx.SaveChanges();
return true;
} else return false;
}
public string GetOrSetBuddyCode(Viking viking)
{
if (viking.BuddyCode != null) return viking.BuddyCode;
else
{
// generate a buddy code and set it
Random rnd = new Random();
string code = "";
do
{
for( int i = 0; i < 5; i++ )
{
code += BuddyCodeCharacterList[rnd.Next(0, BuddyCodeCharacterList.Length)];
}
} while (ctx.Vikings.FirstOrDefault(e => e.BuddyCode == code) != null);
// set code on viking
viking.BuddyCode = code;
ctx.SaveChanges();
// return it
return viking.BuddyCode;
}
}
public bool RemoveBuddy(Viking viking, Viking buddyViking)
{
// find relation
Model.Buddy? buddy = ctx.Buddies.Where(e => e.VikingId == viking.Id || e.BuddyVikingId == viking.Id)
.FirstOrDefault(e => e.BuddyVikingId == buddyViking.Id || e.VikingId == buddyViking.Id);
if (buddy != null)
{
// remove it
ctx.Buddies.Remove(buddy);
ctx.SaveChanges();
return true;
}
else return false;
}
public BuddyList ConstructBuddyList(Viking viking)
{
// get all relationships viking has made
List<Model.Buddy> buddies = ctx.Buddies.Where(e => e.VikingId == viking.Id || e.BuddyVikingId == viking.Id)
.ToList();
List<Schema.Buddy> schemaBuddies = new();
foreach (var buddy in buddies)
{
// show differently depending on requester
if (buddy.VikingId == viking.Id)
schemaBuddies.Add(new Schema.Buddy
{
UserID = buddy.BuddyViking.Uid.ToString(),
DisplayName = XmlUtil.DeserializeXml<AvatarData>(buddy.BuddyViking.AvatarSerialized).DisplayName,
Status = buddy.BuddyStatus2,
CreateDate = buddy.CreatedAt,
Online = buddy.BuddyViking.Online ?? false,
OnMobile = false,
BestBuddy = buddy.IsBestFriend2
});
else
schemaBuddies.Add(new Schema.Buddy
{
UserID = buddy.Viking.Uid.ToString(),
DisplayName = XmlUtil.DeserializeXml<AvatarData>(buddy.Viking.AvatarSerialized).DisplayName,
Status = buddy.BuddyStatus1,
CreateDate = buddy.CreatedAt,
Online = buddy.Viking.Online ?? false,
OnMobile = false,
BestBuddy = buddy.IsBestFriend1
});
}
// return buddy list
return new BuddyList { Buddy = schemaBuddies.ToArray() };
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Security.Cryptography.X509Certificates;
using Microsoft.EntityFrameworkCore;
using sodoff.Model;
using sodoff.Schema;
@ -15,7 +16,7 @@ public class MessagingService
this.ctx = ctx;
}
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, 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)
{
// get execution UTC timestamp
DateTime now = DateTime.UtcNow;
@ -26,8 +27,6 @@ public class MessagingService
// construct message
Model.Message message = new Model.Message
{
Viking = viking,
VikingId = viking.Id,
ToViking = toViking,
ToVikingId = toViking.Id,
QueueID = rnd.Next(1000, 9999),
@ -41,10 +40,21 @@ public class MessagingService
CreatedAt = now,
LastUpdatedAt = now,
IsDeleted = IsDeleted,
IsNew = IsNew
IsNew = IsNew,
IsPrivate = isPrivate
};
if (isReply)
if (viking == null)
{
message.Viking = null;
message.VikingId = null;
} else
{
message.Viking = viking;
message.VikingId = viking.Id;
}
if (isReply && parentMessageId > 0)
{
// get message this is in reply to
Model.Message? messageToReplyTo = ctx.Messages.FirstOrDefault(e => e.Id == parentMessageId);
@ -54,6 +64,9 @@ public class MessagingService
message.ParentMessage = messageToReplyTo;
message.ParentMessageId = messageToReplyTo.Id;
message.ConversationID = messageToReplyTo.ConversationID;
// add a message to the replier's board saying a thread update has occured (if reply isn't to self)
if (message.VikingId != message.ToVikingId) AddMessageToViking(message.Viking, message.ToViking, MessageType.Data, MessageTypeID.ThreadUpdate, MessageLevel.WhiteList, "[[Line1]]=[[{{BuddyUserName}} Replied To Your Message In {{BuddyUserName}}'s Message Board!]]", "[[Line1]]=[[{{BuddyUserName}} Replied To Your Message In {{BuddyUserName}}'s Message Board!]]", "[[Line1]]=[[{{BuddyUserName}} Replied To Your Message In {{BuddyUserName}}'s Message Board!]]", isPrivate: true);
} else throw new InvalidOperationException("Tried To Reply To A Message That Doesn't Exist");
}
@ -93,11 +106,13 @@ public class MessagingService
message.IsDeleted = IsDeleted;
message.LastUpdatedAt = now;
ctx.SaveChanges();
return message;
} else return null;
}
public ArrayOfCombinedListMessage ConstructCombinedMessageArray(Viking viking)
public ArrayOfCombinedListMessage ConstructCombinedMessageArray(Viking viking, Viking publicViking)
{
// get all messages in viking board
List<Model.Message> messages = ctx.Messages.Where(e => e.ToVikingId == viking.Id).ToList();
@ -113,6 +128,8 @@ public class MessagingService
continue;
}
if (message.IsPrivate && (viking.Id != publicViking.Id) || message.VikingId == -1) continue;
Viking? msgAuthor = ctx.Vikings.FirstOrDefault(e => e.Id == message.VikingId) ?? new Viking();
// construct a CombinedListMessage based on Model.Message
@ -130,7 +147,7 @@ public class MessagingService
MessageID = message.Id,
ConversationID = message.ConversationID ?? 0,
ReplyToMessageID = message.ParentMessageId,
Creator = msgAuthor.Uid.ToString() ?? "Ghost",
Creator = msgAuthor.Uid.ToString() ?? new Guid().ToString(),
CreateTime = message.CreatedAt,
UpdateDate = message.LastUpdatedAt,
MessageType = message.MessageType.Value,
@ -160,7 +177,7 @@ public class MessagingService
UserMessageQueueID = message.QueueID,
MessageID = message.Id,
MessageTypeID = (int?)message.MessageTypeID,
FromUserID = msgAuthor.Uid.ToString() ?? "NotFound",
FromUserID = msgAuthor.Uid.ToString() ?? new Guid().ToString(),
MemberMessage = message.MemberMessage ?? "NoData",
NonMemberMessage = message.NonMemberMessage ?? "NoData",
Data = data
@ -173,7 +190,7 @@ public class MessagingService
UserMessageQueueID = message.QueueID,
MessageID = message.Id,
MessageTypeID = (int?)message.MessageTypeID,
FromUserID = msgAuthor.Uid.ToString() ?? "NotFound",
FromUserID = msgAuthor.Uid.ToString() ?? new Guid().ToString(),
MemberMessage = message.MemberMessage ?? "NoData",
NonMemberMessage = message.NonMemberMessage ?? "NoData",
Data = message.Data
@ -196,7 +213,7 @@ public class MessagingService
MessageID = reply.Id,
ConversationID = reply.ConversationID ?? 0,
ReplyToMessageID = reply.ParentMessageId,
Creator = replyAuthor.Uid.ToString() ?? "Ghost",
Creator = replyAuthor.Uid.ToString() ?? new Guid().ToString(),
CreateTime = reply.CreatedAt,
UpdateDate = reply.LastUpdatedAt,
MessageType = reply.MessageType.Value,
@ -242,20 +259,27 @@ public class MessagingService
{
Viking? msgAuthor = ctx.Vikings.FirstOrDefault(e => e.Id == message.VikingId) ?? new Viking();
if(!showOldMessages && message.IsNew && DateTime.Compare(message.CreatedAt.AddMinutes(30), now) > 0 || message.IsDeleted) continue; // sometimes clients won't set IsNew flag when updating messages, so do not add messages more than 30 minutes old to response
if(message.IsDeleted && !showDeletedMessages) { ctx.Messages.Remove(message); continue; }
if(DateTime.Compare(now, message.CreatedAt.AddMinutes(30)) > 0 && !showOldMessages) { message.IsNew = false; continue; } // sometimes clients won't set IsNew flag when updating messages, so do not add messages more than 30 minutes old to response
if(!message.IsNew && !showOldMessages) continue;
messageInfos.Add(new MessageInfo
MessageInfo messageInfo = new MessageInfo
{
MessageID = message.Id,
UserMessageQueueID = message.QueueID,
FromUserID = msgAuthor.Uid.ToString() ?? "NotFound",
FromUserID = msgAuthor.Uid.ToString() ?? new Guid().ToString(),
MessageTypeID = (int?)message.MessageTypeID,
Data = message.Data ?? "NoData",
MemberMessage = message.MemberMessage ?? "NoMessage",
NonMemberMessage = message.NonMemberMessage ?? "NoMessage"
});
};
messageInfos.Add(messageInfo);
}
// save any database changes
ctx.SaveChanges();
// add list as array to response
response.MessageInfo = messageInfos.ToArray();