• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Remove hero skills by selling towers.

Status
Not open for further replies.
Level 2
Joined
May 15, 2019
Messages
14
Hello, I am currently making my first map which is a tower defense. The concept of one of my available races is to play a hero and build towers to improve him.

Among the available towers, there is a tower that gives the Chain Lightning spell to my hero and then it is possible to upgrade this tower to increase the level of the spell.
So there are 3 towers; Book of Knowledge which can be upgraded to Chain of Lightning - Level 1 which can be upgraded to Chain of Lightning - Level 2.
Here is how the tower works (I have no issue with this trigger at the moment, I just thought it might be useful to share):

  • Upgrades
    • Events
      • Unit - A unit Finishes an upgrade
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of (Triggering unit)) Equal to Thunder Clap - Level 1
        • Then - Actions
          • Unit Group - Pick every unit in (Units owned by (Owner of (Triggering unit)) of type Battle Golem) and do (Actions)
            • Loop - Actions
              • Unit - Add Thunder Clap to (Picked unit)
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Unit-type of (Triggering unit)) Equal to Thunder Clap - Level 2
            • Then - Actions
              • Unit Group - Pick every unit in (Units owned by (Owner of (Triggering unit)) of type Battle Golem) and do (Actions)
                • Loop - Actions
                  • Unit - Set level of Thunder Clap for (Picked unit) to 2
            • Else - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Unit-type of (Triggering unit)) Equal to Chain Lightning - Level 1
                • Then - Actions
                  • Unit Group - Pick every unit in (Units owned by (Owner of (Triggering unit)) of type Battle Golem) and do (Actions)
                    • Loop - Actions
                      • Unit - Add Chain Lightning to (Picked unit)
                • Else - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • (Unit-type of (Triggering unit)) Equal to Chain Lightning - Level 2
                    • Then - Actions
                      • Unit Group - Pick every unit in (Units owned by (Owner of (Triggering unit)) of type Battle Golem) and do (Actions)
                        • Loop - Actions
                          • Unit - Set level of Chain Lightning for (Picked unit) to 2
                    • Else - Actions
                      • Do nothing

Where I get into trouble is when I try to do the sales trigger.

This is what my trigger looks like:
  • Selling towers
    • Events
      • Unit - A unit Begins casting an ability
    • Conditions
      • (Ability being cast) Equal to Sell
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of (Casting unit)) Equal to Chain Lightning - Level 1
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Number of living Chain Lightning - Level 1 units owned by (Owner of (Casting unit))) Less than or equal to 1) and ((Number of living Chain Lightning - Level 2 units owned by (Owner of (Casting unit))) Less than or equal to 0)
            • Then - Actions
              • Unit Group - Pick every unit in (Units owned by (Owner of (Casting unit)) of type Battle Golem) and do (Actions)
                • Loop - Actions
                  • Unit - Remove Chain Lightning from (Picked unit)
            • Else - Actions
              • Do nothing
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Unit-type of (Casting unit)) Equal to Chain Lightning - Level 2
            • Then - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • ((Number of units in (Units owned by (Owner of (Casting unit)) of type Chain Lightning - Level 1)) Less than or equal to 0) and ((Number of units in (Units owned by (Owner of (Casting unit)) of type Chain Lightning - Level 2)) Less than or equal to 0)
                • Then - Actions
                  • Unit Group - Pick every unit in (Units owned by (Owner of (Casting unit)) of type Battle Golem) and do (Actions)
                    • Loop - Actions
                      • Unit - Remove Chain Lightning from (Picked unit)
                • Else - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • (Number of units in (Units owned by (Owner of (Casting unit)) of type Chain Lightning - Level 1)) Greater than or equal to 1
                    • Then - Actions
                      • Unit Group - Pick every unit in (Units owned by (Owner of (Casting unit)) of type Battle Golem) and do (Actions)
                        • Loop - Actions
                          • Unit - Set level of Chain Lightning for (Picked unit) to 1
                    • Else - Actions
                      • Do nothing
            • Else - Actions
              • Do nothing

So this trigger allows me that when I sell my tower (whether level 1 or 2), if I already had an identical tower or a tower of a lower level, the spell will either be removed from the hero or downgraded to level 1. It's when I try to sell more than one level 1 tower at the same time that the skill doesn't get removed from the hero.


I don't know if I'm clear enough in what I'm trying to do, tell me if you don't understand.

If you have a better way to do it or a solution, I'm all ears.

Thanks in advance for those who will take the time to help me.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,562
I recommend looking into Arrays and figuring out how to take advantage of Variables that use Player Numbers in their Arrays. This is especially important for tower defense maps where Players are all practically the same. In your map's case I imagine each player controls a Single hero or Builder (Battle Golem for this race) so let's start by keeping track of that.

Create a Unit variable and tick the Array checkbox. This makes the Variable an Array and adds these [] brackets to it.
Call this Variable something like PlayerHero.

