• 🏆 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!

Help with multiplayer testing and scripts review of a custom map

Status
Not open for further replies.
Level 1
Joined
Jan 2, 2023
Messages
15
Don't know where to put this thread, to "Map Development" or "Triggers&Scripts".

As the title implies.
First time I'm seriously developing a map in world editor. This one is a Tower Defense map based (very loosely) on a Green TD and Diablo 2. Before releasing it to the public I would like to fix a maximum possible amount of bugs. Primarily I'm interested in bugs that are hidden in scripts.
Most of the scripts present are written from scratch, which increases the possibility of a failure. I'm using both GUI and JASS (not vJass) types of scripts. JASS scripts are checked in JassCraft. There are unfinished scripts that are disabled by default.

Would love to hear feedback on scripts, bugs and playtesting.
I know about balancing issues, but at the moment I'm interested in making sure that the map doesn't crash during the game.
 

Attachments

  • diabloTD_008.w3x
    215.6 KB · Views: 3
Last edited:
Level 1
Joined
Jan 2, 2023
Messages
15
Kind of fixed the main issue (at least no one is getting desynced at the very start of the game when 4 or less players are present) by streamlining initialization scripts.
It goes like this:
  • Events
    • Time - Elapsed game time is 2.00 seconds
  • Actions
    • -------- Set weather conditions for the map --------
    • Trigger - Run Game Init Weather <gen> (ignoring conditions)
    • -------- Find how many players are there, add them to structures --------
    • Trigger - Run Game Init Players <gen> (ignoring conditions)
    • -------- Initialize a game mode (team vs team by default) --------
    • Trigger - Run Game Init Mode <gen> (ignoring conditions)
    • -------- Give bountry to players who kill monsters --------
    • Player - Turn Gives bounty On for Player 11 (Dark Green)
    • Player - Turn Gives bounty On for Player 12 (Brown)
    • -------- Load monster spawn information in the "Waves Control" --------
    • Trigger - Run Waves Init <gen> (ignoring conditions)
    • -------- Initialize the leaderboard after a small wait, otherwise it will fail --------
    • Wait 1.00 seconds
    • Set WaveLevelTimer = (Max(0.00, (WaveLevelTimer - 2.00)))
    • Trigger - Turn on Leaderboard Init <gen>
    • Trigger - Run Leaderboard Init <gen> (ignoring conditions)
    • -------- final cleanup --------
    • Custom script: call DestroyTrigger( GetTriggeringTrigger() )
    • Trigger - Turn off (This trigger)
Now the map loads properly, if lagging a bit at the start, then it goes pretty smooth for what it's worth.
 
Last edited:
Level 1
Joined
Jan 2, 2023
Messages
15
That sort of fuckery is never necessary. Unless you are allocating events dynamically or need to rebuild the trigger post-runtime there is no reason to destroy a trigger. The small amount of memory you are recovering doesn't benefit you in any meaningful way.
Thanks for your input. Monkey see - monkey stole this line from somewhere, contaminating the codebase with it. Never worked with distributed systems before, don't know which stuff might cause desync, which won't.

Would you mind taking a look / reviewing following scripts as well?
This code is responsible for setting a critical strike for units in a small area around related aura emitting units. Those scripts are working fine, but maybe there is a hidden bug or two.

Adding a built unit to the group if it is of a special unit type:
  • Passive Critical Strike Init
    • Events
      • Unit - A unit Finishes construction
    • Conditions
      • (Unit-type of (Triggering unit)) Equal to Amazon - Passive - Critical Strike
    • Actions
      • -------- Add the unit to the built unit group --------
      • Unit Group - Add (Triggering unit) to SpellAuraCrititicalStrike
      • Special Effect - Create a special effect attached to the origin of (Triggering unit) using Abilities\Spells\NightElf\ThornsAura\ThornsAura.mdl
      • Special Effect - Destroy (Last created special effect)
      • -------- Immediately call an update of the aura script --------
      • Trigger - Run Passive Critical Strike Update <gen> (ignoring conditions)
