| 03-11-2009, 03:38 AM | #1 |
Ok guys, this is the last thing I'll release to the public from my project Power of Corruption. This script has been modified so it can work standalone. SMOOTH MOVEMENT SYSTEM
By moyack. 2009. What does it do?? This system (actually a script) allow to the user to move a unit from one point to another given an initial speed, a final speed and in a defined time. In order to use it, you must know what is a vector (not how to operate, just to know what's it). Allows to define events: when the unit starts moving, when it moves and when it ends moving. Requirements:
Is this a physic system? NO!!! it's just for moving a unit very smoothly. But this could emulate realistic movements too. Can I emulate the most desirable thing in WC3: Knockbacks??? Of course, in fact I have a module for knockbacks, with the possibility to make them fly too. How can I set the parameters? You'll need to define a unit to move, a vector which will store the initial position, other vector to define the final position, other vector which will define the starting speed, other vector to define the final speed, the time required to do this movement, a boolean to set if the vectors should be destroyed after the movement and a boolean to set if the unit will be paused during the movement. Check the following picture: Speed vectors can be defined in 2 ways: in cartesian or spherical coordinates. Main library://****************************************************************************************** //* //* Smooth movement script 2.0 By Moyack. 2009. //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ //* //* If you use this script, please be nice and give me credits. //* //* This library has been designed to simulate movement defined by kinematic parameters. //* //* You only need to define the Projectile unit, the position and velocity vectors, //* and the time the Projectile will fly. The rest is managed, controlled and destroyed //* automatically by the library. //* //* Additionally, you can define custom "events" when the Projectile starts, moves and ends, //* very convenient to set and control the Projectile behaviour. //* //* How to use: //* ¯¯¯¯¯¯¯¯¯¯¯ //* This library has two data types, the vector and the Projectile structs. //* //* Vector Struct: This struct allows you to group data in the way (X, Y, Z). The vector //* type allows the following methods: //* //* - create( real x, real y, real z) returns vector //* - setdata(real x, real y, real z) returns nothing //* - destroy() returns nothing //* //* The first method allocates a new vector variable and sets at the same time data on it. //* The second method allows you to change the data of the vector. The third method destroys //* the vector. Example: //* //* function VecStuff takes nothing returns nothing //* local vector v = vector.create(0., 10., 20.) // creates a vector [0, 10, 20] //* call v.setdata(15., 20., -10.) // changes the values in the vector v to [15, 20, -10] //* call v.destroy() // destroys the vector, freeing the memory used. //* endfunction //* //* Projectile Struct: This one is the one in charge to do the nice things. This structs //* allows the following methods: //* //* Aknowledges: //* ¯¯¯¯¯¯¯¯¯¯¯¯ //* - Mad[Lion] because he proposed the problem that inspire me to make this library. //* - Pipedream because he had the same apporach, but opposite :) and for the idea of //* partial factoring. //* - Vexorian, PitzerMike and all the people involved in the development of vJASS. //* without this tool, this idea would be still just that: an idea. //* //****************************************************************************************** library SmoothMovSys //*************************************************************************************************************** //* Configuration globals globals private constant real dt = 0.022 // sets the timer value endglobals //*************************************************************************************************************** //* End configuration globals globals private unit LastProj = null // Stores temporally the last Projectile created / destroyed private vector StartPos // Stores the start position of the Projectile. private vector StartVel // Stores the start velocity of the Projectile. private vector LastPos // Stores the last position of the Projectile. private vector LastVel // Stores the last velocity of the Projectile. private Projectile MovingProj // Stores the evaluating Projectile. Only for use when endglobals // it's called on the CodeMove function struct vector // struct used to manage vectors LOL!!! real x = 0. real y = 0. real z = 0. static method create takes real x, real y, real z returns vector local vector v = vector.allocate() set v.x = x set v.y = y set v.z = z return v endmethod method setdata takes real x, real y, real z returns nothing set .x = x set .y = y set .z = z endmethod endstruct private constant function GetA takes real x0, real x1, real v0, real v1, real tf returns real return (tf * (v1-v0) - 2 * (x1 - x0 - v0*tf)) / (tf*tf*tf) endfunction private constant function GetB takes real x0, real x1, real v0, real v1, real tf returns real return (3 * (x1 - x0 - v0*tf) - tf * (v1 - v0)) / (tf*tf) endfunction private constant function GetX takes real t, real A, real B, real C, real D returns real return D + t*(C + t*(B + t*A)) // thanks Pipedream for the idea :) endfunction private constant function GetV takes real t, real A, real B, real C returns real return (3*A*t + 2*B)*t + C // thanks Pipedream for the idea :) endfunction struct Projectile private static timer T = CreateTimer() private static region reg = null private static trigger TRG = CreateTrigger() static group ProjGroup = CreateGroup() private static integer counter = 0 private static Projectile array Projectiles private integer i unit p = null// Projectile unit vector pi // initial and final position coordinates vector pf vector vi // initial and final velocity coordinates vector vf real tf = 0. // time that the Projectile will take to reach the final point vector vp // variable position and velocity vars vector vv real t = 0. vector A // Constant Values used for calculation stuff vector B // Miscelaneous data boolean DV // this flag indicates if the Projectile is destroyed with the input vectors boolean pU // this flag indicates if the unit used as projectile will be paused or not trigger CodeMove //Stores the function name that will be evaluated every time any Projectile is moving trigger CodeEnd //Stores the function name used when the Projectile dies. method SetData takes unit p, vector pi, vector pf, vector vi, vector vf, real t, boolean DV, boolean pU returns nothing local real Ax = GetA(pi.x, pf.x, vi.x, vf.x, t) local real Ay = GetA(pi.y, pf.y, vi.y, vf.y, t) local real Az = GetA(pi.z, pf.z, vi.z, vf.z, t) local real Bx = GetB(pi.x, pf.x, vi.x, vf.x, t) local real By = GetB(pi.y, pf.y, vi.y, vf.y, t) local real Bz = GetB(pi.z, pf.z, vi.z, vf.z, t) set .p = p call UnitAddAbility(p, 'Amrf') call UnitRemoveAbility(p, 'Amrf') call GroupAddUnit(Projectile.ProjGroup, p) if pU then call PauseUnit(p, true) endif set .pi = pi set .pf = pf set .vi = vi set .vf = vf set .A = vector.create(Ax, Ay, Az) set .B = vector.create(Bx, By, Bz) set .vp = vector.create(0., 0., 0.) set .vv = vector.create(0., 0., 0.) if t <= 0. then set .tf = dt else set .tf = t endif set .DV = DV set .pU = pU endmethod private method update takes nothing returns nothing local real x local real y local real z if .CodeMove != null then set MovingProj = this call TriggerExecute(.CodeMove) endif set .t = .t + dt set x = GetX(.t, .A.x, .B.x, .vi.x, .pi.x) set y = GetX(.t, .A.y, .B.y, .vi.y, .pi.y) set z = GetX(.t, .A.z, .B.z, .vi.z, .pi.z) call .vp.setdata(x, y, z) set x = GetV(.t, .A.x, .B.x, .vi.x) set y = GetV(.t, .A.y, .B.y, .vi.y) set z = GetV(.t, .A.z, .B.z, .vi.z) call .vv.setdata(x, y, z) endmethod method SetStartCode takes code start returns nothing call TriggerClearActions(Projectile.TRG) if start != null then set LastProj = .p call StartPos.setdata(.pi.x, .pi.y, .pi.z) call StartVel.setdata(.vi.x, .vi.y, .vi.z) call LastPos.setdata(.pf.x, .pf.y, .pf.z) call LastVel.setdata(.vf.x, .vf.y, .vf.z) call TriggerAddAction(Projectile.TRG, start) else call TriggerAddAction(Projectile.TRG, function DoNothing) endif call TriggerExecute(Projectile.TRG) endmethod method SetMoveCode takes code move returns nothing if .CodeMove == null then set .CodeMove = CreateTrigger() else call TriggerClearActions(.CodeMove) endif if move != null then call TriggerAddAction(.CodeMove, move) else call TriggerAddAction(.CodeMove, function DoNothing) endif endmethod method SetEndCode takes code end returns nothing if .CodeEnd == null then set .CodeEnd = CreateTrigger() else call TriggerClearActions(.CodeEnd) endif if end != null then call TriggerAddAction(.CodeEnd, end) else call TriggerAddAction(.CodeEnd, function DoNothing) endif endmethod method GetVelocity takes nothing returns vector local vector v = vector.create(0,0,0) local real x = GetV(.t, .A.x, .B.x, .vi.x) local real y = GetV(.t, .A.y, .B.y, .vi.y) local real z = GetV(.t, .A.z, .B.z, .vi.z) call v.setdata(x, y, z) return v endmethod method GetPosition takes nothing returns vector return vector.create(GetUnitX(.p), GetUnitY(.p), GetUnitFlyHeight(.p)) endmethod private method onDestroy takes nothing returns nothing call GroupRemoveUnit(Projectile.ProjGroup, .p) if .pU then call PauseUnit(.p, false) endif set LastProj = .p set .p = null call StartPos.setdata(.pi.x, .pi.y, .pi.z) call StartVel.setdata(.vi.x, .vi.y, .vi.z) call LastPos.setdata(.pf.x, .pf.y, .pf.z) call LastVel.setdata(.vf.x, .vf.y, .vf.z) if .CodeEnd != null then call TriggerExecute(.CodeEnd) endif if .DV then call .pi.destroy() call .pf.destroy() call .vi.destroy() call .vf.destroy() endif call .A.destroy() call .B.destroy() call .vp.destroy() call .vv.destroy() set Projectile.counter = Projectile.counter - 1 set Projectile.Projectiles[Projectile.counter].i = .i set Projectile.Projectiles[.i] = Projectile.Projectiles[Projectile.counter] if Projectile.counter == 0 then call PauseTimer(Projectile.T) endif endmethod private static method Loop takes nothing returns nothing local integer i = 0 loop exitwhen i >= Projectile.counter call Projectile.Projectiles[i].update() if Projectile.Projectiles[i].t > Projectile.Projectiles[i].tf or not IsPointInRegion(Projectile.reg, Projectile.Projectiles[i].vp.x, Projectile.Projectiles[i].vp.y) then // destroys the Projectile data if it finished its time or if it's going to leave the playable map area call Projectile.Projectiles[i].destroy() else // updates the location and angle facing of the unit call SetUnitX(Projectile.Projectiles[i].p, Projectile.Projectiles[i].vp.x) call SetUnitY(Projectile.Projectiles[i].p, Projectile.Projectiles[i].vp.y) call SetUnitFlyHeight(Projectile.Projectiles[i].p, Projectile.Projectiles[i].vp.z, 0.) endif set i = i + 1 endloop endmethod static method create takes unit p, vector pi, vector pf, vector vi, vector vf, real t, boolean DestroyVectors, boolean pauseUnit returns Projectile local Projectile pr = Projectile.allocate() call pr.SetData(p, pi, pf, vi, vf, t, DestroyVectors, pauseUnit) set pr.i = Projectile.counter set Projectile.Projectiles[Projectile.counter] = integer(pr) set Projectile.counter = Projectile.counter + 1 if Projectile.counter == 1 then call TimerStart(Projectile.T, dt, true, function Projectile.Loop) endif return pr endmethod static method createSpherical takes unit p, vector pi, vector pf, vector vsi, vector vsf, real t, boolean DestroyVectors, boolean pauseUnit returns Projectile // this method allows you to define the speed vectors in spherical coordinates [r, t, f] // where r is the vector magnitude // t is the angle in XY plane // f is the angle in the plane defined by the vector R and the Z axis // ALL the angles are in RADIANS // when the Projectile is created, all the entered vectors will be changed to cartesian system call vsi.setdata(vsi.x * Cos(vsi.z) * Cos(vsi.y), vsi.x * Cos(vsi.z) * Sin(vsi.y), vsi.x * Sin(vsi.z)) call vsf.setdata(vsf.x * Cos(vsf.z) * Cos(vsf.y), vsf.x * Cos(vsf.z) * Sin(vsf.y), vsf.x * Sin(vsf.z)) return Projectile.create(p, pi, pf, vsi, vsf, t, DestroyVectors, pauseUnit) endmethod private static method onInit takes nothing returns nothing set Projectile.reg = CreateRegion() call RegionAddRect(Projectile.reg, bj_mapInitialPlayableArea) set StartPos = vector.create(0,0,0) set StartVel = vector.create(0,0,0) set LastPos = vector.create(0,0,0) set LastVel = vector.create(0,0,0) endmethod endstruct // functions used to get the the unit Projectile and the vectors in custom triggers // those functions are to be used when the unit starts and stops being a Projectile function GetUnitProjectile takes nothing returns unit return LastProj endfunction function GetInitPos takes nothing returns vector return vector.create(StartPos.x, StartPos.y, StartPos.z) endfunction function GetInitVel takes nothing returns vector return vector.create(StartVel.x, StartVel.y, StartVel.z) endfunction function GetLastPos takes nothing returns vector return vector.create(LastPos.x, LastPos.y, LastPos.z) endfunction function GetLastVel takes nothing returns vector return vector.create(LastVel.x, LastVel.y, LastVel.z) endfunction function GetMovingProjectile takes nothing returns Projectile // only to use when you need to get the projectile data in a CodeMove function return MovingProj endfunction function IsUnitProjectile takes unit u returns boolean return IsUnitInGroup(u, Projectile.ProjGroup) endfunction endlibrary Check the test map and review the sample spell for better understanding. Last update: 27/04/2009 |
| 03-24-2009, 02:33 AM | #2 |
Projectile Utilities + KnockBack implementation:library ProjSys initializer InitMovSys requires BoolexprUtils, SmoothMovSys //*************************************************************************************************************** //* Configuration globals globals private constant string MovDustFX = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl" private constant string MovSplashFX = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl" endglobals //*************************************************************************************************************** //* End configuration globals globals private constant rect TreeReg = Rect(0,0,1,1) endglobals private function KillTrees takes nothing returns nothing local destructable d = GetEnumDestructable() call KillDestructable(d) set d = null endfunction private function RemoveTreesInRange takes real x, real y, real r returns nothing call SetRect(TreeReg, x-r, y-r, x+r, y+r) call EnumDestructablesInRect(TreeReg, BOOLEXPR_TRUE, function KillTrees) endfunction private function MoveKnockBackBasic takes nothing returns nothing local Projectile P = GetMovingProjectile() local real x = GetUnitX(P.p) local real y = GetUnitY(P.p) if GetUnitFlyHeight(P.p) <= 0. then if not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then call DestroyEffect(AddSpecialEffect(MovDustFX, x, y)) elseif not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) then call DestroyEffect(AddSpecialEffect(MovSplashFX, x, y)) endif if IsUnitType(P.p, UNIT_TYPE_GROUND) == true and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then call SetUnitPosition(P.p, x, y) call P.destroy() endif endif endfunction private function MoveKnockBack takes nothing returns nothing local Projectile P = GetMovingProjectile() call MoveKnockBackBasic() call RemoveTreesInRange(GetUnitX(P.p), GetUnitY(P.p), 100.) endfunction private function MoveMissile takes nothing returns nothing local Projectile p = GetMovingProjectile() call SetUnitFacing(p.p, Atan2(p.vv.y, p.vv.x) * bj_RADTODEG) endfunction private function InitMovSys takes nothing returns nothing call Preload(MovDustFX) call Preload(MovSplashFX) endfunction private function ParabolicA takes real h, real d returns real // returns the coeficient A in the parabolic function // h, d, and x are in the same units of distance in WC3, which is more comfortable to use :) if d > 0. then return -4 * h / (d * d) else return 0. endif endfunction private function ParabolicB takes real h, real d returns real // returns the coeficient B in the parabolic function // h, d, and x are in the same units of distance in WC3, which is more comfortable to use :) // NOTE: THIS VALUE IS THE TAN OF THE PROJECTILE ANGLE if d > 0. then return 4 * h / d else return 0. endif endfunction //*********************************************************************************************** //* public usage functions //*********************************************************************************************** function Atan3 takes real x1, real y1, real x2, real y2 returns real local real a = Atan2(y2 - y1, x2 - x1) if a >= 0. then return a else return 2 * bj_PI + a endif endfunction function Dist takes real x1, real y1, real x2, real y2 returns real local real dx = x2 - x1 local real dy = y2 - y1 return SquareRoot( dx * dx + dy * dy ) endfunction function GetParabolicAngle takes real h, real d returns real return Atan(ParabolicB(h, d)) endfunction function GetUnitsAngle takes unit a, unit b returns real return Atan3(GetUnitX(a), GetUnitY(a), GetUnitX(b), GetUnitY(b)) endfunction function GetUnitsDist takes unit a, unit b returns real return Dist(GetUnitX(a), GetUnitY(a), GetUnitX(b), GetUnitY(b)) endfunction function GetUnitProjPoint takes unit u, real angle, real dist returns location local real x = GetUnitX(u) + dist * Cos(angle) local real y = GetUnitY(u) + dist * Sin(angle) return Location(x, y) endfunction //****************************************************************************** //* KnockBack Function: //* - unit t: the unit that will become into a projectile //* - real dist: the distance that the projectile will move //* - real dir: the angle in the plane XY which indicates the movemente direction //* - real height: the max height that the projectile will fly //* - real speed: the initial projectile speed. If height = 0 then the projectile will change the speed until reach 0 //* - Code InitCode: the name of the function that will be evaluated when the projectile starts moving //* - Code EndCode: the name of the function that will be evaluated when the projectile stops. //* - boolean DestroyDestructables: Indicates if the knockbacked units will destroy nearby trees while they move function KnockBack takes unit t, real dist, real dir, real height, real speed, code InitCode, code EndCode, boolean DestroyDestructables returns nothing local real a = GetParabolicAngle(height, dist) local real tm = dist / (speed * Cos(a)) local vector p1 = vector.create(GetUnitX(t), GetUnitY(t), GetUnitFlyHeight(t)) local vector p2 = vector.create(p1.x + dist * Cos(dir), p1.y + dist * Sin(dir), p1.z) local vector vi local vector vf local Projectile P if height > 0. then set vi = vector.create(speed, dir, a) set vf = vector.create(vi.x, vi.y, -vi.z) else set vi = vector.create(speed, dir, 0.) set vf = vector.create(0., 0., 0.) endif set P = Projectile.createSpherical(t, p1, p2, vi, vf, tm, true, true) call P.SetStartCode(InitCode) if DestroyDestructables then call P.SetMoveCode(function MoveKnockBack) else call P.SetMoveCode(function MoveKnockBackBasic) endif call P.SetEndCode(EndCode) endfunction endlibrary |
| 03-24-2009, 09:04 PM | #3 |
awesome, been waiting awhile for this. anyways, one thing about the spell that i might recommend is that a minimum casting range be applied, since it's kinda weird when a person jumps at another person point blank. |
| 03-24-2009, 10:29 PM | #4 |
you can add constant to many of your functions... for 1% speed bonus -.- |
| 03-24-2009, 11:29 PM | #5 |
Hmm yes... I'll take into account. Every penny is worthy :) |
| 03-26-2009, 03:13 AM | #6 |
huh, just curious but, in the spell, what is the berserk caster attachment for? i dont think i see it show up ingame. |
| 03-26-2009, 10:42 AM | #7 |
At the moment I can't answer you because I don't have WC3 at hand, I'll do it this night. |
| 04-28-2009, 03:45 AM | #8 |
Updated. |
| 09-09-2009, 12:01 AM | #9 |
bother, 1.23/1.24 seems to have broken some stuff here. |
| 09-09-2009, 01:59 PM | #10 |
I'll check it out this weekend if the time allows me. |
