/** * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. * * This is the concrete implementation */ class McpUserInventoryManager extends McpUserInventoryBase; `include(Engine\Classes\HttpStatusCodes.uci) /** The URL to use when making save slot for a user */ var config string CreateSaveSlotUrl; /** The URL to use when deleting a save slot for a user (along with all inventory items belonging to it) */ var config string DeleteSaveSlotUrl; /** The URL to use when listing the save slots that exist for a user */ var config string ListSaveSlotUrl; /** The URL to use when listing user's inventory items for a specific save slot */ var config string ListItemsUrl; /** The URL to use when purchasing an item to be placed in the user's inventory */ var config string PurchaseItemUrl; /** The URL to use when selling an item from the user's inventory */ var config string SellItemUrl; /** The URL to use when earning an item to be placed in the user's inventory */ var config string EarnItemUrl; /** The URL to use when consuming an item from the user's inventory */ var config string ConsumeItemUrl; /** The URL to use when deleting an item from the user's inventory */ var config string DeleteItemUrl; /** The URL to use when recording an IAP has occurred */ var config string IapRecordUrl; /** Cached list of user save slots that have been retrieved from the server */ var array SaveSlots; /** Holds the state information for an outstanding save slot request */ struct SaveSlotRequestState { /** The MCP id for the request */ var string McpId; /** The save slot involved */ var string SaveSlotId; /** The HTTP request object for this request */ var HttpRequestInterface Request; }; /** Holds the state information for an outstanding inventory item request */ struct InventoryItemRequestState extends SaveSlotRequestState { /** id of the item that is being operated on. Either instance or global id */ var string ItemId; }; /** The set of create/delete save slot requests that are pending */ var array SaveSlotRequests; /** The set of list save slot requests that are pending */ var array ListSaveSlotRequests; /** The set of list inventory items requests that are pending */ var array ListItemsRequests; /** The set of purchase item requests that are pending */ var array ItemRequests; /** * Creates a new save slot for the user * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot that is being create * @param ParentSaveSlotId [optional] if specified then all existing IAP purchases * from the parent save slot carry over to the newly created save slot */ function CreateSaveSlot(string McpId, string SaveSlotId, optional string ParentSaveSlotId) { local string Url; local HttpRequestInterface Request; local int AddAt,ExistingIndex; // Check for pending request ExistingIndex = FindSaveSlotRequest(McpId,SaveSlotId,SaveSlotRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ CreateSaveSlotUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId; if (Len(ParentSaveSlotId) > 0) { Url $= "&parentSaveSlotId=" $ ParentSaveSlotId; } // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("POST"); Request.OnProcessRequestComplete = OnCreateSaveSlotRequestComplete; // Store off the data for reporting later AddAt = SaveSlotRequests.Length; SaveSlotRequests.Length = AddAt + 1; SaveSlotRequests[AddAt].McpId = McpId; SaveSlotRequests[AddAt].SaveSlotId = SaveSlotId; SaveSlotRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start CreateSaveSlot web request for URL(" $ Url $ ")"); } `Log("Create save slot URL is " $ Url); } } else { `Log("Already have a pending save slot request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId); } } /** * Called once the request/response has completed. Used to process the create save slot result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnCreateSaveSlotRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index, SaveSlotIndex; local int ResponseCode; local string ResponseString; // Search for the corresponding entry in the array Index = SaveSlotRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { // Clear out old entry if it exists SaveSlotIndex = FindSaveSlotIndex( SaveSlotRequests[Index].McpId, SaveSlotRequests[Index].SaveSlotId); if (SaveSlotIndex != INDEX_NONE) { SaveSlots.Remove(SaveSlotIndex,1); } ResponseString = Response.GetContentAsString(); // Parse the JSON payload of default values for this save slot ParseInventoryForSaveSlot( SaveSlotRequests[Index].McpId, SaveSlotRequests[Index].SaveSlotId, ResponseString); } // Notify anyone waiting on this OnCreateSaveSlotComplete( SaveSlotRequests[Index].McpId, SaveSlotRequests[Index].SaveSlotId, bWasSuccessful, Response.GetContentAsString()); `Log("Create save slot:" $" McpId=" $ SaveSlotRequests[Index].McpId $" SaveSlot=" $ SaveSlotRequests[Index].SaveSlotId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); SaveSlotRequests.Remove(Index,1); } } /** * Deletes an existing user save slot and all the inventory items that belong to it * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot that is being deleted */ function DeleteSaveSlot(string McpId, string SaveSlotId) { local string Url; local HttpRequestInterface Request; local int AddAt, ExistingIndex; // Check for pending request ExistingIndex = FindSaveSlotRequest(McpId,SaveSlotId,SaveSlotRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ DeleteSaveSlotUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId; // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("DELETE"); Request.OnProcessRequestComplete = OnDeleteSaveSlotRequestComplete; // Store off the data for reporting later AddAt = SaveSlotRequests.Length; SaveSlotRequests.Length = AddAt + 1; SaveSlotRequests[AddAt].McpId = McpId; SaveSlotRequests[AddAt].SaveSlotId = SaveSlotId; SaveSlotRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start DeleteSaveSlot web request for URL(" $ Url $ ")"); } `Log("Delete save slot URL is " $ Url); } } else { `Log("Already have a pending save slot request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId); } } /** * Called once the request/response has completed. Used to process the delete save slot result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnDeleteSaveSlotRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index,SaveSlotIndex; local int ResponseCode; // Search for the corresponding entry in the array Index = SaveSlotRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { // Clear out old entry if it exists SaveSlotIndex = FindSaveSlotIndex( SaveSlotRequests[Index].McpId, SaveSlotRequests[Index].SaveSlotId); if (SaveSlotIndex != INDEX_NONE) { SaveSlots.Remove(SaveSlotIndex,1); } } // Notify anyone waiting on this OnDeleteSaveSlotComplete( SaveSlotRequests[Index].McpId, SaveSlotRequests[Index].SaveSlotId, bWasSuccessful, Response.GetContentAsString()); `Log("Delete save slot:" $" McpId=" $ SaveSlotRequests[Index].McpId $" SaveSlot=" $ SaveSlotRequests[Index].SaveSlotId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); SaveSlotRequests.Remove(Index,1); } } /** * Query for the list of save slots that exist for the user * * @param McpId the id of the user that made the request */ function QuerySaveSlotList(string McpId) { local string Url; local HttpRequestInterface Request; local int AddAt, ExistingIndex; // Check for pending request ExistingIndex = ListSaveSlotRequests.Find('McpId',McpId); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ ListSaveSlotUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId; // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("GET"); Request.OnProcessRequestComplete = OnQuerySaveSlotListRequestComplete; // Store off the data for reporting later AddAt = ListSaveSlotRequests.Length; ListSaveSlotRequests.Length = AddAt + 1; ListSaveSlotRequests[AddAt].McpId = McpId; ListSaveSlotRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start QuerySaveSlotList web request for URL(" $ Url $ ")"); } `Log("Query save slot list URL is " $ Url); } } else { `Log("Already have a pending list save slot request for" $" McpId="$ McpId); } } /** * Called once the request/response has completed. Used to process the list save slot result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnQuerySaveSlotListRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index; local int ResponseCode; local string ResponseString; // Search for the corresponding entry in the array Index = ListSaveSlotRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { ResponseString = Response.GetContentAsString(); // Parse the JSON payload of default values for this save slot ParseSaveSlotList( ListSaveSlotRequests[Index].McpId, ResponseString); } // Notify anyone waiting on this OnQuerySaveSlotListComplete( ListSaveSlotRequests[Index].McpId, bWasSuccessful, Response.GetContentAsString()); `Log("List save slots:" $" McpId=" $ ListSaveSlotRequests[Index].McpId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); ListSaveSlotRequests.Remove(Index,1); } } /** * Get the cached list of save slot ids for a user * * @param McpId the id of the user that made the request * * @return the list of save slot ids for the user */ function array GetSaveSlotList(string McpId) { local array OutSaveSlots; local int SaveSlotIndex; OutSaveSlots.Length = SaveSlots.Length; for (SaveSlotIndex=0; SaveSlotIndex < SaveSlots.Length; SaveSlotIndex++) { OutSaveSlots[SaveSlotIndex] = SaveSlots[SaveSlotIndex].SaveSlotId; } return OutSaveSlots; } /** * Query for the list of current inventory items that exist for the user within a save slot * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot to find items for */ function QueryInventoryItems(string McpId, string SaveSlotId) { local string Url; local HttpRequestInterface Request; local int AddAt,ExistingIndex; // Check for pending request ExistingIndex = FindSaveSlotRequest(McpId,SaveSlotId,ListItemsRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ ListItemsUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId; // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("GET"); Request.OnProcessRequestComplete = OnQueryInventoryItemsRequestComplete; // Store off the data for reporting later AddAt = ListItemsRequests.Length; ListItemsRequests.Length = AddAt + 1; ListItemsRequests[AddAt].McpId = McpId; ListItemsRequests[AddAt].SaveSlotId = SaveSlotId; ListItemsRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start QueryInventoryItems web request for URL(" $ Url $ ")"); } `Log("Query inventory items URL is " $ Url); } } else { `Log("Already have a pending save slot request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId); } } /** * Called once the request/response has completed. Used to process the list inventory result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnQueryInventoryItemsRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index,SaveSlotIndex; local int ResponseCode; local string ResponseString; // Search for the corresponding entry in the array Index = ListItemsRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { // Clear out old item entries if they exists SaveSlotIndex = FindSaveSlotIndex( ListItemsRequests[Index].McpId, ListItemsRequests[Index].SaveSlotId); if (SaveSlotIndex != INDEX_NONE) { SaveSlots[SaveSlotIndex].Items.Length = 0; } ResponseString = Response.GetContentAsString(); // Parse the JSON payload of default values for this save slot ParseInventoryForSaveSlot( ListItemsRequests[Index].McpId, ListItemsRequests[Index].SaveSlotId, ResponseString); } // Notify anyone waiting on this OnQueryInventoryItemsComplete( ListItemsRequests[Index].McpId, ListItemsRequests[Index].SaveSlotId, bWasSuccessful, Response.GetContentAsString()); `Log("Query inventory items:" $" McpId=" $ ListItemsRequests[Index].McpId $" SaveSlot=" $ ListItemsRequests[Index].SaveSlotId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); ListItemsRequests.Remove(Index,1); } } /** * Access the currently cached inventory items that have been downloaded for a user's save slot * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot to find items for * @param OutInventoryItems the list of inventory items that should be filled in */ function GetInventoryItems(string McpId, string SaveSlotId, out array OutInventoryItems) { local int SaveSlotIndex; // Clear it out OutInventoryItems.Length = 0; // Find the save slot for the user SaveSlotIndex = FindSaveSlotIndex(McpId,SaveSlotId); if (SaveSlotIndex != INDEX_NONE) { OutInventoryItems = SaveSlots[SaveSlotIndex].Items; } else { `Log("No save slot found for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId); } } /** * Access the currently cached inventory item that have been downloaded for a user's save slot by global id * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot to find items for * @param InstanceItemId id for the item that is to be found * @param OutInventoryItem the item that should be filled in * * @return true if found */ function bool GetInventoryItem(string McpId, string SaveSlotId, string InstanceItemId, out McpInventoryItem OutInventoryItem) { local int SaveSlotIndex; local int ItemIndex; // Find the save slot for the user SaveSlotIndex = FindSaveSlotIndex(McpId,SaveSlotId); if (SaveSlotIndex != INDEX_NONE) { ItemIndex = SaveSlots[SaveSlotIndex].Items.Find('InstanceItemId', InstanceItemId); if (ItemIndex != INDEX_NONE) { OutInventoryItem = SaveSlots[SaveSlotIndex].Items[ItemIndex]; return true; } else { `Log("No inventory item found for " $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId $" InstanceItemId="$ InstanceItemId); } } else { `Log("No save slot found for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId); } return false; } /** * Parses the json of inventory items for a user's save slot * * @param McpId the user that owns the data * @param SaveSlot the save slot where the data is stored * @param JsonPayload the json data to parse */ function array ParseInventoryForSaveSlot(string McpId, string SaveSlotId, string JsonPayload) { local JsonObject ParsedJson; local JsonObject ParsedJsonAttrs; local int JsonIndex; local int SaveSlotIndex; local int ItemIndex; local int JsonAttrsIndex; local string GlobalItemId, InstanceItemId; local array UpdatedItemIds; local array ObjectArray; // Find the save slot for the user SaveSlotIndex = FindSaveSlotIndex(McpId, SaveSlotId); if (SaveSlotIndex == INDEX_NONE) { // Add the save slot since it is missing SaveSlotIndex = SaveSlots.Length; SaveSlots.Length = SaveSlotIndex + 1; SaveSlots[SaveSlotIndex].OwningMcpId = McpId; SaveSlots[SaveSlotIndex].SaveSlotId = SaveSlotId; } `Log("DEBUGSZ" $" JsonPayload="$ JsonPayload); // Parse json to obj graph ParsedJson = class'JsonObject'.static.DecodeJson(JsonPayload); ObjectArray = ParsedJson.ObjectArray; if (ObjectArray.Length == 0) { ObjectArray.AddItem(ParsedJson); } // Add/update each inventory item to the user's save slot for (JsonIndex = 0; JsonIndex < ObjectArray.Length; JsonIndex++) { // Grab the item id we need to store InstanceItemId = ObjectArray[JsonIndex].GetStringValue("instance_item_id"); // global template that this item instance was created from GlobalItemId = ObjectArray[JsonIndex].GetStringValue("global_item_id"); if (Len(InstanceItemId) > 0) { // keep track of items that were updated/added UpdatedItemIds.AddItem(InstanceItemId); // Find the existing item with the same id ItemIndex = SaveSlots[SaveSlotIndex].Items.Find('InstanceItemId', InstanceItemId); if (ItemIndex == INDEX_NONE) { // Not stored yet, so add one ItemIndex = SaveSlots[SaveSlotIndex].Items.Length; SaveSlots[SaveSlotIndex].Items.Length = ItemIndex + 1; SaveSlots[SaveSlotIndex].Items[ItemIndex].GlobalItemId = GlobalItemId; SaveSlots[SaveSlotIndex].Items[ItemIndex].InstanceItemId = InstanceItemId; } // Store the item SaveSlots[SaveSlotIndex].Items[ItemIndex].Quantity = ObjectArray[JsonIndex].GetIntValue("quantity"); SaveSlots[SaveSlotIndex].Items[ItemIndex].QuantityIAP = ObjectArray[JsonIndex].GetIntValue("iap_quantity"); SaveSlots[SaveSlotIndex].Items[ItemIndex].Scalar = ObjectArray[JsonIndex].GetFloatValue("scalar"); SaveSlots[SaveSlotIndex].Items[ItemIndex].LastUpdateTime = ObjectArray[JsonIndex].GetStringValue("last_update_time"); // Store the item attributes SaveSlots[SaveSlotIndex].Items[ItemIndex].Attributes.Length = 0; ParsedJsonAttrs = ObjectArray[JsonIndex].GetObject("attributes"); SaveSlots[SaveSlotIndex].Items[ItemIndex].Attributes.Length = ParsedJsonAttrs.ObjectArray.Length; for (JsonAttrsIndex = 0; JsonAttrsIndex < ParsedJsonAttrs.ObjectArray.Length; JsonAttrsIndex++) { SaveSlots[SaveSlotIndex].Items[ItemIndex].Attributes[JsonAttrsIndex].AttributeId = ParsedJsonAttrs.ObjectArray[JsonAttrsIndex].GetStringValue("attribute_id"); SaveSlots[SaveSlotIndex].Items[ItemIndex].Attributes[JsonAttrsIndex].Value = ParsedJsonAttrs.ObjectArray[JsonAttrsIndex].GetIntValue("value"); } } } return UpdatedItemIds; } /** * Parses the json of for a user's save slot list * * @param McpId the user that owns the data * @param JsonPayload the json data to parse */ function ParseSaveSlotList(string McpId, string JsonPayload) { local JsonObject ParsedJson; local int JsonIndex; local int SaveSlotIndex; local string SaveSlotId; // Parse json to obj graph ParsedJson = class'JsonObject'.static.DecodeJson(JsonPayload); // Add/update each save slot entry for (JsonIndex = 0; JsonIndex < ParsedJson.ObjectArray.Length; JsonIndex++) { SaveSlotId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("save_slot_id"); // Find existing save slot entry SaveSlotIndex = FindSaveSlotIndex(McpId, SaveSlotId); if (SaveSlotIndex == INDEX_NONE) { // Add the save slot since it is missing SaveSlotIndex = SaveSlots.Length; SaveSlots.Length = SaveSlotIndex + 1; SaveSlots[SaveSlotIndex].OwningMcpId = McpId; SaveSlots[SaveSlotIndex].SaveSlotId = SaveSlotId; } } } /** * Searches the stored save slots for the ones for this user * * @param McpId the user being searched for * @param SaveSlotId the save slot being searched for * * @return the index of the save slot if found */ function int FindSaveSlotIndex(string McpId, string SaveSlotId) { local int SaveSlotIndex; // Search all of the save slots for one that matches the user and slot id for (SaveSlotIndex = 0; SaveSlotIndex < SaveSlots.Length; SaveSlotIndex++) { if (SaveSlots[SaveSlotIndex].OwningMcpId == McpId && SaveSlots[SaveSlotIndex].SaveSlotId == SaveSlotId) { return SaveSlotIndex; } } return INDEX_NONE; } /** * Find the request entry in the given pending list of requests * * @param McpId the id of the user to find * @param SaveSlotId the save slot to be found * @param SaveSlotRequests list of pending requests to find entry in * * @return index into the list if found INDEX_NONE if not */ function int FindSaveSlotRequest(string McpId, string SaveSlotId, const out array InSaveSlotRequests) { local int Index; for (Index=0; Index < InSaveSlotRequests.Length; Index++) { if (InSaveSlotRequests[Index].McpId == McpId && InSaveSlotRequests[Index].SaveSlotId == SaveSlotId) { return Index; } } return INDEX_NONE; } /** * Find the request entry in the given pending list of requests * * @param McpId the id of the user to find * @param SaveSlotId the save slot to be found * @param ItemId the item id to be found * @param InstanceItemId the instance item id to be found * @param SaveSlotRequests list of pending requests to find entry in * * @return index into the list if found INDEX_NONE if not */ function int FindItemRequest(string McpId, string SaveSlotId, string ItemId, const out array InItemRequests) { local int Index; for (Index=0; Index < InItemRequests.Length; Index++) { if (InItemRequests[Index].McpId == McpId && InItemRequests[Index].SaveSlotId == SaveSlotId && InItemRequests[Index].ItemId == ItemId) { return Index; } } return INDEX_NONE; } /** * Purchase item and add it to the user's inventory * Server determines the transaction is valid * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot that the item is in * @param GlobalItemId id for the item that is being purchased * @param PurchaseItemIds list of instance item ids to be used when making the purchase * @param Quantity number of the item to purchase * @param StoreVersion current known version # of backend store by client */ function PurchaseItem(string McpId, string SaveSlotId, string GlobalItemId, array PurchaseItemIds, int Quantity, int StoreVersion, float Scalar) { local string Url, paymentItemsJson; local HttpRequestInterface Request; local int AddAt,ExistingIndex,Index; // Check for pending request ExistingIndex = FindItemRequest(McpId,SaveSlotId,GlobalItemId,ItemRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ PurchaseItemUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId $ "&globalItemId=" $ GlobalItemId $ "&quantity=" $ Quantity $ "&storeVersion=" $ StoreVersion $ "&scalar=" $ Scalar; if (PurchaseItemIds.Length > 0) { // list of inventory items that are to be used in exchange for the purchase paymentItemsJson = "[ "; for (Index = 0; Index < PurchaseItemIds.Length; Index++) { paymentItemsJson $= "\"" $ PurchaseItemIds[Index] $ "\""; if (Index + 1 < PurchaseItemIds.Length) { paymentItemsJson $= ","; } } paymentItemsJson $= " ]"; Url $= "&paymentItemsJson=" $ paymentItemsJson; } // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("POST"); Request.OnProcessRequestComplete = OnPurchaseItemRequestComplete; // Store off the data for reporting later AddAt = ItemRequests.Length; ItemRequests.Length = AddAt + 1; ItemRequests[AddAt].McpId = McpId; ItemRequests[AddAt].SaveSlotId = SaveSlotId; ItemRequests[AddAt].ItemId = GlobalItemId; ItemRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start PurchaseItem web request for URL(" $ Url $ ")"); } `Log("Purchase item URL is " $ Url); } } else { `Log("Already have a pending item request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId $" GlobalItemId="$ GlobalItemId); } } /** * Called once the request/response has completed. Used to process the purchase item result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnPurchaseItemRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index, SaveSlotIndex, UpdatedItemIdIndex, FoundItemIndex; local int ResponseCode; local string ResponseString; local array UpdatedItemIds; // Search for the corresponding entry in the array Index = ItemRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { ResponseString = Response.GetContentAsString(); // Parse the JSON payload for the new item values UpdatedItemIds = ParseInventoryForSaveSlot( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ResponseString); // delete durable items with quantity=0 SaveSlotIndex = FindSaveSlotIndex(ItemRequests[Index].McpId,ItemRequests[Index].SaveSlotId); if (SaveSlotIndex != INDEX_NONE) { // Iterate over list of items that were updated by server for (UpdatedItemIdIndex=0; UpdatedItemIdIndex < UpdatedItemIds.Length; UpdatedItemIdIndex++) { FoundItemIndex = SaveSlots[SaveSlotIndex].Items.Find('InstanceItemId', UpdatedItemIds[UpdatedItemIdIndex]); if (FoundItemIndex != INDEX_NONE && SaveSlots[SaveSlotIndex].Items[FoundItemIndex].Quantity == 0 && SaveSlots[SaveSlotIndex].Items[FoundItemIndex].QuantityIAP == 0) { SaveSlots[SaveSlotIndex].Items.Remove(FoundItemIndex,1); } } } } // Notify anyone waiting on this OnPurchaseItemComplete( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ItemRequests[Index].ItemId, UpdatedItemIds, bWasSuccessful, Response.GetContentAsString()); `Log("Purchase item:" $" McpId=" $ ItemRequests[Index].McpId $" SaveSlot=" $ ItemRequests[Index].SaveSlotId $" ItemId=" $ ItemRequests[Index].ItemId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); ItemRequests.Remove(Index,1); } } /** * Sell item and remove it from the user's inventory * Server determines the transaction is valid * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot that the item is in * @param InstanceItemId id for the item that is being sold * @param Quantity number of the item to sell * @param StoreVersion current known version # of backend store by client * @param ExpectedResultItems optional list of items/quantities that the client expects to receive from the sale */ function SellItem(string McpId, string SaveSlotId, string InstanceItemId, int Quantity, int StoreVersion, optional const out array ExpectedResultItems) { local string Url, expectedResultsItemsJson; local HttpRequestInterface Request; local int AddAt,ExistingIndex,Index; // Check for pending request ExistingIndex = FindItemRequest(McpId,SaveSlotId,InstanceItemId,ItemRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ SellItemUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId $ "&instanceItemId=" $ InstanceItemId $ "&quantity=" $ Quantity $ "&storeVersion=" $ StoreVersion; if (ExpectedResultItems.Length > 0) { // optional list of items/quantities provided to the server that specify what we expect to receive from the sale expectedResultsItemsJson = "[ "; for (Index = 0; Index < ExpectedResultItems.Length; Index++) { expectedResultsItemsJson $= "{"; expectedResultsItemsJson $= "\"global_item_id\":" $ "\"" $ ExpectedResultItems[Index].GlobalItemId $ "\","; expectedResultsItemsJson $= "\"quantity\":" $ ExpectedResultItems[Index].Quantity; expectedResultsItemsJson $= "}"; if (Index + 1 < ExpectedResultItems.Length) { expectedResultsItemsJson $= ","; } } expectedResultsItemsJson $= " ]"; } // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("POST"); Request.SetContentAsString(expectedResultsItemsJson); Request.SetHeader("Content-Type","multipart/form-data"); Request.OnProcessRequestComplete = OnSellItemRequestComplete; // Store off the data for reporting later AddAt = ItemRequests.Length; ItemRequests.Length = AddAt + 1; ItemRequests[AddAt].McpId = McpId; ItemRequests[AddAt].SaveSlotId = SaveSlotId; ItemRequests[AddAt].ItemId = InstanceItemId; ItemRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start SellItem web request for URL(" $ Url $ ")"); } `Log("Sell item URL is " $ Url); `Log("Payload: " $ expectedResultsItemsJson); } } else { `Log("Already have a pending item request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId $" InstanceItemId="$ InstanceItemId); } } /** * Called once the request/response has completed. Used to process the sell item result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnSellItemRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index, SaveSlotIndex, UpdatedItemIdIndex, FoundItemIndex; local int ResponseCode; local string ResponseString; local array UpdatedItemIds; // Search for the corresponding entry in the array Index = ItemRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { ResponseString = Response.GetContentAsString(); // Parse the JSON payload for the new item values UpdatedItemIds = ParseInventoryForSaveSlot( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ResponseString); // delete durable items with quantity=0 SaveSlotIndex = FindSaveSlotIndex(ItemRequests[Index].McpId,ItemRequests[Index].SaveSlotId); if (SaveSlotIndex != INDEX_NONE) { // Iterate over list of items that were updated by server for (UpdatedItemIdIndex=0; UpdatedItemIdIndex < UpdatedItemIds.Length; UpdatedItemIdIndex++) { FoundItemIndex = SaveSlots[SaveSlotIndex].Items.Find('InstanceItemId', UpdatedItemIds[UpdatedItemIdIndex]); if (FoundItemIndex != INDEX_NONE && SaveSlots[SaveSlotIndex].Items[FoundItemIndex].Quantity == 0 && SaveSlots[SaveSlotIndex].Items[FoundItemIndex].QuantityIAP == 0) { SaveSlots[SaveSlotIndex].Items.Remove(FoundItemIndex,1); } } } } // Notify anyone waiting on this OnSellItemComplete( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ItemRequests[Index].ItemId, UpdatedItemIds, bWasSuccessful, Response.GetContentAsString()); `Log("Sell item:" $" McpId=" $ ItemRequests[Index].McpId $" SaveSlot=" $ ItemRequests[Index].SaveSlotId $" ItemId=" $ ItemRequests[Index].ItemId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); ItemRequests.Remove(Index,1); } } /** * Earn item and add it to the user's inventory * Server determines the transaction is valid * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot that the item is in * @param GlobalItemId id for the item that is being earned * @param Quantity number of the items that were earned * @param StoreVersion current known version # of backend store by client */ function EarnItem(string McpId, string SaveSlotId, string GlobalItemId, int Quantity, int StoreVersion) { local string Url; local HttpRequestInterface Request; local int AddAt,ExistingIndex; // Check for pending request ExistingIndex = FindItemRequest(McpId,SaveSlotId,GlobalItemId,ItemRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ EarnItemUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId $ "&globalItemId=" $ GlobalItemId $ "&quantity=" $ Quantity $ "&storeVersion=" $ StoreVersion; // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("POST"); Request.OnProcessRequestComplete = OnEarnItemRequestComplete; // Store off the data for reporting later AddAt = ItemRequests.Length; ItemRequests.Length = AddAt + 1; ItemRequests[AddAt].McpId = McpId; ItemRequests[AddAt].SaveSlotId = SaveSlotId; ItemRequests[AddAt].ItemId = GlobalItemId; ItemRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start EarnItem web request for URL(" $ Url $ ")"); } `Log("Earn item URL is " $ Url); } } else { `Log("Already have a pending item request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId $" GlobalItemId="$ GlobalItemId); } } /** * Called once the request/response has completed. Used to process the earn item result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnEarnItemRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index; local int ResponseCode; local string ResponseString; local array UpdatedItemIds; // Search for the corresponding entry in the array Index = ItemRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { ResponseString = Response.GetContentAsString(); // Parse the JSON payload for the new item values UpdatedItemIds = ParseInventoryForSaveSlot( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ResponseString); } // Notify anyone waiting on this OnEarnItemComplete( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ItemRequests[Index].ItemId, UpdatedItemIds, bWasSuccessful, Response.GetContentAsString()); `Log("Earn item:" $" McpId=" $ ItemRequests[Index].McpId $" SaveSlot=" $ ItemRequests[Index].SaveSlotId $" ItemId=" $ ItemRequests[Index].ItemId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); ItemRequests.Remove(Index,1); } } /** * Consume item and remove it's consumed quantity from the user's inventory * Server determines the transaction is valid * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot that the item is in * @param InstanceItemId id for the item that is being consumed * @param Quantity number of the items that were consumed * @param StoreVersion current known version # of backend store by client */ function ConsumeItem(string McpId, string SaveSlotId, string InstanceItemId, int Quantity, int StoreVersion) { local string Url; local HttpRequestInterface Request; local int AddAt,ExistingIndex; // Check for pending request ExistingIndex = FindItemRequest(McpId,SaveSlotId,InstanceItemId,ItemRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ ConsumeItemUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId $ "&instanceItemId=" $ InstanceItemId $ "&quantity=" $ Quantity $ "&storeVersion=" $ StoreVersion; // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("POST"); Request.OnProcessRequestComplete = OnConsumeItemRequestComplete; // Store off the data for reporting later AddAt = ItemRequests.Length; ItemRequests.Length = AddAt + 1; ItemRequests[AddAt].McpId = McpId; ItemRequests[AddAt].SaveSlotId = SaveSlotId; ItemRequests[AddAt].ItemId = InstanceItemId; ItemRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start ConsumeItem web request for URL(" $ Url $ ")"); } `Log("Consume item URL is " $ Url); } } else { `Log("Already have a pending item request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId $" InstanceItemId="$ InstanceItemId); } } /** * Called once the request/response has completed. Used to process the consume item result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnConsumeItemRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index; local int ResponseCode; local string ResponseString; local array UpdatedItemIds; // Search for the corresponding entry in the array Index = ItemRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { ResponseString = Response.GetContentAsString(); // Parse the JSON payload for the new item values UpdatedItemIds = ParseInventoryForSaveSlot( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ResponseString); } // Notify anyone waiting on this OnConsumeItemComplete( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ItemRequests[Index].ItemId, UpdatedItemIds, bWasSuccessful, Response.GetContentAsString()); `Log("Consume item:" $" McpId=" $ ItemRequests[Index].McpId $" SaveSlot=" $ ItemRequests[Index].SaveSlotId $" ItemId=" $ ItemRequests[Index].ItemId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); ItemRequests.Remove(Index,1); } } /** * Delete item and remove it from the user's inventory * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot that the item is in * @param InstanceItemId id for the item that is being deleted * @param StoreVersion current known version # of backend store by client */ function DeleteItem(string McpId, string SaveSlotId, string InstanceItemId, int StoreVersion) { local string Url; local HttpRequestInterface Request; local int AddAt,ExistingIndex; // Check for pending request ExistingIndex = FindItemRequest(McpId,SaveSlotId,InstanceItemId,ItemRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ DeleteItemUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId $ "&instanceItemId=" $ InstanceItemId $ "&storeVersion=" $ StoreVersion; // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("DELETE"); Request.OnProcessRequestComplete = OnDeleteItemRequestComplete; // Store off the data for reporting later AddAt = ItemRequests.Length; ItemRequests.Length = AddAt + 1; ItemRequests[AddAt].McpId = McpId; ItemRequests[AddAt].SaveSlotId = SaveSlotId; ItemRequests[AddAt].ItemId = InstanceItemId; ItemRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start DeleteItem web request for URL(" $ Url $ ")"); } `Log("Delete item URL is " $ Url); } } else { `Log("Already have a pending item request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId $" InstanceItemId="$ InstanceItemId); } } /** * Called once the request/response has completed. Used to process the delete item result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnDeleteItemRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index,SaveSlotIndex,ItemIndex; local int ResponseCode; // Search for the corresponding entry in the array Index = ItemRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { // Clear out the item entry if it exists SaveSlotIndex = FindSaveSlotIndex( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId); if (SaveSlotIndex != INDEX_NONE) { ItemIndex = SaveSlots[SaveSlotIndex].Items.Find('InstanceItemId', ItemRequests[Index].ItemId); if (ItemIndex != INDEX_NONE) { SaveSlots[SaveSlotIndex].Items.Remove(ItemIndex,1); } } } // Notify anyone waiting on this OnDeleteItemComplete( ItemRequests[Index].McpId, ItemRequests[Index].SaveSlotId, ItemRequests[Index].ItemId, bWasSuccessful, Response.GetContentAsString()); `Log("Delete item:" $" McpId=" $ ItemRequests[Index].McpId $" SaveSlot=" $ ItemRequests[Index].SaveSlotId $" ItemId=" $ ItemRequests[Index].ItemId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); ItemRequests.Remove(Index,1); } } /** * Record an IAP (In App Purchase) by sending receipt to server for validation * Results in list of items being added to inventory if successful * * @param McpId the id of the user that made the request * @param SaveSlotId the save slot that the item(s) will be placed in once validated * @param Receipt IAP receipt to validate the purchase on the server */ function RecordIap(string McpId, string SaveSlotId, string Receipt) { local string Url; local HttpRequestInterface Request; local int AddAt,ExistingIndex; // Check for pending request ExistingIndex = FindSaveSlotRequest(McpId,SaveSlotId,SaveSlotRequests); if (ExistingIndex == INDEX_NONE) { Request = class'HttpFactory'.static.CreateRequest(); if (Request != None) { Url = GetBaseURL() $ IapRecordUrl $ GetAppAccessURL() $ GetUserAuthURL(McpId) $ "&uniqueUserId=" $ McpId $ "&saveSlotId=" $ SaveSlotId; // Build our web request with the above URL Request.SetURL(Url); Request.SetVerb("POST"); Request.OnProcessRequestComplete = OnRecordIapRequestComplete; Request.SetContentAsString(Receipt); Request.SetHeader("Content-Type","multipart/form-data"); // Store off the data for reporting later AddAt = SaveSlotRequests.Length; SaveSlotRequests.Length = AddAt + 1; SaveSlotRequests[AddAt].McpId = McpId; SaveSlotRequests[AddAt].SaveSlotId = SaveSlotId; SaveSlotRequests[AddAt].Request = Request; // Now kick off the request if (!Request.ProcessRequest()) { `Log("Failed to start RecordIap web request for URL(" $ Url $ ")"); } `Log("Iap record URL is " $ Url); } } else { `Log("Already have a pending record IAP request for" $" McpId="$ McpId $" SaveSlotId="$ SaveSlotId); } } /** * Called once the request/response has completed. Used to process the record iap result * and notify any registered delegate * * @param Request the request object that was used * @param Response the response object that was generated * @param bWasSuccessful whether or not the request completed successfully */ function OnRecordIapRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful) { local int Index; local int ResponseCode; local string ResponseString; local array UpdatedItemIds; // Search for the corresponding entry in the array Index = SaveSlotRequests.Find('Request', Request); if (Index != INDEX_NONE) { ResponseCode = `HTTP_STATUS_SERVER_ERROR; if (Response != None) { ResponseCode = Response.GetResponseCode(); } // Both of these need to be true for the request to be a success bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK; if (bWasSuccessful) { ResponseString = Response.GetContentAsString(); // Parse the JSON payload for the new item values UpdatedItemIds = ParseInventoryForSaveSlot( SaveSlotRequests[Index].McpId, SaveSlotRequests[Index].SaveSlotId, ResponseString); } // Notify anyone waiting on this OnRecordIapComplete( SaveSlotRequests[Index].McpId, SaveSlotRequests[Index].SaveSlotId, UpdatedItemIds, bWasSuccessful, Response.GetContentAsString()); `Log("Iap record:" $" McpId=" $ SaveSlotRequests[Index].McpId $" SaveSlot=" $ SaveSlotRequests[Index].SaveSlotId $" Successful=" $ bWasSuccessful $" ResponseCode=" $ ResponseCode ); SaveSlotRequests.Remove(Index,1); } }