diff --git a/CannedNet/CannedNet.csproj b/CannedNet/CannedNet.csproj
index 6afdecd..80b6515 100644
--- a/CannedNet/CannedNet.csproj
+++ b/CannedNet/CannedNet.csproj
@@ -5,6 +5,7 @@
enable
enable
CannedNet
+ true
diff --git a/CannedNet/Data/AppDbContext.cs b/CannedNet/Data/AppDbContext.cs
index 527b25a..27d7e31 100644
--- a/CannedNet/Data/AppDbContext.cs
+++ b/CannedNet/Data/AppDbContext.cs
@@ -14,6 +14,7 @@ public class AppDbContext : DbContext
public DbSet PlayerSettings { get; set; }
public DbSet AvatarItems { get; set; }
public DbSet PlayerAvatars { get; set; }
+ public DbSet SavedOutfits { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -80,6 +81,16 @@ public class AppDbContext : DbContext
entity.HasIndex(e => e.OwnerAccountId).IsUnique();
entity.ToTable("player_avatars");
});
+
+ // saved_outfits
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.Id);
+ entity.Property(e => e.Id).ValueGeneratedOnAdd();
+ entity.Property(e => e.OwnerAccountId).IsRequired();
+ entity.HasIndex(e => e.OwnerAccountId).IsUnique();
+ entity.ToTable("saved_outfits");
+ });
}
}
diff --git a/CannedNet/Migrations/20260401141417_AddSavedOutfits.Designer.cs b/CannedNet/Migrations/20260401141417_AddSavedOutfits.Designer.cs
new file mode 100644
index 0000000..c967875
--- /dev/null
+++ b/CannedNet/Migrations/20260401141417_AddSavedOutfits.Designer.cs
@@ -0,0 +1,302 @@
+//
+using System;
+using CannedNet.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace CannedNet.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20260401141417_AddSavedOutfits")]
+ partial class AddSavedOutfits
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("CannedNet.Account", b =>
+ {
+ b.Property("AccountId")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "accountId");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasAnnotation("Relational:JsonPropertyName", "createdAt");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "displayName");
+
+ b.Property("IdentityFlags")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "identityFlags");
+
+ b.Property("IsJunior")
+ .HasColumnType("boolean")
+ .HasAnnotation("Relational:JsonPropertyName", "isJunior");
+
+ b.Property("PersonalPronouns")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "personalPronouns");
+
+ b.Property("Platforms")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "platforms");
+
+ b.Property("ProfileImage")
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "profileImage");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "username");
+
+ b.HasKey("AccountId");
+
+ b.ToTable("accounts", (string)null);
+ });
+
+ modelBuilder.Entity("CannedNet.AvatarItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AvatarItemDesc")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "avatarItemDesc");
+
+ b.Property("AvatarItemType")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "avatarItemType");
+
+ b.Property("FriendlyName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "friendlyName");
+
+ b.Property("OwnerAccountId")
+ .HasColumnType("integer");
+
+ b.Property("PlatformMask")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "platformMask");
+
+ b.Property("Rarity")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "rarity");
+
+ b.Property("Tooltip")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "tooltip");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerAccountId");
+
+ b.ToTable("avatar_items", (string)null);
+ });
+
+ modelBuilder.Entity("CannedNet.CachedLogin", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AccountId")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "accountId");
+
+ b.Property("LastLoginTime")
+ .HasColumnType("timestamp with time zone")
+ .HasAnnotation("Relational:JsonPropertyName", "lastLoginTime");
+
+ b.Property("Platform")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "platform");
+
+ b.Property("PlatformID")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "platformId");
+
+ b.Property("RequirePassword")
+ .HasColumnType("boolean")
+ .HasAnnotation("Relational:JsonPropertyName", "requirePassword");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Platform", "PlatformID")
+ .IsUnique();
+
+ b.ToTable("cached_logins", (string)null);
+ });
+
+ modelBuilder.Entity("CannedNet.PlayerAvatar", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FaceFeatures")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "FaceFeatures");
+
+ b.Property("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "HairColor");
+
+ b.Property("OutfitSelections")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "OutfitSelections");
+
+ b.Property("OwnerAccountId")
+ .HasColumnType("integer");
+
+ b.Property("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "SkinColor");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerAccountId")
+ .IsUnique();
+
+ b.ToTable("player_avatars", (string)null);
+ });
+
+ modelBuilder.Entity("CannedNet.PlayerProgression", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Level")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "level");
+
+ b.Property("PlayerId")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "playerId");
+
+ b.Property("Xp")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "xp");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PlayerId")
+ .IsUnique();
+
+ b.ToTable("player_progressions", (string)null);
+ });
+
+ modelBuilder.Entity("CannedNet.PlayerSetting", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "key");
+
+ b.Property("PlayerId")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "playerId");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "value");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PlayerId", "Key")
+ .IsUnique();
+
+ b.ToTable("player_settings", (string)null);
+ });
+
+ modelBuilder.Entity("CannedNet.SavedOutfit", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FaceFeatures")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "FaceFeatures");
+
+ b.Property("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "HairColor");
+
+ b.Property("OutfitSelections")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "OutfitSelections");
+
+ b.Property("OwnerAccountId")
+ .HasColumnType("integer");
+
+ b.Property("PreviewImageName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "PreviewImageName");
+
+ b.Property("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "SkinColor");
+
+ b.Property("Slot")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "Slot");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerAccountId")
+ .IsUnique();
+
+ b.ToTable("saved_outfits", (string)null);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/CannedNet/Migrations/20260401141417_AddSavedOutfits.cs b/CannedNet/Migrations/20260401141417_AddSavedOutfits.cs
new file mode 100644
index 0000000..df1883a
--- /dev/null
+++ b/CannedNet/Migrations/20260401141417_AddSavedOutfits.cs
@@ -0,0 +1,47 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace CannedNet.Migrations
+{
+ ///
+ public partial class AddSavedOutfits : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "saved_outfits",
+ columns: table => new
+ {
+ Id = table.Column(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ OwnerAccountId = table.Column(type: "integer", nullable: false),
+ Slot = table.Column(type: "integer", nullable: false),
+ PreviewImageName = table.Column(type: "text", nullable: false),
+ OutfitSelections = table.Column(type: "text", nullable: false),
+ HairColor = table.Column(type: "text", nullable: false),
+ SkinColor = table.Column(type: "text", nullable: false),
+ FaceFeatures = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_saved_outfits", x => x.Id);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_saved_outfits_OwnerAccountId",
+ table: "saved_outfits",
+ column: "OwnerAccountId",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "saved_outfits");
+ }
+ }
+}
diff --git a/CannedNet/Migrations/AppDbContextModelSnapshot.cs b/CannedNet/Migrations/AppDbContextModelSnapshot.cs
index 84d2215..ed3489a 100644
--- a/CannedNet/Migrations/AppDbContextModelSnapshot.cs
+++ b/CannedNet/Migrations/AppDbContextModelSnapshot.cs
@@ -245,6 +245,54 @@ namespace CannedNet.Migrations
b.ToTable("player_settings", (string)null);
});
+
+ modelBuilder.Entity("CannedNet.SavedOutfit", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FaceFeatures")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "FaceFeatures");
+
+ b.Property("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "HairColor");
+
+ b.Property("OutfitSelections")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "OutfitSelections");
+
+ b.Property("OwnerAccountId")
+ .HasColumnType("integer");
+
+ b.Property("PreviewImageName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "PreviewImageName");
+
+ b.Property("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasAnnotation("Relational:JsonPropertyName", "SkinColor");
+
+ b.Property("Slot")
+ .HasColumnType("integer")
+ .HasAnnotation("Relational:JsonPropertyName", "Slot");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerAccountId")
+ .IsUnique();
+
+ b.ToTable("saved_outfits", (string)null);
+ });
#pragma warning restore 612, 618
}
}
diff --git a/CannedNet/Models/SavedOutfit.cs b/CannedNet/Models/SavedOutfit.cs
new file mode 100644
index 0000000..3d15e68
--- /dev/null
+++ b/CannedNet/Models/SavedOutfit.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace CannedNet;
+
+public class SavedOutfit
+{
+ public int Id { get; set; }
+ public int OwnerAccountId { get; set; }
+ [JsonPropertyName("Slot")]
+ public int Slot { get; set; }
+ [JsonPropertyName("PreviewImageName")]
+ public string PreviewImageName { get; set; }
+ [JsonPropertyName("OutfitSelections")]
+ public string OutfitSelections { get; set; }
+ [JsonPropertyName("HairColor")]
+ public string HairColor { get; set; }
+ [JsonPropertyName("SkinColor")]
+ public string SkinColor { get; set; }
+ [JsonPropertyName("FaceFeatures")]
+ public string FaceFeatures { get; set; }
+}
\ No newline at end of file
diff --git a/CannedNet/Services/APIService.cs b/CannedNet/Services/APIService.cs
index 7afb38d..f5c6142 100644
--- a/CannedNet/Services/APIService.cs
+++ b/CannedNet/Services/APIService.cs
@@ -170,6 +170,31 @@ public class APIService
await db.SaveChangesAsync();
return Results.Ok(avatar);
});
+
+ app.MapGet("/api/avatar/v3/saved", async (HttpRequest request, AppDbContext db) =>
+ {
+ var authHeader = request.Headers.Authorization.ToString();
+
+ if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
+ return Results.Unauthorized();
+
+ var token = authHeader.Substring("Bearer ".Length);
+ var accountId = jwtService.ValidateAndGetAccountId(token);
+
+ if (string.IsNullOrEmpty(accountId))
+ return Results.Unauthorized();
+
+ if (!int.TryParse(accountId.AsSpan(), out var id))
+ return Results.Unauthorized();
+
+ request.EnableBuffering();
+
+ var items = await db.SavedOutfits
+ .Where(i => i.OwnerAccountId == id)
+ .ToListAsync();
+
+ return Results.Json(items);
+ });
app.MapGet("/api/PlayerReporting/v1/moderationBlockDetails", () =>
Results.Content("{\"ReportCategory\":0,\"Duration\":0,\"GameSessionId\":0,\"IsHostKick\":false,\"Message\":\"\",\"PlayerIdReporter\":null,\"IsBan\":false}", "application/json"));
@@ -245,5 +270,16 @@ public class APIService
await db.SaveChangesAsync();
return Results.Ok();
});
+
+ app.MapGet("/api/equipment/v2/getUnlocked", async (HttpRequest request, AppDbContext db) =>
+ {
+ // TODO ADD FUNCTIONALITY
+ return "[]";
+ });
+ app.MapGet("/api/consumables/v2/getUnlocked", async (HttpRequest request, AppDbContext db) =>
+ {
+ // TODO ADD FUNCTIONALITY
+ return "[]";
+ });
}
}
diff --git a/CannedNet/Services/MatchmakingService.cs b/CannedNet/Services/MatchmakingService.cs
index 6f3ad12..a6b7b6b 100644
--- a/CannedNet/Services/MatchmakingService.cs
+++ b/CannedNet/Services/MatchmakingService.cs
@@ -31,5 +31,34 @@ public class MatchmakingService
return Results.Json(accounts);
});
+
+ app.MapPost("/goto/room/{room}", (HttpRequest request, string room) =>
+ {
+ // dormroom location id : 76d98498-60a1-430c-ab76-b54a29b7a163
+
+ return Results.Json(new
+ {
+ errorCode = 0,
+ roomInstance = new
+ {
+ roomInstanceId = 1,
+ roomId = 1,
+ subRoomId = 1,
+ roomInstanceType = 2,
+ location = "76d98498-60a1-430c-ab76-b54a29b7a163",
+ dataBlob = "",
+ eventId = 0,
+ clubId=0,
+ photonRegionId = "us",
+ photonRoomId = "CannedNet_"+Guid.NewGuid(),
+ name = "DormRoom",
+ maxCapacity = 4,
+ isFull = false,
+ isPrivate = true,
+ isInProgress = false,
+ EncryptVoiceChat=false
+ }
+ });
+ });
}
}