NOTE: You have been redirected in order for our attachments to be made available to you. This will only last two minutes; these measures where taken to avoid hotlinking and bandwidth theft. To avoid these restrictions Log in or Register
PAS v3.1 - by Ammorth
Background:
Over a year ago, I wrote PAS 1.0 and updated it through to PAS 2.40 (2.40 was never released). I then took a break from modding wc3 and PAS has since become obsolete. With better syntax and modding experience, I have since re-wrote PAS from the ground up. PAS 3.0 is now born. The older version of PAS can be found here.
About PAS:
PAS is a framework for plugins that use the PAS system. It is a combination of nodes and edges, mixed in with an Astar algorithm that traverses the node. It can generate paths along the nodes which plugins can use.
PAS v3.1 includes 3 plugins:
PASMove
PASMoveSafety
PASGPS
There are many more possible plugins one could write to utilize PAS, these are only the beginning.
PASMove will move units through the paths created by PAS
PASGPS creates a GPS-like Arrow which can guide players to locations within the PAS paths
PASMoveSafety is an added safety net for PASMove. It will check for stuck units as well as dead/removed units.
PAS Code:
PAS:
// ==================================================================================// Pathing Algorithm System - by Ammorth Feb 17, 2010// **Requires JassHelper (vJass) to compile**// Requires: LinkedList (by Ammorth, at wc3c)// LineSegment (by Ammorth, at wc3c)//// about:// Pathing Algorithm System (PAS) can be used to construct a set of "roads" and// generating paths through the nodes. These paths can then be utilized to create// dynamic systems. For example, the PASMovement and PASGPS libraries utilize the// paths generated from PAS to move units and provide a GPS-like arrow.//// Requirements:// - LinkedList (by Ammorth, at wc3c)// - LineSegment (by Ammorth, at wc3c)//// Installation:// - Create a new trigger called PAS// - Copy this code to the new trigger// // Setup:// - Make sure the textmacro for the object merger line provides a unique rawcode id to your map// (aka, if 'pasn' conflicts with another unit in your map, change it to something else)// - Setup the node data (read Setting Up Nodes)//// Function List:// Node.add(integer id, real x, real y, string linkData, real size) -> PAS_Node *// Node.addRect(integer id, rect whichRect, string linkData, real size) -> PAS_Node *// PAS_Node[id] -> PAS_Node// PAS_Node.getNearest -> PAS_Node// PAS_Node.changeWeight(integer link, real weight) -> nothing// PAS_Node.changeWeightForNode(PAS_Node for, real weight) -> nothing// PAS_Node.setLinkEnabled(integer link, boolean enabled) -> nothing// PAS_Node.setLinkEnabledForNode(PAS_Node for, boolean enabled) -> nothing// PAS_GetPath(real startx, real starty, real endx, real endy) -> PAS_Path//// * The PAS prefix isn't required for Node.add/.addRect as they are used within the PAS library exclusively//// Setting Up Nodes:// You can add nodes with 2 functions://// call Node.add(integer ID, real x, real y, string nodeData, real size)//// or//// call Node.addRect(integer ID, rect r, string nodeData, real size)//// The only difference is one takes a rect and the other takes a cordinate.// I find laying out your nodes with rects and then using the rect method is much// easier than via cordinates. Although, if someone would rather use cordinates// the option is there aswell.//// Nodes should only be added in the specified location within the setup portion// of this script. Any other location will cause errors! Aswell, you cannot call// .add or .addRect outside of this PAS main library, so your hands are tied!//// Integer ID is the node ID. This is how you can tell each node apart. Each ID// needs to be a unique, positive value from 1 to MAX_NODES and you must create a// node for each possible ID between 1 and MAX_NODES. The easiest way to do this is// to start at 1 and work your way up. MAX_NODES can then be changed to the last ID.// // The rect or cordinates specify the location of the node. As you can seen in this// testmap, I have placed nodes at all the major intersections.//// The nodeData string is by far the trickest to setup. It contains all the data// required for each node, to generate the edges between nodes. The format is as// follows://// "Node=Weight-Node=Weight-Node=Weight..."//// Breaking this down to one Node we get://// "Node=Weight"//// The "Node" is the ID number of the node we wish to create a link to// The "Weight" is a real value to multiply the distance between nodes by, to make// node links act as if they were longer or shorter. Example://// "1-4=0.5-6=1.2"//// Links this node to 1 (with a default weight), 4 (with a weight of 0.5) and 6 (with// a weight of 1.2)//// The default weight for a node is 1.0. As you can see from the example, leaving out// the "=Weight" part in the string gives it a deault weight of 1.0. You can use a // combination of nodes with weights and nodes without.//// A weight of 1 is normal, a weight of 0 is as if the nodes were at the same point// and a weight of a very large number (think infinity) is if there was no link between// the nodes. Weights are powerful as you can make roads that are used less-frequent// or more-frequent by increasing or decreasing their weight. Keep in mind, a weight of// a large value still allows a unit to path through it, just only if no other paths with// less combined weight exist. If you don't want units to path through, you can disable// a link after setup (check the PAS_Node struct section).//// In short: "Node" is the ID of the node to link to// use "-" to separate nodes// if you want to add a weight, use "=Weight"//// The real size is the radius of a node. Some systems may use this to determine the// distance from a node or if a unit is at a node. For example, PASMovement utilizes// node sizes to check for units at active nodes. If a value below NODE_MIN_SIZE is// passed, the node will get the size NODE_DEFAULT_SIZE (both can be changed, although// there will be errors if NODE_MIN_SIZE <= 0.//// Once you have setup your nodes. You should take a look through all the constants// to make sure everything is set how you like it. A quick breakdown for each value// can be found in the globals section//// The PAS_Node struct:// Within these structs, you can get access to their position, size, links, etc...// You can get a PAS_Node struct either via://// PAS_Node[id]//// which will return the raw node for the specified ID number or://// PAS_Node.getNearest(x, y)//// which will return the nearest node at the given point.//// The data included in the PAS_Node struct is as follows://// real x// real y// unit marker// PAS_Node array links// real array linkLength// real array linkWeight// boolean array linkEnabled// integer linkSize// real nodeSize// integer id//// x and y are the cordinates of the node.//// marker is the dummy unit which is used to get nearby nodes/edges//// links are the PAS_Nodes this node is linked to//// linkLength is the weighted distance between the nodes, relative to this node//// linkWeight is the weight between the nodes, relative to this node//// linkEnabled is a boolean which is true if the link is enabled or false if disabled,// relative to this node//// linkSize is the number of links this node links to//// nodeSize is the size of the node, set by the Node.create() function//// id is the unique id number of this node (which you also set in Node.create()//// You usually only need to use .x, .y, .nodeSize and .id, but the others are there as well.// You shouldn't remove or kill the marker unit, or you won't be able to find nearby nodes// and edges.//// You can use the following methods to change the weight of a link://// PAS_Node.changeWeight(integer i, real weight)//// or//// PAS_Node.changeWeightForNode(PAS_Node node, real weight)//// The first one takes the link index while the second takes a node and will find the link// index for you, if it exists.//// As well, you can use the following methods to enable or disable links://// PAS_Node.setLinkEnabled(integer i, boolean enabled)//// or//// PAS_Node.setLinkEnabledForNode(PAS_Node node, boolean enabled)//// Again, the first method takes a link index while the second takes a node.//// There are a few limitations to changing weights and enabling/disabling links. You cannot // change weights or disable links within the node setup function (changes will be over-written).// Any path that was generated before the change and is still being used by a system, will// not update or change paths to reflect changes. Therefore, you could have units moving// between nodes which you have since disabled the links for. You can't create links after// the system is initialized, but you can disable links you setup. Keep this in mind when // you setup your paths.//// Also, keep in mind, links are local to the node they originate from. Changing the weight// of the link from node A to B does not change the weight from node B to A. Similarly,// disabling (or even creating a link at setup) from node A to B does not mean B to A changed// as well.// // The PAS_Path struct:// The PAS_Path struct has a fair bit of data that can be used to do whatever you want.// It is not recommended to change any values of the PAS_Path struct other than calling// .destroy() once you are done. You can get a PAS_Path struct using the following// funtion://// function PAS_GetPath takes real startx, real starty, real endx, real endy returns PAS_Path//// The arguments should be self-explanitory. The path generated will provide you with information// on how to traverse the nodes you set up, going from start to end. From there, it is up to the// plugin to use the data to do whatever.//// The data included in the PAS_Path struct is as follows://// Point start// Point end// Point nearStart// Point nearEnd// List nodes//// All of the Point objects have both a real x and a real y component which are accessed// via://// Point.x//// and//// Point.y //// The start point and end point are the cordinates you passed to the PAS_GetPath function.// The nearStart is the nearest point on the nearest edge to your start point. The nearEnd// is the nearest point on the nearest edge to the end point. Think of these as entrance and// exit points. To get into the paths from start.x and start.y, you need to go to nearStart.x// and nearStart.y. Once you are there, you are on an edge, and can now move to the first node.// Once you get to the last node, nearEnd.x and nearEnd.y are the cordinated to the closest// point to your endpoint on the edge. If you still don't get what I mean, you could most likely// ignore them.//// nodes is a Linked List of all the nodes between .nearStart and .nearEnd. The nodes are in// order from start to end, where .nodes.first is the first node and .nodes.last is the last node.// the nodes are stored as their raw values and not as their ID values. So, you can cast link.data// directly into a PAS_Node using PAS_Node(link.data). From there, you can access anything you want// about the node (.x, .y, .nodeSize, .id, etc...). You will need to be familiar with how// LinkedList works to be able to read the nodes.//// Once you are done with a path, call .destroy() on the path to clean it up. This will remove all// points and the linkedlist aswell.//// Notes:// Although PAS has be re-designed and coded to be as fast as possible, PAS_GetPath is still an // expensive function to call. Therefore, if you find you need to move many units at once, use// a quick periodic timer or use .evaluate() to create new threads to run PAS_GetPath in.//// Changelog://// PAS 3.1// Added functionality for changing link weights after setup// Added functionality for enabling and disabling links after setup// Made objects private/read-only where applicable (mostly PAS_Node objects)// Added function lists to all the script and plugins// Added Object Merger macros to create units in the object editor// PASMove: Changed the plugin name from PASMovement to PASMove (shorter)// PASMoveSafety: Changed the plugin name from PASMovementSaftey to PASMoveSafety (shorter)// PASGPS: Added installation instruction about the custom model and changed the paths for the model// PASGPS: The system will now register arriving at the end even if you didn't take the recommended path//// PAS 3.0// Complete re-write from PAS 2.40//// ==================================================================================libraryPASinitializeronInitrequiresLinkedList, LineSegment// ==================================================================================//// Start Object Merger Line//// set the value inside the quotes to a unique rawcode that isn't used in your map//! runtextmacro PAS_MARKER_ID("pasn")// // This will auto-generate a unit in the object editor as well as asign the global// PAS_MARKER_ID to the value here.//// End Object Merger Line//// ==================================================================================globals// ==================================================================================// Start of Setup//// Start of Constant Globals// the maximum number of Nodes you have setuppublicconstantintegerMAX_NODES = 48// cannot be larger than 8190// the maximum number of Links a node can havepublicconstantintegerMAX_LINKS = 4// limits the number of nodes to 8192/MAX_LINKS// the minimum size a node can be. publicconstantrealNODE_MIN_SIZE = 32// values less than this will get NODE_DEFAULT_SIZE// the defualt size of a node, if the sized passed is less than the minimum sizepublicconstantrealNODE_DEFAULT_SIZE = 128// the test radius from a point to find near-by nodes with PAS_Node.getNearest()publicconstantrealNODE_TEST_RANGE = 1024// the test radius from a point to find an edge with PAS_Edge.getNearest()// because edges can be long, and only the nodes can truely be found (units are placed at nodes)// this number should be large enough to find a node on the nearest edge.// aka, if the longest distance between 2 nodes is 1024, this should be slightly greater than 512// which is half of the distance. When the point is directly between the nodes, on the edge, 512// will get both nodes of the edge. Since it is hard for units to be directly on an edge (they deviate)// it should be larger than 512, so a value of 640 may be better. Try experimenting with finding the// smallest value that provides no "dead-points" (can't find the nearest edge). Remember, larger values// of this will increase the calculations required as many nodes could be found if they are tight-together.publicconstantrealLINK_TEST_RANGE = 2560// while debugging, sometimes it is nice to see which nodes a generated path will visit. Setting this// to true will provide you with a print-out of the points passed and the nodes contained in the path privateconstantbooleanDEBUG_SHOW_PATHS = false// only works if Debug Mode is enabled in JassHelper//// End of Constant Globals //// ================================================================================== endglobalspublickeywordNodeprivatekeywordEdgeprivatekeywordadd// haha! try using these methods outside of PAS now!privatekeywordaddRectprivatefunctionSetupNodestakesnothingreturnsnothing// ================================================================================== //// Start of Setting up Nodes //// nodes up to 28 are main roads, set their weight to 0.5 to use them more than back-roadscallNode.addRect(1, gg_rct_Rect_001, "2=0.5-24=0.5", 128)
callNode.addRect(2, gg_rct_Rect_002, "1=0.5-3=0.5-29", 128)
callNode.addRect(3, gg_rct_Rect_003, "2=0.5-4=0.5-26=0.5", 128)
callNode.addRect(4, gg_rct_Rect_004, "3=0.5-5=0.5", 128)
callNode.addRect(5, gg_rct_Rect_005, "4=0.5-6=0.5-48", 128)
callNode.addRect(6, gg_rct_Rect_006, "5=0.5-7=0.5-44", 128)
callNode.addRect(7, gg_rct_Rect_007, "6=0.5-8=0.5-47", 128)
callNode.addRect(8, gg_rct_Rect_008, "7=0.5-9=0.5-45", 128)
callNode.addRect(9, gg_rct_Rect_009, "8=0.5-10=0.5", 128)
callNode.addRect(10, gg_rct_Rect_010, "9=0.5-11=0.5", 128)
callNode.addRect(11, gg_rct_Rect_011, "10=0.5-12=0.5", 128)
callNode.addRect(12, gg_rct_Rect_012, "11=0.5-13=0.5", 128)
callNode.addRect(13, gg_rct_Rect_013, "12=0.5-14=0.5", 128)
callNode.addRect(14, gg_rct_Rect_014, "13=0.5-15=0.5-46", 128)
callNode.addRect(15, gg_rct_Rect_015, "14=0.5-16=0.5", 128)
callNode.addRect(16, gg_rct_Rect_016, "15=0.5-17=0.5-28=0.5", 128)
callNode.addRect(17, gg_rct_Rect_017, "16=0.5-18=0.5", 128)
callNode.addRect(18, gg_rct_Rect_018, "17=0.5-19=0.5-25=0.5", 128)
callNode.addRect(19, gg_rct_Rect_019, "18=0.5-20=0.5-36", 128)
callNode.addRect(20, gg_rct_Rect_020, "19=0.5-21=0.5", 128)
callNode.addRect(21, gg_rct_Rect_021, "20=0.5-22=0.5", 128)
callNode.addRect(22, gg_rct_Rect_022, "21=0.5-23=0.5", 128)
callNode.addRect(23, gg_rct_Rect_023, "22=0.5-24=0.5-25=0.5", 128)
callNode.addRect(24, gg_rct_Rect_024, "23=0.5-1=0.5-29", 128)
callNode.addRect(25, gg_rct_Rect_025, "18=0.5-23=0.5-35-36", 128)
callNode.addRect(26, gg_rct_Rect_026, "3=0.5-27=0.5-37", 128)
callNode.addRect(27, gg_rct_Rect_027, "26=0.5-28=0.5-32", 128)
callNode.addRect(28, gg_rct_Rect_028, "27=0.5-16=0.5-33-42", 128)
callNode.addRect(29, gg_rct_Rect_029, "2-24-30", 64)
callNode.addRect(30, gg_rct_Rect_030, "29-31-33", 64)
callNode.addRect(31, gg_rct_Rect_031, "30-32", 64)
callNode.addRect(32, gg_rct_Rect_032, "31-27", 64)
callNode.addRect(33, gg_rct_Rect_033, "30-34-28", 64)
callNode.addRect(34, gg_rct_Rect_034, "33-35", 64)
callNode.addRect(35, gg_rct_Rect_035, "34-25", 64)
callNode.addRect(36, gg_rct_Rect_036, "19-25", 64)
callNode.addRect(37, gg_rct_Rect_037, "26-38", 64)
callNode.addRect(38, gg_rct_Rect_038, "37-39", 64)
callNode.addRect(39, gg_rct_Rect_039, "38-40", 64)
callNode.addRect(40, gg_rct_Rect_040, "39-41", 64)
callNode.addRect(41, gg_rct_Rect_041, "40-42-43", 64)
callNode.addRect(42, gg_rct_Rect_042, "41-45-28", 64)
callNode.addRect(43, gg_rct_Rect_043, "41-44", 64)
callNode.addRect(44, gg_rct_Rect_044, "6-43", 64)
callNode.addRect(45, gg_rct_Rect_045, "8-42-46", 64)
callNode.addRect(46, gg_rct_Rect_046, "14-45", 64)
callNode.addRect(47, gg_rct_Rect_047, "7-48", 64)
callNode.addRect(48, gg_rct_Rect_048, "5-47", 64)
//// End of Setting up Nodes//// End of Setup//// ================================================================================== endfunctionprivatefunctionErrortakesstringmsgreturnsnothingdebugcallDisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PAS Error: "+msg)
endfunctionprivatefunctionisNodeFunctakesnothingreturnsbooleanreturnGetUnitTypeId(GetFilterUnit()) == MARKER_IDendfunction//! textmacro PAS_MARKER_ID takes VALUE//! external ObjectMerger w3u hfoo $VALUE$ uabi ACmi,Avul umdl .mdl ushu "" uaen "" utar "" umvs 0 umvt fly ucol 0 ufoo 0 uhom 1 uhpm 99999 uhpr 99999 upgr "" unam "PAS Marker"globalspublicconstantintegerMARKER_ID = '$VALUE$'
endglobals//! endtextmacroglobalsprivateconstanthashtablestorage = InitHashtable()
privateconstantintegerMAX_STRUCT = MAX_LINKS*MAX_NODESprivateconstantintegergetNode = -1privateconstantintegergetLink = -2privateconstantgroupTempGroup = CreateGroup()
privateboolexprisNode = nullendglobalspublicstructPoint// easier than having so many reals...realxrealystaticmethodcreatetakesrealx, realyreturnsthistypelocalthistypethis = thistype.allocate()
set.x = xset.y = yreturnthisendmethodendstructprivatetypelinkHelperextendsNodearray[MAX_LINKS]
privatetypelengthHelperextendsrealarray[MAX_LINKS]
privatetypeweightHelperextendsrealarray[MAX_LINKS]
privatetypeenabledHelperextendsbooleanarray[MAX_LINKS]
privatekeywordxxprivatekeywordxyprivatekeywordxmarkerprivatekeywordxlinksprivatekeywordxlinkWeightprivatekeywordxlinkMultiprivatekeywordxlinkSizeprivatekeywordxlinkEnabledprivatekeywordxnodeSizeprivatekeywordxlinkDataprivatekeywordxidpublicstructNoderealxxrealxyunitxmarkerlinkHelperxlinkslengthHelperxlinkLengthweightHelperxlinkWeightenabledHelperxlinkEnabledintegerxlinkSizerealxnodeSizestringxlinkDataintegerxidprivatestaticthistypearraynodes[MAX_NODES]
staticmethodoperator[] takesintegeridreturnsthistypereturnthistype.nodes[id]
endmethod//! textmacro PAS_ReadOnly takes NAME, TYPEmethodoperator$NAME$takesnothingreturns$TYPE$return.x$NAME$endmethod//! endtextmacro//! runtextmacro PAS_ReadOnly("x", "real")//! runtextmacro PAS_ReadOnly("y", "real")//! runtextmacro PAS_ReadOnly("marker", "unit")//! runtextmacro PAS_ReadOnly("linkSize", "integer")//! runtextmacro PAS_ReadOnly("nodeSize", "real")//! runtextmacro PAS_ReadOnly("id", "integer")//! runtextmacro PAS_ReadOnly("links", "linkHelper")//! runtextmacro PAS_ReadOnly("linkLength", "lengthHelper")//! runtextmacro PAS_ReadOnly("linkWeight", "weightHelper")//! runtextmacro PAS_ReadOnly("linkEnabled", "enabledHelper")methodchangeWeighttakesintegeri, realweightreturnsnothinglocalPAS_Nodefor = .xlinks[i]
set.xlinkLength[i] = SquareRoot((.xx-for.xx)*(.xx-for.xx)+(.xy-for.xy)*(.xy-for.xy)) * weightset.xlinkWeight[i] = weightendmethodmethodchangeWeightForNodetakesNodefor, realweightreturnsnothinglocalintegeri = 0loopexitwheni >= .xlinkSizeif.xlinks[i] == forthen// right linkset.xlinkLength[i] = SquareRoot((.xx-for.xx)*(.xx-for.xx)+(.xy-for.xy)*(.xy-for.xy)) * weightset.xlinkWeight[i] = weightreturnendifseti = i + 1endloopendmethodmethodsetLinkEnabledtakesintegeri, booleanenabledreturnsnothingset.xlinkEnabled[i] = enabledendmethodmethodsetLinkEnabledForNodetakesNodefor, booleanenabledreturnsnothinglocalintegeri = 0loopexitwheni >= .xlinkSizeif.xlinks[i] == forthen// right linkset.xlinkEnabled[i] = enabledreturnendifseti = i + 1endloopendmethodstaticmethodfromMarkertakesunitureturnsthistypereturnLoadInteger(storage, getNode, GetHandleId(u))
endmethodstaticmethodgetNearesttakesrealx, realyreturnsthistypelocalunitu = nulllocalrealdist = 999999localrealcurDist = 0localthistypeclosestlocalthistypecurcallGroupEnumUnitsInRange(TempGroup, x, y, NODE_TEST_RANGE, isNode)
loopsetu = FirstOfGroup(TempGroup)
exitwhenu == nullsetcur = thistype.fromMarker(u)
setcurDist = (cur.xx-x)*(cur.xx-x)+(cur.xy-y)*(cur.xy-y)
ifcurDist < distthensetclosest = cursetdist = curDistendifcallGroupRemoveUnit(TempGroup, u)
endloopsetu = nullreturnclosestendmethodprivatestaticmethodcreatetakesnothingreturnsthistypecallError("I don't know how you called PAS_Node.create(), but don't!")
return0endmethodstaticmethodaddtakesintegerid, realx, realy, stringlinkData, realsizereturnsthistypelocalthistypethis = thistype.allocate()
staticifDEBUG_MODEthenifid < 1thencallError("ID "+I2S(id)+" is not a valid Node ID!")
elseifid > MAX_NODESthencallError("ID "+I2S(id)+" exceeds MAX_NODES!")
endififlinkData == ""thencallError("Node "+I2S(id)+" has no Link data!")
endifendifset.xx = xset.xy = yset.xid = idifsize < NODE_MIN_SIZEthensetsize = NODE_DEFAULT_SIZEendifset.xmarker = CreateUnit(Player(15), MARKER_ID, .xx, .xy, 0)
callSaveInteger(storage, getNode, GetHandleId(.xmarker), this)
set.xnodeSize = sizeset.xlinkData = linkDataset.xlinkSize = 0set.xlinks = linkHelper.create()
set.xlinkLength = lengthHelper.create()
set.xlinkWeight = weightHelper.create()
set.xlinkEnabled = enabledHelper.create()
set.nodes[id] = thisreturnthisendmethodstaticmethodaddRecttakesintegerid, rectr, stringlinkData, realsizereturnsthistypeifr == nullthencallError("Node "+I2S(id)+" received a null rect from its addRect method!")
return0endifreturnthistype.add(id, GetRectCenterX(r), GetRectCenterY(r), linkData, size)
endmethodendstructprivatestructNearEdgeEdgeePointnearrealdiststaticmethodcreatetakesEdgee, realx, realy, realdistreturnsthistypelocalthistypethis = thistype.allocate()
set.e = eset.near = Point.create(x, y)
set.dist = distreturnthisendmethodmethodonDestroytakesnothingreturnsnothingif.near != 0thencall.near.destroy()
endifendmethodendstructprivatestructEdgeNodeaNodebstaticmethodgettakesNodea, Nodebreturnsthistypeifa.xid < b.xidthenreturnLoadInteger(storage, getLink, a*MAX_NODES+b)
elsereturnLoadInteger(storage, getLink, b*MAX_NODES+a)
endifendmethod// create is duplicate safe, so you can't create the same link twicestaticmethodcreatetakesNodea, Nodebreturnsthistypelocalthistypethis = thistype.get(a, b)
ifthis == 0then// link hasn't been created yetsetthis = thistype.allocate()
ifa.xid > b.xidthen// make sure lowest is at aset.a = bset.b = aelseset.a = aset.b = bendifcallSaveInteger(storage, getLink, a*MAX_NODES+b, this)
endifreturnthisendmethodstaticmethodgetNearesttakesrealx, realyreturnsNearEdgelocalintegerarraychecked// 0 = no, 1 = yeslocalunitu = nulllocalrealdist = LINK_TEST_RANGE*LINK_TEST_RANGE+50// amounts should never be longer than thislocalthistypeshortest = 0localrealshortx = 0localrealshorty = 0localthistypecur = 0localintegeri = 0localNoden = 0locallocationl = nulllocalrealtempx = 0localrealtempy = 0localrealtempdist = 0callGroupEnumUnitsInRange(TempGroup, x, y, LINK_TEST_RANGE, isNode) // gather all nodes nearbyloopsetu = FirstOfGroup(TempGroup) // loop through the nodes checking each linkexitwhenu == nullsetn = Node.fromMarker(u) // get the node from the markerseti = 0loopexitwheni >= n.xlinkSizeifn.xlinkEnabled[i] then// don't check disabled linkssetcur = thistype.get(n, n.xlinks[i])
ifchecked[cur] != 1then// avoid checking the same edge multiple timessetchecked[cur] = 1setl = GetNearestPointOnSegment(cur.a.xx, cur.a.xy, cur.b.xx, cur.b.xy, x, y) // get the nearest pointsettempx = GetLocationX(l) // save the points to varaibles so we can reduce calls now as well settempy = GetLocationY(l) // as pass them in the edge struct on the return.settempdist = (tempx-x)*(tempx-x)+(tempy-y)*(tempy-y) // we can square the distance at the endiftempdist < distthen// nearestsetshortest = cursetdist = tempdistsetshortx = tempxsetshorty = tempyendifcallRemoveLocation(l)
endifendifseti = i + 1endloopcallGroupRemoveUnit(TempGroup, u)
endloopsetl = nullsetu = nullifshortest != 0thenreturnNearEdge.create(shortest, shortx, shorty, SquareRoot(dist))
endifreturn0endmethodendstructprivatekeywordsetStartprivatekeywordsetEndprivatekeywordxcreatepublicstructPathListnodesPointstartPointendPointnearStartPointnearEndstaticmethodcreatetakesnothingreturnsthistypecallError("PAS_Path.create() is private! Use PAS_GetPath()")
return0endmethodstaticmethodxcreatetakesNearEdgestart, NearEdgeend, Listnreturnsthistypelocalthistypethis = thistype.allocate()
set.nearStart = Point.create(start.near.x, start.near.y)
set.nearEnd = Point.create(end.near.x, end.near.y)
set.nodes = nset.start = 0set.end = 0returnthisendmethodmethodsetStarttakesrealx, realyreturnsnothingif.start != 0thencall.start.destroy()
endifset.start = Point.create(x, y)
endmethodmethodsetEndtakesrealx, realyreturnsnothingif.end != 0thencall.end.destroy()
endifset.end = Point.create(x, y)
endmethodmethodonDestroytakesnothingreturnsnothingif.nodes != 0thencall.nodes.destroy()
endifif.start != 0thencall.start.destroy()
endifif.end != 0thencall.end.destroy()
endifif.nearStart != 0thencall.nearStart.destroy()
endifif.nearEnd != 0thencall.nearEnd.destroy()
endifendmethodendstructprivatefunctionAStartakesNearEdgestart, NearEdgeendreturnsPathlocalNodecurNode = 0localNodecurLink = 0localNodearrayfromlocalEdgese = start.elocalEdgeee = end.elocalintegerlink = 0localintegerarraynodeStatuslocalrealarrayfscorelocalrealarraygscorelocalListl = List.create()
localLinktempLink// this version improves the routes as it takes into account the links as much as the noodes// we give each node in the link their g and f scores, based from the nearest point on the link. from there we pick the best// node to start with and add the other to the list of open nodessetgscore[se.a] = SquareRoot((start.near.x-se.a.xx)*(start.near.x-se.a.xx)+(start.near.y-se.a.xy)*(start.near.y-se.a.xy))
setgscore[se.b] = SquareRoot((start.near.x-se.b.xx)*(start.near.x-se.b.xx)+(start.near.y-se.b.xy)*(start.near.y-se.b.xy))
setfscore[se.a] = gscore[se.a] + SquareRoot((se.a.xx-end.near.x)*(se.a.xx-end.near.x)+(se.a.xy-end.near.y)*(se.a.xy-end.near.y))
setfscore[se.b] = gscore[se.b] + SquareRoot((se.b.xx-end.near.x)*(se.b.xx-end.near.x)+(se.b.xy-end.near.y)*(se.b.xy-end.near.y))
iffscore[se.a] < fscore[se.b] thensetcurNode = se.acallLink.create(l, se.b)
setnodeStatus[se.b] = 1elsesetcurNode = se.bcallLink.create(l, se.a)
setnodeStatus[se.a] = 1endifloop// once we get to either end node, it will be the shortest path, as the best possible route is always takenexitwhencurNode == ee.aorcurNode == ee.bifcurNode == 0thencalll.destroy()
return0endifsetnodeStatus[curNode] = 2setlink = 0loop//Link Loopexitwhenlink >= curNode.xlinkSizeifcurNode.xlinkEnabled[link] thensetcurLink = curNode.xlinks[link]
ifnodeStatus[curLink] == 0thensetgscore[curLink] = curNode.xlinkLength[link] + gscore[curNode]
setfscore[curLink] = gscore[curLink] + SquareRoot((curLink.xx-end.near.x)*(curLink.xx-end.near.x)+(curLink.xy-end.near.y)*(curLink.xy-end.near.y))
setfrom[curLink] = curNodesetnodeStatus[curLink] = 1settempLink = l.firstloopiftempLink == 0thencallLink.createLast(l, curLink)
exitwhentrueendififfscore[curLink] < fscore[tempLink.data] thencalltempLink.insertBefore(curLink)
exitwhentrueendifsettempLink = tempLink.nextendloopendifendifsetlink = link + 1endloopsetcurNode = l.first.dataifl.first != 0thencalll.first.destroy()
endifendloop// now l does double-dutycalll.destroy()
setl = List.create()
loopexitwhencurNode == 0callLink.create(l, curNode)
setcurNode = from[curNode]
endloopreturnPath.xcreate(start, end, l)
endfunctionpublicfunctionGetPathtakesrealstartx, realstarty, realendx, realendyreturnsPathlocalNearEdgestart = 0localNearEdgeend = 0localPathp = 0debuglocalstringmsgdebuglocalLinktempdebuglocalbooleancomma = falsesetstart = Edge.getNearest(startx, starty)
ifstart == 0then// no nearby edgereturn0endifsetend = Edge.getNearest(endx, endy)
ifend == 0then// no nearby edgecallstart.destroy()
return0endif// need to check if start and end are the same edge, cause then we don't have to go to nodesifstart.e == end.ethensetp = Path.xcreate(start, end, 0) // no nodes, don't need a pathelsesetp = AStar(start, end)
ifp == 0then// AStar couldn't find pathcallstart.destroy()
callend.destroy()
callError("Couldn't find path from "+R2S(startx)+", "+R2S(starty)+" to "+R2S(endx)+", "+R2S(endy))
return0endifendifcallp.setStart(startx, starty)
callp.setEnd(endx, endy)
staticifDEBUG_MODEthenifDEBUG_SHOW_PATHSthensettemp = p.nodes.firstsetmsg = "GetPath("+R2S(startx)+","+R2S(starty)+","+R2S(endx)+","+R2S(endy)+")= ["loopexitwhentemp == 0ifcommathensetmsg = msg + ", "elsesetcomma = trueendifsetmsg = msg + "|cff00ff00"+I2S(Node(temp.data).xid)+"|r"settemp = temp.nextendloopsetmsg = msg + "]"callDisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, msg)
endifendifcallstart.destroy()
callend.destroy()
returnpendfunctionprivatefunctionLinkNodestakesnothingreturnsnothinglocalintegeri = 1localNodeto = 0localNodecur = 0localstringtemp = ""localrealweight = 1.localintegersi = 0localintegersa = 0localintegersl = 0localintegerwi = 0localintegerwa = 0loopexitwheni > MAX_NODESsetcur = Node[i]
ifcur != 0then// calculate linkssetsi = 1setsa = 0setsl = StringLength(cur.xlinkData)
loopsettemp = SubString(cur.xlinkData, si-1, si)
iftemp == "-"ortemp == "="orsi > slthensetto = Node[S2I(SubString(cur.xlinkData, sa, si-1))]
ifto != 0andto != curandcur.xlinkSize < MAX_LINKStheniftemp == "="thensetsa = siloopexitwhenSubString(cur.xlinkData, si, si+1) == "-"orsi >= slsetsi = si + 1endloopifsa == sithen// there were no chars after the equal signcallError("Node "+I2S(i)+" has missing weight data for link "+I2S(cur.xlinkSize+1)+" (has an = but no value for weight)")
setweight = 1elsesetweight = S2R(SubString(cur.xlinkData, sa, si))
endifsetsi = si + 1setcur.xlinkWeight[cur.xlinkSize] = weightsetcur.xlinkLength[cur.xlinkSize] = weight * SquareRoot((to.xx-cur.xx)*(to.xx-cur.xx)+(to.xy-cur.xy)*(to.xy-cur.xy))
elsesetcur.xlinkWeight[cur.xlinkSize] = 1.setcur.xlinkLength[cur.xlinkSize] = SquareRoot((to.xx-cur.xx)*(to.xx-cur.xx)+(to.xy-cur.xy)*(to.xy-cur.xy))
endifcallEdge.create(cur, to)
setcur.xlinks[cur.xlinkSize] = tosetcur.xlinkEnabled[cur.xlinkSize] = truesetcur.xlinkSize = cur.xlinkSize + 1elseifto == 0thencallError("Link "+I2S(cur.xlinkSize+1)+" for Node "+I2S(i)+", doesn't link to a valid node!")
elseifto == curthencallError("Link "+I2S(cur.xlinkSize+1)+" for Node "+I2S(i)+", links to self!")
endififcur.xlinkSize >= MAX_LINKSthencallError("Node "+I2S(i)+" links to more than MAX_LINKS Links!")
endifendifsetsa = siendifexitwhensi > slsetsi = si + 1endloopelsecallError("Node not created for ID "+I2S(i))
endifseti = i + 1endloopendfunctionprivatefunctiononInittakesnothingreturnsnothingsetisNode = Filter(functionisNodeFunc)
ifMAX_STRUCT > 8190thencallError("MAX_NODES and MAX_LINKS exceed 8190. Either reduce the number of nodes or the number of links!")
endifcallSetupNodes.execute()
callLinkNodes.execute()
endfunctionendlibrary
PASMove:
// ==================================================================================// PASMove Plugin - by Ammorth Feb 17, 2010// **Requires JassHelper (vJass) to compile**// Requires: PAS (in this map)// optional PASMoveSafety (in this map)//// about:// This is a plugin for Pathing Algorithm System that can order units around on paths.// It really only has 2 function wrappers, but you can do a few extra things using// the base types of this library//// Requirements:// - PAS// - optional PASMoveSafety//// Installation:// - Create a new trigger called PASMove// - Copy this code to the new trigger// // Setup:// - Modify the constant globals below to your liking//// Function List:// PASMove_IssuePointPathOrder(unit whichUnit, real x, real y, string order) -> boolean// PASMove_IssuePointPathOrderById(unit whichUnit, real x, real y, string order) -> boolean// PASMove_UnitPath.create(unit whichUnit, PAS_Path, path, integer order) -> PASMove_UnitPath//// Usage:// You can quickly order units to cordinates using the following 2 functions://// PASMove_IssuePointPathOrder(unit whichUnit, real x, real y, string order)//// or//// PASMove_IssuePointPathOrderById(unit whichUnit, real x, real y, string order)//// They function just like their native IssuePointOrder() counter-part, execept they use the PAS// path to move units on. The unit will move to the nearest edge, around the path, and then off// the path once they are near the desired point. The order passed is the order given to the unit// at each node, to the next node. Therefore, you should choose only orders that will move units// ("move", "smart", "attack", etc) otherwise units will get stuck and not move around.//// Aswell, you can directly use the UnitPath struct via the following methods://// PASMove_UnitPath.create(unit whichUnit, PAS_Path, path, integer order)//// will create the PASMove_UnitPath struct, from there you can use://// PASMove_UnitPath.registerAfterPath(afterPath function)//// this will allow you to register a function that will be called once a unit has completed moving.// the function must take a unit (which will be the unit that finished pathing) and return nothing.// You can then do other stuff with the unit or order it to move somewhere else. (check the Populate// City test trigger for an example.//// You can then call://// PASMove_UnitPath.startPath()//// which will begin ording the unit around the supplied path. Calling this again after a unit is// already moving will cause it to start moving from the start again (causing it to run straight// from the where it currently is, to the nearPoint). Therefore it is recommended to call this// only once and right after you have created the UnitPath.//// To get a UnitPath attached to a unit, use://// PASMove_UnitPath[whichUnit]//// to get the path (if any). A unit without a path will return 0 (null).//// If you want to remove a UnitPath pre-maturly, you can call .destroy() on the UnitPath to remove it.// // Keep in mind, once a unit is done pathing, the UnitPath will be cleared automatically, before your// afterPath function is called. Aswell, if you generate a new UnitPath for a unit, any old UnitPath// associated with that unit will be destroyed. Destroying a UnitPath also doesn't cause a unit to stop,// nor will it call the afterPath function. The unit will keep moving to its next location before comming// to a stop.//// Notes://// When a unit dies or is removed, their UnitPath is not removed. Aswell, if a unit get stuck while trying// to move to a node or a point, there is no way to really detect it. For these reasons I wrote a plugin// for PASMove, PASMoveSafety. If this is included in your map, it will periodically check for stuck,// dead or removed units, and either clean up the UnitPath or re-order the unit on its way. This way, you can// be assured units will reach their destination and will be cleaned up upon dying/being removed. Some maps// may not run into issues with either, which is why I made it optional, as it does take up a bit of CPU power.//// ==================================================================================libraryPASMoveinitializeronInitrequiresPASoptionalPASMoveSafetyglobals// ==================================================================================//// Start of Constant Globals//// the range at which a unit is deemed to be at a point. This is not used for nodes, only points// such as nearStart, nearEnd and end.privateconstantrealIN_RANGE_OF_POINT = 64// The period between checking if units are at nodes. Lower numbers means more checks per second// but more CPU intensive. Higher numbers means less frequent checks. This is a time in seconds.privateconstantrealTIMER_PERIOD = 0.025// if Debug Mode is on and this is true, it will show an effect on all active nodes.// About ActiveNodes. to reduce calculations, only nodes that have units moving to them// will be checked for nearby units. These nodes are considered active. This is good// to debug PASMovement, and it can also be fun to watch how the plugin operates.privateconstantbooleanSHOW_ACTIVE_EFFECT = false// only works if DebugMode is enabled in JassHelper//// End of Constant Globals//// ==================================================================================endglobalspublickeywordUnitPathprivatekeywordActiveNodeprivatekeywordActivePoint// hides these methods from the outside so you can't call them// you shouldn't have to call them, ever.privatekeywordorderUnitToNextglobalsprivatetimermainTimer = nullprivateconstanthashtablestorage = InitHashtable()
privateconstantintegergetPath = -1// negatives can't interfere with stored structsprivategroupTempGroup = CreateGroup()
endglobalsprivatefunctionErrortakesstringmsgreturnsnothingdebugcallDisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PASMovement Error: "+msg)
endfunctionprivatestructActiveNodePAS_NodenintegercountintegerslotstaticthistypearrayactivesstaticintegeractiveSize = 0staticthistypearrayfromNodesstaticmethodcreatetakesPAS_Nodenreturnsthistypelocalthistypethis = thistype.allocate()
set.n = nset.count = 1set.slot = .activeSizeset.actives[.slot] = thisset.activeSize = .activeSize + 1set.fromNodes[n] = thisreturnthisendmethodstaticmethodaddtakesPAS_Nodenreturnsnothinglocalthistypethis = thistype.fromNodes[n]
ifthis == 0thencallthistype.create(n)
elseset.count = .count + 1endifendmethodmethodremovetakesnothingreturnsboolean// true if removedlocalthistypelastifthis != 0and.count != 0then// prevent double-freeset.count = .count - 1if.count == 0then// no more counts for node, remove from activeset.activeSize = .activeSize - 1set.fromNodes[.n] = 0setlast = .actives[.activeSize]
set.actives[.slot] = last// move the last active node to heresetlast.slot = .slot// update the last active node to the new slotcall.destroy()
returntrueendifelsecallError("Double-free of type ActiveNode!")
endifreturnfalseendmethodmethodcheckForPathertakesnothingreturnsbooleanlocalunitu = nulllocalUnitPathpathcallGroupEnumUnitsInRange(TempGroup, .n.x, .n.y, .n.nodeSize, null)
loopsetu = FirstOfGroup(TempGroup)
exitwhenu == nullsetpath = UnitPath[u]
ifpath.step.data == .nthen// this unit as at the proper nodecallpath.orderUnitToNext()
if.remove() then// was removed, stop checkingcallGroupClear(TempGroup)
setu = nullreturntrueendifendifcallGroupRemoveUnit(TempGroup, u)
endloopsetu = nullreturnfalseendmethodendstructprivatestructActivePointPAS_PointpintegerslotstaticthistypearrayactivesstaticintegeractiveSize = 0staticthistypearrayfromPointsstaticmethodcreatetakesPAS_Pointpreturnsthistypelocalthistypethis = thistype.allocate()
set.p = pset.slot = .activeSizeset.actives[.slot] = thisset.activeSize = .activeSize + 1set.fromPoints[p] = thisreturnthisendmethodstaticmethodaddtakesPAS_Pointpreturnsnothinglocalthistypethis = thistype.fromPoints[p]
ifthis == 0thencallthistype.create(p)
elsecallError("Double add of Point to ActivePoint!")
endifendmethodmethodremovetakesnothingreturnsboolean// true if removedlocalthistypelastifthis != 0then// prevent double-freeset.activeSize = .activeSize - 1set.fromPoints[.p] = 0setlast = .actives[.activeSize]
set.actives[.slot] = last// move the last active node to heresetlast.slot = .slot// update the last active node to the new slotcall.p.destroy()
call.destroy()
returntrueelsecallError("Double-free of type ActivePoint!")
endifreturnfalseendmethodmethodcheckForPathertakesnothingreturnsbooleanlocalPAS_PointtempPoint = 0localunitu = nulllocalUnitPathpathcallGroupEnumUnitsInRange(TempGroup, .p.x, .p.y, IN_RANGE_OF_POINT, null)
loopsetu = FirstOfGroup(TempGroup)
exitwhenu == nullsetpath = UnitPath[u]
ifpath.point == .pthen// this unit as at the proper point// only 1 unit should be moving to a specific pointsetpath.point = 0callpath.orderUnitToNext()
if.remove() then// was removed, stop checkingcallGroupClear(TempGroup)
setu = nullreturntrueendifendifcallGroupRemoveUnit(TempGroup, u)
endloopsetu = nullreturnfalseendmethodendstructprivatefunctiononTimerPeriodtakesnothingreturnsnothinglocalintegeri = 0localActiveNoden = 0localActivePointp = 0loopexitwheni >= ActiveNode.activeSizesetn = ActiveNode.actives[i]
staticifDEBUG_MODEthenifSHOW_ACTIVE_EFFECTthencallDestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\GeneralAuraTarget\\GeneralAuraTarget.mdl", n.n.x, n.n.y))
endifendififnotn.checkForPather() then// don't increment i if we removed a nodeseti = i + 1endifendloopseti = 0loopexitwheni >= ActivePoint.activeSizesetp = ActivePoint.actives[i]
staticifDEBUG_MODEthenifSHOW_ACTIVE_EFFECTthencallDestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\GeneralAuraTarget\\GeneralAuraTarget.mdl", p.p.x, p.p.y))
endifendififnotp.checkForPather() then// don't increment i if we removed a nodeseti = i + 1endifendloopendfunctionfunctioninterfaceafterPathtakesunitureturnsnothingprivatekeywordorderUnitToNextpublicstructUnitPathunitpatherPAS_PointpointPAS_PathpathLinkstepintegerstepTypeintegerorderafterPathfuncrealtoxrealtoybooleantoExiststaticmethodoperator[] takesunitureturnsthistypereturnLoadInteger(storage, getPath, GetHandleId(u))
endmethodstaticmethodcreatetakesunitu, PAS_Pathp, integerorderreturnsthistypelocalthistypethis = thistype.allocate()
set.toExist = falseset.pather = uset.point = 0set.path = pset.step = 0set.stepType = 0set.order = orderset.func = 0callSaveInteger(storage, getPath, GetHandleId(.pather), this)
returnthisendmethodmethodonDestroytakesnothingreturnsnothingstaticifLIBRARY_PASMoveSafetythen// link into safety sytemcallPASMoveSafety_RemoveUnitPath(this)
endififLoadInteger(storage, getPath, GetHandleId(.pather)) == thisthencallSaveInteger(storage, getPath, GetHandleId(.pather), 0)
endifset.pather = nullif.point != 0thencallActivePoint.fromPoints[.point].remove() // destroys the point aswellset.point = 0endifif.step != 0then// need to clean-up an active nodecallActiveNode.fromNodes[.step.data].remove()
set.step = 0endifif.path != 0thencall.path.destroy()
set.path = 0endifset.toExist = falseendmethodmethodregisterAfterPathtakesafterPathapreturnsnothingset.func = apendmethodmethodorderUnitToNexttakesnothingreturnsnothinglocalunitu = nulllocalPAS_Noden = 0localafterPathfunc = 0if.stepType == 0then// havn't done anything yet// move to the nearest point on the starting edgeset.stepType = 1ifIsUnitInRangeXY(.pather, .path.nearStart.x, .path.nearStart.y, IN_RANGE_OF_POINT) thencall.orderUnitToNext()
elseset.point = PAS_Point.create(.path.nearStart.x, .path.nearStart.y)
callActivePoint.add(.point)
set.tox = .path.nearStart.xset.toy = .path.nearStart.ycallIssuePointOrderById(.pather, .order, .path.nearStart.x, .path.nearStart.y)
endifelseif.stepType == 1then// moving to nodesif.step == 0then// first nodeset.step = .path.nodes.firstelseset.step = .step.nextendifif.step == 0then// last nodeset.stepType = 2ifIsUnitInRangeXY(.pather, .path.nearEnd.x, .path.nearEnd.y, IN_RANGE_OF_POINT) thencall.orderUnitToNext()
elseset.point = PAS_Point.create(.path.nearEnd.x, .path.nearEnd.y)
callActivePoint.add(.point)
set.tox = .path.nearEnd.xset.toy = .path.nearEnd.yset.toExist = truecallIssuePointOrderById(.pather, .order, .path.nearEnd.x, .path.nearEnd.y)
endifelse// moving to a normal nodesetn = PAS_Node(.step.data)
ifIsUnitInRangeXY(.pather, n.x, n.y, n.nodeSize) thencall.orderUnitToNext()
elsecallActiveNode.add(n)
set.tox = n.xset.toy = n.yset.toExist = truecallIssuePointOrderById(.pather, .order, n.x, n.y)
endifendifelseif.stepType == 2then// moving to final pointset.stepType = 3ifIsUnitInRangeXY(.pather, .path.end.x, .path.end.y, IN_RANGE_OF_POINT) thencall.orderUnitToNext()
elseset.point = PAS_Point.create(.path.end.x, .path.end.y)
callActivePoint.add(.point)
set.tox = .path.end.xset.toy = .path.end.yset.toExist = truecallIssuePointOrderById(.pather, .order, .path.end.x, .path.end.y)
endifelseif.stepType == 3then// at final pointset.toExist = falseif.func != 0thensetu = .pathersetfunc = .funccall.destroy()
callfunc.execute(u)
setu = nullreturnelsecall.destroy()
endifendifendmethodmethodstartPathtakesnothingreturnsnothingset.stepType = 0set.step = 0if.step != 0then// was walking to a node, clean it upcallActiveNode.fromNodes[.step.data].remove()
set.step = 0endifif.point != 0then// was walking to a point, clean it upcallActivePoint.fromPoints[.point].remove()
set.point = 0endifstaticifLIBRARY_PASMovementSafetythen// link into safety sytemcallPASMoveSafety_AddUnitPath(this)
endifcall.orderUnitToNext()
endmethodendstructpublicfunctionIssuePointPathOrderByIdtakesunitu, realx, realy, integerorderreturnsbooleanlocalrealsx = GetUnitX(u)
localrealsy = GetUnitY(u)
localPAS_Pathp = PAS_GetPath(sx, sy, x, y)
localUnitPathunitpath = UnitPath[u]
ifunitpath != 0then// already on a pathcallunitpath.destroy()
endififp != 0then// path exists// assign the path to the unitsetunitpath = UnitPath.create(u, p, order)
// start the unit on the pathcallunitpath.startPath()
returntrueendifreturnfalseendfunctionpublicfunctionIssuePointPathOrdertakesunitu, realx, realy, stringorderreturnsbooleanlocalintegero = OrderId(order)
ifo != 0thenreturnIssuePointPathOrderById(u, x, y, o)
endifreturnfalseendfunctionprivatefunctiononInittakesnothingreturnsnothingsetmainTimer = CreateTimer()
callTimerStart(mainTimer, TIMER_PERIOD, true, functiononTimerPeriod)
endfunctionendlibrary
PASMoveSafety:
// ==================================================================================// PASMoveSafety Plugin - by Ammorth Feb 17, 2010// **Requires JassHelper (vJass) to compile**// Requires: PAS (in this map)// LinkedList (by Ammorth, wc3c)//// about:// This plugin works with PASMove and makes sure units reach their destinations as well as // provides clean-up to removed/dead units. If a unit is trapped while trying to move to// a node, it can fail moving in the wc3 pathing engine and stop. Because PASMove does// not detect failed paths, the units will be forgotten forever. This sript remedies that// by searching through moving units for ones that get stuck, and push them on their way.// Some maps may not run into this issue (usually happens when many units are trying to move// within a few nodes) and so this feature is optional to PASMove.//// Requirements:// - LinkedList (by Ammorth, at wc3c)// - PAS// - although PASMove isn't required for this script, it makes little sense to include this// if PASMove is not present... //// Installation:// - Create a new trigger called PASMoveSafety// - Copy this code to the new trigger// // Setup:// - Modify the constant globals below to your liking//// Usage:// - None, just setup and enjoy!//// ==================================================================================libraryPASMoveSafetyinitializeronInitrequiresPAS, LinkedListglobals// ==================================================================================//// Start of Constant Globals//// the number of units to check per period. This is to make sure you don't check too many units at once.// Also note, if the number of moving units is less than this, it will check all units every period// It will also not check the same unit multiple times per periodprivateintegerAMOUNT_TO_CHECK_PER_PERIOD = 25// the time in seconds between checks. Setting this to higher values will reduce load while setting// it to lower values will increase load but find stuck unit fasterprivaterealTIMER_PERIOD = 0.25// End of Constant Globals//// ==================================================================================privatetimermainTimer = nullendglobalsprivatestructSafetyPASMove_UnitPathpLinkmyLinkstaticListtoCheckstaticLinkcurrentstaticthistypearraypathToSafetystaticmethodcreatetakesPASMove_UnitPathpreturnsthistypelocalthistypethis = thistype.allocate()
set.pathToSafety[p] = thisset.p = pset.myLink = Link.createLast(.toCheck, this)
returnthisendmethodmethodonDestroytakesnothingreturnsnothingif.myLink == .currentthen// make sure we arn't removing the current linkif.current.prev != 0thenset.current = .current.prevelseset.current = .toCheck.lastendifendifcall.myLink.destroy()
set.pathToSafety[.p] = 0set.p = 0endmethodstaticmethoddoChecktakesnothingreturnsnothinglocalthistypes = 0localintegercount = 0localintegerarraycheckedifthistype.toCheck.size == 0then// no units to checkreturnendififthistype.current == 0thensetthistype.current = thistype.toCheck.lastendifloopexitwhencount >= AMOUNT_TO_CHECK_PER_PERIODsets = Safety(thistype.current.data)
ifchecked[s] == 1then//looped aroundreturnendifsetchecked[s] = 1// we check for null units, dead units and units that stopped movingifGetWidgetLife(s.p.pather) <= 0.405then// dead or removedcalls.p.destroy()
elseifGetUnitCurrentOrder(s.p.pather) == 0then// stopped movingifs.p.toExist == truethencallIssuePointOrderById(s.p.pather, s.p.order, s.p.tox, s.p.toy)
endifendififthistype.current.prev != 0thensetthistype.current = thistype.current.prevelsesetthistype.current = thistype.toCheck.lastendifsetcount = count + 1endloopendmethodendstructpublicfunctionAddUnitPathtakesPASMove_UnitPathpreturnsnothingifSafety.pathToSafety[p] == 0then// already added, no point to add againcallSafety.create(p)
//call BJDebugMsg("registered safety")endifendfunctionpublicfunctionRemoveUnitPathtakesPASMove_UnitPathpreturnsnothingifSafety.pathToSafety[p] != 0thencallSafety.pathToSafety[p].destroy()
//call BJDebugMsg("unregistered safety")endifendfunctionprivatefunctiononInittakesnothingreturnsnothingsetSafety.toCheck = List.create()
setSafety.current = 0setmainTimer = CreateTimer()
callTimerStart(mainTimer, TIMER_PERIOD, true, functionSafety.doCheck)
endfunctionendlibrary
PASGPS:
// ==================================================================================// PASGPS Plugin - by Ammorth Feb 17, 2010// **Requires JassHelper (vJass) to compile**// Requires: ARGB (by Vexorian, at wc3c)// PAS (in this map)// // about:// This is a plugin for Pathing Algorithm System that creates a GPS-style waypoint// arrow above a unit, directing it to a given point, along the PAS nodes.//// Requirements:// - PAS// - ARGB//// Installation:// - Create a new trigger called PASGPS// - Copy this code to the new trigger// - Import both PAS_GPS_Arrow.mdx and PAS_GPS_Arrow.blp to your map// - Modify the path for both imports to remove "war3mapImported\"// // Setup:// - Modify the constant globals below to your liking// - Make sure the textmacro for the object merger line provides a unique rawcode id to your map// (aka, if 'pasg' conflicts with another unit in your map, change it to something else)//// Usage:// The following 2 functions are used to add and remove the GPS arrow on the given unit://// PASGPS_UnitSetGPS(unit whichUnit, real x, real y)//// sets a GPS arrow to the provided location while://// PASGPS_UnitRemoveGPS(unit whichUnit)// // will remove the GPS arrow (if it exists)//// Because of the nature of GPS arrows, I have decided to make it only 1 per player. Therefore,// if you create a new GPS arrow for a unit and the player already has an arrow on another unit,// that arrow will be removed and the new one will be created as normal.//// Also, arrows will only be visible to the owning player. Think of them like a UI extension.//// ==================================================================================libraryPASGPSinitializeronInitrequiresPAS, ARGB// ==================================================================================//// Start Object Merger Line//// set the value inside the quotes to a unique rawcode that isn't used in your map//! runtextmacro PASGPS_GPS_ID("pasg")//// This will auto-generate a unit in the object editor as well as asign the global// PASGPS_GPS_ID to the value here.//// End Object Merger Line//// ==================================================================================privatekeywordUnitGPSglobals// ==================================================================================//// Start of Constant Globals// the time, in seconds that the timer updates the visuals/path for each unit// setting this value lower will provide smoother visuals but with increased loadprivateconstantrealTIMER_PERIOD = 0.025// how long a GPS arrow will remain after the destination is reached// this is mainly to play the death animation of the arrowprivateconstantrealCLEAN_TIME = 0.4// should be longer than death animation// the ARGB color of the arrow for when you are on courseprivateARGBARROW_COLOR_ON = 0xFF00FF00// the ARGB color of the arrow for when you are slightly off courseprivateARGBARROW_COLOR_MID = 0xFFFFFF00// the ARGB color of the arrow for when you are totally off courseprivateARGBARROW_COLOR_OFF = 0xFFFF0000// the angle, in degrees, you can be off course before the arrow start changing colorprivateconstantrealARROW_ANGLE_GIVE = 33// the angle, in degress, for when you are totally off course// note, angles between ARROW_ANGLE_GIVE and ARROW_ANGLE_OFF will use a blend of// the ARROW_COLOR_ON, ARROW_COLOR_MID and ARROW_COLOR_OFF. When the unit is// farther than this angle, ARROW_COLOR_OFF will be used.privateconstantrealARROW_ANGLE_OFF = 90// when the unit gets within IS_AT_POINT to its current target, it will get the next target// basically, how close you have to get to each point for it to register you arrivedprivateconstantrealIS_AT_POINT = 192// how far you must deviate from progress before the path is recalculated.// sometimes players choose their own route and when they stop following the path for this distance// a new route is generated to the end point for them. This way, if they find an alternative path// the system will tell them to go down that path instead. This value shouldn't be too small, as it could cause// the arrow to continually point back onto the path, if the unit gets too far off.privateconstantrealDEVIATE_PATH = 512// End of Constant Globals//// ==================================================================================privateARGBarrowHide = 0x00FFFFFF// used to set alpha to 0 for other playerprivateconstantrealarrowGive = ARROW_ANGLE_GIVE*bj_DEGTORADprivateconstantrealarrowOff = ARROW_ANGLE_OFF*bj_DEGTORAD - arrowGiveprivateconstantrealarrowMid = arrowOff/2privatetimermainTimer = nullprivateUnitGPSarrayGPSunits// we only support 1 unit per playerendglobals//! textmacro PASGPS_GPS_ID takes VALUE//! external ObjectMerger w3u hfoo $VALUE$ uabi Aloc umdl "PAS_GPS_Arrow.mdl" ushu "" uaen "" utar "" umvh 200 umvs 522 umvr 3 umvt fly ucol 0 ufoo 0 uhom 1 uhpm 99999 uhpr 99999 upgr "" unam "PASGPS Arrow"globalspublicconstantintegerGPS_ID = '$VALUE$'
endglobals//! endtextmacro// requires radians to be passed and returns radiansprivatefunctionAngleBetweenAnglestakesrealangle1, realangle2returnsreallocalrealax = Cos(angle1)
localrealay = Sin(angle1)
localrealbx = Cos(angle2)
localrealby = Sin(angle2)
returnAcos(ax * bx + ay * by)
endfunctionprivatestructPointrealxrealyendstructprivatestructUnitGPSunituunitarrowintegercurPointrealtargetxrealtargetyrealgoodxrealgoodyrealgooddistrealcurxrealcuryrealfacerealendxrealendybooleanonCleanUprealcleanForPAS_PathpathLinkcurmethodisInRangetakesrealx, realyreturnsbooleanreturnSquareRoot((.curx-x)*(.curx-x)+(.cury-y)*(.cury-y)) <= IS_AT_POINTendmethodmethodcleanUptakesnothingreturnsnothingif.arrow == nullthen// we were told to path to where we were, no visuals to clean upcall.destroy()
elsecallSetUnitAnimation(.arrow, "Death")
set.onCleanUp = trueset.cleanFor = 0endifendmethodmethodgetTargettakesnothingreturnsbooleanifisInRange(.path.end.x, .path.end.y) then// already at end, no need to pathreturnfalseendifif.curPoint == 0then// just startingifisInRange(.path.nearStart.x, .path.nearStart.y) thenset.curPoint = 1// move onset.cur = .path.nodes.firstset.gooddist = 999999elseset.targetx = .path.nearStart.xset.targety = .path.nearStart.yreturntrueendifendifif.curPoint == 1then// nodesloopif.cur != 0thenifisInRange(PAS_Node(.cur.data).x, PAS_Node(.cur.data).y) thenset.cur = .cur.nextset.gooddist = 999999elseset.targetx = PAS_Node(.cur.data).xset.targety = PAS_Node(.cur.data).yreturntrueendifelseset.curPoint = 2set.gooddist = 999999exitwhentrueendifendloopendifif.curPoint == 2then// near endifisInRange(.path.nearEnd.x, .path.nearEnd.y) thenset.curPoint = 3// move onset.gooddist = 999999elseset.targetx = .path.nearEnd.xset.targety = .path.nearEnd.yreturntrueendifendifif.curPoint == 3thenifisInRange(.path.end.x, .path.end.y) thenset.curPoint = 4set.gooddist = 999999elseset.targetx = .path.end.xset.targety = .path.end.yreturntrueendifendifreturnfalse// at the endendmethodmethodpointAttakesnothingreturnsnothinglocalrealrad = Atan2(.targety - .cury, .targetx - .curx)
localrealoff = AngleBetweenAngles(.face, rad)
localrealr = 0localARGBcolor = 0ifoff < arrowGivethen// all first colorsetcolor = ARROW_COLOR_ONelsesetoff = off-arrowGive// mix colorsifoff < arrowMidthen// mix first 2 colorssetr = off/arrowMidsetcolor = ARGB.mix(ARROW_COLOR_ON, ARROW_COLOR_MID, r)
elsesetoff = off-arrowMidifoff < arrowOffthen// mix second colorssetr = off/arrowOffsetcolor = ARGB.mix(ARROW_COLOR_MID, ARROW_COLOR_OFF, r)
elsesetcolor = ARROW_COLOR_OFF// all last colorendifendifendif// hide arrow for other playersifGetLocalPlayer() != GetOwningPlayer(.u) thensetcolor = ARROW_COLOR_OFFendifif.arrow == nullthen// create an arrowset.arrow = CreateUnit(GetOwningPlayer(.u), GPS_ID, .curx, .cury, rad*bj_RADTODEG)
callSetUnitBlendTime(.arrow, 0.)
callSetUnitAnimation(.arrow, "birth")
callQueueUnitAnimation(.arrow, "stand")
elsecallSetUnitFacing(.arrow, rad*bj_RADTODEG)
endifcallcolor.recolorUnit(.arrow)
callSetUnitX(.arrow, .curx)
callSetUnitY(.arrow, .cury)
endmethodmethodnewPathtakesnothingreturnsbooleanif.path != 0thencall.path.destroy()
endifset.path = PAS_GetPath(.curx, .cury, .endx, .endy)
if.path == 0then// there is no pathcall.cleanUp()
returnfalseendifset.goodx = .curxset.goody = .curyset.curPoint = 0if.getTarget() then// returns true if there is a targetset.gooddist = SquareRoot((.curx-.targetx)*(.curx-.targetx)+(.cury-.targety)*(.cury-.targety))
call.pointAt()
returntrueelse// done the path or already at the endcall.cleanUp()
endifreturnfalseendmethodmethodupdatetakesnothingreturnsnothinglocalrealdistset.face = GetUnitFacing(.u)*bj_DEGTORAD// we work in radiansset.curx = GetUnitX(.u)
set.cury = GetUnitY(.u)
if.onCleanUpthen// cleaning upset.cleanFor = .cleanFor + TIMER_PERIODcallSetUnitX(.arrow, .curx)
callSetUnitY(.arrow, .cury)
if.cleanFor >= CLEAN_TIMEthen// animation is done, time to removecall.destroy()
endifreturnendifif.getTarget() then// we arn't at the endsetdist = SquareRoot((.curx-.targetx)*(.curx-.targetx)+(.cury-.targety)*(.cury-.targety))
call.pointAt()
ifdist <= .gooddistthen// getting closer, or a new targetset.gooddist = distset.goodx = .curxset.goody = .curyelse// getting farther awaysetdist = SquareRoot((.curx-.goodx)*(.curx-.goodx)+(.cury-.goody)*(.cury-.goody))
ifdist >= DEVIATE_PATHthen// we are too far away from where we should be, recalculate pathcall.newPath()
endifendifelsecall.cleanUp()
endifendmethodmethodonDestroytakesnothingreturnsnothinglocalintegerp = GetPlayerId(GetOwningPlayer(.u))
setGPSunits[p] = 0set.u = nullif.arrow != nullthencallRemoveUnit(.arrow)
set.arrow = nullendifendmethodstaticmethodcreatetakesunitfor, realx, realyreturnsthistypelocalthistypethis = thistype.allocate()
localintegerp = GetPlayerId(GetOwningPlayer(for))
localrealangleifGPSunits[p] != 0then// already GPS for this playercallGPSunits[p].destroy()
endifset.onCleanUp = falseset.endx = xset.endy = yset.u = forset.face = GetUnitFacing(.u)*bj_DEGTORADset.curx = GetUnitX(for)
set.cury = GetUnitY(for)
set.arrow = nullsetGPSunits[GetPlayerId(GetOwningPlayer(for))] = thiscall.newPath() // does all the other settingsreturnthisendmethodendstructprivatefunctiononTimerPeriodtakesnothingreturnsnothinglocalintegeri = 0loopexitwheni > bj_MAX_PLAYERSifGPSunits[i] != 0thencallGPSunits[i].update()
endifseti = i + 1endloopendfunctionpublicfunctionUnitSetGPStakesunitu, realx, realyreturnsnothingcallUnitGPS.create(u, x, y)
endfunctionpublicfunctionUnitRemoveGPStakesunitureturnsnothinglocalintegerp = GetPlayerId(GetOwningPlayer(u))
ifu != nullandGPSunits[p] != 0andGPSunits[p].u == uthencallGPSunits[p].cleanUp()
endifendfunctionprivatefunctiononInittakesnothingreturnsnothingsetmainTimer = CreateTimer()
callTimerStart(mainTimer, TIMER_PERIOD, true, functiononTimerPeriod)
endfunctionendlibrary
If you notice any issues or have any improvements/suggestions, please speak up.
I wish you could optionally require either LinkedList or Stack. That'd be great, but I don't think Vex ever added it to JassHelper.
My only thing is I need Linked Lists for PASMovementSafety (need to loop without disrupting). Stack can't do that. Aswell, providing a list of nodes which the user can loop through on their own is more user-friendly than a stack that has to be popped.
Currently writing the next version, so if you have improvements/updates, speak up!
Would I be able to use this in a custom grid movement system?
Where I have a grid setup. say 20x20, with each tile indexed and each tile with a "passable" boolean. Would this system be able to integrate with my custom movement? Basically just give me an array of the shortest path nodes?
Would I be able to use this in a custom grid movement system?
Where I have a grid setup. say 20x20, with each tile indexed and each tile with a "passable" boolean. Would this system be able to integrate with my custom movement? Basically just give me an array of the shortest path nodes?
Version 3.1 will be able to do this.
It will support both modifying weights after setup (during the game) as well as disabling a link entirely. So its just a matter of looping through all linking nodes and disabling the link to the node you wish to turn off.
I am holding off on releasing the next version, in case I get more ideas/suggestions.
And with no current suggestions, version 3.1 is out.
changelog:
PAS 3.1
Added functionality for changing link weights after setup
Added functionality for enabling and disabling links after setup
Made objects private/read-only where applicable (mostly PAS_Node objects)
Added function lists to all the script and plugins
Added Object Merger macros to create units in the object editor
PASMove: Changed the plugin name from PASMovement to PASMove (shorter)
PASMoveSafety: Changed the plugin name from PASMovementSaftey to PASMoveSafety (shorter)
PASGPS: Added installation instruction about the custom model and changed the paths for the model
PASGPS: The system will now register arriving at the end even if you didn't take the recommended path
the changelog can always be found at the end of the README section of the PAS script.
PAS takes all the nodes you add within the system and does work on them during map init (in its onInit function). Once the onInit function runs, you cannot add nodes to PAS. Therefore, if you require PAS, it's onInit function will run before your library and the nodes will already be set. This is a limitation to the design of PAS. However, if you can provide me with some code and what you plan to do, I may be able to modify PAS to run your code to add nodes. PM or even here works great.
This would be the basis for the code (I hope you can read Zinc):
Zinc:
libraryPASGridrequiresPAS
{
publicstructPAS_Grid
{
privateconstantintegerMY_NODES = 0xFFFF;
realx0, x1, y0, y1, gridSpace;
integerwidth;
integerheight;
privatemethodcreate_child_child (realx, realy, integerw, integerh)
{
// notice I don't add any links here -- I'd like a separate "linkTo" // function -- maybe your strings could be in a "linkToMulti" //function that parses the strings?PAS_Node.add(MY_NODES + w + width * h, x, y, gridSpace);
// <code to link this node to the ones around it -- will be a fairly// complicated loop, so I'm not going to work it out right now>
}
privatemethodcreate_child (realx, integerw)
{
realy;
for (y = x0; y0 <= y <= y1; y += gridSpace)
{
this.create_child_child.execute(x, y, w, R2I((y - y0) / gridSpace));
}
}
staticmethodcreate (realx0, realy0, realx1, realy1, realgridSpace) -> thistype
{
thistypethis = thistype.allocate();
realx;
this.x0 = x0; this.x1 = x1;
this.y0 = y0; this.y1 = y1;
this.gridSpace = gridSpace;
this.width = R2I((x1 - x0) / gridSpace);
this.height = R2I((y1 - y0) / gridSpace);
for (x = x0; x0 <= x <= x1; x += gridSpace)
{
this.create_child.execute(x, R2I((x - x0) / gridSpace));
}
returnthis;
}
}
}
As you can see, I'd like to be able to dynamically add points and link them together. I could construct something like a pathmap by disabling some nodes when (for example) a tower is built over it.
I suppose I could just write a whole new system, but since you have this A* algorithm already included into your code, it'd be nice to utilise this library.
Let me see if I get this: you create nodes, giving each a unique id and a string that specifies which other nodes this node is linked to, and then your code parses these strings to create the links? This seems like a rather messy approach, why couldn't users create nodes first, store them in their own array, and then create links between them? Something like: