• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Relativistic Missiles [vJASS][LUA][GUI]

This bundle is marked as high quality. It exceeds standards and is highly desirable.
Relativistic Missiles
v2.8 by Chopinski
Introduction
First of all, Credits and thanks to BPower, Dirac, and Vexorian for their missile systems at which I based this system upon, and to AGD for the help with effect orientation methods.

This is a Missile system that I put together initially to fulfill my resources needs. Very often while I was creating system, spells and maps I found myself in need of a such systems, but all the available options usually have a little flaw that forced me to used something else.

For example, let's say I wanted a missile that can arc and homing to a target, well there goes BPower's system. It can arc/curve but can't do homing with arc/curve natively. So the options left are Vexorian xemissile which can arc and do homing but cant curve, and Dirac's Missile system. Then again more problems with those, because Vexorian system do not have a lot of the useful events like Dirac's and BPower's systems. Dirac's system can arc/curve and do homing but it's T32 timer is never stopped and it do not have some useful events that the BPower system has. So I decided to get all three system together and build my own system with the stuff I think is useful and with the syntax I think is the most friendly, because I'm not a genius of vJASS myself.

What's different in this system?
Ok, so why it's called "Relativistic Missiles"? Well because mid development I realize that the way missiles were being processed could be optimized and the Einstein postulates about Time Dilation by Gravity gave the idea behind the implementation. Yeah, I know, WTH?

When it comes to Missile systems, performance is everything, and all missiles systems do basically the same thing when processing missiles instances, they loop every set period, which is usually 1/32, through all instances and do their calculations and event calls. The problem with it, is that the more instances existing at a given moment the greater will be the tanking of the game frame rate because the game will lower its fps so all that's need to be done before the next frame gets drawn have time to do so, and that's where the Einstein postulates gave me an idea.

See, when we study Relativity, we learn that gravity dilates time, the greater the gravitational potential (more or less mass) the slower time passes relative to an outside point of reference. Gravity in this analogy would be the amount of missiles instances currently existing, so instead of letting the game frame rate drops drastically to move all those instances, I make the missile instances have their time dilated.

Ok, but how?? Simple, first we need to find what I'll be calling the system Sweet Spot, which is the amount of missiles the game can handle without dropping to much performance per given period, which I found out to be between 150 and 200 missiles. Then we limit the amount of missiles processed per period to this sweet spot, and move the next missile in the line to be processed and all other behind it to the front, In terms of data structures, think of a circular list with a head and tail, and we move the head and tail to change the initial and final positions. I did it with arrays because they are a bit faster and because I'm lazy :p.

But that's not all, because by limiting the amount of missiles processed, we delay their next process call by (total amount)/(sweet spot), so now their speed needs to be corrected and relativity comes full circle now, because time dilation has it's relation with Length Contraction, so we increment the missiles speed by this factor.

Also, something very important is that this system is using only effects as missiles, thanks to the new natives introduced in 1.31, so no dummy units, no CreateUnit() and SetUnitFacing() blasphemy. I've included in the test map a version of this system that do use the dummy method for those that do not have the newer versions of the game. I Also included a version of this system that uses the usual process method, so processing all missiles in a single blow.

Results
I've taken the liberty to test this system against the king of Missile systems for some time, BPower's System. Below, there are 3 videos I make for stress test. The first from the left is BPower's Missile System reworked by AGD. Amazing job btw. You will be able to notice what I was talking about earlier about the sweet spot, when there are more than 150 or so missiles being processed, the fps goes basically to 0 and it freezes until the missiles are finished.