The "Update" script, which adds aura to all units around those towers each N seconds:
JASS:
function Trig_Passive_Critical_Strike_Update_SavedGroup takes nothing returns nothing
    local unit ucurr = GetEnumUnit()
    local location lpoint = GetUnitLoc(ucurr)
    local group granged = GetUnitsInRangeOfLocAll(500.00, lpoint)
 
    // Add abilities to all units in the area around the building
    loop
        set ucurr = FirstOfGroup(granged)
        exitwhen ucurr == null
        call GroupRemoveUnit(granged, ucurr)
        // add amazon critical strike x2 to selected unit
        if ( IsUnitType(ucurr, UNIT_TYPE_STRUCTURE) ) then
            if ( GetUnitAbilityLevel( ucurr, 'A00G' ) == 0 ) then
                call UnitAddAbilityBJ( 'A00G', ucurr )
            endif
        endif
    endloop
 
    // clean up local variable pointers
    call DestroyGroup( granged )
    set granged = null
    call RemoveLocation( lpoint )
    set lpoint = null
    set ucurr = null
endfunction

function Trig_Passive_Critical_Strike_Update_Actions takes nothing returns nothing
    // Get specific units that we have saved in the unit group
    call ForGroup( udg_SpellAuraCrititicalStrike, function Trig_Passive_Critical_Strike_Update_SavedGroup )
endfunction

//===========================================================================
function InitTrig_Passive_Critical_Strike_Update takes nothing returns nothing
    set gg_trg_Passive_Critical_Strike_Update = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Passive_Critical_Strike_Update, 10.00 )
    call TriggerAddAction( gg_trg_Passive_Critical_Strike_Update, function Trig_Passive_Critical_Strike_Update_Actions  )
