Merge 693a73a71e080520d6f1781eec2e5bbaeaa4423b into f8b26e468b7d6d3bcd69628c11255a4ca2798417

This commit is contained in:
rpaciorek 2025-10-07 22:21:14 +02:00 committed by GitHub
commit 0cfc8afd15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 282 additions and 18 deletions

View File

@ -1186,6 +1186,42 @@ public class ContentController : Controller {
return Ok(new BuddyList { Buddy = new Buddy[0] }); return Ok(new BuddyList { Buddy = new Buddy[0] });
} }
[HttpPost]
[Produces("application/xml")]
[Route("/ContentWebService.asmx/RedeemItems")]
[VikingSession]
public IActionResult RedeemItems(Viking viking, [FromForm] string request) {
var req = XmlUtil.DeserializeXml<RedeemRequest>(request);
Dictionary<int, int> inventoryItemsToAdd = new();
// resolve items in the bundle
ItemData bundleItem = itemService.GetItem(req.ItemID);
foreach (var reward in bundleItem.Relationship.Where(e => e.Type == "Bundle")) {
int quantity = itemService.GetItemQuantity(reward, 1);
inventoryItemsToAdd.TryAdd(reward.ItemId, 0);
inventoryItemsToAdd[reward.ItemId] += quantity;
}
var addedItems = inventoryService.AddItemsToInventoryBulk(viking, inventoryItemsToAdd);
// build response
List<CommonInventoryResponseItem> items = new List<CommonInventoryResponseItem>();
foreach (var i in inventoryItemsToAdd) {
items.AddRange(Enumerable.Repeat(
new CommonInventoryResponseItem {
CommonInventoryID = addedItems.ContainsKey(i.Key) ? addedItems[i.Key] : 0, // return inventory id if this item was added to the DB
ItemID = i.Key,
Quantity = 0
}, i.Value));
}
return Ok(new CommonInventoryResponse{
Success = true,
CommonInventoryIDs = items.ToArray(),
UserGameCurrency = achievementService.GetUserCurrency(viking)
});
}
[HttpPost] [HttpPost]
[Produces("application/xml")] [Produces("application/xml")]
[Route("/ContentWebService.asmx/RedeemMysteryBoxItems")] [Route("/ContentWebService.asmx/RedeemMysteryBoxItems")]

View File

