Implemented neighborhoods, fixed default houses

Also:
* implemented ``GetAvatarByUserID`` from @Moonbase's previous PR.
* implemented ``GetPeriodicGameDataByGame`` and ``GetGamePlayDataForDateRange``

---------

Co-authored-by: Alan Moon <alanmoonbase2004@gmail.com>
This commit is contained in:
meleestars 2024-07-10 10:09:20 +02:00 committed by Robert Paciorek
parent 2ec4358a39
commit 9993198a9b
13 changed files with 410 additions and 10 deletions

View File

@ -23,10 +23,23 @@ public class ContentController : Controller {
private InventoryService inventoryService; private InventoryService inventoryService;
private GameDataService gameDataService; private GameDataService gameDataService;
private DisplayNamesService displayNamesService; private DisplayNamesService displayNamesService;
private NeighborhoodService neighborhoodService;
private Random random = new Random(); private Random random = new Random();
private readonly IOptions<ApiServerConfig> config; private readonly IOptions<ApiServerConfig> config;
public ContentController(DBContext ctx, KeyValueService keyValueService, ItemService itemService, MissionService missionService, RoomService roomService, AchievementService achievementService, InventoryService inventoryService, GameDataService gameDataService, DisplayNamesService displayNamesService, IOptions<ApiServerConfig> config) { public ContentController(
DBContext ctx,
KeyValueService keyValueService,
ItemService itemService,
MissionService missionService,
RoomService roomService,
AchievementService achievementService,
InventoryService inventoryService,
GameDataService gameDataService,
DisplayNamesService displayNamesService,
NeighborhoodService neighborhoodService,
IOptions<ApiServerConfig> config
) {
this.ctx = ctx; this.ctx = ctx;
this.keyValueService = keyValueService; this.keyValueService = keyValueService;
this.itemService = itemService; this.itemService = itemService;
@ -36,6 +49,7 @@ public class ContentController : Controller {
this.inventoryService = inventoryService; this.inventoryService = inventoryService;
this.gameDataService = gameDataService; this.gameDataService = gameDataService;
this.displayNamesService = displayNamesService; this.displayNamesService = displayNamesService;
this.neighborhoodService = neighborhoodService;
this.config = config; this.config = config;
} }
@ -426,6 +440,20 @@ public class ContentController : Controller {
return Ok(avatarData); return Ok(avatarData);
} }
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetAvatarByUserID")] // used by World Of Jumpstart, only for public information
public IActionResult GetAvatarByUserId([FromForm] Guid userId)
{
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
AvatarData avatarData = XmlUtil.DeserializeXml<AvatarData>(viking.AvatarSerialized);
avatarData.Id = viking.Id;
if (viking != null && avatarData != null) return Ok(avatarData);
else return Ok(new AvatarData());
}
[HttpPost] [HttpPost]
[Produces("application/xml")] [Produces("application/xml")]
[Route("ContentWebService.asmx/SetAvatar")] // used by World Of Jumpstart [Route("ContentWebService.asmx/SetAvatar")] // used by World Of Jumpstart
@ -1598,7 +1626,7 @@ public class ContentController : Controller {
); );
if (ret != null) if (ret != null)
return Ok(ret); return Ok(ret);
return Ok(""); return Ok(XmlUtil.ReadResourceXmlString("defaulthouse"));
} }
[HttpPost] [HttpPost]
@ -1658,6 +1686,21 @@ public class ContentController : Controller {
return Ok(true); return Ok(true);
} }
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/SetNeighbor")] // used by World Of Jumpstart
[VikingSession(UseLock=true)]
public IActionResult SetNeighbor(Viking viking, string neighboruserid, int slot) {
return Ok(neighborhoodService.SaveNeighbors(viking, neighboruserid, slot));
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetNeighborsByUserID")] // used by World Of Jumpstart
public IActionResult GetNeighborsByUserID(string userId) {
return Ok(neighborhoodService.GetNeighbors(userId));
}
[HttpPost] [HttpPost]
[Produces("application/xml")] [Produces("application/xml")]
[Route("V2/ContentWebService.asmx/GetGameData")] [Route("V2/ContentWebService.asmx/GetGameData")]
@ -2096,10 +2139,18 @@ public class ContentController : Controller {
[HttpPost] [HttpPost]
[Produces("application/xml")] [Produces("application/xml")]
[Route("ContentWebService.asmx/GetPeriodicGameDataByGame")] // used by Math Blaster [Route("ContentWebService.asmx/GetPeriodicGameDataByGame")] // used by Math Blaster and WoJS (probably from 24 hours ago to now)
public IActionResult GetPeriodicGameDataByGame() { [VikingSession(UseLock = true)]
// TODO: This is a placeholder public IActionResult GetPeriodicGameDataByGame(Viking viking, [FromForm] int gameId, bool isMultiplayer, int difficulty, int gameLevel, string key, int count, bool AscendingOrder, int score, bool buddyFilter, string apiKey) {
return Ok(new GameDataSummary()); return Ok(gameDataService.GetGameData(viking, gameId, isMultiplayer, difficulty, gameLevel, key, count, AscendingOrder, buddyFilter, apiKey, DateTime.Now.AddHours(-24), DateTime.Now));
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetGamePlayDataForDateRange")] // used by WoJS
public IActionResult GetGamePlayDataForDateRange(Viking viking, string startDate, string endDate) {
// stub, didn't work for some reason, even with the correct response
return Ok(new ArrayOfGamePlayData());
} }
[HttpPost] [HttpPost]

View File

@ -8,13 +8,14 @@ namespace sodoff.Controllers.Common;
public class RatingController : Controller public class RatingController : Controller
{ {
[HttpPost] [HttpPost]
[Produces("application/xml")] [Produces("application/xml")]
[Route("MissionWebService.asmx/GetPayout")] // used by World Of Jumpstart [Route("MissionWebService.asmx/GetPayout")] // used by World Of Jumpstart
public IActionResult GetPayout([FromForm] int points) { public IActionResult GetPayout([FromForm] int points, [FromForm] string ModuleName) {
// TODO - placeholder // TODO: better calculations, improve module determination code
return Ok(points / 100); // for now, a trusty placeholder
return Ok(points / (350 / 3));
} }
[HttpPost] [HttpPost]

View File

@ -23,6 +23,9 @@ public class DBContext : DbContext {
public DbSet<ProfileAnswer> ProfileAnswers { get; set; } = null!; public DbSet<ProfileAnswer> ProfileAnswers { get; set; } = null!;
public DbSet<MMORole> MMORoles { get; set; } = null!; public DbSet<MMORole> MMORoles { get; set; } = null!;
public DbSet<Party> Parties { get; set; } = null!; public DbSet<Party> Parties { get; set; } = null!;
public DbSet<Neighborhood> Neighborhoods { get; set; } = null!;
// we had a brief debate on whether it's neighborhoods or neighborheed
private readonly IOptions<ApiServerConfig> config; private readonly IOptions<ApiServerConfig> config;
public DBContext(IOptions<ApiServerConfig> config) { public DBContext(IOptions<ApiServerConfig> config) {
@ -130,6 +133,9 @@ public class DBContext : DbContext {
builder.Entity<Viking>().HasMany(v => v.MMORoles) builder.Entity<Viking>().HasMany(v => v.MMORoles)
.WithOne(e => e.Viking); .WithOne(e => e.Viking);
builder.Entity<Viking>().HasOne(v => v.Neighborhood)
.WithOne(e => e.Viking);
// Dragons // Dragons
builder.Entity<Dragon>().HasOne(d => d.Viking) builder.Entity<Dragon>().HasOne(d => d.Viking)
.WithMany(e => e.Dragons) .WithMany(e => e.Dragons)
@ -241,5 +247,10 @@ public class DBContext : DbContext {
builder.Entity<MMORole>().HasOne(r => r.Viking) builder.Entity<MMORole>().HasOne(r => r.Viking)
.WithMany(e => e.MMORoles) .WithMany(e => e.MMORoles)
.HasForeignKey(e => e.VikingId); .HasForeignKey(e => e.VikingId);
// Neighborhoods
builder.Entity<Neighborhood>().HasOne(r => r.Viking)
.WithOne(e => e.Neighborhood)
.HasForeignKey<Neighborhood>(e => e.VikingId);
} }
} }

33
src/Model/Neighborhood.cs Normal file
View File

@ -0,0 +1,33 @@
using sodoff.Schema;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Diagnostics.CodeAnalysis;
namespace sodoff.Model
{
public class Neighborhood
{
public virtual Viking? Viking { get; set; }
[Key]
public int Id { get; set; }
[Required]
public int VikingId { get; set; }
[Required]
public Guid Slot0 { get; set; }
[Required]
public Guid Slot1 { get; set; }
[Required]
public Guid Slot2 { get; set; }
[Required]
public Guid Slot3 { get; set; }
[Required]
public Guid Slot4 { get; set; }
}
}

View File

@ -37,6 +37,7 @@ public class Viking {
public virtual ICollection<SavedData> SavedData { get; set; } = null!; public virtual ICollection<SavedData> SavedData { get; set; } = null!;
public virtual ICollection<Party> Parties { get; set; } = null!; public virtual ICollection<Party> Parties { get; set; } = null!;
public virtual ICollection<MMORole> MMORoles { get; set; } = null!; public virtual ICollection<MMORole> MMORoles { get; set; } = null!;
public virtual Neighborhood? Neighborhood { get; set; } = null!;
public virtual Dragon? SelectedDragon { get; set; } public virtual Dragon? SelectedDragon { get; set; }
public DateTime? CreationDate { get; set; } public DateTime? CreationDate { get; set; }

View File

@ -38,6 +38,7 @@ builder.Services.AddScoped<InventoryService>();
builder.Services.AddScoped<AchievementService>(); builder.Services.AddScoped<AchievementService>();
builder.Services.AddScoped<GameDataService>(); builder.Services.AddScoped<GameDataService>();
builder.Services.AddScoped<ProfileService>(); builder.Services.AddScoped<ProfileService>();
builder.Services.AddScoped<NeighborhoodService>();
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");

View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<HouseData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Part>
<PartTypes>Foundation_GEO</PartTypes>
<PartNames>RS_SHARED/MyHouseFoundations01.unity3d/PfFoundationCottage</PartNames>
</Part>
<Part>
<PartTypes>Foundation_STL</PartTypes>
<PartNames>RS_SHARED/MyHouseFoundations01.unity3d/StyleFoundationsCottageLogTex</PartNames>
</Part>
<Part>
<PartTypes>Foundation_SKN</PartTypes>
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
</Part>
<Part>
<PartTypes>Foundation_GLS</PartTypes>
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
</Part>
<Part>
<PartTypes>Foundation_TRM</PartTypes>
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
</Part>
<Part>
<PartTypes>Stairs_GEO</PartTypes>
<PartNames>RS_SHARED/MyHouseStairs01.unity3d/PfStairCottage</PartNames>
</Part>
<Part>
<PartTypes>Stairs_STL</PartTypes>
<PartNames>RS_SHARED/MyHouseStairs01.unity3d/StyleStairsCottageLogTex</PartNames>
</Part>
<Part>
<PartTypes>Stairs_SKN</PartTypes>
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
</Part>
<Part>
<PartTypes>Stairs_GLS</PartTypes>
<PartNames>EMPTY</PartNames>
</Part>
<Part>
<PartTypes>Stairs_TRM</PartTypes>
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
</Part>
<Part>
<PartTypes>Wall_GEO</PartTypes>
<PartNames>RS_SHARED/MyHouseWalls01.unity3d/PfWallCottage</PartNames>
</Part>
<Part>
<PartTypes>Wall_STL</PartTypes>
<PartNames>RS_SHARED/MyHouseWalls01.unity3d/StyleWallsCottageLogTex</PartNames>
</Part>
<Part>
<PartTypes>Wall_SKN</PartTypes>
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
</Part>
<Part>
<PartTypes>Wall_GLS</PartTypes>
<PartNames>EMPTY</PartNames>
</Part>
<Part>
<PartTypes>Wall_TRM</PartTypes>
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
</Part>
<Part>
<PartTypes>Roof_GEO</PartTypes>
<PartNames>RS_SHARED/MyHouseRoofs01.unity3d/PfRoofCottage</PartNames>
</Part>
<Part>
<PartTypes>Roof_STL</PartTypes>
<PartNames>RS_SHARED/MyHouseRoofs01.unity3d/StyleRoofsCottageHauntedTex</PartNames>
</Part>
<Part>
<PartTypes>Roof_SKN</PartTypes>
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
</Part>
<Part>
<PartTypes>Roof_GLS</PartTypes>
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
</Part>
<Part>
<PartTypes>Roof_TRM</PartTypes>
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
</Part>
<Part>
<PartTypes>Window_GEO</PartTypes>
<PartNames>RS_SHARED/MyHouseWindows01.unity3d/PfWindowCottage</PartNames>
</Part>
<Part>
<PartTypes>Window_STL</PartTypes>
<PartNames>RS_SHARED/MyHouseWindows01.unity3d/StyleWindowsCottageLogTex</PartNames>
</Part>
<Part>
<PartTypes>Window_SKN</PartTypes>
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
</Part>
<Part>
<PartTypes>Window_GLS</PartTypes>
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
</Part>
<Part>
<PartTypes>Window_TRM</PartTypes>
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
</Part>
<Part>
<PartTypes>Door_GEO</PartTypes>
<PartNames>RS_SHARED/MyHouseDoors01.unity3d/PfDoorCottage</PartNames>
</Part>
<Part>
<PartTypes>Door_STL</PartTypes>
<PartNames>RS_SHARED/MyHouseDoors01.unity3d/StyleDoorsCottageLogTex</PartNames>
</Part>
<Part>
<PartTypes>Door_SKN</PartTypes>
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
</Part>
<Part>
<PartTypes>Door_GLS</PartTypes>
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
</Part>
<Part>
<PartTypes>Door_TRM</PartTypes>
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
</Part>
<Part>
<PartTypes>Chimney_GEO</PartTypes>
<PartNames>RS_SHARED/MyHouseChimneys01.unity3d/PfChimneyCottage</PartNames>
</Part>
<Part>
<PartTypes>Chimney_STL</PartTypes>
<PartNames>RS_SHARED/MyHouseChimneys01.unity3d/StyleChimneysCottageHauntedTex</PartNames>
</Part>
<Part>
<PartTypes>Chimney_SKN</PartTypes>
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
</Part>
<Part>
<PartTypes>Chimney_GLS</PartTypes>
<PartNames>EMPTY</PartNames>
</Part>
<Part>
<PartTypes>Chimney_TRM</PartTypes>
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
</Part>
<Part>
<PartTypes>DEFAULT_GLASS</PartTypes>
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
</Part>
<Part>
<PartTypes>DEFAULT_TRIM</PartTypes>
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
</Part>
</HouseData>

View File

@ -0,0 +1,11 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "ArrayOfGamePlayData", Namespace = "")]
[Serializable]
public class ArrayOfGamePlayData
{
[XmlElement(ElementName = "GamePlayData", IsNullable = true)]
public GamePlayData[]? GamePlayData;
}

View File

@ -0,0 +1,14 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "GamePlayData", Namespace = "")]
[Serializable]
public class GamePlayData
{
[XmlElement(ElementName = "GMID")] // Game ID
public int GMID;
[XmlElement(ElementName = "PLCT")] // presumably Player Count
public int PLCT;
}

14
src/Schema/Neighbor.cs Normal file
View File

@ -0,0 +1,14 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "Neighbor", Namespace = "")]
[Serializable]
public class Neighbor
{
[XmlElement(ElementName = "NeighborUserID")]
public Guid NeighborUserID;
[XmlElement(ElementName = "Slot")]
public int Slot;
}

