Merge branch 'WorldOfJumpStart'

add support for World of JumpStart, MathBlaster and some other JS online games
This commit is contained in:
Robert Paciorek 2024-03-03 18:52:26 +00:00
commit a420c774d1
23 changed files with 4192 additions and 48 deletions

View File

@ -90,6 +90,20 @@ public class AchievementController : Controller {
return Ok("OK");
}
[HttpPost]
[Produces("application/xml")]
[Route("AchievementWebService.asmx/GetUserAchievementInfo")] // used by World Of Jumpstart
public IActionResult GetUserAchievementInfo([FromForm] Guid apiToken) {
Viking? viking = ctx.Sessions.FirstOrDefault(x => x.ApiToken == apiToken).Viking;
if (viking != null) {
return Ok(
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP)
);
}
return null;
}
[HttpPost]
[Produces("application/xml")]
[Route("AchievementWebService.asmx/GetAchievementsByUserID")]

View File

@ -33,11 +33,18 @@ public class AuthenticationController : Controller {
[Route("v3/AuthenticationWebService.asmx/LoginParent")]
[DecryptRequest("parentLoginData")]
[EncryptResponse]
public IActionResult LoginParent() {
public IActionResult LoginParent([FromForm] string apiKey) {
ParentLoginData data = XmlUtil.DeserializeXml<ParentLoginData>(Request.Form["parentLoginData"]);
// Authenticate the user
User? user = ctx.Users.FirstOrDefault(e => e.Username == data.UserName);
User? user = null;
uint gameVersion = ClientVersion.GetVersion(apiKey);
if (gameVersion == ClientVersion.WoJS || gameVersion == ClientVersion.MB) {
user = ctx.Users.FirstOrDefault(e => e.Email == data.UserName);
} else {
user = ctx.Users.FirstOrDefault(e => e.Username == data.UserName);
}
if (user is null || new PasswordHasher<object>().VerifyHashedPassword(null, user.Password, data.Password) != PasswordVerificationResult.Success) {
return Ok(new ParentLoginInfo { Status = MembershipUserStatus.InvalidPassword });
}
@ -52,6 +59,11 @@ public class AuthenticationController : Controller {
ctx.Sessions.Add(session);
ctx.SaveChanges();
var childList = new List<sodoff.Schema.UserLoginInfo>();
foreach (var viking in user.Vikings) {
childList.Add(new sodoff.Schema.UserLoginInfo{UserName = viking.Name, UserID = viking.Uid.ToString()});
}
var response = new ParentLoginInfo {
UserName = user.Username,
//Email = user.Email, /* disabled to avoid put email in client debug logs */
@ -59,7 +71,8 @@ public class AuthenticationController : Controller {
UserID = user.Id.ToString(),
Status = MembershipUserStatus.Success,
SendActivationReminder = false,
UnAuthorized = false
UnAuthorized = false,
ChildList = childList.ToArray()
};
return Ok(response);
@ -121,10 +134,25 @@ public class AuthenticationController : Controller {
return Ok(new UserInfo {});
}
[HttpPost]
[Produces("application/xml")]
[Route("AuthenticationWebService.asmx/IsValidApiToken")] // used by World Of Jumpstart (FutureLand)
public IActionResult IsValidApiToken_V1([FromForm] Guid? apiToken) {
if (apiToken is null)
return Ok(false);
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.User;
Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking;
if (user is null && viking is null)
return Ok(false);
return Ok(true);
}
[HttpPost]
[Produces("application/xml")]
[Route("AuthenticationWebService.asmx/IsValidApiToken_V2")]
public IActionResult IsValidApiToken([FromForm] Guid apiToken) {
public IActionResult IsValidApiToken([FromForm] Guid? apiToken) {
if (apiToken is null)
return Ok(ApiTokenStatus.TokenNotFound);
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.User;
Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking;
if (user is null && viking is null)

View File

@ -20,10 +20,11 @@ public class ContentController : Controller {
private AchievementService achievementService;
private InventoryService inventoryService;
private GameDataService gameDataService;
private DisplayNamesService displayNamesService;
private Random random = new Random();
private readonly IOptions<ApiServerConfig> config;
public ContentController(DBContext ctx, KeyValueService keyValueService, ItemService itemService, MissionService missionService, RoomService roomService, AchievementService achievementService, InventoryService inventoryService, GameDataService gameDataService, 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) {
this.ctx = ctx;
this.keyValueService = keyValueService;
this.itemService = itemService;
@ -32,9 +33,98 @@ public class ContentController : Controller {
this.achievementService = achievementService;
this.inventoryService = inventoryService;
this.gameDataService = gameDataService;
this.displayNamesService = displayNamesService;
this.config = config;
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetRaisedPetGrowthStates")] // used by World Of Jumpstart 1.1
public RaisedPetGrowthState[] GetRaisedPetGrowthStates()
{
return new RaisedPetGrowthState[] {
new RaisedPetGrowthState {GrowthStateID = 0, Name = "none"},
new RaisedPetGrowthState {GrowthStateID = 1, Name = "powerup"},
new RaisedPetGrowthState {GrowthStateID = 2, Name = "find"},
new RaisedPetGrowthState {GrowthStateID = 3, Name = "eggInHand"},
new RaisedPetGrowthState {GrowthStateID = 4, Name = "hatching"},
new RaisedPetGrowthState {GrowthStateID = 5, Name = "baby"},
new RaisedPetGrowthState {GrowthStateID = 6, Name = "child"},
new RaisedPetGrowthState {GrowthStateID = 7, Name = "teen"},
new RaisedPetGrowthState {GrowthStateID = 8, Name = "adult"},
};
}
[HttpPost]
//[Produces("application/xml")]
[Route("ContentWebService.asmx/GetProduct")] // used by World Of Jumpstart
[VikingSession(UseLock=false)]
public string? GetProduct(Viking viking, [FromForm] string apiKey) {
return Util.SavedData.Get(
viking,
ClientVersion.GetVersion(apiKey)
);
}
[HttpPost]
//[Produces("application/xml")]
[Route("ContentWebService.asmx/SetProduct")] // used by World Of Jumpstart
[VikingSession]
public bool SetProduct(Viking viking, [FromForm] string contentXml, [FromForm] string apiKey) {
Util.SavedData.Set(
viking,
ClientVersion.GetVersion(apiKey),
contentXml
);
ctx.SaveChanges();
return true;
}
// NOTE: "Pet" (Petz) system (GetCurrentPetByUserID, GetCurrentPet, SetCurrentPet, DelCurrentPet) is a totally different system than "RaisedPet" (Dragons)
[HttpPost]
//[Produces("application/xml")]
[Route("ContentWebService.asmx/GetCurrentPetByUserID")] // used by World Of Jumpstart
public string GetCurrentPetByUserID([FromForm] Guid userId) {
return GetCurrentPet(ctx.Vikings.FirstOrDefault(e => e.Uid == userId));
}
[HttpPost]
//[Produces("application/xml")]
[Route("ContentWebService.asmx/GetCurrentPet")] // used by World Of Jumpstart
[VikingSession]
public string GetCurrentPet(Viking viking) {
string? ret = Util.SavedData.Get(
viking,
ClientVersion.WoJS + 1
);
if (ret is null)
return XmlUtil.SerializeXml<PetData>(null);
return ret;
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/SetCurrentPet")] // used by World Of Jumpstart
[VikingSession]
public bool SetCurrentPet(Viking viking, [FromForm] string? contentXml) {
Util.SavedData.Set(
viking,
ClientVersion.WoJS + 1,
contentXml
);
ctx.SaveChanges();
return true;
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/DelCurrentPet")] // used by World Of Jumpstart
[VikingSession]
public bool DelCurrentPet(Viking viking) {
return SetCurrentPet(viking, null);
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetDefaultNameSuggestion")]
@ -315,6 +405,40 @@ public class ContentController : Controller {
return 0;
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetAvatar")] // used by World Of Jumpstart
[VikingSession(UseLock=false)]
public IActionResult GetAvatar(Viking viking) {
AvatarData avatarData = XmlUtil.DeserializeXml<AvatarData>(viking.AvatarSerialized);
avatarData.Id = viking.Id;
return Ok(avatarData);
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/SetAvatar")] // used by World Of Jumpstart
[VikingSession]
public IActionResult SetAvatarV1(Viking viking, [FromForm] string contentXML) {
if (viking.AvatarSerialized != null) {
AvatarData dbAvatarData = XmlUtil.DeserializeXml<AvatarData>(viking.AvatarSerialized);
AvatarData reqAvatarData = XmlUtil.DeserializeXml<AvatarData>(contentXML);
int dbAvatarVersion = GetAvatarVersion(dbAvatarData);
int reqAvatarVersion = GetAvatarVersion(reqAvatarData);
if (dbAvatarVersion > reqAvatarVersion) {
// do not allow override newer version avatar data by older version
return Ok(false);
}
}
viking.AvatarSerialized = contentXML;
ctx.SaveChanges();
return Ok(true);
}
[HttpPost]
[Produces("application/xml")]
[Route("V2/ContentWebService.asmx/SetAvatar")]
@ -363,7 +487,10 @@ public class ContentController : Controller {
raisedPetData.IsSelected = false; // The api returns false, not sure why
raisedPetData.CreateDate = new DateTime(DateTime.Now.Ticks);
raisedPetData.UpdateDate = new DateTime(DateTime.Now.Ticks);
raisedPetData.GrowthState = new RaisedPetGrowthState { Name = "BABY" };
if (petTypeID == 2)
raisedPetData.GrowthState = new RaisedPetGrowthState { Name = "BABY" };
else
raisedPetData.GrowthState = new RaisedPetGrowthState { Name = "POWERUP" };
int imageSlot = (viking.Images.Select(i => i.ImageSlot).DefaultIfEmpty(-1).Max() + 1);
raisedPetData.ImagePosition = imageSlot;
// NOTE: Placing an egg into a hatchery slot calls CreatePet, but doesn't SetImage.
@ -454,6 +581,28 @@ public class ContentController : Controller {
});
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/SetRaisedPet")] // used by World Of Jumpstart
[VikingSession]
public IActionResult SetRaisedPetv1(Viking viking, [FromForm] string raisedPetData) {
RaisedPetData petData = XmlUtil.DeserializeXml<RaisedPetData>(raisedPetData);
// Find the dragon
Dragon? dragon = viking.Dragons.FirstOrDefault(e => e.Id == petData.RaisedPetID);
if (dragon is null) {
return Ok(new SetRaisedPetResponse {
RaisedPetSetResult = RaisedPetSetResult.Invalid
});
}
dragon.RaisedPetData = XmlUtil.SerializeXml(UpdateDragon(dragon, petData));
ctx.Update(dragon);
ctx.SaveChanges();
return Ok(true);
}
[HttpPost]
[Produces("application/xml")]
[Route("V2/ContentWebService.asmx/SetRaisedPet")] // used by Magic & Mythies
@ -504,6 +653,31 @@ public class ContentController : Controller {
});
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/SetRaisedPetInactive")] // used by World Of Jumpstart
[VikingSession]
public IActionResult SetRaisedPetInactive(Viking viking, [FromForm] int raisedPetID) {
if (raisedPetID == viking.SelectedDragonId) {
viking.SelectedDragonId = null;
} else {
Dragon? dragon = viking.Dragons.FirstOrDefault(e => e.Id == raisedPetID);
if (dragon is null) {
return Ok(false);
}
// check if Minisaurs - we real delete only Minisaurs
RaisedPetData dragonData = XmlUtil.DeserializeXml<RaisedPetData>(dragon.RaisedPetData);
if (dragonData.PetTypeID != 2) {
return Ok(false);
}
viking.Dragons.Remove(dragon);
}
ctx.SaveChanges();
return Ok(true);
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/SetSelectedPet")]
@ -574,6 +748,48 @@ public class ContentController : Controller {
return filteredDragons.ToArray();
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetActiveRaisedPet")] // used by World Of Jumpstart
[VikingSession(UseLock=false)]
public RaisedPetData[] GetActiveRaisedPet(Viking viking, [FromForm] string userId, [FromForm] int petTypeID) {
if (petTypeID == 2) {
// player can have multiple Minisaurs at the same time ... Minisaurs should never have been selected also ... so use GetUnselectedPetByTypes in this case
return GetUnselectedPetByTypes(viking, "2", false);
}
Dragon? dragon = viking.SelectedDragon;
if (dragon is null) {
return new RaisedPetData[0];
}
RaisedPetData dragonData = GetRaisedPetDataFromDragon(dragon);
if (petTypeID != dragonData.PetTypeID)
return new RaisedPetData[0];
// NOTE: returned dragon PetTypeID should be equal value of pair 1967 → CurrentRaisedPetType
return new RaisedPetData[] {dragonData};
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetActiveRaisedPetsByTypes")] // used by Math Blaster
[VikingSession(UseLock=false)]
public RaisedPetData[] GetActiveRaisedPet([FromForm] Guid userId, [FromForm] string petTypeIDs) {
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
Dragon? dragon = viking.SelectedDragon;
if (dragon is null) {
return new RaisedPetData[0];
}
RaisedPetData dragonData = GetRaisedPetDataFromDragon(dragon);
int[] petTypeIDsInt = Array.ConvertAll(petTypeIDs.Split(','), s => int.Parse(s));
if (!petTypeIDsInt.Contains(dragonData.PetTypeID))
return new RaisedPetData[0];
return new RaisedPetData[] {dragonData};
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetSelectedRaisedPet")]
@ -589,6 +805,30 @@ public class ContentController : Controller {
};
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetInactiveRaisedPet")] // used by World Of Jumpstart 1.1
[VikingSession(UseLock=false)]
public RaisedPetData[] GetInactiveRaisedPet(Viking viking, [FromForm] int petTypeID) {
RaisedPetData[] dragons = viking.Dragons
.Where(d => d.RaisedPetData is not null && d.Id != viking.SelectedDragonId)
.Select(d => GetRaisedPetDataFromDragon(d, viking.SelectedDragonId))
.ToArray();
List<RaisedPetData> filteredDragons = new List<RaisedPetData>();
foreach (RaisedPetData dragon in dragons) {
if (petTypeID == dragon.PetTypeID) {
filteredDragons.Add(dragon);
}
}
if (filteredDragons.Count == 0) {
return null;
}
return filteredDragons.ToArray();
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/SetImage")]
@ -1156,6 +1396,35 @@ public class ContentController : Controller {
return Ok(roomService.NextItemState(item, request.OverrideStateCriteria));
}
[HttpPost]
//[Produces("application/xml")]
[Route("ContentWebService.asmx/GetDisplayNames")] // used by World Of Jumpstart
[Route("ContentWebService.asmx/GetDisplayNamesByCategoryID")] // used by Math Blaster
public IActionResult GetDisplayNames() {
// TODO: This is a placeholder
return Ok(XmlUtil.ReadResourceXmlString("displaynames"));
}
[HttpPost]
//[Produces("application/xml")]
[Route("ContentWebService.asmx/SetDisplayName")] // used by World Of Jumpstart
[VikingSession]
public IActionResult SetProduct(Viking viking, [FromForm] int firstNameID, [FromForm] int secondNameID, [FromForm] int thirdNameID) {
AvatarData avatarData = XmlUtil.DeserializeXml<AvatarData>(viking.AvatarSerialized);
avatarData.DisplayName = displayNamesService.GetName(firstNameID, secondNameID, thirdNameID);
viking.AvatarSerialized = XmlUtil.SerializeXml(avatarData);
ctx.SaveChanges();
return Ok();
}
[HttpPost]
//[Produces("application/xml")]
[Route("ContentWebService.asmx/GetScene")] // used by World Of Jumpstart
public IActionResult GetScene() {
// TODO: This is a placeholder
return Ok("<?xml version=\"1.0\" encoding=\"utf-8\"?><SceneData xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\" />");
}
[HttpPost]
[Produces("application/xml")]
[Route("V2/ContentWebService.asmx/GetGameData")]
@ -1570,6 +1839,30 @@ public class ContentController : Controller {
return Ok(gameDataService.GetGameData(viking, gameId, isMultiplayer, difficulty, gameLevel, key, count, AscendingOrder, buddyFilter, DateTime.Parse(startDate, usCulture), DateTime.Parse(endDate, usCulture)));
}
[HttpPost]
[Produces("application/xml")]
[Route("ContentWebService.asmx/GetPeriodicGameDataByGame")] // used by Math Blaster
public IActionResult GetPeriodicGameDataByGame() {
// TODO: This is a placeholder
return Ok(new GameDataSummary());
}
[HttpPost]
[Produces("application/xml")]
[Route("MissionWebService.asmx/GetTreasureChest")] // used by Math Blaster
public IActionResult GetTreasureChest() {
// TODO: This is a placeholder
return Ok(new TreasureChestData());
}
[HttpPost]
[Produces("application/xml")]
[Route("MissionWebService.asmx/GetWorldId")] // used by Math Blaster
public IActionResult GetWorldId() {
// TODO: This is a placeholder
return Ok(0);
}
private static RaisedPetData GetRaisedPetDataFromDragon (Dragon dragon, int? selectedDragonId = null) {
if (selectedDragonId is null)
selectedDragonId = dragon.Viking.SelectedDragonId;

View File

@ -46,6 +46,13 @@ public class ItemStoreController : Controller {
return Ok(itemService.GetItem(itemId));
}
[HttpPost]
[Produces("application/xml")]
[Route("ItemStoreWebService.asmx/GetItemsInStore")] // used by World Of Jumpstart
public IActionResult GetItemsInStore([FromForm] int storeId) {
return Ok(storeService.GetStore(storeId));
}
[HttpPost]
//[Produces("application/xml")]
[Route("ItemStoreWebService.asmx/GetRankAttributeData")]

View File

@ -61,47 +61,6 @@ public class ProfileController : Controller {
[Route("ProfileWebService.asmx/GetQuestions")]
public IActionResult GetQuestions() {
return Ok(XmlUtil.ReadResourceXmlString("questiondata"));
//return Ok(new ProfileQuestionData {
// Lists = new ProfileQuestionList[] {
// new ProfileQuestionList {
// ID = 4,
// Questions = new ProfileQuestion[] {
// new ProfileQuestion {
// CategoryID = 3,
// IsActive = "true", // this is a string, which makes me sad
// Locale = "en-US",
// Ordinal = 1,
// ID = 48,
// DisplayText = "How Did You Hear About US ?",
// Answers = new ProfileAnswer[] {
// new ProfileAnswer {
// ID = 320,
// DisplayText = "TV Commercial",
// Locale = "en-US",
// Ordinal = 1,
// QuestionID = 48
// },
// new ProfileAnswer {
// ID = 324,
// DisplayText = "I bought the RIders Of Berk DVD",
// Locale = "en-US",
// Ordinal = 5,
// QuestionID = 48
// },
// new ProfileAnswer {
// ID = 325,
// DisplayText = "I bought the Defenders of Berk DVD",
// Locale = "en-US",
// Ordinal = 6,
// QuestionID = 48
// }
// }
// }
// }
// }
// }
// });
}
[HttpPost]

View File

@ -53,7 +53,7 @@ public class RegistrationController : Controller {
[Route("v3/RegistrationWebService.asmx/RegisterParent")]
[DecryptRequest("parentRegistrationData")]
[EncryptResponse]
public IActionResult RegisterParent() {
public IActionResult RegisterParent([FromForm] string apiKey) {
ParentRegistrationData data = XmlUtil.DeserializeXml<ParentRegistrationData>(Request.Form["parentRegistrationData"]);
User u = new User {
Id = Guid.NewGuid(),
@ -63,11 +63,30 @@ public class RegistrationController : Controller {
};
// Check if user exists
uint gameVersion = ClientVersion.GetVersion(apiKey);
if (gameVersion == ClientVersion.WoJS || gameVersion == ClientVersion.MB) {
if (ctx.Users.Count(e => e.Email == u.Email) > 0) {
return Ok(new RegistrationResult { Status = MembershipUserStatus.DuplicateEmail });
}
}
if (ctx.Users.Count(e => e.Username== u.Username) > 0) {
return Ok(new RegistrationResult { Status = MembershipUserStatus.DuplicateUserName });
}
ctx.Users.Add(u);
if(gameVersion == ClientVersion.MB) {
Viking v = new Viking {
Uid = Guid.NewGuid(),
Name = data.ChildList[0].ChildName,
User = u,
InventoryItems = new List<InventoryItem>(),
AchievementPoints = new List<AchievementPoints>(),
Rooms = new List<Room>()
};
ctx.Vikings.Add(v);
}
ctx.SaveChanges();
ParentLoginInfo pli = new ParentLoginInfo {

View File

@ -112,6 +112,9 @@ public class DBContext : DbContext {
builder.Entity<Viking>().HasMany(v => v.GameData)
.WithOne(e => e.Viking);
builder.Entity<Viking>().HasMany(v => v.SavedData)
.WithOne(e => e.Viking);
builder.Entity<Viking>().HasMany(v => v.ProfileAnswers)
.WithOne(e => e.Viking);
@ -204,6 +207,13 @@ public class DBContext : DbContext {
.WithMany(e => e.AchievementPoints)
.HasForeignKey(e => e.VikingId);
builder.Entity<SavedData>().HasKey(e => new { e.VikingId, e.SaveId });
builder.Entity<SavedData>()
.HasOne(e => e.Viking)
.WithMany(v => v.SavedData)
.HasForeignKey(e => e.VikingId);
builder.Entity<ProfileAnswer>().HasOne(i => i.Viking)
.WithMany(i => i.ProfileAnswers)
.HasForeignKey(e => e.VikingId);

8
src/Model/SaveData.cs Normal file
View File

@ -0,0 +1,8 @@
namespace sodoff.Model;
public class SavedData {
public int VikingId { get; set; }
public uint SaveId { get; set; }
public string? SerializedData { get; set; }
public virtual Viking Viking { get; set; } = null!;
}

View File

@ -32,5 +32,6 @@ public class Viking {
public virtual ICollection<InventoryItem> InventoryItems { get; set; } = null!;
public virtual ICollection<GameData> GameData { get; set; } = null!;
public virtual ICollection<ProfileAnswer> ProfileAnswers { get; set; } = null!;
public virtual ICollection<SavedData> SavedData { get; set; } = null!;
public virtual Dragon? SelectedDragon { get; set; }
}

View File

@ -26,6 +26,7 @@ builder.Services.AddSingleton<MissionStoreSingleton>();
builder.Services.AddSingleton<AchievementStoreSingleton>();
builder.Services.AddSingleton<ItemService>();
builder.Services.AddSingleton<StoreService>();
builder.Services.AddSingleton<DisplayNamesService>();
builder.Services.AddScoped<KeyValueService>();
builder.Services.AddScoped<MissionService>();

View File

@ -0,0 +1,37 @@
<?xml version='1.0' encoding='utf-8'?>
<DefaultMissions>
<!-- list of default mission for World of Jump Start -->
<Active>
<id>1509</id>
<id>1511</id>
<id>1512</id>
<id>1513</id>
<id>1514</id>
<id>1743</id>
<id>2323</id>
<id>2374</id>
<id>2375</id>
<id>2376</id>
<id>2377</id>
<id>2378</id>
<id>2379</id>
<id>2380</id>
<id>2381</id>
<id>2382</id>
<id>2383</id>
<id>2384</id>
<id>2385</id>
<id>2386</id>
<id>2440</id>
<id>2441</id>
<id>2442</id>
<id>2446</id>
<id>2447</id>
<id>2448</id>
<id>2449</id>
<id>2451</id>
<id>2452</id>
</Active>
<Upcoming>
</Upcoming>
</DefaultMissions>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<DisplayNames xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DisplayName>
<ID>1</ID>
<Name>Aaliyah</Name>
<Ordinal>1</Ordinal>
</DisplayName>
<DisplayName>
<ID>2</ID>
<Name>Abby</Name>
<Ordinal>2</Ordinal>
</DisplayName>
<DisplayName>
<ID>3</ID>
<Name>Adrian</Name>
<Ordinal>3</Ordinal>
</DisplayName>
<DisplayName>
<ID>11</ID>
<Name>Karen</Name>
<Ordinal>2</Ordinal>
</DisplayName>
<DisplayName>
<ID>12</ID>
<Name>Luna</Name>
<Ordinal>2</Ordinal>
</DisplayName>
<DisplayName>
<ID>13</ID>
<Name>Tori</Name>
<Ordinal>2</Ordinal>
</DisplayName>
</DisplayNames>

File diff suppressed because it is too large Load Diff

View File

@ -2303,6 +2303,42 @@
</Answers>
</Qs>
</QL>
<QL>
<ID>4</ID>
<Qs>
<CID>3</CID>
<Img xsi:nil="true" />
<A>true</A>
<L>en-US</L>
<Ord>1</Ord>
<ID>48</ID>
<T>How Did You Hear About US ?</T>
<Answers>
<ID>320</ID>
<T>TV Commercial</T>
<Img xsi:nil="true" />
<L>en-US</L>
<O>1</O>
<QID>48</QID>
</Answers>
<Answers>
<ID>324</ID>
<T>I bought the Riders Of Berk DVD</T>
<Img xsi:nil="true" />
<L>en-US</L>
<O>5</O>
<QID>48</QID>
</Answers>
<Answers>
<ID>325</ID>
<T>I bought the Defenders of Berk DVD</T>
<Img xsi:nil="true" />
<L>en-US</L>
<O>6</O>
<QID>48</QID>
</Answers>
</Qs>
</QL>
<QL>
<ID>5</ID>
<Qs>

View File

@ -0,0 +1,15 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[Serializable]
[XmlRoot(ElementName = "DisplayNames", Namespace = "")]
public class DisplayNameList : List<DisplayName> {
}
public class DisplayName {
[XmlElement("ID")]
public int Id;
[XmlElement("Name")]
public string Name;
}

10
src/Schema/PetData.cs Normal file
View File

@ -0,0 +1,10 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "PetData", Namespace = "")]
[Serializable]
public class PetData {
[XmlElement(ElementName = "Pet")]
public PetDataPet[]? Pet;
}

21
src/Schema/PetDataPet.cs Normal file
View File

@ -0,0 +1,21 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "PetDataPet", Namespace = "")]
[Serializable]
public class PetDataPet {
public string Geometry;
public string Texture;
public string Type;
public string Name;
public float Dirtiness;
public string AccessoryGeometry;
public string AccessoryTexture;
}

View File

@ -0,0 +1,33 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "TreasureChestData", Namespace = "")]
[Serializable]
public class TreasureChestData
{
public int TreasureChestId;
[XmlElement(ElementName = "StartDate", IsNullable = true)]
public string StartDate;
[XmlElement(ElementName = "EndDate", IsNullable = true)]
public string EndDate;
public string ServerTime;
public float RespawnTime;
public int ChestMin;
public int ChestMax;
[XmlElement(ElementName = "GameCurrencyMin", IsNullable = true)]
public int? GameCurrencyMin;
[XmlElement(ElementName = "GameCurrencyMax", IsNullable = true)]
public int? GameCurrencyMax;
[XmlElement(ElementName = "ItemId")]
public int[] ItemId;
}

View File

@ -0,0 +1,20 @@
using sodoff.Schema;
using sodoff.Util;
namespace sodoff.Services;
public class DisplayNamesService {
Dictionary<int, string> displayNames = new();
public DisplayNamesService(ItemService itemService) {
DisplayNameList displayNamesList = XmlUtil.DeserializeXml<DisplayNameList>(XmlUtil.ReadResourceXmlString("displaynames"));
displayNames.Add(0, "");
foreach (var n in displayNamesList) {
displayNames.Add(n.Id, n.Name);
}
}
public string GetName(int firstNameID, int secondNameID, int thirdNameID) {
return displayNames[firstNameID] + " " + displayNames[secondNameID] + displayNames[thirdNameID];
}
}

View File

@ -12,6 +12,8 @@ public class MissionStoreSingleton {
private int[] upcomingMissionsV1;
private int[] activeMissionsMaM;
private int[] upcomingMissionsMaM;
private int[] activeMissionsWoJS;
private int[] upcomingMissionsWoJS;
public MissionStoreSingleton() {
ServerMissionArray missionArray = XmlUtil.DeserializeXml<ServerMissionArray>(XmlUtil.ReadResourceXmlString("missions"));
@ -29,6 +31,14 @@ public class MissionStoreSingleton {
defaultMissions = XmlUtil.DeserializeXml<DefaultMissions>(XmlUtil.ReadResourceXmlString("defaultmissionlistmam"));
activeMissionsMaM = defaultMissions.Active;
upcomingMissionsMaM = defaultMissions.Upcoming;
missionArray = XmlUtil.DeserializeXml<ServerMissionArray>(XmlUtil.ReadResourceXmlString("missions_wojs"));
defaultMissions = XmlUtil.DeserializeXml<DefaultMissions>(XmlUtil.ReadResourceXmlString("defaultmissionlist_wojs"));
foreach (var mission in missionArray.MissionDataArray) {
SetUpRecursive(mission); // TODO: use separate missions dict for WoJS (?)
}
activeMissionsWoJS = defaultMissions.Active;
upcomingMissionsWoJS = defaultMissions.Upcoming;
}
public Mission GetMission(int missionID) {
@ -39,6 +49,9 @@ public class MissionStoreSingleton {
if (gameVersion == ClientVersion.MaM) {
return activeMissionsMaM;
}
if (gameVersion == ClientVersion.WoJS) {
return activeMissionsWoJS;
}
if (gameVersion < 0xa2a00a0a) {
return activeMissionsV1;
}
@ -49,6 +62,9 @@ public class MissionStoreSingleton {
if (gameVersion == ClientVersion.MaM) {
return upcomingMissionsMaM;
}
if (gameVersion == ClientVersion.WoJS) {
return upcomingMissionsWoJS;
}
if (gameVersion < 0xa2a00a0a) {
return upcomingMissionsV1;
}

View File

@ -28,6 +28,10 @@ public class ClientVersion {
apiKey == "1552008f-4a95-46f5-80e2-58574da65875"
) {
return WoJS;
} else if (
apiKey == "b4e0f71a-1cda-462a-97b3-0b355e87e0c8"
) {
return WoJS+10; // WoJS--Adventureland
}
return 0;
}

22
src/Util/SavedData.cs Normal file
View File

@ -0,0 +1,22 @@
using sodoff.Model;
namespace sodoff.Util;
public class SavedData {
public static string? Get(Viking? viking, uint saveId) {
return viking?.SavedData.FirstOrDefault(s => s.SaveId == saveId)?.SerializedData;
}
public static void Set(Viking viking, uint saveId, string? contentXml) {
Console.WriteLine($"\n\n{saveId} {contentXml}\n");
Model.SavedData? savedData = viking.SavedData.FirstOrDefault(s => s.SaveId == saveId);
if (savedData is null) {
savedData = new() {
SaveId = saveId,
SerializedData = contentXml
};
viking.SavedData.Add(savedData);
} else {
savedData.SerializedData = contentXml;
}
}
}

View File

@ -41,6 +41,7 @@
<None Remove="Resources\items.xml" />
<None Remove="Resources\missions.xml" />
<None Remove="Resources\mmo.xml" />
<None Remove="Resources\displaynames.xml" />
<None Remove="Resources\rankattrib.xml" />
<None Remove="Resources\rewardmultiplier.xml" />
<None Remove="Resources\store.xml" />
@ -50,6 +51,7 @@
<None Remove="Resources\defaultmissionlist.xml" />
<None Remove="Resources\defaultmissionlistv1.xml" />
<None Remove="Resources\defaultmissionlistmam.xml" />
<None Remove="Resources\defaultmissionlist_wojs.xml" />
<None Remove="Resources\questiondata.xml" />
</ItemGroup>
<ItemGroup>
@ -79,6 +81,9 @@
<EmbeddedResource Include="Resources\mmo.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\displaynames.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\rewardmultiplier.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
@ -109,6 +114,9 @@
<EmbeddedResource Include="Resources\missions.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\missions_wojs.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\defaultmissionlist.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
@ -118,6 +126,9 @@
<EmbeddedResource Include="Resources\defaultmissionlistmam.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\defaultmissionlist_wojs.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Resources\questiondata.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>