Staredit Network > Forums > SC1 UMS Mapmaking Assistance > Topic: Manipulating health/resources over time and recursive triggers
Manipulating health/resources over time and recursive triggers
Aug 25 2017, 5:45 pm
By: bulletbutter  

Aug 25 2017, 5:45 pm bulletbutter Post #1



I want to say that I remember playing a survival map that had a feature that healed a unit over time, but I can't be certain. Digging through the triggers I am not seeing any solution jumping out at me at the moment. So I'll just ask here if the following is possible.

1. Heal over time - I want to be able to bring a unit to a location and slowly regenerate its health. The only solution I have requires too many triggers and/or would make it seem to the player that its not working. Something like setting hit points to 1% when then enter a location, wait 10 seconds, set to 10% hit points...etc. The issue with that however is that if the player enters at 50%, they loose all but 1%, thus spending more time than they would need to in the healing location. If there is no other solution I fear I may be stuck with doing it this way.

2. Accumulate gas/minerals over time - Sort of the same thing as #1 but with gas/minerals instead of a units health. A player need to be able to accumulate gas/minerals at a specific rate. I've thought about just setting up a mining base but it takes up precious unit counts and the spacing on the map is limited as well. I have thought about making the following trigger but Im concerned that the wait() may cause major issues with other triggers? Also, the wait time will vary depending on how many "upgrades" a player has. I pretty much have it thought out and I am hoping it works but I just didn't know if there was an easier way that I am overlooking. Trying to keep trigger counts to a minimum.