@ -100,7 +100,6 @@ public class RegistrationController : Controller {
[HttpPost] [HttpPost]
[Produces("application/xml")] [Produces("application/xml")]
[Route("V3/RegistrationWebService.asmx/RegisterChild")] // used by Magic & Mythies
[Route("V4/RegistrationWebService.asmx/RegisterChild")] [Route("V4/RegistrationWebService.asmx/RegisterChild")]
[DecryptRequest("childRegistrationData")] [DecryptRequest("childRegistrationData")]
[EncryptResponse] [EncryptResponse]
@ -131,6 +130,34 @@ public class RegistrationController : Controller {
}); });
} }
[HttpPost]
[Produces("application/xml")]
[Route("V3/RegistrationWebService.asmx/RegisterChild")] // used by SoD 1.13 and Magic & Mythies
[DecryptRequest("childRegistrationData")]
public IActionResult RegisterChildV3([FromForm] Guid parentApiToken, [FromForm] string apiKey) {
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == parentApiToken)?.User;
if (user is null) {
return Ok(new RegistrationResult{
Status = MembershipUserStatus.InvalidApiToken
});
}
// Check if name populated
ChildRegistrationData data = XmlUtil.DeserializeXml<ChildRegistrationData>(Request.Form["childRegistrationData"]);
if (String.IsNullOrWhiteSpace(data.ChildName)) {
return Ok(MembershipUserStatus.ValidationError);
}
// Check if viking exists
if (ctx.Vikings.Count(e => e.Name == data.ChildName) > 0) {
return Ok(MembershipUserStatus.DuplicateUserName);
}
Viking v = CreateViking(user, data, ClientVersion.GetVersion(apiKey));
return Ok(MembershipUserStatus.Success);
}
private Viking CreateViking(User user, ChildRegistrationData data, uint gameVersion) { private Viking CreateViking(User user, ChildRegistrationData data, uint gameVersion) {
List<InventoryItem> items = new(); List<InventoryItem> items = new();
if (gameVersion >= ClientVersion.Min_SoD) { if (gameVersion >= ClientVersion.Min_SoD) {

View File

@ -3029,6 +3029,164 @@
<!-- mission reward achievements (`<AID>` tag in `missions.xml` file) --> <!-- mission reward achievements (`<AID>` tag in `missions.xml` file) -->
<!-- do NOT add missions reward achievements to this file to avoid duplication of rewards --> <!-- do NOT add missions reward achievements to this file to avoid duplication of rewards -->
<!-- fish trap rewards (probability of each 10%), do not correspond to the original ones, max 3 reward's item per achievements due to client limitation -->
<AchievementsIdInfo>
<AID>201388</AID>
<AR>
<p>6</p>
<a>3</a>
<t>1</t>
<r>8</r>
<ii>7143</ii><!-- Perch -->
</AR>
<AR>
<p>6</p>
<a>2</a>
<t>1</t>
<r>8</r>
<ii>7139</ii><!-- Brown Trout -->
</AR>
<AR>
<p>6</p>
<a>1</a>
<t>1</t>
<r>8</r>
<ii>7144</ii><!-- Salmon -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201389</AID>
<AR>
<p>6</p>
<a>4</a>
<t>1</t>
<r>8</r>
<ii>7143</ii><!-- Perch -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201390</AID>
<AR>
<p>6</p>
<a>2</a>
<t>1</t>
<r>8</r>
<ii>7143</ii><!-- Perch -->
</AR>
<AR>
<p>6</p>
<a>1</a>
<t>1</t>
<r>8</r>
<ii>7139</ii><!-- Brown Trout -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201391</AID>
<AR>
<p>6</p>
<a>3</a>
<t>1</t>
<r>8</r>
<ii>7140</ii><!-- Eel -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201392</AID>
<AR>
<p>6</p>
<a>1</a>
<t>1</t>
<r>8</r>
<ii>7140</ii><!-- Eel -->
</AR>
<AR>
<p>6</p>
<a>3</a>
<t>1</t>
<r>8</r>
<ii>7144</ii><!-- Salmon -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201393</AID>
<AR>
<p>6</p>
<a>5</a>
<t>1</t>
<r>8</r>
<ii>7143</ii><!-- Perch -->
</AR>
<AR>
<p>6</p>
<a>1</a>
<t>1</t>
<r>8</r>
<ii>7140</ii><!-- Eel -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201394</AID>
<AR>
<p>6</p>
<a>1</a>
<t>1</t>
<r>8</r>
<ii>7143</ii><!-- Perch -->
</AR>
<AR>
<p>6</p>
<a>2</a>
<t>1</t>
<r>8</r>
<ii>7139</ii><!-- Brown Trout -->
</AR>
<AR>
<p>6</p>
<a>2</a>
<t>1</t>
<r>8</r>
<ii>7144</ii><!-- Salmon -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201395</AID>
<AR>
<p>6</p>
<a>5</a>
<t>1</t>
<r>8</r>
<ii>7139</ii><!-- Brown Trout -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201396</AID>
<AR>
<p>6</p>
<a>5</a>
<t>1</t>
<r>8</r>
<ii>7144</ii><!-- Salmon -->
</AR>
</AchievementsIdInfo>
<AchievementsIdInfo>
<AID>201397</AID>
<AR>
<p>6</p>
<a>3</a>
<t>1</t>
<r>8</r>
<ii>7139</ii><!-- Brown Trout -->
</AR>
<AR>
<p>6</p>
<a>2</a>
<t>1</t>
<r>8</r>
<ii>7144</ii><!-- Salmon -->
</AR>
</AchievementsIdInfo>
<!-- stable missions reward achievements --> <!-- stable missions reward achievements -->
<AchievementsIdInfo> <AchievementsIdInfo>
<AID>201323</AID> <AID>201323</AID>

View File

@ -0,0 +1,11 @@
using System.Xml.Serialization;
namespace sodoff.Schema;
[XmlRoot(ElementName = "AchievementCompletion", Namespace = "")]
[Serializable]
public class AchievementCompletion
{
[XmlElement(ElementName = "AchievementID")]
public int AchievementID;
}

View File

@ -12,4 +12,7 @@ public class CompletionAction {
[XmlElement(ElementName = "Transition")] [XmlElement(ElementName = "Transition")]
public StateTransition Transition; public StateTransition Transition;
[XmlElement(ElementName = "AchievementCompletion", IsNullable = true)]
public AchievementCompletion[] AchievementCompletion;
} }

View File

@ -31,8 +31,8 @@ public class UserItemPosition {
[XmlElement(ElementName = "uicid")] [XmlElement(ElementName = "uicid")]
public int? UserInventoryCommonID; public int? UserInventoryCommonID;
[XmlElement(ElementName = "i")] [XmlElement(ElementName = "i", IsNullable = true)]
public ItemData Item; public ItemData? Item;
[XmlElement(ElementName = "px")] [XmlElement(ElementName = "px")]
public double? PositionX; public double? PositionX;

View File

@ -12,6 +12,7 @@ public class RoomService {
private ItemService itemService; private ItemService itemService;
private AchievementService achievementService; private AchievementService achievementService;
private Random random = new Random();
public RoomService(DBContext ctx, ItemService itemService, AchievementService achievementService) { public RoomService(DBContext ctx, ItemService itemService, AchievementService achievementService) {
this.ctx = ctx; this.ctx = ctx;
@ -28,15 +29,21 @@ public class RoomService {
List<int> ids = new(); List<int> ids = new();
List<UserItemState> states = new(); List<UserItemState> states = new();
foreach (var itemRequest in roomItemRequest) { foreach (var itemRequest in roomItemRequest) {
ItemData itemData = itemRequest.Item;
// TODO: Remove item from inventory (using CommonInventoryID) // TODO: Remove item from inventory (using CommonInventoryID)
InventoryItem? i = room.Viking?.InventoryItems.FirstOrDefault(x => x.Id == itemRequest.UserInventoryCommonID); InventoryItem? i = room.Viking?.InventoryItems.FirstOrDefault(x => x.Id == itemRequest.UserInventoryCommonID);
if (i != null) { if (i != null) {
i.Quantity--; i.Quantity--;
if (itemRequest.Item is null) { if (itemData is null) {
itemRequest.Item = itemService.GetItem(i.ItemId); itemData = itemService.GetItem(i.ItemId);
} }
} }
// do not store item definition in serialised xml in database (store item id instead)
itemRequest.Item = null;
itemRequest.ItemID = itemData.ItemID;
RoomItem roomItem = new RoomItem { RoomItem roomItem = new RoomItem {
RoomItemData = XmlUtil.SerializeXml<UserItemPosition>(itemRequest).Replace(" xsi:type=\"UserItemPositionSetRequest\"", "") // NOTE: No way to avoid this hack when we're serializing a child class into a base class RoomItemData = XmlUtil.SerializeXml<UserItemPosition>(itemRequest).Replace(" xsi:type=\"UserItemPositionSetRequest\"", "") // NOTE: No way to avoid this hack when we're serializing a child class into a base class
}; };
@ -44,12 +51,12 @@ public class RoomService {
room.Items.Add(roomItem); room.Items.Add(roomItem);
ctx.SaveChanges(); ctx.SaveChanges();
ids.Add(roomItem.Id); ids.Add(roomItem.Id);
if (itemRequest.Item.ItemStates.Count > 0) { if (itemData.ItemStates.Count > 0) {
ItemState defaultState = itemRequest.Item.ItemStates.Find(x => x.Order == 1)!; ItemState defaultState = itemData.ItemStates.Find(x => x.Order == 1)!;
UserItemState userDefaultState = new UserItemState { UserItemState userDefaultState = new UserItemState {
CommonInventoryID = (int)itemRequest.UserInventoryCommonID!, CommonInventoryID = (int)itemRequest.UserInventoryCommonID!,
UserItemPositionID = roomItem.Id, UserItemPositionID = roomItem.Id,
ItemID = (int)itemRequest.Item.ItemID, ItemID = (int)itemData.ItemID,
ItemStateID = defaultState.ItemStateID, ItemStateID = defaultState.ItemStateID,
StateChangeDate = new DateTime(DateTime.Now.Ticks) StateChangeDate = new DateTime(DateTime.Now.Ticks)
}; };
@ -75,7 +82,7 @@ public class RoomService {
if (itemRequest.UserItemState != null) itemPosition.UserItemState = itemRequest.UserItemState; if (itemRequest.UserItemState != null) itemPosition.UserItemState = itemRequest.UserItemState;
if (itemRequest.UserItemAttributes != null) itemPosition.UserItemAttributes = itemRequest.UserItemAttributes; if (itemRequest.UserItemAttributes != null) itemPosition.UserItemAttributes = itemRequest.UserItemAttributes;
if (itemRequest.UserItemStat != null) itemPosition.UserItemStat = itemRequest.UserItemStat; if (itemRequest.UserItemStat != null) itemPosition.UserItemStat = itemRequest.UserItemStat;
if (itemRequest.Item != null) itemPosition.Item = itemRequest.Item; if (itemRequest.Item != null) itemPosition.ItemID = itemRequest.Item.ItemID;
if (itemRequest.PositionX != null) itemPosition.PositionX = itemRequest.PositionX; if (itemRequest.PositionX != null) itemPosition.PositionX = itemRequest.PositionX;
if (itemRequest.PositionY != null) itemPosition.PositionY = itemRequest.PositionY; if (itemRequest.PositionY != null) itemPosition.PositionY = itemRequest.PositionY;
if (itemRequest.PositionZ != null) itemPosition.PositionZ = itemRequest.PositionZ; if (itemRequest.PositionZ != null) itemPosition.PositionZ = itemRequest.PositionZ;
@ -109,7 +116,10 @@ public class RoomService {
foreach (var item in room.Items) { foreach (var item in room.Items) {
UserItemPosition data = XmlUtil.DeserializeXml<UserItemPosition>(item.RoomItemData); UserItemPosition data = XmlUtil.DeserializeXml<UserItemPosition>(item.RoomItemData);
data.UserItemPositionID = item.Id; data.UserItemPositionID = item.Id;
data.ItemID = data.Item?.ItemID; if (data.ItemID is null)
data.ItemID = data.Item?.ItemID; // for backward compatibility with database entries without set `data.ItemID`
else
data.Item = itemService.GetItem((int)data.ItemID);
if (gameVersion < 0xa3a00a0a && data.Uses is null) if (gameVersion < 0xa3a00a0a && data.Uses is null)
data.Uses = -1; data.Uses = -1;
itemPosition.Add(data); itemPosition.Add(data);
@ -122,11 +132,15 @@ public class RoomService {
Success = true, Success = true,
ErrorCode = ItemStateChangeError.Success ErrorCode = ItemStateChangeError.Success
}; };
UserItemPosition pos = XmlUtil.DeserializeXml<UserItemPosition>(item.RoomItemData); UserItemPosition pos = XmlUtil.DeserializeXml<UserItemPosition>(item.RoomItemData);
if (pos.ItemID is null)
pos.ItemID = pos.Item?.ItemID; // for backward compatibility with database entries without set `data.ItemID`
AchievementReward[]? rewards; AchievementReward[]? rewards;
int? achievementID;
List<ItemStateCriteria> consumables; List<ItemStateCriteria> consumables;
int nextStateID = GetNextStateID(pos, speedup, out rewards, out consumables); int nextStateID = GetNextStateID(pos, speedup, out rewards, out achievementID, out consumables);
foreach (var consumable in consumables) { foreach (var consumable in consumables) {
ItemStateCriteriaConsumable c = (ItemStateCriteriaConsumable)consumable; ItemStateCriteriaConsumable c = (ItemStateCriteriaConsumable)consumable;
@ -138,6 +152,13 @@ public class RoomService {
if (rewards != null) { if (rewards != null) {
response.Rewards = achievementService.ApplyAchievementRewards(item.Room.Viking, rewards); response.Rewards = achievementService.ApplyAchievementRewards(item.Room.Viking, rewards);
} }
if (achievementID != null) {
var newrewards = achievementService.ApplyAchievementRewardsByID(item.Room.Viking, (int)achievementID);
if (response.Rewards is null)
response.Rewards = newrewards;
else
response.Rewards = response.Rewards.Concat(newrewards).ToArray();
}
DateTime stateChange = new DateTime(DateTime.Now.Ticks); DateTime stateChange = new DateTime(DateTime.Now.Ticks);
if (nextStateID == -1) { if (nextStateID == -1) {
@ -150,7 +171,7 @@ public class RoomService {
response.UserItemState = new UserItemState { response.UserItemState = new UserItemState {
CommonInventoryID = (int)pos.UserInventoryCommonID!, CommonInventoryID = (int)pos.UserInventoryCommonID!,
UserItemPositionID = item.Id, UserItemPositionID = item.Id,
ItemID = pos.Item.ItemID, ItemID = (int)pos.ItemID,
ItemStateID = nextStateID, ItemStateID = nextStateID,
StateChangeDate = stateChange StateChangeDate = stateChange
}; };
@ -164,17 +185,26 @@ public class RoomService {
return response; return response;
} }
private int GetNextStateID(UserItemPosition pos, bool speedup, out AchievementReward[]? rewards, out List<ItemStateCriteria> consumables) { private int GetNextStateID(UserItemPosition pos, bool speedup, out AchievementReward[]? rewards, out int? achievementID, out List<ItemStateCriteria> consumables) {
rewards = null; rewards = null;
achievementID = null;
consumables = new List<ItemStateCriteria>(); consumables = new List<ItemStateCriteria>();
var itemStates = itemService.GetItem((int)pos.ItemID).ItemStates;
if (pos.UserItemState == null) if (pos.UserItemState == null)
return pos.Item.ItemStates.Find(x => x.Order == 1)!.ItemStateID; return itemStates.Find(x => x.Order == 1)!.ItemStateID;
ItemState currState = pos.Item.ItemStates.Find(x => x.ItemStateID == pos.UserItemState.ItemStateID)!; ItemState currState = itemStates.Find(x => x.ItemStateID == pos.UserItemState.ItemStateID)!;
rewards = currState.Rewards; rewards = currState.Rewards;
consumables = currState.Rule.Criterias.FindAll(x => x.Type == ItemStateCriteriaType.ConsumableItem); consumables = currState.Rule.Criterias.FindAll(x => x.Type == ItemStateCriteriaType.ConsumableItem);
// achievementID = currState.AchievementID; // TODO we should do this or not? some items use the same rewards in `currState.Rewards` and achievement definition, but some do not contain only achievementID (but then there is generally no definition for achievement)
if (currState.Rule.CompletionAction.AchievementCompletion != null) {
achievementID = currState.Rule.CompletionAction.AchievementCompletion[
random.Next(0, currState.Rule.CompletionAction.AchievementCompletion.Length)
].AchievementID;
}
if (speedup) if (speedup)
return ((ItemStateCriteriaSpeedUpItem)currState.Rule.Criterias.Find(x => x.Type == ItemStateCriteriaType.SpeedUpItem)!).EndStateID; return ((ItemStateCriteriaSpeedUpItem)currState.Rule.Criterias.Find(x => x.Type == ItemStateCriteriaType.SpeedUpItem)!).EndStateID;
@ -187,12 +217,11 @@ public class RoomService {
switch (currState.Rule.CompletionAction.Transition) { switch (currState.Rule.CompletionAction.Transition) {
default: default:
return pos.Item.ItemStates.Find(x => x.Order == currState.Order + 1)!.ItemStateID; return itemStates.Find(x => x.Order == currState.Order + 1)!.ItemStateID;
case StateTransition.InitialState: case StateTransition.InitialState:
return pos.Item.ItemStates.Find(x => x.Order == 1)!.ItemStateID; return itemStates.Find(x => x.Order == 1)!.ItemStateID;
case StateTransition.Deletion: case StateTransition.Deletion:
return -1; return -1;
} }
} }
} }