System Movement Instances

Darius34

New Member
Reaction score
30
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 &quot;MovementPeriodic.&quot; and &quot;MovementEnd.&quot; 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&#039;t modify stuff below if you&#039;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(&quot;Movement Instances - Error: Invalid subject specified.&quot;)
        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(&quot;Movement Instances - Error: Invalid target specified for dynamic movement.&quot;)
            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 &gt;= 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 &lt;= m.speed) or (not m.dynamicMovement and m.iterations &lt;= 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;happening&#039; 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

Here&#039;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; it&#039;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 doesn&#039;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.
 

saw792

Is known to say things. That is all.
Reaction score
280
Hmm add the 'debug' prefix before your BJDebugMsg's. Random messages coming up in game would not look good.
 

Darius34

New Member
Reaction score
30
Added a boolean flag for error message displays, true by default. (I always forget to change that Debug Mode flag before saving final versions.)

Also, a hotfix: I left out a function argument somewhere.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
Instead of doing this
JASS:
 call SetUnitX(m.subject, x + m.speed * Cos(m.angle))

store the m.speed * Cos(m.angle) value in a variable so you don't need to calulate it every time it runs. (Do the same for Sin.)
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top