initial support for XP points (#16)

* GetPetAchievementsByUserID and enum for PointTypes

* add RankService

* use AchievementPointTypes for PointTypeID

... insted of int in schema

* support for player XP, fix dragon XP

- database table
- return correct value in API call
- save XP from mission

* rename RankService to AchievementService

* use addAchievementPoints for all non item reward

this could be a good place for wallet servicing too ... so currency reward too

* return const XP value for farming and fishing

we don't have gathering method for those XPs yet

* fix avatar schema, fix coding style
This commit is contained in:
rpaciorek 2023-08-14 16:34:14 +00:00 committed by GitHub
parent fbc9b8a201
commit 406ebe20c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 207 additions and 55 deletions

View File

@ -4,22 +4,42 @@ using Microsoft.AspNetCore.Mvc;
using sodoff.Attributes;
using sodoff.Model;
using sodoff.Schema;
using sodoff.Services;
using sodoff.Util;
namespace sodoff.Controllers.Common;
public class AchievementController : Controller {
private readonly DBContext ctx;
public AchievementController(DBContext ctx) {
private AchievementService achievementService;
public AchievementController(DBContext ctx, AchievementService achievementService) {
this.ctx = ctx;
this.achievementService = achievementService;
}
[HttpPost]
//[Produces("application/xml")]
[Produces("application/xml")]
[Route("AchievementWebService.asmx/GetPetAchievementsByUserID")]
public IActionResult GetPetAchievementsByUserID() {
// TODO, this is a placeholder
return Ok("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ArrayOfUserAchievementInfo xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://api.jumpstart.com/\" />");
public IActionResult GetPetAchievementsByUserID([FromForm] string apiToken, [FromForm] string userId) {
// TODO: check session
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Id == userId);
if (viking is null) {
return null;
}
List<UserAchievementInfo> dragonsAchievement = new List<UserAchievementInfo>();
foreach (Dragon dragon in viking.Dragons) {
dragonsAchievement.Add(
achievementService.CreateUserAchievementInfo(dragon.EntityId, dragon.PetXP, AchievementPointTypes.DragonXP)
);
}
ArrayOfUserAchievementInfo arrAchievements = new ArrayOfUserAchievementInfo {
UserAchievementInfo = dragonsAchievement.ToArray()
};
return Ok(arrAchievements);
}
[HttpPost]
@ -50,27 +70,19 @@ public class AchievementController : Controller {
[Produces("application/xml")]
[Route("AchievementWebService.asmx/GetAchievementsByUserID")]
public IActionResult GetAchievementsByUserID([FromForm] string userId) {
// TODO: this is a placeholder
// TODO: check session
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Id == userId);
if (viking is null) {
return null;
}
ArrayOfUserAchievementInfo arrAchievements = new ArrayOfUserAchievementInfo {
UserAchievementInfo = new UserAchievementInfo[]{
new UserAchievementInfo {
UserID = Guid.Parse(userId),
AchievementPointTotal = 5000,
RankID = 30,
PointTypeID = 1
},
new UserAchievementInfo {
UserID = Guid.Parse(userId),
AchievementPointTotal = 5000,
RankID = 30,
PointTypeID = 9
},
new UserAchievementInfo {
UserID = Guid.Parse(userId),
AchievementPointTotal = 5000,
RankID = 30,
PointTypeID = 10
},
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP),
achievementService.CreateUserAchievementInfo(viking.Id, 60000, AchievementPointTypes.PlayerFarmingXP), // TODO: placeholder until there is no leveling for farm XP
achievementService.CreateUserAchievementInfo(viking.Id, 20000, AchievementPointTypes.PlayerFishingXP), // TODO: placeholder until there is no leveling for fishing XP
}
};
@ -86,7 +98,7 @@ public class AchievementController : Controller {
return Ok(new AchievementReward[1] {
new AchievementReward {
Amount = 5,
PointTypeID = 5,
PointTypeID = AchievementPointTypes.CashCurrency,
EntityID = Guid.Parse(viking.Id),
EntityTypeID = 1,
RewardID = 552
@ -94,7 +106,7 @@ public class AchievementController : Controller {
});
}
[HttpPost]
[HttpPost]
[Produces("application/xml")]
[Route("V2/AchievementWebService.asmx/SetUserAchievementTask")]
[DecryptRequest("achievementTaskSetRequest")]
@ -112,7 +124,7 @@ public class AchievementController : Controller {
AchievementRewards = new AchievementReward[1] {
new AchievementReward {
Amount = 25,
PointTypeID = 1,
PointTypeID = AchievementPointTypes.PlayerXP,
RewardID = 910,
EntityTypeID =1
}
@ -130,7 +142,7 @@ public class AchievementController : Controller {
return Ok(new AchievementReward[1] {
new AchievementReward {
Amount = 25,
PointTypeID = 1,
PointTypeID = AchievementPointTypes.PlayerXP,
EntityID = Guid.Parse(viking.Id),
EntityTypeID = 1,
RewardID = 552

View File

@ -428,7 +428,7 @@ public class ContentController : Controller {
return null;
}
RaisedPetData[] dragons = viking.Dragons
RaisedPetData[] dragons = viking.Dragons // TODO (multiplayer) we should use userId
.Where(d => d.RaisedPetData is not null)
.Select(GetRaisedPetDataFromDragon)
.ToArray();

View File

@ -2,14 +2,17 @@
using Microsoft.AspNetCore.Mvc;
using sodoff.Model;
using sodoff.Schema;
using sodoff.Services;
using sodoff.Util;
namespace sodoff.Controllers.Common;
public class ProfileController : Controller {
private readonly DBContext ctx;
public ProfileController(DBContext ctx) {
private AchievementService achievementService;
public ProfileController(DBContext ctx, AchievementService achievementService) {
this.ctx = ctx;
this.achievementService = achievementService;
}
[HttpPost]
@ -141,24 +144,9 @@ public class ProfileController : Controller {
RankID = 0, // placeholder
AchievementInfo = null, // placeholder
Achievements = new UserAchievementInfo[] {
new UserAchievementInfo {
UserID = Guid.Parse(viking.Id),
AchievementPointTotal = 5000,
RankID = 30,
PointTypeID = 1
},
new UserAchievementInfo {
UserID = Guid.Parse(viking.Id),
AchievementPointTotal = 5000,
RankID = 30,
PointTypeID = 9
},
new UserAchievementInfo {
UserID = Guid.Parse(viking.Id),
AchievementPointTotal = 5000,
RankID = 30,
PointTypeID = 10
},
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP),
achievementService.CreateUserAchievementInfo(viking.Id, 60000, AchievementPointTypes.PlayerFarmingXP), // TODO: placeholder until there is no leveling for farm XP
achievementService.CreateUserAchievementInfo(viking.Id, 20000, AchievementPointTypes.PlayerFishingXP), // TODO: placeholder until there is no leveling for fishing XP
}
};

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace sodoff.Model;
public class AchievementPoints {
public string VikingId { get; set; }
public int Type { get; set; }
public int Value { get; set; }
public virtual Viking? Viking { get; set; }
}

View File

@ -46,6 +46,9 @@ public class DBContext : DbContext {
builder.Entity<Viking>().HasMany(v => v.Rooms)
.WithOne(e => e.Viking);
builder.Entity<Viking>().HasMany(v => v.AchievementPoints)
.WithOne(e => e.Viking);
builder.Entity<Viking>().HasOne(s => s.User)
.WithMany(e => e.Vikings)
.HasForeignKey(e => e.UserId);
@ -130,5 +133,12 @@ public class DBContext : DbContext {
builder.Entity<RoomItem>().HasOne(i => i.Room)
.WithMany(r => r.Items)
.HasForeignKey(e => e.RoomId);
builder.Entity<AchievementPoints>().HasKey(e => new { e.VikingId, e.Type });
builder.Entity<AchievementPoints>()
.HasOne(e => e.Viking)
.WithMany(e => e.AchievementPoints)
.HasForeignKey(e => e.VikingId);
}
}

View File

@ -18,6 +18,8 @@ public class Dragon {
public string? RaisedPetData { get; set; }
public int? PetXP { get; set; }
public virtual Viking Viking { get; set; } = null!;
public virtual Viking SelectedViking { get; set; } = null!;
}

View File

@ -21,6 +21,7 @@ public class Viking {
public virtual ICollection<Image> Images { get; set; } = null!;
public virtual ICollection<MissionState> MissionStates { get; set; } = null!;
public virtual ICollection<Room> Rooms { get; set; } = null!;
public virtual ICollection<AchievementPoints> AchievementPoints { get; set; } = null!;
public virtual Dragon? SelectedDragon { get; set; }
public int InventoryId { get; set; }

View File

@ -18,6 +18,7 @@ builder.Services.AddSingleton<MissionStoreSingleton>();
builder.Services.AddScoped<MissionService>();
builder.Services.AddSingleton<StoreService>();
builder.Services.AddScoped<RoomService>();
builder.Services.AddSingleton<AchievementService>();
var app = builder.Build();

View File

@ -0,0 +1,35 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
public enum AchievementPointTypes {
[XmlEnum("1")]
PlayerXP = 1,
[XmlEnum("2")]
GameCurrency = 2, // gold
[XmlEnum("4")]
Unknown4 = 4,
[XmlEnum("5")]
CashCurrency = 5, // gems
[XmlEnum("6")]
ItemReward = 6,
[XmlEnum("8")]
DragonXP = 8,
[XmlEnum("9")]
PlayerFarmingXP = 9,
[XmlEnum("10")]
PlayerFishingXP = 10,
[XmlEnum("12")]
UDTPoints = 12,
[XmlEnum("13")]
Unknown13 = 13,
}

View File

@ -13,7 +13,7 @@ public class AchievementReward
public int? Amount;
[XmlElement(ElementName = "p", IsNullable = true)]
public int? PointTypeID;
public AchievementPointTypes? PointTypeID;
[XmlElement(ElementName = "ii")]
public int ItemID;

View File

@ -0,0 +1,11 @@
using System.Diagnostics;
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "ArrayOfUserRank", Namespace = "http://api.jumpstart.com/")]
[Serializable]
public class ArrayOfUserRank {
[XmlElement(ElementName = "UserRank")]
public UserRank[] UserRank;
}

View File

@ -7,6 +7,7 @@ namespace sodoff.Schema;
public class AvatarData
{
[XmlElement(ElementName = "IsSuggestedAvatarName", IsNullable = true)]
public bool? IsSuggestedAvatarName;
public int? Id;

View File

@ -10,7 +10,7 @@ public class ItemStateCriteriaReplenishable : ItemStateCriteria
public bool ApplyRank;
[XmlElement(ElementName = "PointTypeID", IsNullable = true)]
public int? PointTypeID;
public AchievementPointTypes? PointTypeID;
[XmlElement(ElementName = "ReplenishableRates")]
public List<ReplenishableRate> ReplenishableRates;

View File

@ -7,7 +7,7 @@ namespace sodoff.Schema;
public class RewardMultiplier
{
[XmlElement(ElementName = "PT")]
public int PointTypeID;
public AchievementPointTypes PointTypeID;
[XmlElement(ElementName = "MF")]
public int MultiplierFactor;

View File

@ -19,7 +19,7 @@ public class UserAchievementInfo
public int RankID;
[XmlElement(ElementName = "p")]
public int? PointTypeID;
public AchievementPointTypes? PointTypeID;
[XmlElement(ElementName = "FBUID", IsNullable = true)]
public long? FacebookUserID;

20
src/Schema/UserRank.cs Normal file
View File

@ -0,0 +1,20 @@
using System.Diagnostics;
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "UserRank", Namespace = "")]
[Serializable]
public class UserRank {
[XmlElement(ElementName = "PointTypeID")]
public AchievementPointTypes PointTypeID;
[XmlElement(ElementName = "Value")]
public int Value;
[XmlElement(ElementName = "RankID")]
public int? RankID;
[XmlElement(ElementName = "GlobalRankID")]
public int? GlobalRankID;
}

View File

@ -0,0 +1,56 @@
using sodoff.Schema;
using sodoff.Model;
using sodoff.Util;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
namespace sodoff.Services {
public class AchievementService {
Dictionary<AchievementPointTypes, UserRank[]> ranks = new();
public AchievementService() {
ArrayOfUserRank allranks = XmlUtil.DeserializeXml<ArrayOfUserRank>(XmlUtil.ReadResourceXmlString("allranks"));
foreach (var pointType in Enum.GetValues<AchievementPointTypes>()) {
ranks[pointType] = allranks.UserRank.Where(r => r.PointTypeID == pointType).ToArray();
}
}
public int GetRankFromXP(int? xpPoints, AchievementPointTypes type) {
return ranks[type].Count(r => r.Value <= xpPoints);
}
public UserAchievementInfo CreateUserAchievementInfo(string userId, int? value, AchievementPointTypes type) {
if (value is null)
value = 0;
return new UserAchievementInfo {
UserID = Guid.Parse(userId),
AchievementPointTotal = value,
RankID = GetRankFromXP(value, type),
PointTypeID = type
};
}
public UserAchievementInfo CreateUserAchievementInfo(Viking viking, AchievementPointTypes type) {
return CreateUserAchievementInfo(viking.Id, viking.AchievementPoints.FirstOrDefault(a => a.Type == (int)type)?.Value, type);
}
public void AddAchievementPoints(Viking viking, AchievementPointTypes? type, int? value) {
if (type == AchievementPointTypes.DragonXP) {
viking.SelectedDragon.PetXP = (viking.SelectedDragon.PetXP ?? 0) + (value ?? 0);
} else if (type != null) {
AchievementPoints xpPoints = viking.AchievementPoints.FirstOrDefault(a => a.Type == (int)type);
if (xpPoints is null) {
xpPoints = new AchievementPoints {
Type = (int)type,
Value = 0
};
viking.AchievementPoints.Add(xpPoints);
}
xpPoints.Value += value ?? 0;
}
}
}
}

View File

@ -8,8 +8,9 @@ public class MissionService {
private readonly DBContext ctx;
private MissionStoreSingleton missionStore;
private AchievementService achievementService;
public MissionService(DBContext ctx, MissionStoreSingleton missionStore) {
public MissionService(DBContext ctx, MissionStoreSingleton missionStore, AchievementService achievementService) {
this.ctx = ctx;
this.missionStore = missionStore;
}
@ -49,7 +50,7 @@ public class MissionService {
missionState.UserAccepted = null;
}
foreach (var reward in mission.Rewards) {
if (reward.PointTypeID == 6) {
if (reward.PointTypeID == AchievementPointTypes.ItemReward) {
// TODO: This is not a pretty solution. Use inventory service in the future
InventoryItem? ii = viking.Inventory.InventoryItems.FirstOrDefault(x => x.ItemId == reward.ItemID);
if (ii is null) {
@ -60,6 +61,8 @@ public class MissionService {
viking.Inventory.InventoryItems.Add(ii);
}
ii.Quantity += (int)reward.Amount!;
} else { // currencies, all types of player XP and dragon XP
achievementService.AddAchievementPoints(viking, reward.PointTypeID, reward.Amount);
}
}
ctx.SaveChanges();

View File

@ -125,7 +125,7 @@ public class RoomService {
if (rewards != null) {
response.Rewards = rewards;
foreach (var reward in rewards) {
if (reward.PointTypeID == 6) {
if (reward.PointTypeID == AchievementPointTypes.ItemReward) {
// TODO: This is not a pretty solution. Use inventory service in the future
InventoryItem? ii = item.Room.Viking.Inventory.InventoryItems.FirstOrDefault(x => x.ItemId == reward.ItemID);
if (ii is null) {