Mods IAP Shop Mod - BLKTower/TestWiki GitHub Wiki

Table of Contents

Mods\IAP Shop Mod

The IAP Shop Mod is used to create and manage in-game shop UI for IAP and currency spending.

Features

  • Create and manage in-game shop page UI
  • Hookup shop items as IAP or custom currency (such as gems)
  • Customizable UI for shop items via XML
  • Supports portrait and landscape games

Need a feature prioritized or a feature missing from this list? Let @coffee know!

Download the demo maps HERE or play them on the arcade:

landscape

Usage Instructions

  • To use this mod, add the shop registry module to your project using the latest version ^1.0.0.

Shop Mod 2.0.0 Compatibility

Shop Mod 2.0.0 has some significant changes that break compatibility with earlier versions. Here are the key changes to look out for when upgrading from an earlier version.

Shop Methods

For ease of use, the shop layout (as created by NewShopUI will be referenced as ui.shop throughout this documentation.

The following functions can be used such as:

local shop = require("shop")

local ui = {
  root = DCEI.GetUiRoot()
}

-- product definitions
local product_list = {
  {
    product_id = "com.wildsky.example.iap.gold01",
    label = "Handful of Gold",
    display = "shop_gold_lv2",
    price = { usd = 2.99 },
    items = { gold = 1000 }
  },
  {
    product_id = "com.wildsky.example.iap.gold02",
    label = "Barrel of Gold",
    display = "shop_gold_lv4",
    price = { usd = 4.99 },
    items = { gold = 5000 }
  }
}

-- initialize IAP items
shop.InitializeIAP({
  on_purchase_attempt_callback = AttemptAnyItemPurchase,
  on_purchase_success_callback = OnAnyItemPurchase,
  product_list = product_list,
})

-- creates a new shop UI
ui.shop = shop.NewShopUI({ parent = ui.root })

-- initializes a shop with a "Gold" section and gold items
local shop_data = {
  {
    name = "gold",
    display_name = "Gold",
    rows = {
      {
        "com.wildsky.example.iap.gold01",
        "com.wildsky.example.iap.gold02",
      }
    }
  }
}

ui.shop:Initialize(shop_data)

-- show the shop after it's been initialized
ui.shop:Show()

ui.shop:Show()

Shows the in-game shop UI.

ui.shop:Hide()

Hides the in-game shop UI. Note that is automatically called when the close button (ui.shop.CloseButton) on the upper right of the shop UI is pressed.

ui.shop:Initialize( table shop_data )

Initializes the shop layout and generates the shop's sections and starting items. The shop_data parameter should be a list of section_data tables that describe each shop section.

In portrait games, shop sections are created in a vertical list separated by banners. In landscape games, shop sections are created as individual shop tabs. Whether a shop is landscape or portrait is automatically determined by the resolution of the game window.

Example Usage

local shop_data = {
  {
    name = "bundles",
    display_name = "Value Packs",
    navigation = {
      use_display_name = true,
    },
    rows = {
      {
        "com.wildsky.test.iap.bundle.starter01"
      }
    },
    custom = {
      layout = "Portrait/Shop/Section_StarterPack"
    }
  },
  {
    name = "chests",
    display_name = "Chests",
    navigation = {
      use_display_name = true,
    },
    display_timer = {
      label = "Next free chest in:"
    },
    rows = {
      {
        "com.wildsky.test.shop.chest01",
        "com.wildsky.test.shop.chest02"
      }
    }
  },
}

ui.shop:Initialize(shop_data)

table section_data Parameters

  • string name defines the section name so it can be referenced elsewhere.
  • string display_name determines the text to display on the shop section banner. This is a separate value from name so that it can use localized text.
  • table rows a list of row tables that determine the items displayed in each row.
  • (optional) table items a list of product id's to display that can be used instead of rows when a section contains only one row (such as for landscape shops).
  • (optional) bool scrollable if true, sets the section to be scrollable horizontally. Only works with landscape shops. Useful for displaying more than 5 items per row.
  • (optional) table navigation used in portrait shops to generate a navigation button for the section with additional parameters.
    • (optional) bool use_display_name if true, the navigation button will use the section's display name.
    • (optional) string display_name if set, the navigation button will use this text. Cannot be used with use_display_name
    • (optional) string icon if set, the navigation button will display this icon in addition to any displayed text.
  • (optional) table display_timer creates a timer display for the shop section that counts down to the next server day.
    • string label sets the text of the timer display.
  • (optional) table custom used to define custom attributes of the section.
    • (optional) string layout the name of the XML layout to use for this shop section.
    • (optional) string layout_func this function will be run using the section layout as self when the section is created.

ui.shop:AddItemsToSection( string section_name, table items_list, table options )

Dynamically adds items to a shop section. By default, these items will added to the current row for landscape shops and added to a new row for portrait shops.

Items added to the shop in this way can be set to expire at a given time using the expires_at and expires_in options, which will save the expiry time of the item to player save data. An item's expiration time can be cleared with ClearItemTimerData(). Note that item expiry time is tracked with local OS time and NOT with server time, and thus can used without needing to call InitializeServer().

If an item is given an expiry time and its layout contains a frame with the id TimerFrame, it will attempt to hookup and activate the TimerFrame to display the time remaining. If the timer frame has a TimerValueLabel text frame, this text will display the remaining time using FormatTimeDHM() formatting. If the timer frame has a ClockHand frame, it will become animated. The included UI templates Landscape/Template/TimerFrame and Portrait/Template/TimerFrame are for this expressed purpose.

Example Usage

-- adds an item that will expire in 3 days
local day_length_in_seconds = 86400
local options = { expires_in = day_length_in_seconds * 3 }
ui.shop:AddItemsToSection("bundles", { "com.wildsky.test.iap.bundle.starter01" }, options)

Parameters

  • string section_name the section to add the items to.
  • table items_list a list of product id's to add to the section.
  • (optional) table options
    • (optional) number expires_at if set, the items will be removed from the shop at the time of the given unix timestamp.
    • (optional) number expires_in if set, the items will be removed from the shop after this many seconds have passed.
    • (optional) number row if set, the items will be added to the specified row index.

ui.shop:RemoveItems( table items_list )

Dynamically removes items from the shop. Rows with no items will be removed. Sections with no items will be hidden until they have items again.

Example Usage

local items_list = {
  "com.wildsky.test.iap.bundle.starter01",
  "com.wildsky.test.iap.bundle.starter02",
}
ui.shop:RemoveItems(items_list)

ui.shop:RemoveAllItemsFromSection( string section_name )

Dynamically removes all items from the specified shop section.

Example Usage

ui.shop:RemoveAllItemsFromSection("gold")

ui.shop:RemoveAllItems()

Dynamically removes ALL items from the shop.

Example Usage

ui.shop:RemoveAllItems()

ui.shop:AddTimerUpdateFunction( string timer_id, function update_function )

Adds an update function to the shop's update timer. The shop's update timer is a Real Timer that pauses itself when the shop is hidden and resumes itself when the shop is shown. Any update functions added in this way will be executed each time the shop's update timer ticks, using the global tick rate.

This can be useful for creating your own custom timer displays or otherwise want to constantly update something while the shop is open.

Example Usage

local TIME_SPENT_WITH_SHOP_OPEN = 0
local GLOBAL_TICK_RATE = 0.0625

local timer_id = "shop_time_tracking_timer"
function UpdateShopTimeTracking()
  TIME_SPENT_WITH_SHOP_OPEN = TIME_SPENT_WITH_SHOP_OPEN + GLOBAL_TICK_RATE
end

-- adds a timer that tracks the total time the shop has been open
ui.shop:AddTimerUpdateFunction(timer_id, UpdateShopTimeTracking)

ui.shop:RemoveTimerUpdateFunction( string timer_id )

Removes an existing update function from the shop's update timer.

Example Usage

-- stop tracking the total time the shop has been open
local timer_id = "shop_time_tracking_timer"
ui.shop:RemoveTimerUpdateFunction(timer_id)

Popup Methods

For ease of use, the popup layout (as created by NewPopupUI will be referenced as ui.popup throughout this documentation.

The following functions can be used such as:

local shop = require("shop")

local ui = {
  root = DCEI.GetUiRoot()
}

-- product definitions
local product_list = {
  {
    product_id = "com.wildsky.example.iap.gold01",
    label = "Handful of Gold",
    display = "shop_gold_lv2",
    price = { usd = 2.99 },
    items = { gold = 1000 }
  },
  {
    product_id = "com.wildsky.example.iap.gold02",
    label = "Barrel of Gold",
    display = "shop_gold_lv4",
    price = { usd = 4.99 },
    items = { gold = 5000 }
  }
}

-- initialize IAP items
shop.InitializeIAP({
  on_purchase_attempt_callback = AttemptAnyItemPurchase,
  on_purchase_success_callback = OnAnyItemPurchase,
  product_list = product_list,
})

-- creates a new popup UI
ui.popup = shop.NewPopupUI()

-- shows a popup with the "Handful of Gold" item
local items_list = { "com.wildsky.example.iap.gold01" }
local options = { display_name = "Deal of the Day" }
ui.popup:Show(items_list, options)

ui.popup:Show( table items_list, table options )

Shows the UI popup with the given items and display options.

Example Usage

local items_list = { "com.wildsky.example.iap.gold01" }
local options = { display_name = "Deal of the Day" }
ui.popup:Show(items_list, options)

Parameters

  • table items_list a list of product id's to add to the popup.
  • (optional) table options
    • (optional) string display_name determines the banner text to display on the popup. The default text is "Daily Deals".

ui.popup:Update( table items_list )

Removes all existing items in the popup and replaces them with the given items.

Example Usage

local items_list = { "com.wildsky.example.iap.gold02" }
ui.popup:Update(items_list)

Parameters

  • table items_list a list of product id's to update the popup with.

ui.popup:Hide()

Hides the popup. Note that is automatically called when the close button (ui.popup.CloseButton) on the upper right of the popup UI is pressed.

ui.popup:AddTimerUpdateFunction( string timer_id, function update_function )

Adds an update function to the popup's update timer. Functions the same as the shop update timer, but instead pauses when the popup is hidden.

ui.popup:RemoveTimerUpdateFunction( string timer_id )

Removes an existing update function from the popup's update timer.

Item Methods

These methods can be used on item layouts in the shop or popup UI. Use shop.GetItemLayout() to get an item's shop layout and shop.GetItemLayoutFromPopup() to get an item's popup layout.

item:Remove()

Removes an item, similiar to calling ui.shop.RemoveItems() with a single product id.

Example Usage

local product_id = "com.wildsky.example.iap.gold01"
local item = shop.GetItemLayout(product_id)
item:Remove()

item:ShowPip()

Shows a red pip on the top left of item's layout and the item's section banner. All pips are cleared when the shop is closed.

item:HidePip()

Hide's an item's red pip.

Mod Functions

These are the functions exported by the shop mod. They can be used such as:

local shop = require("shop")

local ui = {
  root = DCEI.GetUiRoot()
}

-- creates and shows the shop UI
ui.shop = shop.NewShopUI({ parent = ui.root })

-- initialize IAP data
shop.InitializeIAP({
  on_purchase_attempt_callback = AttemptAnyItemPurchase,
  on_purchase_success_callback = OnAnyItemPurchase,
  product_list = GetProductList(),
  currency_list = CURRENCIES
})

InitializeIAP( table iap_data )

Initializes IAP from IAP data. This requires a product list, functions for purchase attempt and purchase success, and can optionally initialize currencies. This should be called in OnMapStart().

Example Usage

local shop = require("shop")

local ui = {
  root = DCEI.GetUiRoot()
}

-- product definitions
local product_list = {
  {
    product_id = "com.wildsky.example.iap.gold01",
    label = "1,000 Gold",
    display = "shop_gold_lv2",
    price = { usd = 2.99 },
    items = { gold = 1000 }
  },
  {
    product_id = "com.wildsky.example.iap.gold02",
    label = "5,000 Gold",
    display = "shop_gold_lv4",
    price = { usd = 4.99 },
    items = { gold = 5000 }
  }
}

-- currency definitions
local currency_list = {
  {
    name = "gold",
    icon = "icon_item_coin"
  }
}

function AttemptAnyItemPurchase(product_id)
  -- if your game has products that can be purchased for in-game currency or by watching ads, you should include the purchase attempt logic for those items here

  if DCEI.Platform == "WindowsPlayer" then
    -- simulate successful purchase when using editor
    -- NOTE: shop.OnAnyItemPurchase() calls the on_purchase_success_callback after updating the item's layout and stock data
    shop.OnAnyItemPurchase(product_id)
  else
    -- otherwise continue standard IAP flow
    -- NOTE: DCEI.PurchaseIapProduct() takes a few seconds to execute and thus acts similarly to a DCEI.Wait() so avoid subsequent code in the same thread
    DCEI.PurchaseIapProduct(product_id)
  end
end

function OnAnyItemPurchase(product_id)
  local item_data = shop.GetProductData(product_id)

  -- deliver purchased items to player
  local items = item_data:GetItemsData()
  if items and items.gold then
    local player_id = 1
    DCEI.AddGold(player_id, items.gold)
  end
end

-- initialize IAP data
shop.InitializeIAP({
  on_purchase_attempt_callback = AttemptAnyItemPurchase,
  on_purchase_success_callback = OnAnyItemPurchase,
  product_list = product_list,
  currency_list = currency_list
})

-- creates a new shop UI
ui.shop = shop.NewShopUI({ parent = ui.root })

-- initializes a shop with a "Gold" section and gold items
local shop_data = {
  {
    name = "gold",
    display_name = "Gold",
    rows = {
      {
        "com.wildsky.example.iap.gold01",
        "com.wildsky.example.iap.gold02",
      }
    }
  }
}

ui.shop:Initialize(shop_data)

-- show the shop after it's been initialized
ui.shop:Show()

table iap_data Parameters

  • function on_purchase_attempt_callback this function is called when an item purchase button is pressed, with the item's product_id as the first argument. See Purchase Attempt Function for more details.
  • function on_purchase_success_callback this function is called when DCEI.PurchaseIapProduct(product_id) succeeds, with the item's product_id as the first argument. See Purchase Success Function for more details.
  • table product_list the list of IAP in item_data format.
  • (optional) table currency_list the list of currencies used in the shop.
  • (optional) bool is_game_localized if set, uses localized text for all built-in text values of the shop instead of the hardcoded english text (ex: DHM formatting). Set this to true if your game is using localization.

OnAnyItemPurchase( string product_id )

Use this function instead of the on_purchase_success_callback defined in InitializeIAP() when you need to directly call your item purchase function, such as when simulating successful purchase in editor play mode or when successfully purchasing items that cost in-game currency or watching ads.

This function will update the item's layout and stock data before calling the locally defined on_purchase_success_callback.

InitializeServer( table server_data )

Initializes server time tracking from the given data. This is useful for creating items that refresh stock on a daily basis or rotate depending on the day of the week. In server data you can define a callback that occurs whenever server time is fetched or a new day occurs, along with some additional debugging options.

A new server day occurs at 6AM PST. Note that the editor uses your local OS time as it's not connected to the game server.

Once initalized, the shop will attempt to fetch server time whenever any of the following occurs:

  1. When ui.shop:Initialize() is called
  2. When a new server day occurs (at 6AM PST)
  3. The next time the shop is opened IF the previous fetch failed

You can manually call a shop fetch server attempt with AttemptGetServerTime().

table server_data Parameters

  • function on_server_time_update_callback this function is called whenever server time is successfully fetched, regardless of whether a new day has occurred. This is where you want to add "daily deal" items to the shop, though take care to not add duplicate items.
  • function on_new_server_day_callback this function is called when a new server day is detected. This typically occurs when the shop is initialized during the player's first session of the day but can also occur at 6AM PST. This where you want to advance any "daily deal" index and reset the stock limit of items that refresh on a daily basis.
  • (optional) table options
    • (optional) number update_delay this debug option will add a delay on shop fetch server time calls to simulate network latency.
    • (optional) bool update_fails this debug option will cause all shop fetch server time calls to fail.
    • (optional) bool use_os_time this debug option will cause shop fetch server time calls to use local OS time. This is only useful for publishing to arcade, where server time currently fails.

Example Usage

function OnShopFetchNewDay()
  -- reset daily ads chest
  local product_id = "com.wildsky.test.shop.chest01"
  local layout = shop.GetItemLayout(product_id)

  shop.ClearItemPurchaseData(product_id)
  layout:Update()

  ui.shop.sections["chests"].timer_display:Reset()
end

-- initialize server data
shop.InitializeServer({
  on_new_server_day_callback = OnShopFetchNewDay 
})

AttemptGetServerTime()

Manually attempts to fetch server time for shop server time tracking using the data given in InitializeServer(). If successful this will call the on_server_time_update_callback. If successful and a new day has occurred since the last server time check, this will call the on_new_server_day_callback.

NewShopUI( transform parent, table options )

Initializes and returns the shop UI layout. If no parent frame is specified by options.parent, the shop will be created in ui.root. By default, whether a shop is landscape or portrait is automatically determined by the resolution of the game window.

The shop UI must be initialized before any shop methods can be used.

Parameters

  • (optional) table options
    • (optional) tranform parent sets the parent frame for the shop UI.
    • (optional) bool is_landscape if true, the shop will be created using landscape mode regardless of game resolution. If false, the shop will be created using portrait mode regardless of game resolution.

NewPopupUI( table options )