endfunction
And finally almost identical "on Sell" and "on Kill" triggers
  • Passive Critical Strike Killed
    • Events
      • Unit - A unit owned by Player 1 (Red) Dies
      • Unit - A unit owned by Player 2 (Blue) Dies
      • Unit - A unit owned by Player 3 (Teal) Dies
      • Unit - A unit owned by Player 4 (Purple) Dies
      • Unit - A unit owned by Player 5 (Yellow) Dies
      • Unit - A unit owned by Player 6 (Orange) Dies
      • Unit - A unit owned by Player 7 (Green) Dies
      • Unit - A unit owned by Player 8 (Pink) Dies
    • Conditions
      • (Unit-type of (Triggering unit)) Equal to Amazon - Passive - Critical Strike
    • Actions
      • -------- Remove abilities in the area near the destroyed unit --------
      • Set TempPoint = (Position of (Dying unit))
      • Set TempUnitGroup = (Units within 500.00 of TempPoint)
      • Unit Group - Pick every unit in TempUnitGroup and do (Actions)
        • Loop - Actions
          • Set TempUnit = (Picked unit)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (TempUnit is A structure) Equal to True
            • Then - Actions
              • Unit - Remove Amazon - Critical Strike from TempUnit
            • Else - Actions
      • -------- clean up local variable pointers --------
      • Custom script: call DestroyGroup (udg_TempUnitGroup)
      • Custom script: call RemoveLocation (udg_TempPoint)
      • -------- Delete removed unit from the group, run update procedure on towers that have lost their ability inappropriately --------
      • Unit Group - Remove (Dying unit) from SpellAuraCrititicalStrike
      • Unit - Remove (Dying unit) from the game
      • Trigger - Run Passive Battle Order Update <gen> (ignoring conditions)

  • Passive Critical Strike Sold
    • Events
      • Unit - A unit owned by Player 1 (Red) Finishes training a unit
      • Unit - A unit owned by Player 2 (Blue) Finishes training a unit
      • Unit - A unit owned by Player 3 (Teal) Finishes training a unit
      • Unit - A unit owned by Player 4 (Purple) Finishes training a unit
      • Unit - A unit owned by Player 5 (Yellow) Finishes training a unit
      • Unit - A unit owned by Player 6 (Orange) Finishes training a unit
      • Unit - A unit owned by Player 7 (Green) Finishes training a unit
      • Unit - A unit owned by Player 8 (Pink) Finishes training a unit
    • Conditions
      • And - All (Conditions) are true
        • Conditions
          • (Unit-type of (Triggering unit)) Equal to Amazon - Passive - Critical Strike
          • (Unit-type of (Trained unit)) Equal to |c00FFFF00Sell|r (common)
    • Actions
      • -------- Remove abilities in the area near the destroyed unit --------
      • Set TempPoint = (Position of (Triggering unit))
      • Set TempUnitGroup = (Units within 500.00 of TempPoint)
      • Unit Group - Pick every unit in TempUnitGroup and do (Actions)
        • Loop - Actions
          • Set TempUnit = (Picked unit)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (TempUnit is A structure) Equal to True
            • Then - Actions
              • Unit - Remove Amazon - Critical Strike from TempUnit
            • Else - Actions
      • -------- clean up local variable pointers --------
      • Custom script: call DestroyGroup (udg_TempUnitGroup)
      • Custom script: call RemoveLocation (udg_TempPoint)
      • -------- Delete removed unit from the group, run update procedure on towers that have lost their ability inappropriately --------
      • Unit Group - Remove (Triggering unit) from SpellAuraCrititicalStrike
      • Unit - Remove (Triggering unit) from the game
      • Trigger - Run Passive Critical Strike Update <gen> (ignoring conditions)
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,016
People used to do it all the time for Damage Detection Systems and the practice proliferated without it providing any real benefit in most scenarios.

  1. Why manually create a thorns effect when the unit is spawned? This special effect could be automatically played by some passive ability on the constructed unit, which would then repeat like an aura normally does.

  2. I see this is a TD, but if there are any structures that are able to move around and should benefit from this aura it will not work properly all the time. The 10s update interval is too infrequent to reliably catch units as they move around; units that should have the buff could have to wait up to 10s to get it, and those that should have lost it might retain it for 10s too long.

  3. Why does the second trigger have to be JASS? You can nest Unit Group - Picks, you just need to store the outer Picked Unit in some variable to access it in the inner loop. If you're learning JASS and that was an experiment you appear to have done everything properly!

  4. The events for p1-p8 unit deaths are technically optimal, but there would be no noticeable performance loss for just using Unit - A unit dies and the condition (Owner of (Triggering Unit)) not equal to RELEVANT_MOB_PLAYER.

  5. If a building is in range of two different Amazon towers while one of them is sold it will lose its crit strike buff until the next update (could be as long as 10s)

  6. Technically GroupEnumUnitsInRange... only grabs units whose origin point is within the radius. If a unit has a large collision size/model/selection scale and it overlaps into the search radius it will still only be picked up if its origin is within 500 of the source tower no matter how much it overlapped. The simplest solution for this is to add 1/2 of the average collision size in your map to your search radius, though that is not always accurate. Depending on grid spacing and unit sizes it might be that there is no possible way to place a tower to have this bug happen, or it might impact a variety of different type of ranged towers; if you really feel adventurous you could go test to find out.
Resolving 2 would realistically just take updating the frequency to maybe 2s to mimic default auras, but if nothing can move in this TD then it likely doesn't matter. 5 has a simple solution too: run the update trigger right after any Amazon tower is sold/killed.
 
