Movement Instances v1.0
Note: this system is far from done, so it's a little lightweight at the moment - I plan to add lots more functionality in future, including stuff like collisions, parabolic paths, etc. I'm submitting this now because I intend to use the system in a public map, so.
Requirements:
-vJass preprocessor (NewGen pack).
-JASS knowledge. This isn't very useful in GUI.
Implementation:
-Copy the code/trigger into a blank text trigger called Movement Instances. Keep the readme for reference, too, in a disabled trigger.
What is it for?
Moving units, over distances or to specific points. The concept's not new, but it also uses the pseudo-OOP capabilities of vJass to implement some (hopefully) friendly syntax, and accessible options. It's also fairly flexible, in that it's usable in most situations without a user having to calculate coordinates and stuff. Finally, it's good for simplifying projectile spells (Elune's Arrow from DotA, or some kind of triggered Shockwave) and jump spells.
JASS:
library MovementInstances initializer Init
//***************************************************************************
//* Movement Instances v1.0, by Darius34
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* <a href="http://www.thehelper.net/forums/showthread.php?p=878606" class="link link--internal">http://www.thehelper.net/forums/showthread.php?p=878606</a>
//*
//* Some utility code for moving units in spells, over distances or to
//* specific points. Utilises the pseudo-OOP capabilities of vJass with
//* struct members to provide a lots of accessible, configurable options.
//*
//* Usage/Syntax
//* ¯¯¯¯¯¯¯¯¯¯¯¯
//* Refer to the list of properties for details.
//*
//* - User-defined functions for the periodic and end actions must carry
//* the "MovementPeriodic." and "MovementEnd." prefixes, respectively.
//* The former takes a unit and an integer, in that order, while the
//* latter takes only a unit.
//*
//* Credits
//* ¯¯¯¯¯¯¯
//* - The people who made vJass possible, in all its pseudo-OOP glory.
//*
//***************************************************************************
globals
// Configuration
private constant real TIMER_PERIOD = .03 // Global timer period for this module.
private constant boolean SHOW_ERROR_MESSAGES = true // Toggles error message displays on and off.
endglobals
// System starts here. Don't modify stuff below if you're not sure how to.
function interface MovementPeriodic takes unit subject, integer mvinstance returns nothing
function interface MovementEnd takes unit subject returns nothing
globals
private timer Timer = null
private integer Index = 0
private moveinstance array MoveInstance
endglobals
struct moveinstance
unit subject
real speed
real dist = 0.
real angle = 0. // Radians
real targetx = 0.
real targety = 0.
boolean targetCoords = false
unit target = null
boolean dynamicMovement = false // NYI
boolean interruptOrders = false
boolean keepUnitFacing = false
boolean killSubjectOnEnd = false
boolean removeSubjectOnEnd = false
MovementPeriodic periodicFunction = 0
MovementEnd endFunction = 0
boolean InstanceTerminated = false
real originaldist
integer iterations = 0
static method create takes unit subject, real speed returns moveinstance
local moveinstance m = moveinstance.allocate()
set m.subject = subject
set m.speed = speed * TIMER_PERIOD
return m
endmethod
method onDestroy takes nothing returns nothing
if .killSubjectOnEnd then
call KillUnit(.subject)
elseif .removeSubjectOnEnd then
call RemoveUnit(.subject)
endif
endmethod
method start takes nothing returns nothing
local real dx
local real dy
set MoveInstance[Index] = this
set Index = Index + 1
if .subject == null and SHOW_ERROR_MESSAGES then
call BJDebugMsg("Movement Instances - Error: Invalid subject specified.")
endif
if .targetCoords then
if not .dynamicMovement and .target != null then
set .targetx = GetUnitX(.target)
set .targety = GetUnitY(.target)
endif
set dx = .targetx - GetUnitX(.subject)
set dy = .targety - GetUnitY(.subject)
set .angle = Atan2(dy, dx)
set .dist = SquareRoot(dx * dx + dy * dy)
endif
set .originaldist = .dist
if .dynamicMovement then
if .target == null and SHOW_ERROR_MESSAGES then
call BJDebugMsg("Movement Instances - Error: Invalid target specified for dynamic movement.")
endif
endif
set .iterations = R2I(.originaldist/.speed)
endmethod
endstruct
private function PeriodicMovement takes nothing returns nothing
local integer a = 0
local real dx
local real dy
local moveinstance m
local real x
local real y
loop
exitwhen a >= Index
set m = MoveInstance[a]
set x = GetUnitX(m.subject)
set y = GetUnitY(m.subject)
if m.interruptOrders then
call SetUnitPosition(m.subject, x + m.speed * Cos(m.angle), y + m.speed * Sin(m.angle))
else
call SetUnitX(m.subject, x + m.speed * Cos(m.angle))
call SetUnitY(m.subject, y + m.speed * Sin(m.angle))
endif
if m.keepUnitFacing then
call SetUnitFacing(m.subject, m.angle * bj_RADTODEG)
endif
call m.periodicFunction.execute(m.subject, m)
set m.iterations = m.iterations - 1
if (m.dynamicMovement and m.dist <= m.speed) or (not m.dynamicMovement and m.iterations <= 0) then
call m.endFunction.execute(m.subject)
set m.InstanceTerminated = true
endif
if m.InstanceTerminated then
call m.destroy()
set MoveInstance[a] = MoveInstance[Index-1]
set MoveInstance[Index-1] = 0
set Index = Index - 1
endif
set a = a + 1
endloop
endfunction
private function Init takes nothing returns nothing
set Timer = CreateTimer()
call TimerStart(Timer, TIMER_PERIOD, true, function PeriodicMovement)
endfunction
endlibrary
Readme:
JASS:
//***************************************************************************
//* Movement Instances v1.0, by Darius34
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//***************************************************************************
Movement Instances is a utility for moving units in any way, be it over distances or to specific points.
All this is implemented with a struct that is declared and later started with a method call: think of it
as representative of the actual 039;happening039; of movement, the properties and workings of which can be
modified and controlled, and on the fly too, if desired.
To begin using the system, a movement instance has to be declared, like such:
private function Init takes nothing returns nothing
local moveinstance m = moveinstance.create(subject, 522.)
...
endfunction
The method .create() takes two arguments, the subject unit being moved and the speed at which this will happen,
in distance units per second. These are necessary arguments in any situation.
Properties are then assigned, via the struct members of the moveinstance struct.
private function Init takes nothing returns nothing
local moveinstance m = moveinstance.create(subject, 522.)
set m.dist = 900.
set m.angle = GetUnitFacing(GetTriggerUnit()) * bj_DEGTORAD
...
endfunction
Here039;s a list of the properties available. Properties are initialised with their default values.
Hence, properties can often be omitted, based on frequency of use. Only necessary properties need be specified in
general, in other words.
real dist = 0.
The distance to be moved by the unit, in total. Is disregarded if .targetCoords is true.
real angle = 0. // Taken in radians
The angle at which the unit will be moved. Is disregarded if .targetCoords is true.
boolean targetCoords = false
This flag controls if the system moves a unit to destination coordinates, or just a distance at an angle.
real targetx = 0.
real targety = 0.
The coordinates to which the unit will be moved, if .targetCoords is true.
boolean dynamicMovement = false // NYI
Controls if the subject unit moves dynamically towards another unit, like a projectile.
unit target = null
The target the subject unit will track dynamically, if .dynamicMovement is true. If .dynamicMovement is false and .targetCoords is true,
the coordinates of this unit will be those moved to, as if specified via .targetx/.targety.
boolean interruptOrders = false
Controls whether SetUnitPosition() or SetUnitX()/SetUnitY() are used to move the subject unit. SetUnitPosition() is slower but does
more checks on pathability and such, and so might be useful for the sake of omitting a manual check.
boolean keepUnitFacing = false
Controls whether the facing of the target unit is kept throughout.
boolean killSubjectOnEnd = false
boolean removeSubjectOnEnd = false
Controls whether the subject unit is disposed of upon the movement conluding, and how.
MovementPeriodic periodicFunction = 0
MovementEnd endFunction = 0
User-defined functions that can be specified, executed during the movement and after it, respectively. Functions must be prefixed with their
types. Also, the latter function takes a unit (the subject) and an integer (the instance of movement), while the latter takes a unit.
An example to illustrate:
private function Actions takes unit subject, integer mvinstance returns nothing
...
endfunction
private function FinalActions takes unit subject returns nothing
...
endfunction
private function Init takes nothing returns nothing
...
set m.periodicFunction = MovementPeriodic.Actions
set m.endFunction = MovementEnd.FinalActions
...
endfunction
Note: MovementPeriodic functions provide the instance of movement itself as an argument, which allows control over all the properties on
the fly. This has its advantages and disadvantages; it039;s possible for the end-user to break the working of the function. Be warned, and
change properties cautiously.
Finally, the .start() method begins the movement. It doesn039;t take any arguments.
private function Init takes nothing returns nothing
local moveinstance m = moveinstance.create(subject, 522.)
set m.dist = 900.
set m.angle = GetUnitFacing(GetTriggerUnit()) * bj_DEGTORAD
call m.start()
endfunction
It destroys the struct instance automatically, so a .destroy() later is not required. Once called, the rest is left to the system,
and no more cleanup is required by the end-user.
Examples of usage and instructions can be found in the readme.
Comments are appreciated.
Changelog
v1.0
- Initial release.
- Initial release.