How to Implement a Custom Skin Purchase and Equip System with Save/Load Support in a MOBA Map?

Hello, everyone!

I'm currently developing a MOBA map in Warcraft III called "Defense of the Core", and I'm looking to implement a custom skin system where players can purchase and equip skins using a custom currency (similar to systems like Direct Strike or Tower Survivors). Additionally, I want to make this system persistent, meaning that skins purchased by players are saved and loaded between matches.

Key Features I Want to Implement:​

  1. Skin Purchase System:
    • Players can purchase skins for their heroes or units using a custom currency.
    • This could be done through custom frames (if possible) or a unit/shop system.
  2. Skin Management:
    • A system to track which skins each player has purchased.
    • Ability for players to equip and change skins during gameplay.
  3. Save/Load System for Skins:
    • I already use the Codeless Save/Load System to save and load players' MMR between matches, but I don't know how to extend this system to include:
      • Which skins a player has purchased.
      • Which hero/unit each skin belongs to.
      • Whether a skin is currently equipped by the player.
  4. The system should also load the player's currency, alongside their purchased skins, at the start of each match.

My Main Questions:​

  • How can I adapt the Codeless Save/Load System to handle the saving and loading of purchased skins and their equipped states?
  • Should I use custom UI frames or stick to a unit-based shop system for the skin purchasing interface?
  • Are there existing templates, tutorials, or best practices for implementing a persistent skin system like this?

Additional Information:​

  • My map is called Defense of the Core, and it's an open-source project. If anyone is interested, you're free to take a look or even try implementing this system yourself! However, I would greatly appreciate it if you could teach me how it works so I can maintain and expand it in the future.
  • I'm comfortable working with triggers, variables, and GUI, but I have limited experience with JASS/LUA.
  • Any advice, code examples, or resources would be greatly appreciated!
Thank you in advance for your help!
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,866
The Codeless system can only Save and Load Integers, so the logic isn't that difficult when you think within those constraints. For example, look at how TriggerHappy saved Items in his demo map. You don't save the Item-Type Id or the Item-Type variable directly, instead, you save the Integer that represents the [INDEX] of that Item-Type. Like so:
  • Set Variable Savable_Item_Types[4] = Boots of Speed
^ So if you wanted to Save the fact that a hero has Boots of Speed, you would Save the value 4. When it comes time to Load that information you can simply plug that saved value 4 back into the Savable_Item_Types array to get the actual Boots of Speed.

In your case you need to make an Integer represent a Skin. What is a skin, though? I assume you planned on using different types of Units to represent these. Alternatively, there's the relatively new Set Unit Skin function which you could use but it has some weird side effects that you probably want to avoid.

So sticking with GUI and assuming that you want to rely on alternate Unit-Types to represent Skins here's what I would do:
  • Events
    • Time - Elapsed game time is 0.00 seconds
  • Conditions
  • Actions
    • Set Variable Hero_Unit_Type = Paladin
    • Set Variable Hero_Skin_Type[1] = Paladin (Uther)
    • Set Variable Hero_Skin_Type[2] = Paladin (Arthas)
    • Set Variable Hero_Skin_Count = 2
    • Trigger - Run Store Hero Skins (ignoring conditions)
    • Set Variable Hero_Unit_Type = Archmage
    • Set Variable Hero_Skin_Type[1] = Archmage (Antonidas)
    • Set Variable Hero_Skin_Count = 1
    • Trigger - Run Store Hero Skins (ignoring conditions)
  • Store Hero Skins
    • Events
    • Conditions
    • Actions
      • Custom script: set udg_Hero_Id = udg_Hero_Unit_Type
      • Hashtable - Save Hero_Skin_Count as 0 of Hero_Id in HeroSkinHashtable
      • For each Integer Skin_X from 1 to Hero_Skin_Count do (Actions)
        • Loop - Actions
          • Custom script: set udg_Skin_Id = udg_Hero_Skin_Type[udg_Skin_X]
          • Hashtable - Save Skin_Id as Skin_X of Hero_Id in HeroSkinHashtable