The video in the middle is my system in its normal version (not relativistic). It performs a little better (I've build it a little differently), but in the same way, when the missile count near 250, the fps starts to go down hill real quick.

The video in the right is the relativistic version, notice the missile count and fps compared to the 2 other system. At about 600 missiles fps get really low, but not so much because of the amount of missiles being processed but because of the amount of visual effects in the screen. If the screen was moved to the side where not so many stuff is visible, fps goes up. In my internal tests I was reaching more than 1200 missiles with about 16 fps but with no screen freezes.

And Last but not Least, a video with the examples.

vJASS:
event onPeriod takes nothing returns boolean
    -> if declared will run whenever every missile period

event onHit takes unit hit returns boolean
    -> if declared and collision is greater than 0
    -> will run whenever the missile collides with a unit

event onMissile takes Missiles missile returns boolean
    -> if declared and collision is greater than 0
    -> will run whenever the missile collides with another missile

event onDestructable takes destructable dest returns boolean
    -> if declared and collision is greater than 0
    -> will run whenever the missile collides with a destructable

event onItem takes item i returns boolean
    -> if declared and collision is greater than 0
    -> will run whenever the missile collides with an item

event onCliff takes nothing returns boolean
    -> if declared, will run whenever the missile collides with a cliff wall
    -> with height greater than the current missile height

event onTerrain takes nothing returns boolean
    -> if declared, will run whenever the missile collides with terrain

event onTileset takes nothing returns boolean
    -> if declared, will run whenever the missile changes tileset types

event onFinish takes nothing returns boolean
    -> if declared, will run when the missiles reaches its destination

event onBoundaries takes nothing returns boolean
    -> if declared, will run whenever the missile tries to go out
    -> of the map boundaries.

event onPause takes nothing returns boolean
    -> if declared, will run when the missile is paused

event onResume takes nothing returns boolean
    -> if declared, will run when the missile is unpaused

event onRemove takes nothing returns nothing
    -> if declared, will run when the missile is removed
vJASS:
x             -> The current x coordinate of the missile (readonly)
y             -> The current y coordinate of the missile (readonly)
z             -> The current z coordinate of the missile (readonly)
prevX         -> The previous x coordinate of the missile (readonly)
prevY         -> The previous y coordinate of the missile (readonly)
prevZ         -> The previous z coordinate of the missile (readonly)
nextX         -> The next x coordinate of the missile (readonly)
nextY         -> The next y coordinate of the missile (readonly)
nextZ         -> The next z coordinate of the missile (readonly)
travel        -> The current traveled distance of the missile (readonly)
source        -> The source unit for the missile (read and write)
target        -> The target unit for the missile. If set the missile will be roaming (read and write)
owner         -> The owning player of the missile (read and write)
vision        -> The sight range of the missile (read and write)
collideZ      -> If true, the missile will take into consideration the unit collision size, destructable oclusion height and item size when running the onHit, onDestructable, onItem events (read and write)
collision     -> The collision size of the missile. If not set, the events onHit, onDestructable, onItem and onMissile will no run (read and write)
damage        -> Stores the amount of damage to be used inside an event (read and write)
acceleration  -> The missile acceleration (read and write)
data          -> Stores information to be used inside any event (read and write)
model         -> The model of the missile (read and write)
curve         -> The curve of the missile (read and write)
arc           -> The Arc of the missile (read and write)
scale         -> The scale of the missile (read and write)
speed         -> The speed of the missile (read and write)
duration      -> Set the speed of the missile to match a duration (read and write)
roll          -> If true, the missile will roll as well (1.31+ only) (read and write)
type          -> Integer that can be used to diferentiate missiles (read and write)
paused        -> True when the missile is paused, false otherwise (read and write)
tileset       -> The current terrain type under the missile (readonly)
timeScale     -> The time scale of the effect model (read and write) (1.31+ non dummmy version only)
alpha         -> The effect model alpha value (read and write) (1.31+ non dummmy version only)
playerColor   -> The effect model player color (read and write) (1.31+ non dummmy version only)
animation     -> The effect model animation type (read and write) (1.31+ non dummmy version only)
impact        -> The object representing the impact coordinates (x, y, z, angle, distance, square, slope, alpha) (read and write)
origin        -> The object representing the origin coordinates (x, y, z, angle, distance, square, slope, alpha) (read and write)
effect        -> The object representing the missile (size, yaw, pitch, roll, path, effect, attachments), and attachments has (x, y, z, size, yaw, pitch, roll, path, effect)
vJASS:
x             -> The current x coordinate of the missile (readonly)
y             -> The current y coordinate of the missile (readonly)
z             -> The current z coordinate of the missile (readonly)
prevX         -> The previous x coordinate of the missile (readonly)
prevY         -> The previous y coordinate of the missile (readonly)
prevZ         -> The previous z coordinate of the missile (readonly)
nextX         -> The next x coordinate of the missile (readonly)
nextY         -> The next y coordinate of the missile (readonly)
nextZ         -> The next z coordinate of the missile (readonly)
travel        -> The current traveled distance of the missile (readonly)
source        -> The source unit for the missile (read and write)
target        -> The target unit for the missile. If set the missile will be roaming (read and write)
owner         -> The owning player of the missile (read and write)
collideZ      -> If true, the missile will take into consideration the unit collision size, destructable oclusion height and item size when running the onHit, onDestructable, onItem events (read and write)
collision     -> The collision size of the missile. If not set, the events onHit, onDestructable, onItem and onMissile will no run (read and write)
damage        -> Stores the amount of damage to be used inside an event (read and write)
acceleration  -> The missile acceleration (read and write)
data          -> Stores information to be used inside any event (read and write)
model         -> The model of the missile (to set use yourMissile:model(string), to get use yourMissile.Model)
curve         -> The curve of the missile (to set use yourMissile:curve(real), to get use yourMissile.Curve)
arc           -> The Arc of the missile (to set use yourMissile:arc(real), to get use yourMissile.Arc)
scale         -> The scale of the missile (to set use yourMissile:scale(real), to get use yourMissile.Scale)
speed         -> The speed of the missile (to set use yourMissile:speed(real), to get use yourMissile.Speed)
duration      -> Set the speed of the missile to match a duration (to set use yourMissile:duration(real), to get use yourMissile.Duration)
vision        -> The sight range of the missile (to set use yourMissile:vision(real), to get use yourMissile.Vision)
roll          -> If true, the missile will roll as well (1.31+ only) (read and write)
type          -> Integer that can be used to diferentiate missiles (read and write)
paused        -> True when the missile is paused, false otherwise (read and write)
timeScale     -> The time scale of the effect model (to set use yourMissile:timeScale(real), to get use yourMissile.TimeScale)
alpha         -> The effect model alpha value (to set use yourMissile:alpha(integer), to get use yourMissile.Alpha)
playerColor   -> The effect model player color (to set use yourMissile:playerColor(integer), to get use yourMissile.playercolor)
animation     -> The effect model animation type (to set use yourMissile:animation(integer), to get use yourMissile.Animation)
tileset       -> The current terrain type under the missile (readonly)
impact        -> The object representing the impact coordinates (x, y, z, angle, distance, square, slope, alpha) (read and write)
origin        -> The object representing the origin coordinates (x, y, z, angle, distance, square, slope, alpha) (read and write)
effect        -> The object representing the missile (size, yaw, pitch, roll, path, effect, attachments), and attachments has (x, y, z, size, yaw, pitch, roll, path, effect)
vJASS:
Missile                 -> The missile
MissileDestroy          -> Set this variable to true inside a missile event trigger to destroy the missile
MissileStart            -> The starting point of the missile. Set this variable before running the MissileCreate trigger
MissileStartZ           -> The starting Z of the missile. Set this variable before running the MissileCreate trigger
MissileFinish           -> The end point of the missile. Set this variable before running the MissileCreate trigger
MissileFinishZ          -> The end Z of the missile. Set this variable before running the MissileCreate trigger
MissileModel            -> The missile model. Set this variable before running the MissileCreate trigger. You can set this varible to change the model at any point inside a missile event trigger
MissileScale            -> The missile model scale. Set this variable before running the MissileCreate trigger. You can set this varible to change the scale at any point inside a missile event trigger
MissileSpeed            -> The missile speed. Set this variable before running the MissileCreate trigger. You can set this varible to change the speed at any point inside a missile event trigger
MissileDuration         -> The missile duration. Set this variable before running the MissileCreate trigger. You can set this varible to change the duration at any point inside a missile event trigger
MissileArc              -> The missile arc. Set this variable before running the MissileCreate trigger. You can set this varible to change the arc at any point inside a missile event trigger
MissileCurve            -> The missile curve. Set this variable before running the MissileCreate trigger. You can set this varible to change the curve at any point inside a missile event trigger
MissileVision           -> The missile sight range. Set this variable before running the MissileCreate trigger. You can set this varible to change the sight range at any point inside a missile event trigger
MissileDamage           -> Stores the damage. Set this variable before running the MissileCreate trigger. You can set this varible to change the damage at any point inside a missile event trigger
MissileCollision        -> The missile collision size. Set this variable before running the MissileCreate trigger. You can set this varible to change the collision at any point inside a missile event trigger
MissileAcceleration     -> The missile acceleration. Set this variable before running the MissileCreate trigger. You can set this varible to change the acceleration at any point inside a missile event trigger
MissileCollideZ         -> If true, the missile will check Z height when colliding. Set this variable before running the MissileCreate trigger. You can set this varible to change the model at any point inside a missile event trigger
MissileSource           -> The missile source unit. Set this variable before running the MissileCreate trigger. You can set this varible to change the source at any point inside a missile event trigger
MissileTarget           -> The missile target unit. If set the missile will roam to the target. Set this variable before running the MissileCreate trigger. You can set this varible to change the target at any point inside a missile event trigger
MissileOwner            -> The owner of the missile. Set this variable before running the MissileCreate trigger. You can set this varible to change the owner at any point inside a missile event trigger
MissilePaused           -> True if the missile is paused, false otherwise
MissileRoll             -> If true the missile will use roll (1.31+)
MissileType             -> Set if you want to categorize the missile. Set this variable before running the MissileCreate trigger. You can set this varible to change the type at any point inside a missile event trigger
MissilePosition         -> The current missile position
MissileZ                -> The current missile Z
MissileLastPosition     -> The last missile position
MissilePrevZ            -> The last missile Z
MissileNextPosition     -> The next missile position
MissileNextZ            -> The next missile Z
MissileVelocity         -> The missile velocity. Velocity is the speed over the system period.
MissileTravelled        -> The missile travelled distance. It is reseted if you deflect or bounce the missile.
MissileRemoveLocations  -> Set to true before running MissileCreate trigger to remove the MissileStart and MissileFinish points automatically.
MissileHitUnit          -> The hitted unit inside an onHit event trigger
MissileHitDestructable  -> The hitted destructable inside an onDestructable event trigger
MissileHitItem          -> The hitted item inside an onItem event trigger
MissileHitMissile       -> The hitted missile inside an onMissile event trigger
MissileTileset          -> The terrain type under the missile
MissileData             -> The missile data variable can be used to hold information (integer)
MissileTimeScale        -> The missile effect time scale. Set this variable before running the MissileCreate trigger. You can set this varible to change the time scale at any point inside a missile event trigger (1.31+ non dummmy version only)
MissileAlpha            -> The missile effect alpha value. Set this variable before running the MissileCreate trigger. You can set this varible to change the alpha value at any point inside a missile event trigger (1.31+ non dummmy version only)
MissilePlayerColor      -> The missile effect player color. Set this variable before running the MissileCreate trigger. You can set this varible to change the player color of the effect at any point inside a missile event trigger (1.31+ non dummmy version only)
MissileAnimation        -> The missile effect animation type. Set this variable before running the MissileCreate trigger. You can set this varible to change the animation type of the effect at any point inside a missile event trigger (1.31+ non dummmy version only)
MissileDeflectTarget    -> Set this variable before running the MissileDeflectTarget trigger to deflect the missile to the unit assigned to this variable
MissileRed              -> Set this variable (and green and blue) after the missile is created and then run the MissileColor trigger to change the missile effect color values (1.31+ non dummmy version only)
MissileGreen            -> Set this variable (and red and blue) after the missile is created and then run the MissileColor trigger to change the missile effect color values (1.31+ non dummmy version only)
MissileBlue             -> Set this variable (and red and green) after the missile is created and then run the MissileColor trigger to change the missile effect color values (1.31+ non dummmy version only)
Missile_onPeriod        -> Set this variable to a trigger that you want to run on every missile period
Missile_onHit           -> Set this variable to a trigger that you want to run when the missile hits an unit. Use the MissileHitUnit variable to access the hitted unit
Missile_onDestructable  -> Set this variable to a trigger that you want to run when the missile hits a destructable. Use the MissileHitDestructable variable to access the hitted destructable
Missile_onItem          -> Set this variable to a trigger that you want to run when the missile hits an item. Use the MissileHitItem variable to access the hitted item
Missile_onMissile       -> Set this variable to a trigger that you want to run when the missile hits another Missile. Use the MissileHitMissile variable to access the hitted Missile
Missile_onCliff         -> Set this variable to a trigger that you want to run when the missile collides with a cliff wall with height greater than the current missile height
Missile_onTerrain       -> Set this variable to a trigger that you want to run when the missile collides with terrain
Missile_onTileset       -> Set this variable to a trigger that you want to run when the missile changes tileset types
Missile_onFinish        -> Set this variable to a trigger that you want to run when the missile reaches its destination
Missile_onBoundaries    -> Set this variable to a trigger that you want to run when the missile tries to leave the map
Missile_onPause         -> Set this variable to a trigger that you want to run when the missile is paused
Missile_onResume        -> Set this variable to a trigger that you want to run when the missile is unpaused
Missile_onRemove        -> Set this variable to a trigger that you want to run when the missile is removed
MissileEvent            -> This variable represents which event is being executed. Compere it to one of the following variables inside a trigger to decide what to do in each event
MissileOnPeriod         -> MissileEvent is set to the value of this variable when Missile_onPeriod is set to a trigger
MissileOnHit            -> MissileEvent is set to the value of this variable when Missile_onHit is set to a trigger
MissileOnDestructable   -> MissileEvent is set to the value of this variable when Missile_onDestructable is set to a trigger
MissileOnItem           -> MissileEvent is set to the value of this variable when Missile_onItem is set to a trigger
MissileOnMissile        -> MissileEvent is set to the value of this variable when Missile_onMissile is set to a trigger
MissileOnCliff          -> MissileEvent is set to the value of this variable when Missile_onCliff is set to a trigger
MissileOnTerrain        -> MissileEvent is set to the value of this variable when Missile_onTerrain is set to a trigger
MissileOnTileset        -> MissileEvent is set to the value of this variable when Missile_onTileset is set to a trigger
MissileOnFinish         -> MissileEvent is set to the value of this variable when Missile_onFinish is set to a trigger
MissileOnBoundaries     -> MissileEvent is set to the value of this variable when Missile_onBoundaries is set to a trigger
MissileOnPause          -> MissileEvent is set to the value of this variable when Missile_onPause is set to a trigger
MissileOnResume         -> MissileEvent is set to the value of this variable when Missile_onResume is set to a trigger
MissileOnRemove         -> MissileEvent is set to the value of this variable when Missile_onRemove is set to a trigger
vJASS:
method bounce takes nothing returns nothing
    -> Bounces the missile, reseting angles

method deflect takes real x, real y, real z returns nothing
    -> Deflects the missile to the x, y and z coordinates

method deflectTarget takes unit u returns nothing
    -> Deflects the missile to a unit

method flushAll takes nothing returns nothing
    -> Flushes the hit group for the missile

method flush takes widget w returns nothing
    -> Remove a unit, destructable, item or missile from the hit group

method hitted takes widget w returns boolean
    -> Returns true if the unit, destructable or item was hit by the missile

method attach takes string model, real dx, real dy, real dz, real scale returns effect
    -> Attach an effect to the missile and return the effect attached
    -> dx, dy, and dz are offsets from the main missile coordinates

method detach takes effect attachment returns nothing
    -> Detach an effect attached to the missile

method pause takes boolean flag returns nothing
    -> If true will pause the misisle, else will unpause it

method color takes integer red, integer green, integer blue returns nothing
    -> Changes the missile effect model color (1.31+ non dummy version only)
vJASS:
MissileDeflectPosition
MissileDeflectZ
MissileDeflectTarget
Set the above variables then run the MissileDeflect or MissileDeflectTarget triggers to deflect the missile

MissileFlushUnit
Set the above variable then run the MissileFlush to make the set unit be able to be hitted again by the missile

MissileHittedUnit
MissileHitted
Set the MissileHittedUnit to a unit then run the MissileHitted trigger. After that you can use the MissileHitted varible to check if the unit was already hitted by the missile

MissileAttachModel
MissileAttachX
MissileAttachY
MissileAttachZ
MissileAttachScale
MissileDetachEffect
Set the above varibles then run the MissileAttach trigger to attach an effect to the missile with MissileAttach(Model, X, Y, Z, Scale)

MissileDetachEffect
Set the above variable to the effect that was previously attached to the missile then run the MissileDetach trigger

Run the MissileBounce trigger to bounce the missile and reset its travalled distance

Run the MissileFlushAll trigger flush all units hitted by the missile

Run the MissilePause trigger pause the missile

Run the MissileResume trigger unpause the missile

Run the MissileBounce trigger to bounce the missile and reset its travalled distance

MissileRed
MissileGreen
MissileBlue
Set the above variables and then run the MissileColor trigger to change the missile effect model color (1.31+ non dummy version only)
vJASS:
function CreateMissileGroup takes nothing returns MissileGroup
    -> Creates a missile group and return it

function DestroyMissileGroup takes MissileGroup missiles returns nothing
    -> Destroys the misisle group

function MissileGroupGetSize takes MissileGroup missiles returns integer
    -> Returns the size of the missile group

function GroupMissileAt takes MissileGroup missiles, integer position returns Missiles
    -> Returns the missile at the given index.

function ClearMissileGroup takes MissileGroup missiles returns nothing
    -> Clear the missile group

function IsMissileInGroup takes Missiles missile, MissileGroup missiles returns boolean
    -> True if the missile is inside the missile group

function GroupRemoveMissile takes MissileGroup missiles, Missiles missile returns nothing
    -> Removes the missile from the missile group

function GroupAddMissile takes MissileGroup missiles, Missiles missile returns nothing
    -> Add the missile to the missile group

function GroupPickRandomMissile takes MissileGroup missiles returns Missiles
    -> Returns a random missile from the misisle group. 0 if empty

function FirstOfMissileGroup takes MissileGroup missiles returns Missiles
    -> Returns the first missile inside the missile group. 0 if empty

function GroupAddMissileGroup takes MissileGroup source, MissileGroup destiny returns nothing
    -> Add the missiles that are in the source group into the destiny group

function GroupRemoveMissileGroup takes MissileGroup source, MissileGroup destiny returns nothing
    -> Remove the missiles that are in the source group from the destiny group

function GroupEnumMissilesOfType takes MissileGroup missiles, integer whichType returns nothing
    -> Enumerates the missiles of matching type

function GroupEnumMissilesOfTypeCounted takes MissileGroup missiles, integer whichType, integer amount returns nothing
    -> Enumerates up to amount of missiles of matching type

function GroupEnumMissilesOfPlayer takes MissileGroup missiles, player p returns nothing
    -> Enumerates the missiles that belongs to a player

function GroupEnumMissilesOfPlayerCounted takes MissileGroup missiles, player p, integer amount returns nothing
    -> Enumerates up to amount of missiles that belongs to a player

function GroupEnumMissilesInRect takes MissileGroup missiles, rect r returns nothing
    -> Enumerates the missiles inside a region

function GroupEnumMissilesInRectCounted takes MissileGroup missiles, rect r, integer amount returns nothing
    -> Enumerates up to amount of missiles inside a region

function GroupEnumMissilesInRangeOfLoc takes MissileGroup missiles, location loc, real radius returns nothing
    -> Enumerates the missiles within range of a location

function GroupEnumMissilesInRangeOfLocCounted takes MissileGroup missiles, location loc, real radius, integer amount returns nothing
    -> Enumerates up to amount of missiles within range of a location

function GroupEnumMissilesInRange takes MissileGroup missiles, real x, real y, real radius returns nothing
    -> Enumerates the missiles within range of x, y

function GroupEnumMissilesInRangeCounted takes MissileGroup missiles, real x, real y, real radius, integer amount returns nothing
    -> Enumerates up to amount of missiles within range of x, y

  • BPower
  • Dirac
  • Vexorian
  • AGD
  • emjlr3
  • AceHart
  • Nestharus
  • Maghteridon96
  • Bribe
  • Flux
  • Forsakn

(v1.0)
  • Release
(v1.1)
  • Merged Normal and Relativistic code for both Legacy and Current versions.
  • You can use the SWEET_SPOT constant to determine the system behavior
    • SWEET_SPOT <= 0 -> normal method
    • SWEET_SPOT > 0 -> relativistic method
  • Included support for DummyRecycler by Flux for the Legacy version. This should improve its performance even further.
(v1.2)
  • Fixed a bug for homing missiles height
  • New configuration member boolean collideZ -> set to true to make the new missile consider z collisions
  • New Global Configuration constant boolean ROLL -> If true missile will consider roll in orientation. Roll can look really fishy for some users and was never possible until patch 1.31, so if you want it set it to true in the globlas block (1.31+ version only).
  • You can now attach effects to the main missile effect with an offset through 2 new methods (1.31+ version only)
    • // dx, dy, and dz are offsets from the main missile coordinates method attach takes string model, real dx, real dy, real dz, real scale returns effect
    • method detach takes effect attachment returns nothing
  • Few minor optmizations.
(v1.3)
  • Fix a minor rect leak
(v1.4)
  • Minor optmizations.
  • WorldBounds not required anymore.
(v1.5)
  • WorldBounds now is required (Centralized requirements and outsourced)
  • New optional event (See Available Events for more info)
    • method onBoundaries takes nothing returns boolean
  • Fixed a spelling mistake on the detach functionality.
(v1.6)
  • MissileEffect library updated to use the BlzSetSpecialEffectOrientation native. (v1.31+ only)
(v1.7)
  • Standardized getting and setting of the members speed, arc and curve. They now read and write in degrees.
(v1.8)
  • Made a system function (GetLocZ) private to avoid conflict with other resources.
(v1.9)
  • System ported to LUA (Credits to Forsakn for the first port).
  • Added instant facing when creating missiles instances.
  • Included a version of Missiles that uses the dummy method and a Dummy Pool utilizing the new natives. This version by default will not let missiles clip through terrain.
  • Code formatting and cleaning.
(v2.0)
  • System ported to GUI!
  • Implemented ways to enumerate missiles (Missile Groups)
  • New method: pause. When called pauses or unpauses the missile
  • 2 new events: onPause and onResume
  • 3 new members: roll, type and paused
  • Added the attach and detach methods to the LUA version
  • Fixed a bug on the collideZ
  • Fixed a bug when deflecting missiles
  • Refactored code to be more readable and easy to maintain
(v2.1)
  • Removed the usage of a post 1.30 native being used in the 1.30 version of the system. Now the 1.30- version should work for patches below that
  • Fixed a bug in the LUA GUI version for the onPause and the onRemove events
  • Added an alternative method of setting up events for the GUI version. It's more compact, you can set up all events you want inside only 1 trigger (See the Trigger Preview for examples on how to do it)
(v2.2)
  • Adjusted the initial position of the missile in the versions that use the dummy method.
(v2.3)
  • 2 new events:
    • onCliff -> Run whenever the missiles collides with a cliff wall and it's height is greater than the current missile height
    • onTileset -> Run whenever the missile changes terrain types
  • 4 new members
    • nextX -> The next missile x value
    • nextY -> The next missile y value
    • nextZ -> The next missile z value
    • tileset -> the current terrain type under the missile
  • Fixed the missile movement on higher ground levels
  • Fixed the movement of missiles when going over terrain/cliffs for the dummy versions
  • Separated the GUI version into vJASS and LUA implementations. For those who want to choose between them.
(v2.4)
  • Missiles can now grant vision using the new member "vision"
    • vJASS:
      // To set (The missile must have a source or a owner)
      yourMissile.vision = value
      
      // To get
      var = yourMissile.vision

    • Lua:
      -- To set (The missile must have a source or a owner)
      yourMissile:vision(value)
      
      -- To get
      var = yoruMissile.Vision
      • Example
        • Actions
          • -- Set the MissileVision variable before running the MissileCreate trigger ou at any event trigger (The missile must have a source or a owner) --
          • Set VariableSet MissileSource = (Triggering unit)
          • Set VariableSet MissileVision = 800.00
          • Creates the missile
          • Trigger - Run MissileCreate <gen> (ignoring conditions)
  • New required library: Alloc
  • Fixed a few bugs in the GUI version
(v2.5)
  • Missiles can now be paused within the onFinish event.
  • Removed the deflectZ method, now the deflect method expects x, y and z parameters
  • New method deflectTarget deflects the missile to a unit
  • New GUI variable MissileData can be used to store information as needed (integer type)
  • version 1.31+ non dummy only changes:
    • 4 new method operator (operators in vJASS, functions in Lua)
      • yourMissile.timeScale => changes the time scale of the effect model (animation speed)
      • yourMissile.alpha => changes the alpha value of the effect model (transparency)
      • yourMissile.playerColor => changes the player color of the effect model
      • yourMissile.animation => changes the animation type used by the effect model
    • 1 new method
      • yourMissile.color(integer, integer, integer) => changes the effect model color (vJASS)
      • yourMissile:color(integer, integer, integer) => changes the effect model color (Lua)
    • vJASS:
      // To set
          yourMissile.timeScale = 3.
          yourMissile.alpha = 128
          yourMissile.playerColor = GetPlayerId(GetOwningPlayer(unit))
          yourMisisle.animation = 3 // ConvertAnimType(3) under the hood
      // To get
          variable = yourMissile.timeScale
          variable = yourMissile.alpha
          variable = yourMissile.playerColor
          variable = yourMisisle.animation
    • Lua:
      -- To set
          yourMissile:timeScale(3.)
          yourMissile:alpha(128)
          yourMissile:playerColor(GetPlayerId(GetOwningPlayer(unit)))
          yourMisisle:animation(3) -- ConvertAnimType(3) under the hood
      -- To get
          variable = yourMissile.TimeScale
          variable = yourMissile.Alpha
          variable = yourMissile.playerColor
          variable = yourMisisle.Animation
      • GUIExamle
        • Actions
          • Set VariableSet MissileAnimation = 5
          • Set VariableSet MissileTimeScale = 3.00
          • Set VariableSet MissilePlayerColor = 3
          • Set VariableSet MissileAlpha = 128
          • Trigger - Run MissileCreate <gen> (ignoring conditions)
          • -------- CHANGING THE COLOR. MUST BE DONE AFTER THE MISSILE IS CREATED (1.31+ NON DUMMY VERSION ONLY) --------
          • Set VariableSet MissileRed = 123
          • Set VariableSet MissileGreen = 67
          • Set VariableSet MissileBlue = 32
          • Trigger - Run MissileColor <gen> (ignoring conditions)
(v2.6)
  • Fixed a bug when pausing missiles inside an onFinish event.
(v2.7)
  • Minor improvements.
(v2.8)
  • Fixed a bug in the deflect method when the onTerrain event is declared.
  • Fixed a bug in the flushAll method in the LUA version.
Contents

Missiles (Map)

Missiles (Map)

Missiles (Map)

Missiles (Map)

Reviews
Wrda
Upgraded to High quality due to how great the implementation and usefulness this offers.
Level 20
Joined
May 16, 2012
Messages
635
This system has been super useful!

However, I kinda want to be able to make projectiles that travel with a from {x1,y1,70} to {x2,y2,70} with a fixed distance to the ground (similar to basic war3 spells).
My first though was with a linear offset on Z, so {x1,y1,1} to {x2,y2,99} would travel 1 -> 99 on Z linearly with the 1 -> 99 being the offset to LocZ, but I don't really need that atm, just anything with fixed distance to LocZ.
Is it possible?

Currently projectiles clip though terrain, and I want some to behave more like classic War3 projectiles

The final Z parameter takes into consideration the Terrain Elevation, so if you pass 70 as you said but your final destination is in a cliff for example than your final Z would be your 70 + the cliff height. It's build this way so that the user do not need to worry about terrain elevation when creating their spells and to allow for more intuitive missiles (you cast a bolt in a unit that's in a cliff and the missile will not clip into it). Just keep in mind that the final Z parameter also considerate the terrain elevation and you can probably figured something out to adjust it to your needs, but the Z calculation is done using a Parabola formula, so i can't do much about how it's being done internally without making huge changes to the system.
 
I can't do much about how it's being done internally without making huge changes to the system.

So it clips if source and destination is at the same level with a cliff in between.
ezgif-1-aacdbbeb4583.gif

This is the problem that I want to solve.
I'm ok with messed up parabola (this doesn't have parabola anyways).
It'd look better than clipping.

I'm currently thinking of a flag "fixed height to ground" and if that, add ground-level to z at each update.
Yes, it'll be strange with some parabola and terrain combinations, but this is also strange.
 
Level 20
Joined
May 16, 2012
Messages
635
So it clips if source and destination is at the same level with a cliff in between.
ezgif-1-aacdbbeb4583.gif

This is the problem that I want to solve.
I'm ok with messed up parabola (this doesn't have parabola anyways).
It'd look better than clipping.

I'm currently thinking of a flag "fixed height to ground" and if that, add ground-level to z at each update.
Yes, it'll be strange with some parabola and terrain combinations, but this is also strange.

Oh i see. Yeah that can happen and if i can recall when i was coding the system, doing what you want breaks the onTerrain Event, since the height is always being compensated. One thing you can do that will work is use the Dummy version since units cant clip through terrain and have their height automatically adjusted.
 
Level 18
Joined
Oct 17, 2012
Messages
818
The Dummy version does not interact with the special effect version. So, I would not be able to have, let's say, an unit as missile and have it collide with a special effect missile.

FYI, you really do not need Flux's dummy system with its all hoodwinking in the background, since there is the new native, BlzSetUnitFacingEx, for instant facing. So yeah, these "advanced" dummy systems are now obsolete. Creating dummies before hand is still sound.

So, I am hoping you would create a dummy missile version for 1.32+.
 
doing what you want breaks the onTerrain Event, since the height is always being compensated.

