Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
I am working on creating an ability for the naval system in my strategy map but I do not yet know how to do it. I am looking to create the following behavior: ports exist across the map, owned by different players. ports can establish a pseudo-waygate between another port via the creation of a transport boat. When a transport boat is trained it tethers itself to the port that trained it. Additionally, the boats have the ability to tether to an additional port, thus being tethered between its creator and its target. This completes a path that allows units to pass from one port to the other for units owned by the boats owner. ports can be connected to multiple boats and players, maybe the limits are configurable.
Once I can get the system to work I am also trying to make it function correctly. Though, I am not focused on the following functionality it. Preferably the tethers would have max ranges that would break outside the range and reconnect within it. Additionally, the tether won't cross a certain percentage of land to water, so that the tether isn't crossing a continent via land. (However, keeping in mind that the boat is tethered to the two ports, it acts as an angle and a way to bend around corners)
I am not asking for someone to write this for me, but honestly I am not sure where to start. I have never really messed with sfx and I am wondering if there are any systems or ideas that can solve this. What resources/systems on the hive are recommended for something like this? Or do you have any ideas of how to do this? I don't often get stuck at the start of a project idea, but this time I am. Any help is appreciated, and the thinktank is always helpful. Thanks!
EDIT: Apologies, I typically use GUI but understand and occasionally rely on JASS.
Whenever approaching complex problems like this, I always recommend breaking it into smaller chunks. Often times, there end up being a lot of "essential" parts that are easy to tackle first, and then you're left with a few very-specific problems that are easier to solve in isolation.
I also recommend trying to implement a small proof-of-concept in a test map first--that way you aren't distracted by all the details of your real map. Often times, you end up designing it better this way too--because you'll eventually need to import it into your real map, so you end up thinking about configurability up front.
So if I were creating a proof-of-concept for this, I'd first start by:
Creating the ports
Allowing one transport boat to be trained
Next, tackle the concept of "tethering". Tethering to the original port might be as simple as setting some data within a hashtable. Hashtables often end up being really useful here because you can just attach whatever data you want to a specific unit and retrieve it anywhere else. If your "ports" are a specific building that trains the ships (e.g. a shipyard), then you might just start off by adding a trigger that says "When a unit finishes training", "Unit-type equal to transport ship", then store the training unit in a hashtable using the handle ID of the transport ship.
Similarly, for the "target", you'll do the same. I'm not sure exactly how a transport ship gets "tethered" to a target port (does the ship need to go to that port and use an ability? or something else?). But at this stage, you'd be forced to decide how you want that to happen--and then you can detect it and similarly store some data.
While this probably doesn't feel like it is doing much, preparing the data is often half the battle. A lot of the other problems become easier if you can easily ask "which ports is this transport ship tethered to?" And those steps will answer that.
Then I'd work on the actual transport. I'm not sure how that is meant to be handled either (e.g. do the units teleport to the other port by interacting with the transport ship, like a waygate? Or are they supposed to be "loaded" and the ship only goes along a specific path?), but the problem becomes a lot smaller once you can look at a transport ship and say "hey, it just needs to go from point A to point B".
Maybe you can start by making a small test map--and then when you get stuck on a particular part, we can try to help out on that specific problem! Or one of us can try to help make a concept map--we just might need more info on: what is a port? (is it a building?), how does tethering work (e.g. is it a spell used by the ships?), how do units interact with the ships (e.g. do they get loaded into a transport?), and how is the ship supposed to "travel" to another port? (does the player have control over it? or does it walk along a designated naval path between two ports?)
Thanks for that advice! Honestly, I never work like this, and it was helpful to just put it together in a new map. I hacked together the simplest version of this system that I could (attached). Transport boats are able to establish a naval route with a port chosen by its ability and the port that trained it. Each port can only train 1 transport boat.
But there are still some glaring issues that I am working through:
Transport Ships should only act as a naval route for the player that owns them. The way it currently works is that it established a way gate, but way gates are usable by all players and I don't like this. The use of way gates in this test map shows the functionality I am after though. This is my biggest issue
Naval routes should work two-way, but they currently act one-way. I imagine this will fall into place once the first issue is figured out.
There are no SFX, though this is not currently a focus. I'm imagining beams (like mana drain) connecting the ship to the two ports it is linking. Absolutely no idea how to do this... hahah
Cheers! And thanks for that post, it inspired me to get a jump start, and I hope to hear from ya!
Awesome! Yeah things definitely get a bit easier to work with when it is all separated out.
So for the first issue, I think we'll need to resort to triggering the waygate behavior ourselves. That way you have full control over the "acceptance" conditions (e.g. you must own the shipyard to use it to teleport). Triggering waygates is pretty straightforward--you need to create some regions around the waygates, detect when units enter them, check your conditions, and then move them to their destination.
But it does get slightly more complicated with this system, since you don't know which region to send the unit to until a naval route has been established.
My first idea was to use the unit indexer event to create regions around each shipyard, and then add "a unit enters a region" events to a separate trigger called "Handle Port Entry". This way, you don't have to bother placing regions yourself. So initially, it looked like this:
Port Region Setup
Events
Game - UnitIndexEvent becomes Equal to 1.00
Conditions
(Unit-type of UDexUnits[UDex]) Equal to Human Shipyard
Actions
Set VariableSet ShipSystem_TempLoc = (Position of UDexUnits[UDex])
Set VariableSet Waygate_Region = (Region centered at ShipSystem_TempLoc with size (512.00, 512.00))
Trigger - Add to Handle Port Entry <gen> the event (Unit - A unit enters Waygate_Region)
"Game - UnitIndexEvent becomes Equal to 1.00" - this is an event that the Unit Indexer fires when it assigns an ID to a given unit. This event is a pretty nice way to cover all your bases to detect when a unit enters the map. It handles both the case where a unit is pre-placed on the map and the cases where a unit gets added later (e.g. gets built or trained).
Condition - this just makes sure we only add this waygate functionality to shipyards. UDexUnits[UDex] in this case is the unit that was indexed.
Actions - this creates a 512x512 region around the shipyard and adds an event to the trigger
...but this has a bit of a problem. When we go into "Handle Port Entry", ideally we want to look up where to send the unit (based off the shipyard that you are approaching). Sadly, the "A unit enters region" event doesn't have too much information. In GUI, it only has "Entering unit". In JASS, there is GetTriggeringRegion() though, which could be useful. One idea is to find the shipyard in the region (e.g. by using Pick every unit in region...), but that might be a bit brittle. Instead, I opted to "attach" information to the region using a hashtable.
First, we'll need to set-up the hashtable--make sure this trigger is placed above the "Unit Indexer" trigger so it runs before the indexer events get fired:
Hashtable Setup
Events
Map initialization
Conditions
Actions
Hashtable - Create a hashtable
Set VariableSet ShipSystem_Hashtable = (Last created hashtable)
Next, we'll adjust our "Port Region Setup" trigger. Due to some (annoying) details around how "regions" are represented under the hood, I had to use some JASS (regions in GUI are technically the "rect" type in JASS. And "region" is its own separate type, which can contain many rects). But it is basically doing the same as the trigger above, just with the extra detail of storing the shipyard in the hashtable under the ID of the region we're tracking an event for.
Port Region Setup
Events
Game - UnitIndexEvent becomes Equal to 1.00
Conditions
(Unit-type of UDexUnits[UDex]) Equal to Human Shipyard
Actions
-------- --------
-------- Create a temporary region around the shipyard, and register when a unit enters into it --------
-------- This is the same underlying logic as "A unit enters <region>"... --------
-------- ...but we do it manually so we can associate some data with the "region". --------
-------- --------
Custom script: local region rectRegion = CreateRegion()
Set VariableSet ShipSystem_TempLoc = (Position of UDexUnits[UDex])
Set VariableSet Waygate_Region = (Region centered at ShipSystem_TempLoc with size (512.00, 512.00))
Awesome, so now in our "Handle Port Entry"--we can detect when a unit enters the range of any shipyard and we can get the shipyard that was approached like so:
Handle Port Entry
Events
Conditions
Actions
Custom script: set udg_TempInt = GetHandleId(GetTriggeringRegion())
Set VariableSet Waygate_Shipyard = (Load 0 of TempInt in ShipSystem_Hashtable.)
Next, we want to check if the entering unit matches our conditions (they own the shipyard), and if so, we want to teleport them. We have most of the information we need--but we need to decide how to track where a shipyard should teleport a unit to. For now, I've made a new variable "ShipSystem_PortDestinationUnit" (unit array) to track which shipyard one goes to. You can picture it like in the diagram below, for a shipyard A and a shipyard B.
We'll worry about establishing that relationship later (that will be the responsibility of the "Tether Ability" trigger), but for now we'll just assume that it will be set up.
So we can adjust the trigger to look like this:
Handle Port Entry
Events
Conditions
Actions
-------- --------
-------- Look up which shipyard the unit has entered in range of --------
-------- --------
Custom script: set udg_TempInt = GetHandleId(GetTriggeringRegion())
Set VariableSet Waygate_Shipyard = (Load 0 of TempInt in ShipSystem_Hashtable.)
-------- --------
-------- Check the appropriate conditions --------
-------- --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
And - All (Conditions) are true
Conditions
(Owner of (Triggering unit)) Equal to (Owner of Waygate_Shipyard)
(Unit-type of (Triggering unit)) Not equal to ShipUnitType
ShipSystem_PortDestinationUnit[(Custom value of Waygate_Shipyard)] Not equal to No unit
Then - Actions
-------- --------
-------- Create the porting effect --------
-------- --------
Set VariableSet ShipSystem_TempLoc = (Position of (Triggering unit))
Special Effect - Create a special effect at ShipSystem_TempLoc using Abilities\Spells\Human\MassTeleport\MassTeleportTarget.mdl
Special Effect - Destroy (Last created special effect)
This will simulate teleporting the unit (complete with all the "teleport" effects), but only if: the player owns the ship, the entering unit is not a transport ship (not sure if you want ships to teleport or not), and a destination has been established.
There is one last tweak we need to make though. Since waygates work two-ways, if we move the unit to the other shipyard--it'll trigger that shipyards event, which will send the unit back... and that will repeat infinitely. To do this, you can just store a variable to basically say "ignore the next event for this unit". Then at the top of the trigger, you can read that variable and skip the code if needed. So the final trigger looks like this:
Handle Port Entry
Events
Conditions
Actions
-------- --------
-------- This just prevents teleporting back and forth infinitely when using a waygate --------
-------- --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
Waygate_IgnoreNextEventForUnit Equal to (Triggering unit)
Then - Actions
Set VariableSet Waygate_IgnoreNextEventForUnit = No unit
Skip remaining actions
Else - Actions
-------- --------
-------- Look up which shipyard the unit has entered in range of --------
-------- --------
Custom script: set udg_TempInt = GetHandleId(GetTriggeringRegion())
Set VariableSet Waygate_Shipyard = (Load 0 of TempInt in ShipSystem_Hashtable.)
-------- --------
-------- Check the appropriate conditions --------
-------- --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
And - All (Conditions) are true
Conditions
(Owner of (Triggering unit)) Equal to (Owner of Waygate_Shipyard)
(Unit-type of (Triggering unit)) Not equal to ShipUnitType
ShipSystem_PortDestinationUnit[(Custom value of Waygate_Shipyard)] Not equal to No unit
Then - Actions
-------- --------
-------- Create the porting effect --------
-------- --------
Set VariableSet ShipSystem_TempLoc = (Position of (Triggering unit))
Special Effect - Create a special effect at ShipSystem_TempLoc using Abilities\Spells\Human\MassTeleport\MassTeleportTarget.mdl
Special Effect - Destroy (Last created special effect)
Awesome. So now we have the code theoretically working, but we need to set PortDestinationUnit. So let's head over to the "Tether Ability" trigger. First, I'd recommend adding some conditions to make sure the player doesn't misuse the ability. This also makes programming the rest of it easier because then you don't have to worry about handling weird edge conditions, like if the ship tethers to its own shipyard.
I added a few I thought of--such as making sure the ship doesn't tether to its own creator, that the target is a shipyard, and that the player owns that shipyard. And just for clarity, I put the port creator (the shipyard that trained the transport ship) into its own variable.
Tether Ability
Events
Unit - A unit Starts the effect of an ability
Conditions
(Ability being cast) Equal to Establish Naval Route
Actions
Game - Display to (All players) the text: debug.tether
Set VariableSet Tether_ShipCreator = ShipSystem_PortCreator[(Custom value of (Casting unit))]
-------- --------
-------- First, check to make sure we are tethering a valid unit --------
-------- It should be a shipyard that we own, that is not the one that created this ship. --------
-------- --------
Set VariableSet TempForce = (Player group((Owner of (Casting unit))))
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Unit-type of (Target unit of ability being cast)) Not equal to Human Shipyard
Then - Actions
Game - Display to TempForce the text: |cffffcc00Must sele...
Custom script: call DestroyForce(udg_TempForce)
Skip remaining actions
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Target unit of ability being cast) Equal to Tether_ShipCreator
Then - Actions
Game - Display to TempForce the text: |cffffcc00Must sele...
Custom script: call DestroyForce(udg_TempForce)
Skip remaining actions
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Owner of (Target unit of ability being cast)) Not equal to (Owner of (Casting unit))
Then - Actions
Game - Display to TempForce the text: |cffffcc00Must sele...
Custom script: call DestroyForce(udg_TempForce)
Skip remaining actions
Else - Actions
Custom script: call DestroyForce(udg_TempForce)
Awesome. Next, establishing the link between shipyards is pretty simple. You just need to assign the destination unit of the creator to the spell's target. And then you need to do the reverse (assign the destination unit of the target to the shipyard that made the ship). This could look like this:
Set VariableSet ShipSystem_PortDestinationUnit[(Custom value of (Target unit of ability being cast))] = Tether_ShipCreator
Set VariableSet ShipSystem_PortDestinationUnit[(Custom value of Tether_ShipCreator)] = (Target unit of ability being cast)
However, this might run into some issues if you have more than just two shipyards in your map. For example, if you have a path between A and B, and then a ship from B tries to establish a route from B to C, what should happen? For now, I assumed that we should "break" the link between A and B, and then establish the route from B to C. So you'll need to look at what shipyards are already connected to the creator/target, set them to null, and then proceed with establishing the relationship above. The final trigger will look like this:
Tether Ability
Events
Unit - A unit Starts the effect of an ability
Conditions
(Ability being cast) Equal to Establish Naval Route
Actions
Game - Display to (All players) the text: debug.tether
Set VariableSet Tether_ShipCreator = ShipSystem_PortCreator[(Custom value of (Casting unit))]
-------- --------
-------- First, check to make sure we are tethering a valid unit --------
-------- It should be a shipyard that we own, that is not the one that created this ship. --------
-------- --------
Set VariableSet TempForce = (Player group((Owner of (Casting unit))))
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Unit-type of (Target unit of ability being cast)) Not equal to Human Shipyard
Then - Actions
Game - Display to TempForce the text: |cffffcc00Must sele...
Custom script: call DestroyForce(udg_TempForce)
Skip remaining actions
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Target unit of ability being cast) Equal to Tether_ShipCreator
Then - Actions
Game - Display to TempForce the text: |cffffcc00Must sele...
Custom script: call DestroyForce(udg_TempForce)
Skip remaining actions
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Owner of (Target unit of ability being cast)) Not equal to (Owner of (Casting unit))
Then - Actions
Game - Display to TempForce the text: |cffffcc00Must sele...
Custom script: call DestroyForce(udg_TempForce)
Skip remaining actions
Else - Actions
Custom script: call DestroyForce(udg_TempForce)
-------- --------
-------- Next, look at what the previous tethers were --------
-------- --------
Set VariableSet Tether_ShipCreator_PrevUnit = ShipSystem_PortDestinationUnit[(Custom value of Tether_ShipCreator)]
Set VariableSet Tether_Target_PrevUnit = ShipSystem_PortDestinationUnit[(Custom value of (Target unit of ability being cast))]
-------- --------
-------- If the shipyards had previous links to other shipyards, clear them out --------
-------- --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
Tether_ShipCreator_PrevUnit Not equal to No unit
Then - Actions
Set VariableSet ShipSystem_PortDestinationUnit[(Custom value of Tether_ShipCreator_PrevUnit)] = No unit
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
Tether_Target_PrevUnit Not equal to No unit
Then - Actions
Set VariableSet ShipSystem_PortDestinationUnit[(Custom value of Tether_Target_PrevUnit)] = No unit
Else - Actions
-------- --------
-------- Finally, establish the destination for the source -> target shipyard --------
-------- And the target -> source shipyard --------
-------- --------
Set VariableSet ShipSystem_PortDestinationUnit[(Custom value of (Target unit of ability being cast))] = Tether_ShipCreator
Set VariableSet ShipSystem_PortDestinationUnit[(Custom value of Tether_ShipCreator)] = (Target unit of ability being cast)
I've attached some edits to your map if you want to try it out!
I'm down to help you figure out the effects too, but I know this is a lot so I figured I'd leave this here first in case you had more questions about how to set up your system! Good luck!
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.