(Don't forget to create the Hashtable as well)

You now have a database that links a hero's Unit-Type Id to what's essentially an Array of it's associated Skins:
Parent Key = Hero Unit-Type Id.
Child Key 0 = How many skins that hero has.
Child Key 1+ = The actual skins (alternate Unit-Type Ids).

IMPORTANT NOTE: To simplify the save code, which may be necessary to avoid limits, I would recommend storing all of these Unit-Types into one central Array so that you can instead Save/Load the [indexes] in that Array as opposed to the literal Unit-Type Ids which are often large integers in the millions. So the same idea as what I described with the Item example earlier.

Next, you're going to need to track the Skins that a Player owns inside of this Hashtable or inside of a new Hashtable if that's easier. This tracking would be done on an individual Player basis whenever a Skin is purchased and when a save file is Loaded at the start of the game.

When it comes time to Save a save file, you would reference the Player's Hashtable data to determine what you need to Save. This would likely be done by looping over the data found within the Player's Hashtable and Saving the value found there (0 or 1). Your last equipped skin could simply be an Integer that represents an [index] in an Array (like the Item example from earlier).

How you manage the displaying of these Skins is entirely subjective and up to you. Custom UI will obviously be the most flexible design but it comes with a complexity cost, so only do it if you're confident in your programming capabilities and understand how it works.

TLDR: Just get into the mindset that you're working with Integers and that an Integer can represent literally anything. Combine these Integers with Arrays and Hashtables to store important data. Remember that a Boolean is just an Integer in which 0 represents FALSE and 1 represents TRUE. Use this logic to manage the saving/loading of the ownership of Hero Skin data (0 = Unowned, 1 = Owned) as well as which Skin you last equipped (An [index] in an Array that represents a Skin). Remember that Integers have a default value of 0, unless you define a different "Initial value", and this can be taken advantage of to ensure that a new user will not own any skins or have any equipped.
 
Last edited:
The system can only save and load Integers, so the logic isn't that difficult when you think within those constraints. For example, look at how TriggerHappy saved Items in his demo map. You don't save the Item-Type Id or the Item-Type variable directly, instead, you save the Integer that represents the [INDEX] of that Item-Type. Like so:
  • Savable_Item_Types[4] = Boots of Speed
^ So if you wanted to Save that a hero has Boots of Speed, you would Save the value [4].

In your case you need to make an Integer represent a Skin. What is a skin, though? I assume you planned on using different types of Units to represent these. Alternatively, there's the relatively new Set Unit Skin function which you could use but it has some weird side effects that you probably want to avoid.

So sticking with GUI and assuming that you want to rely on alternate Unit-Types to represent Skins here's what I would do:
  • Events
    • Time - Elapsed game time is 0.00 seconds
  • Conditions
  • Actions
    • Set Variable Hero_Unit_Type = Paladin
    • Set Variable Hero_Skin_Type[1] = Paladin (Uther)
    • Set Variable Hero_Skin_Type[2] = Paladin (Arthas)
    • Set Variable Hero_Skin_Count = 2
    • Trigger - Run Store Hero Skins (ignoring conditions)
    • Set Variable Hero_Unit_Type = Archmage
    • Set Variable Hero_Skin_Type[1] = Archmage (Antonidas)
    • Set Variable Hero_Skin_Count = 1
    • Trigger - Run Store Hero Skins (ignoring conditions)
  • Store Hero Skins
    • Events
    • Conditions
    • Actions
      • Custom script: set udg_Hero_Id = udg_Hero_Unit_Type
      • Hashtable - Save Hero_Skin_Count as 0 of Hero_Id in HeroSkinHashtable
      • For each Integer Skin_X from 1 to Hero_Skin_Count do (Actions)
        • Loop - Actions
          • Custom script: set udg_Skin_Id = udg_Hero_Skin_Type[udg_Skin_X]
          • Hashtable - Save Skin_Id as Skin_X of Hero_Id in HeroSkinHashtable
You now have a database that links a hero's Unit-Type Id to what's essentially an Array of it's associated Skins:
Parent Key = Hero Unit-Type Id.
Child Key 0 = How many skins that hero has.
Child Key 1+ = The actual skins (alternate Unit-Type Ids)
(Don't forget to create the Hashtable as well)



Next, you're going to need to track the Skins that a Player owns inside of this Hashtable or a new Hashtable. This tracking would be done on an individual basis whenever a Skin is purchased and when a save file is Loaded at the start of the game.

When it comes time to Save a save file, you would reference the Player's Hashtable data to determine what you need to Save. This would likely be done by looping over the data found within the Player's Hashtable and Saving the value found there (0 or 1). Your last equipped skin could simply be an Integer that represents an [index] in an Array (like the Item example from earlier).

How you manage the displaying of these Skins is entirely subjective and up to you. Custom UI will obviously be the most flexible design but it comes with a complexity cost, so only do it if you're confident in your programming capabilities and understand how it works.

TLDR: Just get into the mindset that you're working with Integers and that an Integer can represent literally anything. Combine these Integers with Arrays and Hashtables to store important data. Remember that a Boolean is just an Integer in which 0 represents FALSE and 1 represents TRUE. Use this logic to manage the saving/loading of the ownership of Hero Skin data (0 = Unowned, 1 = Owned) as well as which Skin you last equipped (An [index] in an Array that represents a Skin). Remember that Integers have a default of 0, unless you define a different "Initial value", and this can be taken advantage of to ensure that a new user will not own any skins or have any equipped.
I think I understand, although I am now in a complicated situation, of all that I have been learning about GUI, the only thing I still do not understand is the use of Hashtables. I would appreciate it if you could explain to me in depth how to implement it in the system that you have been helping me to do for a while.
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,866
You could try to recreate the two triggers in my last post, they show you how to use a Hashtable.

Just don't forget to actually create the Hashtable and store it in a variable, I left this step out:
  • Hashtable - Create a hashtable
  • Set Variable HeroSkinHashtable = (Last created hashtable)
There are tutorials which will likely explain it better than I ever could.


Anyway, maybe this explanation will help as well. I find the easiest way to understand a Hashtable is to think of it as a 2D Array. You already know how to use 1D Arrays so it shouldn't be too difficult to take it a step further.

This is a 1D array which you're likely used to:
  • Set Variable My_Array_Integer[4] = 1000
And this a 2D array which may be new to you:
  • Set Variable My_2D_Array_Integer[4][3] = 1000
^ It's nothing too crazy, you're simply using two [indexes] to store data instead of one [index]. The D stands for dimensions -> two-dimensional array.

This is much more powerful than a 1D Array since you can store way more data and avoid overwriting data. Here's an example:

Let's use the first [index] of our 2D Array to represent a Player Number and the second [index] to behave like a normal 1D Array:
  • Set Variable Player_Hero_Type[1][1] = Paladin
  • Set Variable Player_Hero_Type[1][2] = Mountain King
  • Set Variable Player_Hero_Type[1][3] = Archmage
^ This is tracking Player 1's Hero-Types in the Array.

Let's also track Player 4's Hero-Types using this method:
  • Set Variable Player_Hero_Type[4][1] = Blademaster
  • Set Variable Player_Hero_Type[4][2] = Far Seer
  • Set Variable Player_Hero_Type[4][3] = Tauren Chieftain
None of this data will conflict with one another. Once that makes sense, it's time to move on.

So unfortunately we do not actually have 2D arrays in the Trigger Editor, the above was just an example. Instead, we have Hashtables which like I said are very similar. They're even more powerful than a 2d array but with that power comes complexity.

Here's an example similar to the last two triggers but using an actual Hashtable:
  • Set Variable Hero_Type = Paladin
  • Custom script: set udg_Hero_Id = udg_Hero_Type
  • Hashtable - Save Hero_Id as 1 of 1 in MyNewHashtable
  • -------- ---------
  • Set Variable Hero_Type = Mountain King
  • Custom script: set udg_Hero_Id = udg_Hero_Type
  • Hashtable - Save Hero_Id as 2 of 1 in MyNewHashtable
  • -------- ---------
  • Set Variable Hero_Type = Archmage
  • Custom script: set udg_Hero_Id = udg_Hero_Type
  • Hashtable - Save Hero_Id as 3 of 1 in MyNewHashtable
Storing values in a Hashtable uses the terms Saving and Loading, which is actually a little inconvenient since we're talking about saving/loading files in this same thread. But understand that Saving in this case is basically the same thing as Setting a variable. You're storing some data so that you can keep track of it, or "saving it for later".

Note that this is a more advanced example of using a Hashtable because I'm using a conversion technique. I'm converting the Unit-Type variable, Hero_Type, into an Integer using Custom Script. The Custom Script uses an Integer variable called Hero_Id to convert the Unit-Type into it's Integer form with a simple = sign. A lot of object types in Warcraft 3 can be converted into an Integer this way or back to their original form. You can often Save/Load data in a single (Hashtable - Action) instead of relying on 3.

Now if you wanted to Load this data, in other words reference it later on in some other trigger, you would do something like this:
  • Set Variable Hero_Id = (Load 2 of 1 in MyNewHashtable)
  • Custom script: set udg_Hero_Type = udg_Hero_Id
  • Unit - Create 1 Hero_Type for Player 1 (Red) at (Center of (playable map area))
We've reversed the conversion process here when loading our Hashtable data, making Hero_Type act like a standard Unit-Type variable again and setting it to use the Unit-Type Id of the Mountain King - which was previously Saved at "2 of 1" in the Hashtable. In other words, this trigger will create a Mountain King at the center of the map, since that's what we saved at "2 of 1" in our Hashtable.

I'm getting a little tired here but hopefully this all makes sense. Maybe come back to this post after reading a more beginner friendly tutorial on Hashtables. Good luck!
 
Last edited:
You could try to recreate the two triggers in my last post, they show you how to use a Hashtable.

Just don't forget to actually create the Hashtable and store it in a variable, I left this step out:
  • Hashtable - Create a hashtable
  • Set Variable HeroSkinHashtable = (Last created hashtable)
There are tutorials which will likely explain it better than I ever could.


Anyway, maybe this explanation will help as well. I find the easiest way to understand a Hashtable is to think of it as a 2D Array. You already know how to use 1D Arrays so it shouldn't be too difficult to take it a step further.

This is a 1D array which you're likely used to:
  • Set Variable My_Array_Integer[4] = 1000
And this a 2D array which may be new to you:
  • Set Variable My_2D_Array_Integer[4][3] = 1000
^ It's nothing too crazy, you're simply using two [indexes] to store data instead of one [index]. The D stands for dimensions -> two-dimensional array.

This is much more powerful than a 1D Array since you can store way more data and avoid overwriting data. Here's an example:

Let's use the first [index] of our 2D Array to represent a Player Number and the second [index] to behave like a normal 1D Array:
  • Set Variable Player_Hero_Type[1][1] = Paladin
  • Set Variable Player_Hero_Type[1][2] = Mountain King
  • Set Variable Player_Hero_Type[1][3] = Archmage
^ This is tracking Player 1's Hero-Types in the Array.

Let's also track Player 4's Hero-Types using this method:
  • Set Variable Player_Hero_Type[4][1] = Blademaster
  • Set Variable Player_Hero_Type[4][2] = Far Seer
  • Set Variable Player_Hero_Type[4][3] = Tauren Chieftain
None of this data will conflict with one another. Once that makes sense, it's time to move on.

So unfortunately we do not actually have 2D arrays in the Trigger Editor, the above was just an example. Instead, we have Hashtables which like I said are very similar. They're even more powerful than a 2d array but with that power comes complexity.

Here's an example similar to the last two triggers but using an actual Hashtable:
  • Set Variable Hero_Type = Paladin
  • Custom script: set udg_Hero_Id = udg_Hero_Type
  • Hashtable - Save Hero_Id as 1 of 1 in MyNewHashtable
  • -------- ---------
  • Set Variable Hero_Type = Mountain King
  • Custom script: set udg_Hero_Id = udg_Hero_Type
  • Hashtable - Save Hero_Id as 2 of 1 in MyNewHashtable
  • -------- ---------
  • Set Variable Hero_Type = Archmage
  • Custom script: set udg_Hero_Id = udg_Hero_Type
  • Hashtable - Save Hero_Id as 3 of 1 in MyNewHashtable
Storing values in a Hashtable uses the terms Saving and Loading, which is actually inconvenient since we're talking about saving/loading files in this same thread. But understand that Saving in this case is basically the same thing as Setting a variable. You're storing some data so that you can keep track of it, or "saving it for later".

Note that this is a more advanced example of using a Hashtable because I'm using a conversion technique. I'm converting the Unit-Type variable, Hero_Type, into an Integer using Custom Script. The Custom Script uses an Integer variable called Hero_Id to convert the Unit-Type into it's Integer form with a simple = sign. A lot of object types in Warcraft 3 can be converted into an Integer this way or back to their original form. You can often Save/Load data in one Action instead of needing 3.

Now if you wanted to Load this data, in other words reference it later on in some other trigger, you would do something like this:
  • Set Variable Hero_Id = (Load 2 of 1 in MyNewHashtable)
  • Custom script: set udg_Hero_Type = udg_Hero_Id
  • Unit - Create 1 Hero_Type for Player 1 (Red) at (Center of (playable map area))
We've reversed the conversion process here when loading our Hashtable data, making Hero_Type act like a standard Unit-Type variable again and setting it to use the Unit-Type Id of the Mountain King - which was previously Saved at "2 of 1" in the Hashtable. In other words, this trigger will create a Mountain King at the center of the map, since that's what we saved at "2 of 1" in our Hashtable.

I'm getting a little tired here but hopefully this all makes sense. Maybe come back to this post after reading a more beginner friendly tutorial on Hashtables. Good luck!
I really appreciate the time and the explanation! I will try to continue on my own with what you have explained to me, thank you very much.
 
Top