For this projectile and a few other projectiles, it's fine that it won't fire (don't implement them anyways). I guess what I'm trying to say is if you want your projectile to go over the terrain, it's fint that it won't hit the trigger the onTerrain, because it doesn't hit the terrain.

For other projectiles, the onTerrain is excellent! I'm looking forward to rewrite a boss projectile in my map using it instead of a ton of regions with onUnitEnter events.

since there is the new native, BlzSetUnitFacingEx

Ohh! I've missed that one! Neat!

Edit: Ohh, "BlzSetUnitFacingEx" is reforged only. I'm one of those 1.31 persons...
 
Last edited:
Level 4
Joined
Mar 25, 2021
Messages
18
I've just gotten a version of this in Lua running, will post it here but it needs testing and some refining first and I've only been doing Lua for 2 days so I'm sure it's rattled with unoptimized stuff since its basically a translation of the jass code, but it'd be a starting point for people who want it. @chopinski did you check my post on your profile? maybe I should've posted it here, sorry for that. :grin: Would be great if you could check if there's a possible fix so I could add that to the Lua version when it's ready.
 
Level 20
Joined
May 16, 2012
Messages
635
I've just gotten a version of this in Lua running, will post it here but it needs testing and some refining first and I've only been doing Lua for 2 days so I'm sure it's rattled with unoptimized stuff since its basically a translation of the jass code, but it'd be a starting point for people who want it. @chopinski did you check my post on your profile? maybe I should've posted it here, sorry for that. :grin: Would be great if you could check if there's a possible fix so I could add that to the Lua version when it's ready.
I saw it, but I'm up to my neck with work and college, so I'll only see it when I have a little more time.
 
Level 4
Joined
Mar 25, 2021
Messages
18
No worries @chopinski

Here comes the Lua version I've been working on, it's not finished, but I've done some tests with unit, destructible and terrain hit-detection, which is what I need, will probably fix the rest and post an updated version. It does seem to work well, but no guarantees, I'm a Lua noob and this just a translation of the JASS system, no fancy Lua features used. Formatting, testing, scope-stuff and comments could use a bit of work but I don't have time for that right now. But for you who are eager to try it, here's the code:

check 2nd post
 
Last edited:
Level 20
Joined
May 16, 2012
Messages
635
No worries @chopinski

Here comes the Lua version I've been working on, it's not finished, but I've done some tests with unit, destructible and terrain hit-detection, which is what I need, will probably fix the rest and post an updated version. It does seem to work well, but no guarantees, I'm a Lua noob and this just a translation of the JASS system, no fancy Lua features used. Formatting, testing, scope-stuff and comments could use a bit of work but I don't have time for that right now. But for you who are eager to try it, here's the code:

MissileEffect.lua (Requires [Lua] MapBounds)
Code:
MissileEffect = setmetatable({}, {})
do
    local mt = getmetatable(MissileEffect)
    mt.__index = mt

    function mt:create(x, y, z)
        local e = {}
        setmetatable(e, mt)
       
        e.effect = AddSpecialEffect("", x, y)
        e.path   = ""
        e.size    = 1
        e.yaw    = 0
        e.pitch   = 0
        e.roll     = 0
        BlzSetSpecialEffectZ(e.effect, z)

        return e
    end

    function mt:destroy()
        DestroyEffect(self.effect)
        self.effect = nil
        self.path = nil
        self = nil
    end

    function mt:scale(sfx, scale)
        self.size = scale
        BlzSetSpecialEffectScale(sfx, scale)
    end

    function mt:orient(yaw, pitch, roll)
        self.yaw   = yaw
        self.pitch = pitch
        self.roll  = roll
        BlzSetSpecialEffectOrientation(self.effect, yaw, pitch, roll)
    end

    function mt:move(x, y, z)
        if not (x > WorldBounds.maxX or x < WorldBounds.minX or y > WorldBounds.maxY or y < WorldBounds.minY) then
            BlzSetSpecialEffectPosition(self.effect, x, y, z)
            return true
        end
        return false
    end
end

Missiles.lua
Code:
do
local cnf = {
-- The update period of the system
PERIOD             = 1./40.,
-- The max amount of Missiles processed in a PERIOD
-- You can play around with both these values to find
-- your sweet spot. If equal to 0, the system will
-- process all missiles at once every period.
SWEET_SPOT         = 150,
-- the avarage collision size compensation when detecting
-- collisions
COLLISION_SIZE     = 128.,
-- item size used in z collision
ITEM_SIZE          = 16.,
-- set this to true if you want the system to orient
-- the missile roll. Roll can look really fishy for
-- some users and it was never possible to be set
-- until recent patches, so use it if you want.
ROLL               = false,
-- Needed, dont touch. Seriously, dont touch!
LOC                = Location(0., 0.),
RECT               = Rect(0., 0., 0., 0.),
hitTable          = {}
}

local function GetLocZ(x, y)
    MoveLocation(cnf.LOC, x, y)
    return GetLocationZ(cnf.LOC)
end

-- Coordinates
Coordinates = setmetatable({}, {})

local mt = getmetatable(Coordinates)
mt.__index = mt

function mt:math(a, b)
    local dx
    local dy
    while true do
        dx = b.x - a.x
        dy = b.y - a.y
        dx = dx*dx + dy*dy
        dy = SquareRoot(dx)
        if dx ~= 0. and dy ~= 0. then break end
        b.x = b.x + .01
        b.z = b.z - GetLocZ(b.x -.01, b.y) + GetLocZ(b.x, b.y)
    end

    a.square   = dx
    a.distance = dy
    a.angle    = Atan2(b.y - a.y, b.x - a.x)
    a.slope    = (b.z - a.z)/dy
    a.alpha    = Atan(a.slope)
    -- Set b.
    if b.ref == a then
        b.angle     = a.angle + bj_PI
        b.distance  = dy
        b.slope     = -a.slope
        b.alpha     = -a.alpha
        b.square    = dx
    end
end

function mt:link(a, b)
    a.ref = b
    b.ref = a
    self:math(a, b)
end

function mt:move(toX, toY, toZ)
    self.x = toX
    self.y = toY
    self.z = toZ + GetLocZ(toX, toY)
    if self.ref ~= self then
        math(self, self.ref)
    end
end

function mt:create(x, y, z)
    local c = {}
    setmetatable(c, mt)

    c.ref = c
    c:move(x, y, z)
    return c
end

-- Missiles static members
local Static = {
    t         = CreateTimer(),
    hitGroup  = CreateGroup(),
    didx      = -1,
    last      = 0,
    dilation  = 0.,
    temp      = nil,
    missiles  = {}
}
Static.__index = Static
Static.onDest = function()
    local d = GetEnumDestructable()
    local dz
    local tz
    if cnf.hitTable[Static.temp][GetHandleId(d)] == nil then
        if Static.temp.collideZ then
            dz = GetLocZ(GetWidgetX(d), GetWidgetY(d)) - GetLocZ(Static.temp.x, Static.temp.y)
            tz = GetDestructableOccluderHeight(d)
            if dz + tz >= Static.temp.z - Static.temp.collision and dz <= Static.temp.z + Static.temp.collision then
                cnf.hitTable[Static.temp][GetHandleId(d)] = true
                if Static.temp.allocated and Static.temp.onDestructable(d) then
                    d = nil
                    Static.temp:terminate()
                    return
                end
            end
        else
            cnf.hitTable[Static.temp][GetHandleId(d)] = true
            if Static.temp.allocated and Static.temp.onDestructable(d) then
                d = nil
                Static.temp:terminate()
                return
            end
        end
    end

    d = nil
end

Static.onItem = function()
    local i = GetEnumDestructable()
    local dz

    if cnf.hitTable[Static.temp][GetHandleId(i)] == nil then
        if collideZ then
            dz = GetLocZ(GetItemX(Static.temp.i), GetItemY(Static.temp.i)) - GetLocZ(Static.temp.x, Static.temp.y)
            if dz + cnf.ITEM_SIZE >= Static.temp.z - Static.temp.collision and dz <= Static.temp.z + Static.temp.collision then
                cnf.hitTable[Static.temp][GetHandleId(i)] = true
                if Static.temp.allocated and Static.temp.onItem(i) then
                    i = nil
                    Static.temp:terminate()
                    return
                end
            end
        else
            cnf.hitTable[Static.temp][GetHandleId(i)] = true
            if Static.temp.allocated and Static.temp.onItem(i) then
                i = nil
                Static.temp:terminate()
                return
            end
        end
    end

    i = nil
end

---------------------------- Missiles movement ---------------------------
Static.move = function()
    local        j = 0
    local        i = 0
    local        k = 0
    local        u = nil
    local        a = 0.
    local        d = 0.
    local        s = 0.
    local        h = 0.
    local        c = 0.
    local        dx = 0.
    local        dy = 0.
    local        vel = 0.
    local        yaw = 0.
    local        pitch = 0.
    local        locZ = 0.
    local        missile = nil
    local        o = nil
    local        this = nil

    -- SWEET_SPOT used to enable or disable
    -- relativistic processing
    if cnf.SWEET_SPOT > 0 then
        i = Static.last
    else
        i = 0
    end

    while true do
        if (j >= cnf.SWEET_SPOT and cnf.SWEET_SPOT > 0) or j > Static.didx then break end
            this = Static.missiles[i]
            Static.temp = this

            if this.allocated then
                o    = this.origin
                h    = this.height
                c    = this.open
                d    = o.distance
                locZ = GetLocZ(this.x, this.y)
               
                --onPeriod Event
                if this.onPeriod ~= nil then
                    if allocated and this.onPeriod() then
                        this:terminate()
                    end
                end

                -- onHit Event
                if this.onHit ~= nil then
                    if this.allocated and this.collision > 0 then
                        GroupEnumUnitsInRange(Static.hitGroup, this.x, this.y, this.collision + cnf.COLLISION_SIZE, nil)
                        while true do
                            u = FirstOfGroup(Static.hitGroup)
                            if u == nil then break end
                            if cnf.hitTable[this][GetHandleId(u)] == nil then
                                if IsUnitInRangeXY(u, this.x, this.y, this.collision) then
                                    if this.collideZ then
                                        dx = GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u) - this.locZ
                                        dy = BlzGetUnitCollisionSize(u)
                                        if dx + dy >= this.z - this.collision and dx <= this.z + this.collision then
                                            cnf.hitTable[this][GetHandleId(u)] = true
                                            if this.allocated and this.onHit(u) then
                                                this:terminate()
                                                break
                                            end
                                        end
                                        else
                                        cnf.hitTable[this][GetHandleId(u)] = true
                                        if this.allocated and this.onHit(u) then
                                            this:terminate()
                                            break
                                        end
                                    end
                                end
                            end
                            GroupRemoveUnit(Static.hitGroup, u)
                        end
                    end
                end

                -- onMissile Event
                if this.onMissile ~= nil then
                    if allocated and collision > 0 then
                        k = 0
                        while true do
                            if k > Static.didx then break end
                            missile = Static.missiles[k]
                            if missile ~= this then
                                if cnf.hitTable[this][missile] == nil then
                                    dx = missile.x - this.x
                                    dy = missile.y - this.y
                                    if SquareRoot(dx*dx + dy*dy) <= this.collision then
                                        cnf.hitTable[this][missile] = true
                                        if this.allocated and this.onMissile(missile) then
                                            this:terminate()
                                            break
                                        end
                                    end
                                end
                            end
                            k = k + 1
                        end
                    end
                end

                -- onDestructable Event
                if this.onDestructable ~= nil then
                    if this.allocated and this.collision > 0 then
                        dx = this.collision
                        SetRect(cnf.RECT, this.x - dx, this.y - dx, this.x + dx, this.y + dx)
                        EnumDestructablesInRect(cnf.RECT, nil, Static.onDest)
                    end
                end

                -- onItem Event
                if this.onItem ~= nil then
                    if allocated and collision > 0 then
                        dx = collision
                        SetRect(cnf.RECT, x - dx, y - dx, x + dx, y + dx)
                        EnumItemsInRect(cnf.RECT, nil, Static.onItem)
                    end
                end
               
                -- Homing or not
                u = target
                if u ~= nil and GetUnitTypeId(u) ~= 0 then
                    this.impact.move(GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u) + this.toZ)
                    dx     = this.impact.x - this.prevX
                    dy     = this.impact.y - this.prevY
                    a      = Atan2(dy, dx)
                    this.travel = o.distance - SquareRoot(dx*dx + dy*dy)
                else
                    a = o.angle
                    this.target = nil
                end
               
                -- turn rate
                if this.turn ~= 0 and not (Cos(this.cA-a) >= Cos(this.turn)) then
                    if Sin(a-this.cA) >= 0 then
                        this.cA = this.cA + this.turn
                    else
                        this.cA = this.cA - this.turn
                    end
                else
                    this.cA = a
                end
               
                vel         = this.veloc*Static.dilation
                yaw         = this.cA
                pitch       = o.alpha
                this.x      = this.prevX + vel*Cos(yaw)
                this.y      = this.prevY + vel*Sin(yaw)
                s           = this.travel + vel
                this.prevX  = this.x
                this.prevY  = this.y
                this.prevZ  = this.z
                this.veloc  = this.veloc + this.acceleration
                this.travel = s

                -- arc calculation
                if h ~= 0 or o.slope ~= 0 then
                    this.z = 4*h*s*(d-s)/(d*d) + o.slope*s + o.z
                    pitch = pitch - Atan(((4*h)*(2*s - d))/(d*d))
                end
               
                -- curve calculation
                if c ~= 0 then
                    dx  = 4*c*s*(d-s)/(d*d)
                    a   = yaw + bj_PI/2
                    this.x   = this.x + dx*Cos(a)
                    this.y   = this.y + dx*Sin(a)
                    yaw = yaw + Atan(-((4*c)*(2*s - d))/(d*d))
                end

                -- onTerrain event
                if this.onTerrain ~= nil then
                    if GetLocZ(this.x, this.y) > this.z then
                        if this.allocated and this.onTerrain() then
                            this:terminate()
                        end
                    end
                end

                if s >= d - 0.0001 then
                    -- onFinish event
                    if this.onFinish ~= nil then
                        if this.allocated and this.onFinish() then
                            this:terminate()
                        else
                            -- deflected onFinish
                            if this.travel > 0 then
                                this:terminate()
                            end
                        end
                    else
                        this:terminate()
                    end
                else
                    if cnf.ROLL then
                        this.effect:orient(yaw, -pitch, Atan2(c, h))
                    else
                        this.effect:orient(yaw, -pitch, 0)
                    end
                end

                if not this.effect:move(this.x, this.y, this.z) then
                    -- onBoundaries event
                    if this.onBoundaries ~= nil then
                        if this.allocated and this.onBoundaries() then
                            this:terminate()
                        end
                    end
                end
            else
                i = this:remove(i)
                j = j - 1
            end
       
        i = i + 1
        j = j + 1

        if i > Static.didx and cnf.SWEET_SPOT > 0 then
            i = 0
        end
    end
    Static.last = i

    u = nil