Level 1
Joined
Jan 2, 2023
Messages
15
People used to do it all the time for Damage Detection Systems and the practice proliferated without it providing any real benefit in most scenarios.

  1. Why manually create a thorns effect when the unit is spawned? This special effect could be automatically played by some passive ability on the constructed unit, which would then repeat like an aura normally does.

  2. I see this is a TD, but if there are any structures that are able to move around and should benefit from this aura it will not work properly all the time. The 10s update interval is too infrequent to reliably catch units as they move around; units that should have the buff could have to wait up to 10s to get it, and those that should have lost it might retain it for 10s too long.

  3. Why does the second trigger have to be JASS? You can nest Unit Group - Picks, you just need to store the outer Picked Unit in some variable to access it in the inner loop. If you're learning JASS and that was an experiment you appear to have done everything properly!

  4. The events for p1-p8 unit deaths are technically optimal, but there would be no noticeable performance loss for just using Unit - A unit dies and the condition (Owner of (Triggering Unit)) not equal to RELEVANT_MOB_PLAYER.

  5. If a building is in range of two different Amazon towers while one of them is sold it will lose its crit strike buff until the next update (could be as long as 10s)

  6. Technically GroupEnumUnitsInRange... only grabs units whose origin point is within the radius. If a unit has a large collision size/model/selection scale and it overlaps into the search radius it will still only be picked up if its origin is within 500 of the source tower no matter how much it overlapped. The simplest solution for this is to add 1/2 of the average collision size in your map to your search radius, though that is not always accurate. Depending on grid spacing and unit sizes it might be that there is no possible way to place a tower to have this bug happen, or it might impact a variety of different type of ranged towers; if you really feel adventurous you could go test to find out.
Resolving 2 would realistically just take updating the frequency to maybe 2s to mimic default auras, but if nothing can move in this TD then it likely doesn't matter. 5 has a simple solution too: run the update trigger right after any Amazon tower is sold/killed.
'Preciate your analysis.
Some explanations on my side on the things you pointed out:

1. Just trying out some eye-candy (doesn't work as I hoped it would on doodad models). Wanted to create a visual confirmation of an aura being applied on units. After that it's distracting.

2. Yes, it is a TD, a very simple one. Structures are the only attacking, or should I say defending, types of units. That's why the check can be so simple. Update time is so big, because early optimization, changing it to 5 seconds would be more appropriate, but from a personal experience the update time is almost negligible, unlike other TDs where you have had to wait 20 to 30 seconds for it to update (even after the structure itself was built!).

3. Personal reasons, I was sick and tired of re-clicking everything to fix a small mistake or add a operation in the GUI. Also I like local variables much more and code can be easier to optimize. Yes, there isn't a proper IDE, but the GUI compiler produces a hot garbage where each condition is a giant new function with negative returns. Yeah, I'm learning a bit of JASS, a different beast than LUA.

4. My reasoning is that there could be up to 8 players each with up to 10 towers (only 5..10 initiate said triggers) and a bit less than 200 enemy monsters per player dying in droves each second.

5. There is a call for an "Update" script on the bottom of "killed"/"sold" triggers, so the overlapping tower auras will be immediately restored for appropriate units.

6. Experienced a bug where the origin point (I guess) wasn't in the geometric center of the model. It would pick one unit on the right side and wouldn't pick a unit on the left side, even though the tile distance is the same for both of them. (Should have checked the calculated distance between points then.) If this bug requires some serious workaround I wouldn't bother to save the performance. But if there is a way to catch all units whose selection radius is caught in the area, would you mind sharing it?

If You're still okay with me pestering You, I have few more triggers I would like reviewed.

Personally I'm okay with how triggers work, the map runs smooth, even on later levels with a 100 of enemies on the screen, unlike Green TDs i have been playing prior that stall on later levels. Map needs a proper balancing because the mob count does not correlate with how much health the mobs possess. Would like to add a "finer" grade difficulty setting, game mode selection, change aura visuals here and there, update help (or a quest menu?) and a colored text.
 
Level 39
Joined
Feb 27, 2007
Messages
5,016
Just trying out some eye-candy (doesn't work as I hoped it would on doodad models).
Instead of attaching it to a unit, you could create the effect on its position and set the effect's height (z) and/or xy offsets manually. There are (new-ish) effect natives to change pitch/roll/yaw as well to get exactly what you envision. Slowing down/speeding up the animation speed can be beneficial as well.
Yeah, I'm learning a bit of JASS, a different beast than LUA.
Then by all means continue doing what you're doing and experimenting! Best way to learn. Lua is, uhh, much more flexible/useful/reasonable than (v)JASS is. Only downside of fully committing to Lua is that you can't easily use JASS resources you find online.
a bit less than 200 enemy monsters per player dying in droves each second.
Trigger conditions are incredibly quick to evaluate, and the instant any of them return false the rest are never checked. I don't think it would matter either way.
There is a call for an "Update" script on the bottom of "killed"/"sold" triggers
Ah, I am blind and you are smart. Upon reading it, though, the kill trigger runs Critical Strike Update while the sell trigger runs Battle Order Update; is that an error?
if there is a way to catch all units whose selection radius is caught in the area, would you mind sharing it?
Basically this, but you have to call the functions manually with JASS.
unlike Green TDs i have been playing prior that stall on later levels
Do those maps actually lag or is it just pathfinding being awful because there are too many units and player mazes to deal with? The latter seems a lot more likely to me.
If You're still okay with me pestering You, I have few more triggers I would like reviewed.
Go ahead, happy to help.
 
Level 1
Joined
Jan 2, 2023
Messages
15
.....
Go ahead, happy to help.
Thanks for your help and for pointing out glaring mistakes that i was too blind to notice. More eyes - more bugs. Was busy balancing gameplay and mobs. Will release a public beta soon, so even more bugs could be found.

Instead of attaching it to a unit, you could create the effect on its position
Yeah, I'm having problems with an effect show-up. Don't know about "newish" methods, I'm developing for v1.27. The server I play on uses it.
Trigger conditions are incredibly quick to evaluate, and the instant any of them return false the rest are never checked. I don't think it would matter either way.
I don't like generalizations and abstractions too much. Furthermore, bare LUA is extremely slow during execution, we are talking Python levels of slow. If I can make the script perform faster faster easily, why not do that? I know that the LUA-JIT has JavaScript levels of speed, but the development has stopped completely.
...the kill trigger runs Critical Strike Update while the sell trigger runs Battle Order Update
Ye.. My bad. Copy and pasting stuff.
Basically this, but you have to call the functions manually with JASS.
I may go back to this, but i think "native" calls should be faster to process, so for now I'm sticking to them.
Do those maps actually lag or is it just pathfinding being awful because there are too many units and player mazes to deal with? The latter seems a lot more likely to me.
Now that is an interesting question. They seem to lag a lot during late levels (which is quite typical). Now the issue with them is that the later level flying monsters do not seem to receive the move order properly, since they always seem to be stuck in a spawn for a good while. And as we know, the more objects are present on the screen at the same time, the FPS rate is lower and the user experience is worse.

Mine runs surprisingly smooth (unless there are 200+ monsters on the screen at the same time) and the pathing does not seem to be an issue most of the time. But when the unit loses an order (he used a blink spell, or was charmed for a moment) he will attack a closest tower and will update the pathing for other mobs, whose next order is to reach the point near the destroyed tower. If mobs are present right in the position of a recently destroyed tower and you try to build it, they will destroy it and destroy your base in the process.
This should be caught with a "generic unit -> attacks" script, but it might be computationally hard to solve it. There are several ways as I see it:
  • store latest order and the location of the order end point in a giant, constantly scrubbed array; either constantly update units order and made sure he "move"s or trigger catches an "order" change and reissues the last order;
  • calculate the distance to the nearest "movement point" (one out of 8 possible regions for a single enemy player) and issue order to simply move there.
Also a unit might be correct to attack a tower, since it's blocking its way to the point. Which might be solved using an invisible detonator unit constantly flying and blowing those towers up.
 
Status
Not open for further replies.
Top