View File

@ -0,0 +1,14 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "NeighborData", Namespace = "")]
[Serializable]
public class NeighborData
{
[XmlElement(ElementName = "UserID")]
public Guid UserID;
[XmlElement(ElementName = "Neighbors")]
public Neighbor[] Neighbors;
}

View File

@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using sodoff.Configuration;
using sodoff.Model;
using sodoff.Schema;
using sodoff.Util;
namespace sodoff.Services
{
public class NeighborhoodService
{
private readonly DBContext ctx;
// default neighborhood slots (NPCs)
Guid slot0 = new Guid("aaaaaaaa-0000-0000-0000-000000000000");
Guid slot1 = new Guid("bbbbbbbb-0000-0000-0000-000000000000");
Guid slot2 = new Guid("cccccccc-0000-0000-0000-000000000000");
Guid slot3 = new Guid("dddddddd-0000-0000-0000-000000000000");
Guid slot4 = new Guid("eeeeeeee-0000-0000-0000-000000000000");
public NeighborhoodService(DBContext ctx) {
this.ctx = ctx;
}
public bool SaveNeighbors(Viking viking, string neighborUid, int slot) {
Model.Neighborhood? neighborhood = viking.Neighborhood;
if (neighborhood == null) // if viking has no neighborhood yet, create a default one
viking.Neighborhood = new Model.Neighborhood {
VikingId = viking.Id,
Slot0 = this.slot0,
Slot1 = this.slot1,
Slot2 = this.slot2,
Slot3 = this.slot3,
Slot4 = this.slot4
};
// couldn't find a better way to do this
switch (slot) {
case 0:
viking.Neighborhood.Slot0 = new Guid(neighborUid);
break;
case 1:
viking.Neighborhood.Slot1 = new Guid(neighborUid);
break;
case 2:
viking.Neighborhood.Slot2 = new Guid(neighborUid);
break;
case 3:
viking.Neighborhood.Slot3 = new Guid(neighborUid);
break;
case 4:
viking.Neighborhood.Slot4 = new Guid(neighborUid);
break;
}
ctx.SaveChanges();
return true;
}
public NeighborData GetNeighbors(string userId) {
Model.Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == new Guid(userId));
bool isNull = viking == null || viking.Neighborhood == null;
Neighbor[] neighbors = {
new Neighbor {
NeighborUserID = isNull ? this.slot0 : viking.Neighborhood.Slot0,
Slot = 0
},
new Neighbor {
NeighborUserID = isNull ? this.slot1 : viking.Neighborhood.Slot1,
Slot = 1
},
new Neighbor {
NeighborUserID = isNull ? this.slot2 : viking.Neighborhood.Slot2,
Slot = 2
},
new Neighbor {
NeighborUserID = isNull ? this.slot3 : viking.Neighborhood.Slot3,
Slot = 3
},
new Neighbor {
NeighborUserID = isNull ? this.slot4 : viking.Neighborhood.Slot4,
Slot = 4
}
};
return new NeighborData {
UserID = new Guid(userId),
Neighbors = neighbors
};
}
}
}

View File

@ -59,6 +59,7 @@
<None Remove="Resources\content_arcade.xml" /> <None Remove="Resources\content_arcade.xml" />
<None Remove="Resources\content_learning.xml" /> <None Remove="Resources\content_learning.xml" />
<None Remove="Resources\profiletags.xml" /> <None Remove="Resources\profiletags.xml" />
<None Remove="Resources\defaulthouse.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="Resources\childlist.xml"> <None Update="Resources\childlist.xml">
@ -155,6 +156,9 @@
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Resources\profiletags.xml"> <EmbeddedResource Include="Resources\profiletags.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\defaulthouse.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
</Project> </Project>