end

-- Missiles
Missiles = setmetatable({}, {})

local mt = getmetatable(Missiles)
mt.__index = mt

-------------------------- Model of the missile --------------------------
function mt:setModel(fx)
    DestroyEffect(self.effect.effect)
    self.effect.path = fx
    self.effect.effect = AddSpecialEffect(fx, self.origin.x, self.origin.y)
    BlzSetSpecialEffectZ(self.effect.effect, self.origin.z)
end

----------------------------- Curved movement ----------------------------
function mt:setCurve(value)
    self.open = Tan(value*bj_DEGTORAD)*self.origin.distance
end

----------------------------- Arced Movement -----------------------------
function mt:setArc(value)
    self.height = Tan(value*bj_DEGTORAD)*self.origin.distance/4
end

------------------------------ Effect scale ------------------------------
function mt:setScale(value)
    self.effect.size = value
    self.effect:scale(self.effect.effect, value)
end

------------------------------ Missile Speed -----------------------------
function mt:setSpeed(value)
    self.veloc = value*cnf.PERIOD
end

------------------------------- Flight Time ------------------------------
function mt:setDuration(value)
    self.veloc = RMaxBJ(0.00000001, (self.origin.distance - self.travel)*cnf.PERIOD/RMaxBJ(0.00000001, value))  
end

---------------------------- Bound and Deflect ---------------------------
function mt:bounce()
    local locZ = GetLocZ(self.x, self.y)

    -- This is here just to avoid an infinite loop
    -- with a deflect being called within an onTerrain
    -- event
    if z < locZ then
        self.z = locZ
        self.impact:move(self.impact.x, self.impact.y, self.origin.z - GetLocZ(self.impact.x, self.impact.y))
    end
    self.origin:move(self.x, self.y, self.origin.z - GetLocZ(self.origin.x, self.origin.y))
    self.travel = 0
end

function mt:deflect(tx, ty)
    if self.target ~= nil then
        self.target = nil
    end

    self.impact:move(self.tx, self.ty, self.impact.z - GetLocZ(self.impact.x, self.impact.y))
    self:bounce()
end

function mt:deflectZ(tx, ty, tz)
    self.impact:move(self.impact.x, self.impact.y, tz)
    self:deflect(tx, ty)
end

---------------------------- Flush hit targets ---------------------------
function mt:flushAll()
    cnf.hitTable[self] = nil
end

function mt:flush(w)
    if w ~= nil then
        cnf.hitTable[self][GetHandleId(w)] = nil
    end
end

function mt:hitted(w)
    return cnf.hitTable[self][GetHandleId(w)]
end

----------------------- Missile attachment methods -----------------------
-- dx, dy, and dz are offsets from the main missile coordinates
--method attach takes string model, real dx, real dy, real dz, real scale returns effect
--    return effect.attach(model, dx, dy, dz, scale)
--endmethod
--
--method detach takes effect attachment returns nothing
--    if attachment != null then
--        call effect.detach(attachment)
--    endif
--endmethod

------------------------------ Reset members -----------------------------
function mt:reset()
    self.launched        = false
    self.collideZ        = false
    self.source          = nil
    self.target          = nil
    self.owner           = nil
    self.open            = 0.
    self.height          = 0.
    self.veloc           = 0.
    self.acceleration    = 0.
    self.collision       = 0.
    self.damage          = 0.
    self.travel          = 0.
    self.turn            = 0.
    self.data            = 0.
    self.onHit           = nil
    self.onMissile       = nil
    self.onDestructable  = nil
    self.onItem          = nil
    self.onTerrain       = nil
    self.onFinish        = nil
    self.onBoundaries    = nil
    self.onRemove        = nil
end
-------------------------------- Terminate -------------------------------
function mt:terminate()
    if self.allocated then
        self.allocated = false

        -- onRemove event
        if self.onRemove ~= nil then
            self.onRemove()
        end
       
        cnf.hitTable[self] = nil
    end
end

-------------------------- Destroys the missile --------------------------
function mt:remove(i)
    self:terminate()

    --self.origin:destroy()
    --self.impact:destroy()
    self.effect:destroy()
    self:reset()

    Static.missiles[i]   = Static.missiles[Static.didx]
    Static.didx          = Static.didx - 1

    -- Compensation for time dilation
    if Static.didx + 1 > cnf.SWEET_SPOT and cnf.SWEET_SPOT > 0 then
        Static.dilation = (Static.didx + 1)/cnf.SWEET_SPOT
    else
        Static.dilation = 1
    end

    if Static.didx == -1 then
        PauseTimer(Static.t)
    end

    self = nil
    --self:deallocate()

    return i - 1
end

--------------------------- Launch the Missile ---------------------------
function mt:launch()
    if not self.launched and self.allocated then
        self.launched         = true
        Static.didx           = Static.didx + 1
        Static.missiles[Static.didx] = self

        -- Compensation for time dilation
        if Static.didx + 1 > cnf.SWEET_SPOT and cnf.SWEET_SPOT > 0 then
            Static.dilation = (Static.didx + 1)/cnf.SWEET_SPOT
        else
            Static.dilation = 1.
        end

        if Static.didx == 0 then
            TimerStart(Static.t, cnf.PERIOD, true, Static.move)
        end
    end
end

--------------------------- Main Creator method --------------------------
function mt:create(x, y, z, toX, toY, toZ)
    local m = {}
    setmetatable(m, mt)

    cnf.hitTable[m] = {}
    m:reset()
    m.origin    = Coordinates:create(x, y, z)
    m.impact    = Coordinates:create(toX, toY, toZ)
    m.allocated = true
    m.x         = x
    m.y         = y
    m.z         = z
    m.prevX     = x
    m.prevY     = y
    m.prevZ     = z
    m.toZ       = toZ
    Coordinates:link(m.origin, m.impact)
    m.cA        = m.origin.angle
    m.effect    = MissileEffect:create(x, y, z)

    return m
end
end

Test ability, trigger how you wish.
Code:
local c       = GetTriggerUnit()
local t       = GetSpellTargetUnit()
local fromX   = GetUnitX(c)
local fromY   = GetUnitY(c)
local fromZ   = GetUnitFlyHeight(c) + 50
local toX     = GetSpellTargetX()
local toY     = GetSpellTargetY()
local missile = Missiles:create(fromX, fromY, fromZ, toX, toY, fromZ)

missile:setModel("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl")
missile:setSpeed(500)
missile.target     = t
missile.source     = c
missile.collision  = 75
missile.caster     = c

missile:setArc(1) --GetRandomReal(10, 40)
--set missile.curve      = GetRandomReal(-25, 25)
--set missile.attachment = missile.attach("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl", 100, 100, 0, 1)

missile.onHit = function(hit)
    if IsPlayerEnemy(GetOwningPlayer(hit), GetOwningPlayer(missile.source)) then
        print("enemy hit" .. GetUnitName(hit))
    end

    return false
end

missile.onDestructable = function(hit)
    print("on dest")
    return true
end

missile.onTerrain = function(hit)
    print("on terrain")
    return true
end

missile:launch()

I never programmed in LUA before but efforts like that is always welcome. When i manage to get my soul back from work and college i might give it a try. Anyway, i think you should attach a test map and do some sort of stress test.
 
Level 4
Joined
Mar 25, 2021
Messages
18
Removing code from my previous post, people should use the code in the attached map instead.

Did some testing and once I get to ~2200 missiles FPS starts dropping below 60, however the movement of the actual missiles start getting choppy before that. You can see it demonstrated in the demo as my character can move around without any issues. Also there's not a lot of hit detection going on since I've only hooked up the "onHit"-event.
Edit: New demo video with SWEET_SPOT at 1200, matches my system pretty well as FPS starts dropping at around that count of effects.

Memory usage seems to be fine, code still needs more work to ensure that. But I do know the GC keeps a bit of memory for a while so the engine doesn't have to reallocate memory all the time. In this particular case it stayed around 1700MB.

Test rig:
980 TI
i7 6700k 4GHz
32GB ram at 2600MHz

Demo:

Before using, read context.

Edit: multiple missile attachments are not implemented yet.
 

Attachments

  • Missiles - lua.w3x
    45.1 KB · Views: 54
Last edited:
Level 20
Joined
May 16, 2012
Messages
635
Removing code from my previous post, people should use the code in the attached map instead.

Did some testing and once I get to ~2200 missiles FPS starts dropping below 60, however the movement of the actual missiles start getting choppy before that. You can see it demonstrated in the demo as my character can move around without any issues. Also there's not a lot of hit detection going on since I've only hooked up the "onHit"-event.

Memory usage seems to be fine, code still needs more work to ensure that. But I do know the GC keeps a bit of memory for a while so the engine doesn't have to reallocate memory all the time. In this particular case it stayed around 1700MB.

Test rig:
980 TI
i7 6700k 4GHz
32GB ram at 2600MHz

Demo:

Before using, read context.

Edit: multiple missile attachments are not implemented yet.

Your fps starts to go down because you have thousands of effects on your screen not because there is 2200 instances of missiles. I did a quick test changing the SWEET_SPOT constant and the PERIOD to truly see what LUA is capable of and oh boy. Using a period of 1/60 and a sweet_spot of 1000, meaning every 1/60 the system will process up to 1000 missiles I still got over 45 fps and my machine is no where near yours (FX-8320 and GTX 980). LUA is many many times faster than JASS. Those 2 constants can be greatly increased. I recommend something like 1/40 for the period and 600 as the sweet_spot. With those 2 values I have more than 100 fps at any given moment, except when looking to 600 effects on my screen, but that's due to my graphics card. LUA is definitely powerful.
 
Level 4
Joined
Mar 25, 2021
Messages
18
Your fps starts to go down because you have thousands of effects on your screen not because there is 2200 instances of missiles. I did a quick test changing the SWEET_SPOT constant and the PERIOD to truly see what LUA is capable of and oh boy. Using a period of 1/60 and a sweet_spot of 1000, meaning every 1/60 the system will process up to 1000 missiles I still got over 45 fps and my machine is no where near yours (FX-8320 and GTX 980). LUA is many many times faster than JASS. Those 2 constants can be greatly increased. I recommend something like 1/40 for the period and 600 as the sweet_spot. With those 2 values I have more than 100 fps at any given moment, except when looking to 600 effects on my screen, but that's due to my graphics card. LUA is definitely powerful.
I'm aware of your first point, bad wording on my part.

Ahh okay now I get why the movement slowed down before my fps did, I'm stupid, didn't read comments, too focused on porting it over haha!

Awesome!
 
Level 4
Joined
Mar 25, 2021
Messages
18
@Forsakn I went through your code and modified it quite a bit. Fixed a few bugs and errors and inlined it to make it faster and more readable. I rewrote all examples in LUA as well. I also fixed that problem of instant facing that you pointed out.
Sweet, well done! I'm sorry for the horrible formatting of the code I uploaded, was eager to share it. I haven't had the time to work more on it yet either, so thanks for fixing it up for me hehe :grin:
 
Level 20
Joined
May 16, 2012
Messages
635
How can I download a version that works for 1.26?
Go to the Preview Triggers of the vJASS version and copy the code of the Relativistic Missiles that is inside the folder 1.30-, you will also need to copy, if you don't have it already, the WorldBounds library and optionally you can also copy the DummyRecycler library. Then paste each to a respective file in your trigger editor and you done.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Go to the Preview Triggers of the vJASS version and copy the code of the Relativistic Missiles that is inside the folder 1.30-, you will also need to copy, if you don't have it already, the WorldBounds library and optionally you can also copy the DummyRecycler library. Then paste each to a respective file in your trigger editor and you done.
Thanks.

Edit: The problem is that the map does not open on 1.26
 
Last edited:

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
I know you've made examples, however, it would be great to have all the API in one place in comment, like it's usually done, so that the user doesn't have to scroll over the code and check if there's a specific method or function he desires.
In the end, I think this one could be claimed to be the best missile system.
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
I know you've made examples, however, it would be great to have all the API in one place in comment, like it's usually done, so that the user doesn't have to scroll over the code and check if there's a specific method or function he desires.
In the end, I think this one could be claimed to be the best missile system.
Where did you write about missile collision?
 
Level 7
Joined
Feb 9, 2021
Messages
301
Usage examples you can see in the Preview Triggers, Missile Q example.
The system looks amazing. The question I have is how I can implement these ideas ([Spell] - Time Stop, Gravity, Spell Destruction and Spell Reflection) into this system?

Edit:
Following up on my last post, here is a good example of the implementation of some of the features: Knock-Back 3D - MMS 3D V. 3.2.1.

1. He has Pause/Resume functions on a specific KB instance or on all the system -> allows for time stop
2. He has a function to take Z into configuration when selecting unit (I am not sure whether you have in yours0
3. He makes it easy to access each instance by ID in order to change the KB instance parameters.
4. Would be great if you could add something for missile reflection (reflection as just changing parameters of a missile and as changing property ownership of the missile)
5. Another idea is to add additional types for missiles. For example, I want certain types of missiles to stop moving on collision with type one missiles but want to continue moving on collision with type two missiles or bounce against type 3..

It would be cool if you could implement these features, so spells that I described in my attached thread become a possibility.

P.S. Hope, you will not forget users below 1.30 : )
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
I tried to make my first spell with this system, but I have a problem with setting the speed. No matter what speed I pick, it is the same. Since I am coding on 1.26, I had to change Blz functions with functions from the MemoryHack. I thought this might be the problem, so I tried the dummy version, and the problem is the same.

Description of my spell:
Caster throws a missile in a certain direction. On hitting an enemy, it drags the enemy with it (max unit 1). On destructible, it stuns the hit unit for x seconds. The missile is deleted after the stun is finished.

JASS:
library Bakudo30 /*

    */uses /*

    */SpellFramework    /*
    */BaseFunctions     /*
    */Missiles          /*
    */CastPoint

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'A012'
        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT
        static constant real SFX_DEATH_TIME         = 1.50
        static constant string MISSILE_EFFECT_ID    = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
        static constant real MISSILE_SPEED          = 1500
        static constant real MISSILE_SCALE          = 1.0
        static constant real MISSILE_COLLSION       = 64
        static constant real MISSILE_BASE_DIST      = 50
        static constant real MISSILE_BASE_Z         = 100
        static constant attacktype ATTACK_TYPE      = ATTACK_TYPE_MAGIC
        static constant damagetype DAMAGE_TYPE      = DAMAGE_TYPE_MAGIC
        static constant weapontype WEAPON_TYPE      = WEAPON_TYPE_WHOKNOWS
        
        static method Dmg takes unit caster, integer level returns real
            return 10.*level + 0.1 * GetHeroStr(caster, true) 
        endmethod
        
        static method DmgOnDestructible takes unit caster, integer level returns real
            return 10.*level + 0.1 * GetHeroStr(caster, true) 
        endmethod
        
        static method StunDur takes integer level returns real
            return 3.0 + 1.*level 
        endmethod 

    endmodule

     private function TargetsFilter takes unit target, unit caster returns boolean
            return GetWidgetLife(target) > 0.405                    and/*
                */ IsUnitEnemy(target, GetOwningPlayer(caster))     and/*
                */ (IsUnitVisible(target, GetOwningPlayer(caster))) and/*
                */ not IsUnitType(target,UNIT_TYPE_STRUCTURE)       and/*
                */ GetUnitAbilityLevel(GetFilterUnit(),'Aloc') == 0 and/*
                */ IsUnitType(target, UNIT_TYPE_HERO)
    endfunction

    /*****************************************************************
    *                   END OF SPELL CONFIGURATION                   *
    *****************************************************************/

    /*========================= SPELL CODE =========================*/
    
    private struct Missile extends Missiles
        implement SpellConfiguration
        private real uCount
        private unit hitTarget
        
        method onHit takes unit hit returns boolean
            if hit != source and TargetsFilter(hit, source) and uCount < 1 then
                call BJDebugMsg("onHit(" + (I2S(this)) + "): " + GetUnitName(hit))
                call UnitDamageTarget(source, hit, damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                call SetUnitPathing(hit, false)
                set uCount = uCount + 1
                set hitTarget = hit
            endif
            
            if hitTarget != null then
               call SetUnitX(hitTarget, x)
               call SetUnitY(hitTarget, y)
            endif
        
            return false
        endmethod
        
        method onDestructable takes destructable dest returns boolean
            local StunBuff sb
            local integer spellLevel = GetUnitAbilityLevel(source, SPELL_ABILITY_ID)
            local real damageOnDestructible = DmgOnDestructible(source, spellLevel)
            set duration = StunDur(spellLevel)
            set speed = 0
            
            call UnitDamageTarget(source, hitTarget, damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
            set sb = StunBuff.add(source, hitTarget)
            set sb.duration = StunDur(spellLevel)

            return false
        endmethod
        
    endstruct
    
    private struct Bakudo30 extends array

        implement SpellConfiguration
        
        private static method onSpellStart takes nothing returns thistype
            local thistype node
            local Missiles m
            local real cX            = GetUnitX(Spell.triggerUnit)
            local real cY            = GetUnitY(Spell.triggerUnit)
            local real cZ            = GetUnitFlyHeight(Spell.triggerUnit)
            local real tX            = GetSpellTargetX()
            local real tY            = GetSpellTargetY()
            local real angle         = GetAngle(cX, tX, cY, tY)
            local real baseX         = GetXWithOffset(cX, MISSILE_BASE_DIST, angle)
            local real baseY         = GetYWithOffset(cY, MISSILE_BASE_DIST, angle)
            
            set node                 = GetUnitId(Spell.triggerUnit)
            set m                    = Missiles.create(baseX, baseY, cZ + MISSILE_BASE_Z, tX, tY,  cZ + MISSILE_BASE_Z)
            set m.model              = MISSILE_EFFECT_ID
            set m.speed              = 1000
            set m.collision          = MISSILE_COLLSION
            set m.owner              = GetOwningPlayer(Spell.triggerUnit)
            set m.damage             = Dmg(Spell.triggerUnit, Spell.level)
            set m.scale              = MISSILE_SCALE
            set m.source             = Spell.triggerUnit
            set m.collideZ           = false
            set m.duration           = 10.0
            
            call m.launch()
            
            return node
        endmethod

        private method onSpellEnd takes nothing returns nothing

        endmethod

        implement SpellEventEx

    endstruct

endlibrary
 
Level 20
Joined
May 16, 2012
Messages
635
I tried to make my first spell with this system, but I have a problem with setting the speed. No matter what speed I pick, it is the same. Since I am coding on 1.26, I had to change Blz functions with functions from the MemoryHack. I thought this might be the problem, so I tried the dummy version, and the problem is the same.

Description of my spell:
Caster throws a missile in a certain direction. On hitting an enemy, it drags the enemy with it (max unit 1). On destructible, it stuns the hit unit for x seconds. The missile is deleted after the stun is finished.

JASS:
library Bakudo30 /*

    */uses /*

    */SpellFramework    /*
    */BaseFunctions     /*
    */Missiles          /*
    */CastPoint

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'A012'
        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT
        static constant real SFX_DEATH_TIME         = 1.50
        static constant string MISSILE_EFFECT_ID    = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
        static constant real MISSILE_SPEED          = 1500
        static constant real MISSILE_SCALE          = 1.0
        static constant real MISSILE_COLLSION       = 64
        static constant real MISSILE_BASE_DIST      = 50
        static constant real MISSILE_BASE_Z         = 100
        static constant attacktype ATTACK_TYPE      = ATTACK_TYPE_MAGIC
        static constant damagetype DAMAGE_TYPE      = DAMAGE_TYPE_MAGIC
        static constant weapontype WEAPON_TYPE      = WEAPON_TYPE_WHOKNOWS
       
        static method Dmg takes unit caster, integer level returns real
            return 10.*level + 0.1 * GetHeroStr(caster, true)
        endmethod
       
        static method DmgOnDestructible takes unit caster, integer level returns real
            return 10.*level + 0.1 * GetHeroStr(caster, true)
        endmethod
       
        static method StunDur takes integer level returns real
            return 3.0 + 1.*level
        endmethod

    endmodule

     private function TargetsFilter takes unit target, unit caster returns boolean
            return GetWidgetLife(target) > 0.405                    and/*
                */ IsUnitEnemy(target, GetOwningPlayer(caster))     and/*
                */ (IsUnitVisible(target, GetOwningPlayer(caster))) and/*
                */ not IsUnitType(target,UNIT_TYPE_STRUCTURE)       and/*
                */ GetUnitAbilityLevel(GetFilterUnit(),'Aloc') == 0 and/*
                */ IsUnitType(target, UNIT_TYPE_HERO)
    endfunction

    /*****************************************************************
    *                   END OF SPELL CONFIGURATION                   *
    *****************************************************************/

    /*========================= SPELL CODE =========================*/
   
    private struct Missile extends Missiles
        implement SpellConfiguration
        private real uCount
        private unit hitTarget
       
        method onHit takes unit hit returns boolean
            if hit != source and TargetsFilter(hit, source) and uCount < 1 then
                call BJDebugMsg("onHit(" + (I2S(this)) + "): " + GetUnitName(hit))
                call UnitDamageTarget(source, hit, damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                call SetUnitPathing(hit, false)
                set uCount = uCount + 1
                set hitTarget = hit
            endif
           
            if hitTarget != null then
               call SetUnitX(hitTarget, x)
               call SetUnitY(hitTarget, y)
            endif
       
            return false
        endmethod
       
        method onDestructable takes destructable dest returns boolean
            local StunBuff sb
            local integer spellLevel = GetUnitAbilityLevel(source, SPELL_ABILITY_ID)
            local real damageOnDestructible = DmgOnDestructible(source, spellLevel)
            set duration = StunDur(spellLevel)
            set speed = 0
           
            call UnitDamageTarget(source, hitTarget, damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
            set sb = StunBuff.add(source, hitTarget)
            set sb.duration = StunDur(spellLevel)

            return false
        endmethod
       
    endstruct
   
    private struct Bakudo30 extends array

        implement SpellConfiguration
       
        private static method onSpellStart takes nothing returns thistype
            local thistype node
            local Missiles m
            local real cX            = GetUnitX(Spell.triggerUnit)
            local real cY            = GetUnitY(Spell.triggerUnit)
            local real cZ            = GetUnitFlyHeight(Spell.triggerUnit)
            local real tX            = GetSpellTargetX()
            local real tY            = GetSpellTargetY()
            local real angle         = GetAngle(cX, tX, cY, tY)
            local real baseX         = GetXWithOffset(cX, MISSILE_BASE_DIST, angle)
            local real baseY         = GetYWithOffset(cY, MISSILE_BASE_DIST, angle)
           
            set node                 = GetUnitId(Spell.triggerUnit)
            set m                    = Missiles.create(baseX, baseY, cZ + MISSILE_BASE_Z, tX, tY,  cZ + MISSILE_BASE_Z)
            set m.model              = MISSILE_EFFECT_ID
            set m.speed              = 1000
            set m.collision          = MISSILE_COLLSION
            set m.owner              = GetOwningPlayer(Spell.triggerUnit)
            set m.damage             = Dmg(Spell.triggerUnit, Spell.level)
            set m.scale              = MISSILE_SCALE
            set m.source             = Spell.triggerUnit
            set m.collideZ           = false
            set m.duration           = 10.0
           
            call m.launch()
           
            return node
        endmethod

        private method onSpellEnd takes nothing returns nothing

        endmethod

        implement SpellEventEx

    endstruct

endlibrary
@maxodors
I'm not sure sure what SpellFramework does, but that's how I would do it. A few points to be aware of:
  • If you are not setting a scale different than 1, than you don't need to set it (1 by default).
  • If you don't want to use the collideZ than just don't set it to true (false by default).
  • Careful when using the duration member. duration is an operator that adjust the missile speed to match the flight time. Setting speed also affects the missile speed and the last one to be set (between speed and duration) takes precedence.
  • You can set speed to 0 to stop the missile but I recommend using the new pause() method call. When you set the speed to 0 the missile will stop but it will remain in the processing queue, while pausing the missile removes it from the processing queue, saving up performance.
  • Don't forget to null handles or you might leak it. With Missiles the moment to clean up is the onRemove event or, like I did, when calling the terminate() method (terminate() will run the onRemove event as well).
  • You might want to pause the unit when it is hit by a missile, just don't forget to unpause it.
vJASS:
scope Example
    private struct Missile extends Missiles
        timer timer
        unit  unit
        real  stun
    
        method onPeriod takes nothing returns boolean
            if unit != null then
                call SetUnitX(unit, x)
                call SetUnitY(unit, y)
            endif
        
            return false
        endmethod
    
        method onHit takes unit u returns boolean
            if u != source and UnitAlive(u) and unit == null then
                set unit = u
                call SetUnitPathing(unit, false)
                call UnitDamageTarget(source, unit, damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
            endif
        
            return false
        endmethod
        
        method onDestructable takes destructable d returns boolean
            if unit != null then
                set timer = NewTimerEx(this)
                
                call pause(true)
                call StunUnit(unit, stun)
                call TimerStart(timer, stun, false, function thistype.onExpire)
            endif
            
            return false
        endmethod
        
        method onFinish takes nothing returns boolean
            if unit != null then
                call SetUnitPathing(unit, true)
            endif
        
            return false
        endmethod
        
        private static method onExpire takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            
            call terminate()
            call SetUnitPathing(unit, true)
            call ReleaseTimer(timer)
            
            set timer = null
            set unit = null
        endmethod
    
        private static method onCast takes nothing returns nothing
            local thistype this = thistype.create(Spell.source.x, Spell.source.y, 50, Spell.x, Spell.y,  50)
            
            set model = "Abilities\\Weapons\\Dryadmissile\\Dryadmissile.mdl"
            set speed = 1000
            set collision = 64
            set owner = Spell.source.player
            set damage = 100
            set source = Spell.source.unit
            set stun = 5
            set unit = null
            
            call launch()
        endmethod
    
        private static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A000', function thistype.onCast)
        endmethod
    endstruct
endscope

full
 
Level 7
Joined
Feb 9, 2021
Messages
301
@maxodors
I'm not sure sure what SpellFramework does, but that's how I would do it. A few points to be aware of:
  • If you are not setting a scale different than 1, than you don't need to set it (1 by default).
  • If you don't want to use the collideZ than just don't set it to true (false by default).
  • Careful when using the duration member. duration is an operator that adjust the missile speed to match the flight time. Setting speed also affects the missile speed and the last one to be set (between speed and duration) takes precedence.
  • You can set speed to 0 to stop the missile but I recommend using the new pause() method call. When you set the speed to 0 the missile will stop but it will remain in the processing queue, while pausing the missile removes it from the processing queue, saving up performance.
  • Don't forget to null handles or you might leak it. With Missiles the moment to clean up is the onRemove event or, like I did, when calling the terminate() method (terminate() will run the onRemove event as well).
  • You might want to pause the unit when it is hit by a missile, just don't forget to unpause it.
vJASS:
scope Example
    private struct Missile extends Missiles
        timer timer
        unit  unit
        real  stun

        method onPeriod takes nothing returns boolean
            if unit != null then
                call SetUnitX(unit, x)
                call SetUnitY(unit, y)
            endif
    
            return false
        endmethod

        method onHit takes unit u returns boolean
            if u != source and UnitAlive(u) and unit == null then
                set unit = u
                call SetUnitPathing(unit, false)
                call UnitDamageTarget(source, unit, damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
            endif
    
            return false
        endmethod
    
        method onDestructable takes destructable d returns boolean
            if unit != null then
                set timer = NewTimerEx(this)
            
                call pause(true)
                call StunUnit(unit, stun)
                call TimerStart(timer, stun, false, function thistype.onExpire)
            endif
        
            return false
        endmethod
    
        method onFinish takes nothing returns boolean
            if unit != null then
                call SetUnitPathing(unit, true)
            endif
    
            return false
        endmethod
    
        private static method onExpire takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
        
            call terminate()
            call SetUnitPathing(unit, true)
            call ReleaseTimer(timer)
        
            set timer = null
            set unit = null
        endmethod

        private static method onCast takes nothing returns nothing
            local thistype this = thistype.create(Spell.source.x, Spell.source.y, 50, Spell.x, Spell.y,  50)
        
            set model = "Abilities\\Weapons\\Dryadmissile\\Dryadmissile.mdl"
            set speed = 1000
            set collision = 64
            set owner = Spell.source.player
            set damage = 100
            set source = Spell.source.unit
            set stun = 5
            set unit = null
        
            call launch()
        endmethod

        private static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A000', function thistype.onCast)
        endmethod
    endstruct
endscope

full
Thank you. I found what was wrong.

I noticed that sometimes the unit would go through destructible. How can I address this? It happens when there are two destructible together and the unit is flying in between.

Another question I have is with the terrain. I want the unit to get stunned when the terrain is surface behind the unit. For example, if the unit is on terrain level 0, and he is pushed against terrain level 1. However, the unit will not get stunned if he flies from terrain level 1 to terrain level 0.

Also, sometimes the unit gets pushed into destructible. I thought about addressing it with IsTerrainWalkable, but then the problem is when the unit goes from terrain level 1 to terrain level 0.
 
Level 2
Joined
Jan 1, 2020
Messages
8
hi editor newb here, i tried exporting this to a new map with copy paste GUI trigger and export all objects, but when I try to run the new map with the spell I get a "Too many nested if statments (max 50)" Error. Any suggestions?
 
Level 11
Joined
Jul 4, 2016
Messages
626
Is there a way to keep the missile speed the same regardless of distance of target? I'm trying to use them to replace native missile behavior.
 
Level 11
Joined
Jul 4, 2016
Messages
626
Missile Speed is constant if you don't set acceleration.
I do not set the acceleration yet the missile's constant speed will either be slow or fast relative to the distance of the target at the time of missile launch instead of just being the speed I set it up with always.
 
Level 7
Joined
Feb 9, 2021
Messages
301
I looking for help with two issues:
(1) I have been trying to make a hook with this system for while, but I can't just make it work. There are two problems that I encounter: (a) the periodic inside the missile finishes faster than all links expire and (b) the only way I see to return is the hook is to deflect it, which triggers onFinish for some reason? Also, not related to the system, I would appreciate some explanation on how to use the list lib in order to enlist all the effects and then add new links when the dist between the first link and the caster or between the last link and the target is more than certain dist.
Here is my code:
JASS:
library CrocHook /*

     */ uses /*

     */ SpellFramework /*
     */ BaseFunctions

    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID = 'A01J'
        static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
     
        //Models and Effects
        static constant string HOOK_MODEL_ID = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl"
        static constant string SAND_EFFECT_ID = "Abilities\\Weapons\\WardenMissile\\WardenMissile.mdl"
     
     
        //Params
        static constant real HOOK_BASE_Z = 70
        static constant real HOOK_ANGLE_ADJUSTMENT = 40
        static constant real HOOK_CAST_DIST = 60
        static constant real HOOK_RETURN_DIST = 25
        static constant real HOOK_RANGE = 1000
        static constant real HOOK_AOE = 80
        static constant real HOOK_SPEED = 1100
     
        static method Dmg takes unit caster returns real[ATTACH type="full"]383957[/ATTACH]
            return 10.0 + 0.1 * GetHeroStr(caster, true)
        endmethod
     
        static method Targets takes unit caster, unit target returns boolean
            return GetWidgetLife(target) > 0.405 and /*
             */ not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
             */ not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) and /*
             */ IsUnitEnemy(target, GetOwningPlayer(caster))
        endmethod
     
    endmodule

    globals
        private group enemies = CreateGroup()

    endglobals
 
    private struct Missile extends Missiles
        implement SpellConfiguration
     
        private boolean hookReturn
        private boolean hit
        private boolean castFinsihed
        private unit unit
        private real hookBaseX
        private real hookBaseY
        private real hookBaseZ
        private integer linkCounter
        private timer timer
     
        Table effectLink
     
        method onPeriod takes nothing returns boolean
            local real cX = GetUnitX(source)
            local real cY = GetUnitY(source)
            local real hookX
            local real hookY
            local real hookAngle
            local real hookZ
            local real hookFacing
            local real dist = GetDist(x, this.hookBaseX, y, this.hookBaseY)
            local real cDist
         
            if this.unit != null then
                call SetUnitX(unit, x)
                call SetUnitY(unit, y)
            endif
         
            if not this.hit and this.hookReturn then
                set hookAngle = GetUnitFacing(source) + HOOK_ANGLE_ADJUSTMENT
                set hookX = GetXWithOffset(cX, HOOK_RETURN_DIST , hookAngle)
                set hookY = GetYWithOffset(cY, HOOK_RETURN_DIST , hookAngle)
                set hookZ = GetUnitZ(source) + HOOK_BASE_Z
                call deflectZ(hookX, hookY, hookZ)
            endif
         
            if (dist <= HOOK_RANGE / 2 and this.hit and not this.castFinsihed ) then
                call Stun.remove(source)
                set this.castFinsihed = true
            endif
         
            //Effect Links
            if not this.hookReturn then
                set hookFacing = GetUnitFacing(dummy)
                set this.linkCounter = this.linkCounter + 1
                set this.effectLink.effect[linkCounter] = AddSpecialEffect(SAND_EFFECT_ID, x, y)
                call SetEffectZ(this.effectLink.effect[linkCounter], z)
                call SetEffectScale(this.effectLink.effect[linkCounter], 1.5)
                call SetEffectFacing(this.effectLink.effect[linkCounter], hookFacing)
            elseif this.hookReturn and this.linkCounter > 0 then
                call DestroyEffect(this.effectLink.effect[linkCounter])
                set this.linkCounter = this.linkCounter - 1
            endif
         
            debug call BJDebugMsg("[CrocHook] Links: " + I2S(this.linkCounter))
         
            if this.linkCounter == 0 then
         
                call this.effectLink.destroy()
                call this.effectLink.flush()
             
                return true
            endif
         
           // set cDist = GetDist(
         
            //call SetEffectX(this.effectLink.effect[linkCounter],
            //call SetEffectY(this.effectLink.effect[linkCounter],

            return false
        endmethod
     
        method onHit takes unit u returns boolean
            if (BaseBool(u) and /*
                 */ IsUnitEnemy(u, owner) and /*
                 */ not hookReturn and /*
                 */ not hit ) then
                call Stun.apply(u)
                call SetUnitPathing(u, false)
                set this.unit = u
                call deflectZ(this.hookBaseX, this.hookBaseY, this.hookBaseZ)
             
                set this.hit = true
                set this.hookReturn = true
            endif
         
            return false
        endmethod
     
        method onTerrain takes nothing returns boolean
             /*
            call deflect(this.hookBaseX, this.hookBaseY)
            set hookReturn = true
            call Stun.remove(source)
             */
            return false
        endmethod
     
        method onDestructable takes destructable d returns boolean
            call deflectZ(this.hookBaseX, this.hookBaseY, this.hookBaseZ)
            set this.hookReturn = true
            set this.castFinsihed = true
            call Stun.remove(source)
     
            return false
        endmethod
     
     
        method onFinish takes nothing returns boolean
            if this.hookReturn then
                debug call BJDebugMsg("[CrocHook] Hook Return")
                //call DestroyEffect(AddSpecialEffect("Objects\\InventoryItems\\HumanCaptureFlag\\HumanCaptureFlag.mdl", this.hookBaseX, this.hookBaseY))
                if this.unit != null then
                    call SetUnitPathing(this.unit, true)
                    call Stun.remove(this.unit)
                endif
             
                set this.unit = null
                //return false
            endif
         
            if not this.hit and not this.castFinsihed and not this.hookReturn then
                call Stun.remove(source)
                set this.castFinsihed = true
            endif
         
            if not this.hookReturn then
                set this.hookReturn = true
                call deflectZ(this.hookBaseX, this.hookBaseY, this.hookBaseZ)
                set speed = HOOK_SPEED * 1.4
                debug call BJDebugMsg("[CrocHook] OnFinish")
            endif
         
            return false
        endmethod
         /*
        method onPause takes nothing returns boolean
            local thistype sourceId = GetUnitId(source)
            call PauseTimer(sourceId.t)
         
            return false
        endmethod
     
        method onResume takes nothing returns boolean
            local thistype sourceId = GetUnitId(source)
            call ResumeTimer(sourceId.t)
         
            return false
        endmethod
         */

        implement SpellConfiguration
     
        private static method onSpellStart takes nothing returns thistype
            local thistype node
            local real cX = GetUnitX(Spell.triggerUnit)
            local real cY = GetUnitY(Spell.triggerUnit)
            local real angle = GetAngle(cX, Spell.targetX, cY, Spell.targetY)
            local real hookAngle = angle + HOOK_ANGLE_ADJUSTMENT
            local real hookX = GetXWithOffset(cX, HOOK_CAST_DIST, hookAngle)
            local real hookY = GetYWithOffset(cY, HOOK_CAST_DIST, hookAngle)
            local real hookZ = GetUnitFlyHeight(Spell.triggerUnit) + HOOK_BASE_Z
            local real hookEndX = GetXWithOffset(cX, HOOK_RANGE, angle)
            local real hookEndY = GetYWithOffset(cY, HOOK_RANGE, angle)
            local thistype this = thistype.create(hookX, hookY , hookZ , hookEndX, hookEndY, HOOK_BASE_Z )
     
            set this.effectLink = Table.create()
            set this.linkCounter = 0
     
            set this.hookBaseX = hookX
            set this.hookBaseY = hookY
            set this.hookBaseZ = hookZ
            set node = GetUnitId(Spell.triggerUnit)
            set speed = HOOK_SPEED
            set model = HOOK_MODEL_ID
            set source = Spell.triggerUnit
            set collideZ = true
            set collision = HOOK_AOE
            set damage = Dmg(Spell.triggerUnit)
            set owner = Spell.triggerPlayer
     
            set this.hookReturn = false
            set this.unit = null
            set this.hit = false
            set this.castFinsihed = false
            set this.timer = CreateTimer()
     
            call Stun.apply(Spell.triggerUnit)
     
            call launch()
     
        //call TimerStart(
     
            return node
        endmethod
     
        implement SpellEventEx

    endstruct

[ATTACH type="full"]383956[/ATTACH]
endlibrary

(2) I can't change the angle of the missile before the launch. I basically have a rocket missile that I allow user to direct with the mouse, so I want to be able to change the angle before the "launch()". I tried call origin.move and impact.move, but it changes something only after the launch.
 

Attachments

  • 2021-07-11 18-34-23_Trim.mp4
    2.8 MB
  • 2021-07-12 09-55-41_Trim.mp4
    7.9 MB
Level 20
Joined
May 16, 2012
Messages
635
@chopinski
Did you delete pure vjass version?
I cannot preview triggers and the zip that gets downloaded is only the 3 other versions...
If not, I'll poke some admin it

EDIT: Found vjass edition in New Bonus [vJASS][LUA]
Weird, it was showing in the resource section but not working properly. I reuploaded de vJASS version. Thx.
 
Level 10
Joined
May 16, 2013
Messages
229
Hello, Im using gui version recently and this system causing desync to low end pc players from time to time. Are there anything I can do? I like that system a lot and if you ask me it's super useful. However I cannot use it with that way. Thanks ^^
 
Level 3
Joined
Aug 22, 2018
Messages
25
Hello, Im using gui version recently and this system causing desync to low end pc players from time to time. Are there anything I can do? I like that system a lot and if you ask me it's super useful. However I cannot use it with that way. Thanks ^^
Some low end pc players also tend to get desynced from my map, also using the GUI version here!
 
Level 24
Joined
Feb 9, 2009
Messages
1,783
Yeah, no problem, when i got a little more time in my hands I'll add that. Any other config you would like?
Oh shoot you reminded me of a really good one, player color.
Things like wisps, moon priestess arrow could really use this to match the player or be for flavor.
1638672132185.png

Or things like custom models that use it.
A few in particular:



Oh and lastly: Color
1638672426910.png
 
Level 7
Joined
Oct 20, 2010
Messages
182
I'm having a bit of trouble understanding the Missile Groups. For example, creating a nova spell in which Missiles are launched in all directions from a point. Units close enough to the point will be hit multiple times, unless I'm assuming the Missiles share a group and share their hitted units list. Is this possible, and how would I achieve it? I'm using GUI, but seeing it in JASS can be just as helpful.
 
Level 4
Joined
Jun 4, 2019
Messages
31
Hey Chopinski, ty for this awesome system, it is really powerful. I just noticed that version 2.4 (and 1.5 Utilities) does not work with your hero spell system. I saw that it comes from the restructuring of the GUI version. Can you maybe make an example how I can implement your spells despite GUI version? For example, I get the error "Member name already in use by a parent type -> trigger onFinish = null" with the onFinish methods from your spells.
 
Last edited:

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,455
I'm probably missing something but is there a way to attach an Integer value to a Missile? To achieve something like this:
  • Set Variable MySpellIndex = MySpellIndex + 1
  • Set Variable MissileData = MySpellIndex
  • // Set the rest of the Missile variables
  • Trigger - Run MissileCreate <gen> (ignoring conditions)

It would be helpful in certain situations like allowing you to group multiple missiles together (as suggested in this thread):
 
Level 20
Joined
May 16, 2012
Messages
635
I'm probably missing something but is there a way to attach an Integer value to a Missile? To achieve something like this:
  • Set Variable MySpellIndex = MySpellIndex + 1
  • Set Variable MissileData = MySpellIndex
  • // Set the rest of the Missile variables
  • Trigger - Run MissileCreate <gen> (ignoring conditions)

It would be helpful in certain situations like allowing you to group multiple missiles together (as suggested in this thread):

There is a data member, i'm just not sure if i added a gui global variable to it in the GUI version. If not, in the next version coming soon i'll make sure to add it.
 
Level 13
Joined
Jun 23, 2009
Messages
294
A very nice job on the system and a super extra nice job for putting the API readily available here for quick access. I kind of wish you could also package all of this goodness in a way that's more readily available on non-Reforged World Editor but with all the versions of the system you make that might become a huge rabbit hole.

Anyway, asking this before taking an actual good look at the libraries and all, assume I switch from xemissile to this, I imagine the syntax is fairly similar, right? Is there anything in particular that I should look out for when it comes to differences in syntax et al.?

Also, if I'm using 1.31+ is it still important for the dummy unit to have attachment points (like vex's dummy and I assume yours) or can I use a dummy with no model specified instead? Are vex's dummy and your dummy functionally identical in model and/or in-game properties?
 
Level 20
Joined
May 16, 2012
Messages
635
A very nice job on the system and a super extra nice job for putting the API readily available here for quick access. I kind of wish you could also package all of this goodness in a way that's more readily available on non-Reforged World Editor but with all the versions of the system you make that might become a huge rabbit hole.

Anyway, asking this before taking an actual good look at the libraries and all, assume I switch from xemissile to this I imagine the syntax is fairly similar, right? Also, if I'm just using 1.31+ is it still important for the dummy unit to have attachment points (like vex's dummy and I assume yours) or can I use a dummy with no model specified instead? Are vex's dummy and your dummy functionally identical in model and/or in-game properties?
I would say that the syntax in this system is easier to get/use than the xemissile or any other missile system in general. There are planty of examples here in this thread and in my Hero Concepts that you can find in my resources page.

Regarding the dummy unit: If you will be using versions 1.31+ of the game, than the dummy unit is only used to grant vision when the "vision" member is specified, ptherwise this version uses only effects as missiles, not dummys, but yes, the dummy unit utilized here is the exact same as in the xemissile, although i recommend copying the dummy unit in the object editor over to your maps because i changed a few properties.
 
Level 13
Joined
Jun 23, 2009
Messages
294
Just a heads up, I used the Missiles library for 1.31+ and it doesn't compile right away on 1.31.1 World Editor, JassHelper complains:
1644014022541.png

I took a look at both Reforged and 1.31.1 common.j and JassHelper is right, 1.31.1 lacks BlzSetUnitFacingEx().
It compiles cleanly after changing it to SetUnitFacing() btw.
 
Last edited:
Top