Triggername
Players

  • Player 1
  • Conditions

  • Player 1 accumulates at most 20 gas
  • Actions

  • Set Gas for Player 1 to Add 1 gas
  • Wait (12000)
  • Perserve Trigger


  • 3. I have some 60+ triggers that I made that all do the same thing. Basically bring 1 unit to location 1, subtract minerals, change ownership from nuetral to current player.....but like I said, its 60+ triggers (thank god for Trigedit!). Other than using a custom trigger tool here on the forums that have been made is there no other way to quickly make the same trigger over and over when all you need to change is like 2 variables?



    None.

    Aug 25 2017, 11:25 pm Wormer Post #2



    Hello, bulletbutter!

    Number one, unfortunately, isn't possible using default trigger SC capabilities, because there is no legit way to detect an hp of a unit using normal triggers. It was possible using EUDs with 1.16 and probably still technically possible (I'm not quite into the state of EUD conditions with the current SC version), but in reality, even if it's still possible, believe me you wouldn't want to do this because it 1) will work only with the given game version on a given OS and 2) requires special low-level knowledge of programming.

    My recommendations would be to go with an old plain heal that restores 100% HP on demand. Don't do an animation from 1% to 100% because unit might get accidentally killed in low HP state.

    The number two. Don't use waits. Use death counters with hyper triggers instead. Basically if you put the following trigger then it makes all triggers run faster.
    Trigger("Player 1"){
    Conditions:
    Actions:
    Wait(0);
    Wait(0);
    // ... etc. 63 copies of wait.
    Preserve trigger();
    }

    Then you need to utilize deaths of an unused unit (like Cantina) for a timer like this:
    Trigger("Player 1"){
    Conditions:
    Actions:
    Set Deaths("Player 1", "Cantina", Subtract, 1); // It won't subtract past 0
    Preserve trigger();
    }

    Trigger("Player 1"){
    Conditions:
    Deaths("Player 1", "Cantina", Exactly, 0);
    Actions:
    Set Deaths("Player 1", "Cantina", Set To, 144);
    // 144 = 12*12, because 1 second is approximately 12 trigger cycles with hyper triggers as far as I remember
    Set Resources("Player 1", Add, 1, Ore)
    Preserve trigger();
    }


    Number three. There is no way to easily do it from SCMDraft, except to use text triggers. I can recommend you MacroTriggers (link) for that. In MacroTriggers you prepare a text file with the following contents:

    ARRAY Locations = { @L("Location 1"), @L("Location 2"), @L("Location 3") }
    ARRAY Units = { @U("Terran Marine"), @U("Zerg Zergling"), @U("Protoss Zealot") }
    ARRAY Cost = { 10, 20, 30 }
    }
    FOR l = 0 TO 2 DO
    FOR u = 0 TO 2 DO
    TRIGGER
    OWNERS: @P("Player 1")
    CONDITIONS:
    Bring(@Neutral, @AtLeast, 1, Units[u], Locations[l])
    Accumulate(@CurrentPlayer, @AtLeast, Cost[u], @Ore)
    ACTIONS:
    SetResources(@CurrentPlayer, @Subtract, Cost[u], @Ore)
    GiveUnits(@All, Units[u], @Neutral, Locations[l], @CurrentPlayer)
    PreserveTrigger()
    ENDT
    ENDL
    ENDL


    Then you feed that input to the program that gives you the following SCMD Triggers you copy-paste into TrigEdit:

    9 SCMD Triggers


    If you're interested I can explain more how to install and use MacroTriggers. It's advantage over the other trigger replication tools is probably that MacroTriggers are syntactically very similar to SCMD text triggers.

    Also have a look at TrigEdit++ (link), probably you might like it more, but it pretends you know basics of Lua.

    Post has been edited 3 time(s), last time on Aug 25 2017, 11:37 pm by Wormer.



    Some.

    Aug 26 2017, 5:21 pm bulletbutter Post #3



    Thanks Wormer!

    I do remember using hyper triggers back in the day. But do hyper triggers have to contain 62-64 waits? Will it have any effect if I have fewer? Will it have any effect if I just add wait(0) to all other triggers with the perserve trigger? Additionally, I was reading up about hyper triggers on the wiki and it did say that I could use hyper triggers and waits so long as a player in the game using a wait doesn't own a hyper trigger....do you know if this applied to neutral (p12)?

    And about the death counter "timer", you have two triggers. The frist one does not have a condition. I would assume that the condition would need to be "playerX" has suffered "exactly 1" deaths of "Cantina"? Would I then have to set up triggers to create and kill the cantina? I took a look at the wiki and it has similar triggers to what you posted but it doesn't say HOW the unit I picked is supposed to die and get created. Wiki Death Counter Timer



    None.

    Aug 26 2017, 6:01 pm Wormer Post #4



    Quote from bulletbutter
    I do remember using hyper triggers back in the day. But do hyper triggers have to contain 62-64 waits? Will it have any effect if I have fewer? Will it have any effect if I just add wait(0) to all other triggers with the perserve trigger? Additionally, I was reading up about hyper triggers on the wiki and it did say that I could use hyper triggers and waits so long as a player in the game using a wait doesn't own a hyper trigger....do you know if this applied to neutral (p12)?

    Yes they do need to have as many waits as a trigger can possibly have which is 63 plus one spot for Preserve Trigger(). Moreover, it's better to have 3 copies of such a trigger filled with waits.

    The exact answer to the question "Why does it work?" is complicated, but the idea is that every time SC encounters a wait action it reschedules the additional trigger loop to happen very fast after the current one is finished. Waits of different players indeed doesn't interfere, but they must be executed by SC engine and SC doesn't ever touch on triggers for players 9-12. Triggers also aren't executed for empty player slots or those that left the game. So it's safe to have a dedicated computer player with hyper triggers and use waits for players actions. But this is error-prone because subsystems for a single player that require waits and must work in parallel start to block each other.

    ADDITION: ---
    If you're making a small test map it's totally fine to use waits, but if you're into a somewhat solid project it's usually better to plan overhead and consistently use death counter "timers" for everything that needs to be delayed.
    ---

    When wait is encountered SC does more or less the following:
    1) remembers the action position in the trigger's action list;
    2) postpones the current trigger in an unfinished state;
    3) checks if a player-specific alarm of the trigger owner is free to use;
    4) if it does,
    4.1) sets the alarm to the amount specified in the wait;
    4.2) schedules an additional trigger check loop to immediately happen after a) the current one finishes and after b) SC makes 1 game step (incidentally due to the origin of the postfix operator++ in C++ the game happens to make 2 steps before the scheduled trigger check starts);
    5) when it does not,
    5.1) just postpones the trigger and continues with the next one.

    Post has been edited 1 time(s), last time on Aug 26 2017, 6:06 pm by Wormer.



    Some.

    Aug 26 2017, 6:21 pm Wormer Post #5



    Quote from bulletbutter
    And about the death counter "timer", you have two triggers. The frist one does not have a condition. I would assume that the condition would need to be "playerX" has suffered "exactly 1" deaths of "Cantina"? Would I then have to set up triggers to create and kill the cantina? I took a look at the wiki and it has similar triggers to what you posted but it doesn't say HOW the unit I picked is supposed to die and get created. Wiki Death Counter Timer

    The initial purpose of death counters is to remember number of unit deaths. However due to the fact that "deaths of a unit" could easily be checked and manipulated by triggers people started to use them as "general variables". For a unit to be suitable for that misused role it must not be ever killed on a map. If that's the case then only triggers are accountable for everything that happens to deaths of that unit.

    Cantina is perfect for this because it's a rarely used special unit. Also note that you have a separate unit death counter for each player. For instance Player 1 and Player 2 both have independent counters for Cantina. That is true even if Player 2 is never used in the game, doesn't have a Start Location, etc.

    Then finally no condition is the same as having a trigger with condition Always(). Conditions are just a list of statements for SC to check before it dives into execution of trigger's actions. The Always() record just unconditionally (that's cumbersome :lol:) says: "I'm true, go to the next one", while Never() says: "I'm false, skip the trigger". If there is no conditions to check, SC dives directly into actions.

    ADDITION:
    So the answer to your question is that Cantina isn't supposed to be created or killed on a map. What happens is that every time P1's deaths of Cantina reach zero something happens (a mineral is added) and it's deaths are raised to 144. The first trigger then gradually decreases that number back to zero which makes up the delay.

    Post has been edited 2 time(s), last time on Aug 26 2017, 6:29 pm by Wormer.



    Some.

    Aug 26 2017, 7:11 pm bulletbutter Post #6



    Thanks again! That actually clears up a lot of things. And my final question is that (in a programming sense) condition statements should be considered as AND (&&) and not OR (||)? So if a trigger has 5 conditions and one condition is false, the trigger defaults to FALSE and trigger will never fire?



    None.

    Aug 26 2017, 10:12 pm Wormer Post #7



    Quote from bulletbutter
    Thanks again! That actually clears up a lot of things. And my final question is that (in a programming sense) condition statements should be considered as AND (&&) and not OR (||)? So if a trigger has 5 conditions and one condition is false, the trigger defaults to FALSE and trigger will never fire?

    That's right: all conditions are connected with logical conjunction (AND). Conditions are treated from top to bottom in the list - the first false condition prevents the rest conditions from evaluation and makes SC skip trigger actions. Conditions that are disabled are of course skipped in the list too. If you're bored instead of putting Always() you can put a disabled Never() condition :teehee: with the same effect.

    Evaluation order can make difference in some corner cases.

    First it helps with efficiency to put conditions that check death counters and switches first. There are some ambitious projects with thousands triggers that suffer from trigger lag, so it's not a useless advice (and who doesn't want his small project to become big and popular some day? :D ).

    Then there are some glitchesfeatures related to created units aren't being detected at locations in the same trigger cycle. Let's say you got 2 different empty locations A and B that doesn't intersect. The following triggers will then surprisingly yield different results when you keep either blue or green trigger in place:

    Trigger("Player 1"){
    Conditions:
    Bring("Player 1", "Terran Marine", "Location A", Exactly, 0);
    Actions:
    Create Unit("Current Player", "Terran Marine", 1, "Location A");
    Preserve trigger();
    }

    Trigger("Player 1"){
    Conditions:
    Bring("Player 1", "Terran Marine", "Location A", Exactly, 0);
    Bring("Player 1", "Terran Marine", "Location B", Exactly, 0);
    Actions:
    Display Text Message(Always Display, "You will see this!");
    Preserve trigger();
    }


    Trigger("Player 1"){
    Conditions:
    Bring("Player 1", "Terran Marine", "Location B", Exactly, 0);
    Bring("Player 1", "Terran Marine", "Location A", Exactly, 0);
    Actions:
    Display Text Message(Always Display, "This text is never seen!");
    Preserve trigger();
    }


    Trigger("Player 1"){
    Conditions:
    Actions:
    Remove Unit At Location("Player 1", "Terran Marine", All, "Location A");
    Preserve trigger();
    }

    I bet sooo many people wracked their brain because of this! Probably it is easy to figure out in this example, but in complex projects with lost of triggers it's a nightmare to have. Try to guess why the first condition in the green trigger is true now!

    Additionally, if you need to check for conditions logical disjunction (OR) then it's not directly supported. The first thing that comes in mind is to make a trigger for each condition:

    Trigger("Player"){
    Conditions:
    Condition one();
    Actions:
    <Actions>
    }

    Trigger("Player"){
    Conditions:
    Condition two();
    Actions:
    <Actions>
    }

    Trigger("Player"){
    Conditions:
    Condition three();
    Actions:
    <Actions>
    }

    This is hard to maintain and error-prone because you have to repeat <Actions> as many times as you got conditions. We can do better:

    Trigger("Player"){
    Conditions:
    Actions:
    Set Switch("Switch 1", Clear);
    Preserve Trigger();
    }

    Trigger("Player"){
    Conditions:
    Condition one();
    Condition two();
    Condition three();
    Actions:
    Set Switch("Switch 1", Set);
    Preserve Trigger();
    }

    Trigger("Player"){
    Conditions:
    Switch("Switch 1", Cleared)
    Actions:
    <Actions>
    }

    The difference might not be obvious, but most important you don't have to repeat your actions 3 times. Also the trigger quantity will be somewhat less when you have 5 conditions and this fragment is put into like a 100-times replication loop.

    Post has been edited 1 time(s), last time on Aug 26 2017, 10:26 pm by Wormer.



    Some.

    Aug 27 2017, 2:58 pm Wormer Post #8



    Some more info about timings with hyper triggers (real time vs game time and numbers) in this post and below.



    Some.

    Aug 28 2017, 5:39 pm rockz Post #9

    ᴄʜᴇᴇsᴇ ɪᴛ!

    1) you can have medics nearby which heal the unit. They heal at 200/256 hp per frame, so it will heal fairly quickly. You can also heal significantly faster with a shield battery (1280/256 shield per frame). You can attempt to balance the HP of the map around this rate.

    2) As others have said, don't use waits. You can use the countdown timer (countdown from 15, give gas at 0?) or you can use hyper triggers and a death count to set it up. You can have multiple deathcounts set in the same trigger:

    Always
    Subtract 1 from Cantina
    Subtract 1 from Cave
    Subtract 1 from Cave-In

    And then you can set the "timer" in the trigger similar to how you'd use a wait:
    Player 1 accumulates at most 20 gas
    Player 1 suffered exactly 0 deaths of Cantina
    Set Gas for Player 1 to Add 1 gas
    Set Deaths of Cantina for Player 1 to 144
    Perserve Trigger

    The opposite is to always add death counts, but then you have to put the timer in the condition rather than the action. There are legitimate reasons for both methods, I just find subtracting easier.

    As for hyper triggers, Sacrieur created a single hyper trigger which uses 2 waits and 1 death counter.
    http://www.staredit.net/topic/16433/#1
    Quote from Sacrieur
    Trigger
    Players
  • All Players
  • Conditions
  • All Players has suffered exactly 0 deaths of Cave.
  • Actions
  • Modify death counts for Current Player: set to 1 for Cave.
  • Wait for 0 milliseconds.
  • Modify death counts for Current Player: set to 0 for Cave.
  • Wait for 0 milliseconds.
  • Preserve trigger.


  • Code
    Trigger("All players"){
    Conditions:
        Deaths("All players", "Cave", Exactly, 0);

    Actions:
        Set Deaths("Current Player", "Cave", Set To, 1);
        Wait(0);
        Set Deaths("Current Player", "Cave", Set To, 0);
        Wait(0);
        Preserve Trigger();
    }
    //-----------------------------------------------------------------//


    Any other unused unit may also be substituted for Cave.

    3) I use Devilisk's Text Trigger Duplicator to duplicate text triggers from Trigedit. I'm not able to find this application anymore, but Roy made a nifty program called Farlap which will replace multiple lines and use regex sort of replacement.

    Generally when I need to change a value or two a bunch of times I use excel and use find/replace to get the data back to a legible format.



    "Parliamentary inquiry, Mr. Chairman - do we have to call the Gentleman a gentleman if he's not one?"

    Aug 30 2017, 9:18 am Stranger Post #10



    Hi,

    In addition to what others said, and depending on your skill and preferences, you can also use unit or building timers which won't require "wait" actions, nor hyper triggers.

    For exemple :

    - Place a specific unit in a hidden location, then make it go all the way to a second location. The duration of the "timer" is based on the distance and the unit's speed. In order to save locations, you could place several units with different moving speed (going from a reaver to a speed ling.. flyers are also a good choice) on the same place and pick those you need, depending on players' upgrades.

    - Place a terran building in a hidden location, give it a round set of HP like 100 or 1000, then set it's current HP to a value from 1% to 30% (or so), so it starts losing HP because of the fire. When the building dies, timer ends.

    Well, Hypers are probably the best solution in most cases.

    I also agree with rockz's suggestion : you can use medics, shield batteries and SCVs to restore HP in a simple way, if this suits your map's mechanics.



    None.

    Aug 30 2017, 6:49 pm rockz Post #11

    ᴄʜᴇᴇsᴇ ɪᴛ!

    SCVs are actually interesting because you can modify the repair rate using the build time, but I don't know how to automatically make them repair in brood war.



    "Parliamentary inquiry, Mr. Chairman - do we have to call the Gentleman a gentleman if he's not one?"

    Aug 31 2017, 7:33 am Stranger Post #12



    Ah, I tried this and even asked about it but never found a way either. I always had to make them repair manually.
    Seems rather complicated (if possible).

    Quote
    because you can modify the repair rate using the build time

    Didn't know that, that's interesting indeed :D



    None.

    Options
      Back to forum
    Please log in to reply to this topic or to report it.
    Members in this topic: None.
    [2024-4-14. : 9:21 pm]
    O)FaRTy1billion[MM] -- there are some real members mixed in those latter pages, but the *vast* majority are spam accounts
    [2024-4-14. : 9:21 pm]
    O)FaRTy1billion[MM] -- there are almost 3k pages
    [2024-4-14. : 9:21 pm]
    O)FaRTy1billion[MM] -- the real members stop around page 250
    [2024-4-14. : 9:20 pm]
    O)FaRTy1billion[MM] -- look at the members list
    [2024-4-12. : 12:52 pm]
    Oh_Man -- da real donwano
    da real donwano shouted: This is the first time I've seen spam bots like this on SEN. But then again, for the last 15 years I haven't been very active.
    it's pretty common
    [2024-4-11. : 9:53 pm]
    da real donwano -- This is the first time I've seen spam bots like this on SEN. But then again, for the last 15 years I haven't been very active.
    [2024-4-11. : 4:18 pm]
    IlyaSnopchenko -- still better than "Pakistani hookers in Sharjah" that I've seen advertised in another forum
    [2024-4-11. : 4:07 pm]
    Ultraviolet -- These guys are hella persistent
    [2024-4-11. : 3:29 pm]
    Vrael -- You know, the outdoors is overrated. Got any indoor gym and fitness equipment?
    [2024-4-10. : 8:11 am]
    Sylph-Of-Space -- Hello!
    Please log in to shout.


    Members Online: lil-Inferno