Now we need to Set this variable when the Hero is first created:
  • Events:
  • Unit - A unit Enters playable map area
  • Conditions:
  • (Triggering unit) is a Hero Equal to True
  • Actions:
  • Set Variable PlayerHero[Player number of (Owner of Triggering unit)] = Triggering unit

This variable gives us access to each Player's Hero at any time. All you have to do is plug in the desired player's number into the [Index] of PlayerHero[] and you'll get that player's hero. So PlayerHero[1] will get you Player 1's hero, PlayerHero[2] will get you Player 2's hero, etc...

Now in your Upgrades trigger you can remove the Pick Every Unit stuff and simply reference the Hero directly:
  • Unit - Add Thunder Clap to PlayerHero[Player number of (Owner of Triggering unit)]

Now for the Selling stuff I would personally use a Hashtable and create a more elaborate system for it all.
Ideally, you would design it in a way that you can store Integer counts of each tower belonging to a player's race.

Essentially, the number of each player's Thunder Clap and Chain Lightning towers would be tracked in some Integer variables:
  • Set Variable ThunderClapLvl1_Count[1] = ThunderClapLvl1_Count[1] + 1

These Variables would increase (or decrease) when the Tower is created/upgraded. So when you build a Thunder Clap lvl 1 tower you would increase it's corresponding Integer by 1 and when you Sell it or upgrade it you would decrease it's variable by 1. Then after doing the arithmetic you can simply check something along the lines of this to determine what should happen to the hero's ability:
  • // first remove the sold tower, then check the unit counts
  • Unit - Remove Triggering unit from the game
  • If ThunderClapLvl2_Count[1] == 0 then
  • Unit - Set level of Thunderclap for PlayerHero[1] to 1
  • Else if ThunderClapLvl1_Count[1] == 0 then
  • Unit - Remove Thunderclap from PlayerHero[1]

Now I wouldn't do EXACTLY that, like I said I'd use a Hashtable and design it in a way that I could have a single trigger and a single For Loop to work for every single Tower in the game. If you're interested in that I could try and throw a system together for you using Hashtables but it'll be a rather intermediate/advanced design.

Edit: All of that stuff aside, your Sell trigger needs to be structured differently in order for it to work. You should first remove the Sold tower then check the unit counts:
  • Unit - Remove Triggering unit from the game
  • If Number of living Thunderclap - Level 2 Equal to 0 then
  • Unit - Set level of Thunderclap to 1 for hero
  • ELSE If Number of living Thunderclap - Level 1 Equal to 0 then
  • Unit - Remove Thunderclap from hero

Also, I don't see you removing the Tower in your Sell trigger. There's a chance that the Tower would still be alive when the trigger runs which would cause problems. Also, some of my triggers are written from memory so that's why they might look weird/different from the ones in the editor.
 
Last edited:
Level 2
Joined
May 15, 2019
Messages
14
I recommend looking into Arrays and figuring out how to take advantage of Variables that use Player Numbers in their Arrays. This is especially important for tower defense maps where Players are all practically the same. In your map's case I imagine each player controls a Single hero or Builder (Battle Golem for this race) so let's start by keeping track of that.

Create a Unit variable and tick the Array checkbox. This makes the Variable an Array and adds these [] brackets to it.
Call this Variable something like PlayerHero.

I hadn't thought of that! This will simplify the code a lot, thanks!

Now for the Selling stuff I would personally use a Hashtable and create a more elaborate system for it all.
Ideally, you would design it in a way that you can store Integer counts of each tower belonging to a player's race.
I had never heard of Hashtable before you mentioned it. I went to read the tutorials available here to understand a little more how it works.

So if I understand correctly, what I need to do is when a tower is created, a link between the variable and the handle id which in this context is the owner is created?

So this will allow to keep the count for each player independently? Because I found myself several times having to manage this problem where I had to separate triggers according to the player who triggered it.
If you're interested in that I could try and throw a system together for you using Hashtables but it'll be a rather intermediate/advanced design.
I prefer to dig into my own head and create my own system. You have already helped me enough by putting me on the right track, I thank you very much!

However, if I ever have any questions about this, would you allow me to ask you in a private message?

Also, I don't see you removing the Tower in your Sell trigger. There's a chance that the Tower would still be alive when the trigger runs which would cause problems.
This is because it is another trigger that removes the towers that are sold:
  • Sell
    • Events
      • Unit - A unit Begins casting an ability
    • Conditions
      • (Ability being cast) Equal to Sell
    • Actions
      • Special Effect - Create a special effect attached to the overhead of (Casting unit) using UI\Feedback\GoldCredit\GoldCredit.mdl
      • Sound - Play ItemReceived <gen> at 50.00% volume, attached to (Casting unit)
      • Wait 0.50 seconds
      • Player - Add (Point-value of (Casting unit)) to (Owner of (Casting unit)).Current gold
      • Unit - Remove (Triggering unit) from the game
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Casting unit)) Equal to Player 1 (Red)
        • Then - Actions
          • Game - Display to Player Group - Player 1 (Red) the text: (You recover + ((|cffffcc00 + (String((Point-value of (Casting unit))))) + |r gold|r from your tower.))
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Casting unit)) Equal to Player 2 (Blue)
        • Then - Actions
          • Game - Display to Player Group - Player 2 (Blue) the text: (You recover + ((|cffffcc00 + (String((Point-value of (Casting unit))))) + |r gold|r from your tower.))
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Casting unit)) Equal to Player 3 (Teal)
        • Then - Actions
          • Game - Display to Player Group - Player 3 (Teal) the text: (You recover + ((|cffffcc00 + (String((Point-value of (Casting unit))))) + |r gold|r from your tower.))
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Casting unit)) Equal to Player 4 (Purple)
        • Then - Actions
          • Game - Display to Player Group - Player 4 (Purple) the text: (You recover + ((|cffffcc00 + (String((Point-value of (Casting unit))))) + |r gold|r from your tower.))
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Casting unit)) Equal to Player 5 (Yellow)
        • Then - Actions
          • Game - Display to Player Group - Player 5 (Yellow) the text: (You recover + ((|cffffcc00 + (String((Point-value of (Casting unit))))) + |r gold|r from your tower.))
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Owner of (Casting unit)) Equal to Player 6 (Orange)
        • Then - Actions
          • Game - Display to Player Group - Player 6 (Orange) the text: (You recover + ((|cffffcc00 + (String((Point-value of (Casting unit))))) + |r gold|r from your tower.))
        • Else - Actions

what do you mean by "sell more than one"? Like hold down shift and queue up multiple sell commands?
Exactly.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,562
Feel free to PM me whenever.

So there's a good chunk of things I want to address. First, you can see the problem with your 2 Sell Triggers. The one that Removes the unit has a 0.50 second delay. The one that checks for Unit Counts (If number of living...) runs immediately. The casting tower needs to be Removed before you make a Unit Count check (since it's still alive), so you need to combine these two triggers into one. And what you said about Hashtables/linking towers to their owners is basically correct. I imagine it'd be very useful to keep track of the count of each tower for each player.

Also, you're entering dangerous territory whenever you use the Wait action:
  • Wait 0.50 seconds
This is because a lot of Event Responses can only be set to one thing at a time. Whenever their corresponding Event fires they are set to the unit/player/item/etc that the Event references, but this applies globally so these references change for every single trigger using them.
For example:

Event: A unit begins casting an ability
Event Response: Casting unit

Whenever ANY unit begins casting an ability the CASTING UNIT Event Response will change.
This means that if your trigger has any delays such as ones created by a Wait:
  • Wait 0.50 seconds
  • Player - Add (Point-value of (Casting unit)) to (Owner of (Casting unit)).Current gold
Then you're leaving yourself room for the CASTING UNIT to change during that Waiting period.

So let's say Player 1 begins casting Sell on their Tower, but then 0.49 seconds later Player 2 begins casting Sell.
Player 1 isn't going to get the Gold from Selling his tower because the Owner of CASTING UNIT is now equal to Player 2.

This thread helps explain the various behaviors of Events/Event Responses: Event Response Myths

So in order to fix your Sell trigger you have various options but the easiest one for now is to replace all instances of CASTING UNIT with TRIGGERING UNIT.

Also, delete all of those If Then Else statements. All you need is a single line:
  • Game - Display to (Player group((Owner of (Triggering unit)))) the text: (You recover + ((|cffffcc00 + (String((Point-value of (Triggering unit))))) + |r gold|r from your tower.))

Ideally, you'd NEVER have a long list of If Then Else statements. It's not always bad depending on the situation, but there's almost always a cleaner and better solution. Whether that be Arrays or Hashtables, putting in the work early to get these Variables setup and tracked makes ALL of your future triggers extremely easy to manage. That being said, sometimes you can get away with using Event Responses to make your single line of code work for all players like I did in that Display Text Message action I posted above.
 
Last edited:
Level 2
Joined
May 15, 2019
Messages
14
Thanks again for all the advices!

I don't even remember why I added the wait line in the script!

Ideally, you'd NEVER have a long list of If Then Else statements. It's not always bad depending on the situation, but there's almost always a cleaner and better solution. Whether that be Arrays or Hashtables, putting in the work early to get these Variables setup and tracked makes ALL of your future triggers extremely easy to manage. That being said, sometimes you can get away with using Event Responses to make your single line of code work for all players like I did in that Display Text Message action I posted above.
I try my best to do that but since I'm new to this, sometimes I have to take the long way around to get a result. But with tips like this, I can only get better, thanks!
 
Status
Not open for further replies.
Top