- Joined
- Jul 11, 2018
- Messages
- 4
INTRODUCTION
Long post incoming since I intend to make it as friendly as possible for learning mapmakers, but I'm trying to make it easier to read and shuffle through by dividing it into topics. This thread will explain how to use WorldEditor's new object-altering triggers to create multiple and subjective layers of string object data.
For applications, as a lone mind I cannot possibly even think of being exhaustive enough, therefore I'll only provide the example of hidden enemy inventories (a feature left to be desired by Blizzard since ticking off the Gameplay Constants value hides it for everyone, not just enemies).
Feel free to post ideas for more applications and ask me to list them here. I will, however, not make more demonstration maps.
BACKGROUND
I'm sure most (if not every single one) of you are aware of the recent patches' additions to World Editor. Personally, the fact that Blizzard is picking up Warcraft III again to update fills me with joy and hope. You wouldn't expect a megacompany to invest time and money in an old game without plans for the future, of course, and that is enough to fill my nights with dreams of a Remastered version, just like what happened to Starcraft.
Amongst the delicious JASS and vJASS changes, there was one addition that I think went partly unnoticed, one set of functions that even received the attention of GUI triggering as actions. That is, of course the ability to straightforward alter object data using triggers during the course of a game. This can be done, for example, by changing an Hero's name with "Hero - Set Hero Proper Name", or a unit's damage output with "Unit - Set Base Damage".
However, I'm here to explain that using the object editor through triggers as you play a map is only a portion of what those functions have to offer.
Also, please note that this is a new finding using new functions and, as such, it may be prone to bugs and error. Although I can claim with safety that I've tested it in all ways I could think of without encountering problems, one tester is never enough.
THE GetLocalPlayer() FUNCTION
For those of you familiar with JASS, you might've already figured out where I'm going with this just by looking at the topic's name. If you did, be my guest to skip it and jump right to the next one, where I go into more detail about how this MultiNaming thing works.
And for those that stayed, I request patience for this brief, reductionistic and pragmatic explanation: GetLocalPlayer() is a short JASS native that may be used to run separate sets of actions for different players in the same game.
If, for example, one lists GetLocalPlayer() == Player(0) as a condition for a trigger, it will only run for Player 1 (in JASS Player(0) means the first player). Note that I'm not saying it will run the trigger taking into consideration only Player 1, but that all its actions will only execute in Red/Player 1's instance of the game. So, in short, for the other players not detected as "Player 1" it would be as if that trigger never ran at all.
This, of course, is as dangerous as it looks, since running things differently for players can cause serious problems relating to disconnections and desyncs. If you command a script to create units only after checking if the local player is Red, for example, it will surely lead to desynchronization with the resulting gameplay-altering data unpairing (one local instance of the game will have more units than the others).
I will opt to not go into much detail as how to safely use GetLocalPlayer() for all intents and purposes, partly because I've already found threads devoted to the subject and it not being the goal of this post (its objective instead being the announcement of my findings & demonstrating how they may be applied), and partly because I myself am still doing research on the matter.
MultiNaming
Despite all the desync problems GetLocalPlayer() might cause when dealing with most handles and other dangerous stuff, it is relatively safe to use and may develop into a wonderful tool when employed to some non-agents, such as names and descriptions of some objects like abilities and items.
This is what I conveniently choose to call here MultiNaming - the technique of using the GetLocalPlayer() native to have objects carrying multiple names in different local instances of the same game session. As simple as it sounds, it may be used for a diversity of features, from simply having resources be available in multiple languages at the same time to making players not able to read the items an enemy possesses in his inventory.
In order to achieve this, you merely have to invoke the GetLocalPlayer() native as a condition and run the naming script matching the player whose local instance you want to change. In GUI, it can be done as simply as through the use of two Custom Script actions before and after the desired actions. For example:
-
Custom script: if GetLocalPlayer() == Player(8) then
-
Custom script: set udg_LocalPlayerName = GetPlayerName(GetLocalPlayer())
-
Game - Set Tooltip of UltimateAbility to LocalPlayerName for level 1
-
Custom script: endif
Here's an example of how this may be done to have the same exact ability, in the same exact game, display descriptions in English and Portuguese, for different players (even though there is no Portuguese WC3 client):
HIDDEN ENEMY INVENTORY
Warcraft III's World Editor has a Gameplay Constant called "Enemy Inventory Display" that may be ticked as True or False. However, setting the value as False will hide an hero's inventory from all other players, regardless if they are enemies or not.
This can be circumvented by using the MultiNaming technique herein introduced to change the name, description and iconpath of all items an enemy Hero picks up, restoring their original data only when they are dropped. This, of course, is done only to a player locally through the use of the GetLocalPlayer() native.
As I've mentioned in the WARNING section, doing this in version 1.29 of Warcraft III is dangerous and leads to all items of the same type being renamed. This can be shown in the following line of 1.30's bug correction changelog:
[World Editor] Renaming an Item no longer changes all Items’ names
Note that this could be done much easier by using the renaming functions whenever the enemy hero is selected instead. But sadly it doesn't work that way since the flag from unit selection EVENT_PLAYER_UNIT_SELECTED, takes too long to be recognized after a player selects an unit, giving him more than enough time to glance at the items. Now you wouldn't want a player to have 0,5 seconds to look at the items before they are masked, would you?
This can be avoided by using MultiNaming when a Hero picks/drops an item, and there are really only three things you need to change in order to hide an enemy's inventory: the item's name, its icon and its description. Those are the only pieces of information displayed when you select enemy Heroes to check their inventories - thus, changing just them is more than enough.
1. HIDING ITEM ON PICKUP
Ideally, you'd want to do this every time a Hero picks up an item, locally renaming an item's attributes for all players that Hero's owner has as an enemy. Sounds complex? Well, not quite. It may be done by simply setting a trigger up like this, in GUI:
-
Events
-
Unit - A unit Acquires an item
-
-
Conditions
-
Actions
-
Player Group - Pick every player in (All players) and do (Actions)
-
Loop - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
((Owner of (Hero manipulating item)) is an enemy of (Picked player)) Equal to True
-
-
Then - Actions
-
Custom script: if GetLocalPlayer() == GetEnumPlayer() then
-
Item - Set Name of (Item being manipulated) to Unknown Item
-
Item - Set Extended Tooltip of (Item being manipulated) to You do not know what item this is!
-
Item - Set Icon Path of (Item being manipulated) to ReplaceableTextures \CommandButtons\BTNSelectHeroOff.blp
-
Custom script: endif
-
-
Else - Actions
-
-
-
-
Good, good, now whenever a player uses his units to pick up an item, it will be hidden for all those he sees as an enemy. But we're not done yet, now we want to make it so that this masked item of ours may be unmasked. This could be done at any time, but for now, let's say we want to unmask an item whenever an Hero drops it.
2. SHOWING ITEM UPON DROP
For this, we would need a way to access the item's data, since for the player it was hid from it would be permanently lost. We could create variables to store information such as an item's name and description everytime it is masked, but we can be smarter than that - instead, it would be easier to just create another item of the same type as the masked item, copy its data to the item to be unmasked, and then remove the item we just created.
We'd end up with a GUI trigger like this:
-
Events
-
Unit - A unit Loses an item
-
-
Conditions
-
Actions
-
Player Group - Pick every player in (All players) and do (Actions)
-
Loop - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
((Owner of (Hero manipulating item)) is an enemy of (Picked player)) Equal to True
-
-
Then - Actions
-
Item - Create (Item-type of (Item being manipulated)) at ((Center of (Playable map area)) offset by (99999.00, 99999.00))
-
Item - Hide (Last created item)
-
Custom script: if GetLocalPlayer() == udg_PlayerToMask then
-
Item - Set Name of (Item being manipulated) to (Name of (Last created item))
-
Item - Set Description of (Item being manipulated) to (Description of (Last created item))
-
Item - Set Icon Path of (Item being manipulated) to (Icon of (Last created item))
-
Custom script: endif
-
Item - Remove (Last created item)
-
-
Else - Actions
-
-
-
-
If you don't want to run a trigger every time an item is picked up, you may set a condition to only fire it whenever the item being picked up is a masked item. But beware! Since the trigger deals with Creating/Removing items, you'd need to make sure the status of an item as masked or unmasked is tracked globally, so that the above trigger would still fire for all players - thus avoiding desyncs. In order to not make this tutorial longer than it already intends to be, I will not be thorough in explaining how to achieve this. Instead, I'll upload a map containing an example of how it may be done using the same item's tooltip (useless data for our purposes, since it is only displayed when the item in question is being viewed in a shop).
3. THE CHARGED ITEMS ISSUE
Hold up, though! Before you go on using this in your maps, there is one small obstacle to overcome. Here, see this?
This happens when an item with charges is picked up. Now, depending on the kind of map you're making, this may not be a problem. If it is, though, don't worry, there's an easy solution available, and that is because you can also locally change an item's number of charges without leading to desync. So, you merely have to return to the trigger you just made for whenever a player picks up an item and insert the following action into the GetLocalPlayer() scope:
-
Item - Set charges remaining in (Item being manipulated) to 0
But that's not all! You see, by doing this we create another problem: in Warcraft III, whenever a charged item with only one remaining charge is used, it is removed from the game. This won't happen automatically to the player we just hid the charged item from - so, if an enemy Hero uses an item with a single charge making it go away, it'll still exist for our local player, causing a serious desync problem!
So, in order to fix that we need to globally and constantly store information relative to an item's charge. In the demonstration map I uploaded here, I do that by assigning that information to the item's custom value, and making it so an item is removed from the game if its custom value ever reaches zero (therefore balancing the number of agents in each local instance of the game before a desync can occur).
In order to achieve this in our example, simply go to the trigger we've just made that fires whenever an Hero picks up an item and, as its first and foremost action (also before the start of the GetLocalPlayer() scope), have a function to set its Custom Value to its charges:
-
Item - Set the custom value of (Item being manipulated) to (Charges remaining in (Item being manipulated))
-
Item - Set charges remaining in (Item being manipulated) to (Custom value of (Item being manipulated))
-
Events
-
Unit - A unit Uses an item
-
-
Conditions
-
Conditions
-
(Item-class of (Item being manipulated)) Equal to Charged
-
(Item-class of (Item being manipulated)) Equal to Purchasable
-
-
-
Actions
-
Item - Set the custom value of (Item being manipulated) to ((Custom value of (Item being manipulated)) - 1)
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
(Custom value of (Item being manipulated)) Less than or equal to 0
-
-
Then - Actions
-
Item - Remove (Item being manipulated)
-
-
Else - Actions
-
-
4. CONCLUSION
With all of that, you're up and set to go with your custom-made hidden inventory! Should you wish to apply this only to determined items, to a few chosen heroes or to some allies as well, you can easily do this by adding more conditions to the first two triggers I've exposed. Of course, since there are numerous ways to adapt the Hidden Enemy Inventory example herein demonstrated, I leave this homework to yourself, dear reader.
Finally, in case your map possesses dynamic alliances, make sure to make an additional trigger to hide all items currently held by Heroes for all enemies whenever allies are changed. There's a viewer-friendly GUI functionality for this in the test map I have attached to this thread, if you want to check it out for yourself.
Hope you've all enjoyed this little trick with GetLocalPlayer() and the renaming functions recently added to Warcraft III's World Editor. For any feedback or clarifications, make sure to leave a comment here.
Good luck, have fun, and enjoy the test map!
Attachments
Last edited: