/* 4 Packages included..
Projectile superclass
TimedBlasts (main system)
Kamehameha (2d example of TimedBlasts System)
AngryKame (3d example of TimedBlasts System

TimedBlasts attempts to give a system to create various "timed blasts" that can be customized to create tons of different spells that follow the general layout

User begins channeling animation, creating the projectile near them..
after a certain size is reached, the projectile is launched to the target point inflicting damage.

If two "clashable" blasts hit each other, the two casters will be in a struggle until one runs out of mana, and the winning blast will change target to its opposing caster unit.

*visual example of what this code attempts to achieve in the case of a 3d clashable blast https://www.youtube.com/watch?v=M7i5Z3C5ZOk
*note clashable and 3d hasn't been tested/made yet*


        protected real impactAoe - size of aoe for damage to be done
        protected real damage - damage to be done
        protected real endScale - final scale of the blast before launching
        protected boolean trailOn - creates a trail following the blast as it flies to the target
        protected boolean dustOn - creates a dust effect around the caster as he fires
        protected boolean pulseOn - makes the missile pulsate as it flies to the targeted area
        protected boolean launchOn - creates a launch effect as the missile is launched
        protected boolean dropOn - if the missile is fired from an aerial position, the missile is lowered to the targetted area
        protected boolean flightOn - if the missile will be fired from an aerial position
        protected string trailSFX - specify what SFX will be used for the trail
        protected string pulseSFX - specify what SFX will be used for the pulse
    protected string launchSFX - " "
    protected string finalSFX - " "
    protected string dustSFX - " "
    protected real trailSize - size of trail created
    protected real pulseSize - size of pulses
    protected real launchSize - size of launch
    protected colorA missileCol - color of missile
    protected colorA trailCol = color of trail
    protected colorA pulseCol = color of pulse
    protected real speed - speed of missile during travel
    protected int channelIndex - animation index to channel
    protected int launchIndex - animation index to launch
    protected real offsetDistance = 50 - small offset from caster
    protected real realDropSpeed = -15 if there is a drop, reasonable dropspeed, can be changed
    private real timeSinceLastTrail = 0 - used to create trail
    private real timeSinceLastPulse = 0 - used to create pulses
    private real timeSinceLastChannel = 0 - uses to create channel
    protected real timeDistance - how long trails and pulses should last, depends on speed, and missile sizes, should be configured to each spell accordingly
    protected sound throwSound = generic missile size
    protected real lowerRate = 150. speed that a missile will lower if it is a 3d missile
    protected real destroyExplode = 1.5 how long the destruction sfx will be played
    protected boolean clashing - setting if the missile is ready to clash
    protected boolean clashable - saves true value for clashable until channeling is complete
    protected boolean clashed - if a missile has clashed, prevents further clashs
    protected boolean channeling = if a missile is still channeling, it cannot clash yet

*/
 
 
 
 
 
 
 
 
package Projectile
import public Entity
 
public class Projectile extends FxEntity
    // Angles
    protected angle xyAngle
    private vec3 startpos
 
    private boolean timed = false
    private boolean ranged = false
 
    // Moving Speed
    private real speed = 0
    private real acc = 1.0
    protected real dist = 0
    private real maxDist = 0
    protected real lifespan = 0
    protected real dropSpeed = 0
 
 
 
    construct( vec3 pos, real radius, player owner, angle xyAngle, string fxpath )
        super(pos, radius, owner, xyAngle, fxpath)
        startpos = pos
        setXYAngle(xyAngle)
        active = false
 
    function setRanged(real maxDistance)
        this.maxDist = maxDistance*maxDistance
        ranged = true
 
    function setTimed(real lifespan)
        this.lifespan = lifespan
        timed = true
 
    function setSpeed(real speed)
        this.speed = speed
        //angle.direction(real distance)
        setVel(vec3(xyAngle.cos()*speed, xyAngle.sin()*speed, dropSpeed))
 
    function setSpeed(real speed, real drop)
        this.speed = speed
        //angle.direction(real distance)
        setVel(vec3(xyAngle.cos()*speed, xyAngle.sin()*speed, drop))
 
    function setAcc(real factor)
        this.acc = factor
 
    function getSpeed() returns real
        return speed
 
    function setXYAngle(angle xyA)
        this.xyAngle = xyA
        fx.setXYAngle(xyA)
 
    function setZAngle(angle zA)
        fx.setZAngle(zA)
 
    override function update()
        vel *= acc
        pos += vel
        fixPos()
        if timed
            lifespan -= ANIMATION_PERIOD
            if lifespan <= 0
                done = true
                return
        if ranged
            if startpos.distToVecSquared(pos) > maxDist
                done = true
 
 
package TimedBlasts
import Projectile
import LinkedListModule
import Fx
import Constants
import ClosureTimers
import Terrain
import LinkedList
 
 
 
 public class TimedBlasts extends Projectile
     use LinkedListModule
     //Editable values
    protected real impactAoe
    protected real damage
    protected real endScale
    protected boolean trailOn = false
    protected boolean dustOn = false
    protected boolean pulseOn = false
    protected boolean launchOn = false
    protected boolean dropOn = false
    protected boolean flightOn = true
    protected string trailSFX
    protected string pulseSFX
    protected string launchSFX
    protected string finalSFX
    protected string dustSFX = dustWave
    protected real trailSize
    protected real pulseSize
    protected real launchSize
    protected colorA missileCol = colorA(255,255,255,255)
    protected colorA trailCol = colorA(255,255,255,255)
    protected colorA pulseCol = colorA(255,255,255,255)
    protected real speed
    protected int channelIndex
    protected int launchIndex
    protected real offsetDistance = 50
    protected real realDropSpeed = -15
    private real timeSinceLastTrail = 0
    private real timeSinceLastPulse = 0
    private real timeSinceLastChannel = 0
    protected real timeDistance = 1
    protected sound throwSound = gg_snd_kamehameha_fire
    protected real lowerRate = 150.
    protected real destroyExplode = 1.5
    protected boolean clashing = false
    protected boolean clashable = false
    protected boolean clashed = false
    protected boolean channeling = false
    protected LinkedList<Fx> trailList = new LinkedList<Fx>()
 
 
     //Instance Variables
    private vec3 target
    protected unit u
    private timer t = getTimer()
    protected real scale
    protected angle targetAngle
    private boolean missileCast = false
 
 
    construct(unit actor,vec3 pos, player owner, vec3 target, string SFX, real originalScale)
        super(pos.addReals(0, 0, pos.withTerrainZ().z), 0, owner, pos.angleTo2d(target), SFX)
        this.targetAngle = pos.angleTo2d(target)
        this.u = actor
        this.target = target
        this.u.pause()
        this.scale = originalScale
 
 
 
    function startMissile()
        this.setSpeed(0)
        this.pos = pos.offset2d(pos.angleTo2d(target), offsetDistance)
        this.pos.z += 50
        this.fixPos()
        this.fx.setColor(missileCol)
        this.fx.setScale(this.scale)
 
        this.t.setData(this castTo int)
        this.t.startPeriodic(.1, function callExpandMissile)
 
        //if dust is activated
        if this.dustOn
            this.createDust()
 
 
 
    function createDust()
            effect x = AddSpecialEffectTarget(dustSFX, u, "origin")
            DestroyEffectBJ(x)
 
    static function callExpandMissile()
        (GetExpiredTimer().getData() castTo TimedBlasts).expandMissile()
 
    function expandMissile()
        if this.timeSinceLastChannel == 0
            this.u.setAnimation(channelIndex)
        if this.timeSinceLastChannel >= (.18)
            this.u.setAnimation(channelIndex)
            this.timeSinceLastChannel -= (.17)
        this.timeSinceLastChannel += ANIMATION_PERIOD
 
        this.scale += .05
        this.fx.setScale(this.scale)
        if this.scale >= endScale
            if this.flightOn
                this.u.setFlyHeight(0, lowerRate)
            if this.launchOn
                this.createLaunchFX()
            if this.dropOn
                this.dropSpeed = realDropSpeed
 
            this.setSpeed(speed)
 
            this.t.release()
            this.u.setAnimation(launchIndex)
            PlaySoundOnUnitBJ(this.throwSound, 70, this.u)
            this.missileCast = true
            if not this.channeling
                this.u.unpause()
            if this.clashing
                this.clashable = true
 
                //calculate frequency of trail creation
 
 
    function createLaunchFX()
                Fx launch = new Fx(fx.getPos2(),this.targetAngle, kameLaunch)
                launch.setScale(launchSize)
                doAfter(.5, () -> destroy launch)
 
 
 
 
 
    function createTrail()
        Fx trail = new Fx(fx.getPos3d(), this.targetAngle, trailSFX)
        ..setScale(trailSize)
        ..setColor(trailCol)
        if flightOn == false
            trail.setZ(100)
    //    doAfter(timeDistance, () -> trail.hiddenDestroy())
        this.trailList.add(trail)
 
 
 
    function createPulse()
            Fx pulse = new Fx(fx.getPos3d(), this.targetAngle, pulseSFX)
            ..setScale(pulseSize)
            ..setZAngle(-5200)
            ..setColor(pulseCol)
            if this.flightOn == false
                pulse.setZ(100)
            // pulse.getDummy().setAnimation(0)
            doAfter(timeDistance, () -> pulse.hiddenDestroy())
 
    override function update()
        super.update()
 
//should be speed dependent at some level.
        if pos.toVec2().distToVecSquared(target.toVec2()) < 120*120
            //timeDistance = 0.15
        if this.getSpeed() > 0
            if this.trailOn
                if this.timeSinceLastTrail > (2/speed)
                    this.createTrail()
                    this.timeSinceLastTrail -= (2/speed)
                this.timeSinceLastTrail += ANIMATION_PERIOD
            if this.pulseOn
                if this.timeSinceLastPulse > (2/speed)
                    this.createPulse()
                    this.timeSinceLastPulse -= (2/speed)
                this.timeSinceLastPulse += ANIMATION_PERIOD
        if not this.clashed
            if this.clashable
                Entity e = Entity.getFirst()
                while e != null
                    if e!= this and this.owner != e.owner
                        if this.owner.isAllyOf(e.owner)
                            TimedBlasts blast2 = e castTo TimedBlasts
                            if blast2.clashable and not blast2.clashed
                                if this.pos.distToVecSquared(e.pos) < 40000.
                                    print(this.pos.distToVecSquared(e.pos).toString())
                                if this.pos.distToVecSquared(e.pos) < (120*120)
                                    //print("hi6!")
                                    this.onHit(blast2)
                    e=e.next
 
        if ((this.u.getPos().distToVecSquared(target.toVec2()) < 150*150) or (pos.toVec2().distToVecSquared(target.toVec2()) < 64*64)) and missileCast
            for unit enemy from ENUM_GROUP..enumUnitsInRange(pos.toVec2(), impactAoe)
                if enemy.isEnemy(owner)
                    this.u.damageTarget(enemy,  damage)
            done = true
            terminate()
 
 
     function onHit(TimedBlasts blast2)
        boolean winner1 = false
        boolean winner2 = false
         //print("hi7!")
        this.clashed = true
        blast2.clashed = true
        blast2.setSpeed(0)
        this.setSpeed(0)
        blast2.active = false
        this.active = false
        //this.fx.setScale(endScale+ 1)
        //blast2.fx.setScale(endScale+ 1)
        doPeriodically(.25, (CallbackPeriodic cb) -> begin
            Fx effect1 = new Fx(fx.getPos2(),this.targetAngle, iceNova)
            ..setScale(1.8)
            ..setZ(100)
            ..setZAngle(-5200)
            Fx effect2 = new Fx(blast2.fx.getPos2(),this.targetAngle, iceNova)
            ..setScale(1.8)
            ..setZ(100)
            ..setZAngle(-5200)
            Fx effect5 = new Fx(((fx.getPos2() + blast2.fx.getPos2()) *.5).toVec3().addReals(0, 0, 50), this.targetAngle, sparklyExplosion)
            ..setScale(5)
            doAfter(.25, () -> begin
                destroy(effect1)
                destroy(effect2)
                destroy(effect5)
            end)
 
            if this.u.getMana() == 0
                winner2 = true
            if blast2.u.getMana() == 0
                winner1 = true
            this.u.setMana(u.getMana()-1)
            blast2.u.setMana(blast2.u.getMana()-2)
            if winner1
                print(this.u.getName())
                blast2.terminate()
                this.target = blast2.u.getPos3()
                this.setSpeed(this.speed)
                this.active = true
                destroy cb
            if winner2 and not winner1
                print(blast2.u.getName())
                terminate()
                blast2.target = this.u.getPos3()
                blast2.setSpeed(blast2.speed)
                blast2.active = true
                destroy cb
         end)
 
    ondestroy
        if this.channeling
            this.u.unpause()
        Fx final = new Fx(fx.getPos3d(), this.targetAngle, finalSFX)
        final.setScale(2)
        doAfter(destroyExplode, () -> final.hiddenDestroy())
        for i = 0 to (this.trailList.getSize() -1)
            this.trailList.get(i).hiddenDestroy()
        this.done = true
 
 
package Kamehameha
import Constants
import TimedBlasts
 
constant real originalScale = .2
public class Kamehameha extends TimedBlasts
 
 
 
    construct(unit actor,vec3 pos, player owner, vec3 target, int level)
        super(actor,pos.addReals(0, 0, 50),owner,target,bigKamehamehaBall,originalScale)
 
        //if (actor.getTypeId() == 'H001')
        this.impactAoe = 500
        this.damage = 150*level.toReal() + (GetHeroInt(actor, true)*10)
        this.trailOn = true
        this.dustOn = true
        this.pulseOn = true
        this.launchOn = true
        int actorID = actor.getTypeId()
        if actorID == 'H000'
            this.channelIndex = 11
            this.launchIndex = 12
            this.throwSound = gg_snd_Kamehameha_Voice
        if actorID == 'H005' or actorID == 'H009'
            this.channelIndex = 20
            this.launchIndex = 18
        if actorID == 'H00A'
            this.channelIndex = 5
            this.launchIndex = 4
        if actorID == 'H001' or actorID == 'H008'
            this.channelIndex = 7
            this.launchIndex = 8
            this.throwSound = gg_snd_GohanKame
        if actorID == 'H00D' or actorID == 'H00E' or actorID == 'H00F'
            this.channelIndex = 8
            this.launchIndex = 12
        if actorID == 'H007'
            this.channelIndex = 9
            this.launchIndex = 13
        if actorID == 'H00G'
            this.channelIndex = 12
            this.launchIndex = 11
        this.speed = 32
        this.endScale = .6
        this.trailSFX = bigKamehamehaBall
        this.trailSize = .3
        this.pulseSFX = bluePulse
        this.launchSFX = kameLaunch
        this.launchSize = 5
        this.finalSFX = bigBlueExplosion
        this.pulseSize = 1
        this.trailCol = colorA(255,255,255,255)
        this.clashing = true
        this.channeling = true
        startMissile()
 
 
package AngryKame
import Constants
import TimedBlasts
import ClosureTimers
 
constant real originalScale = .2
public class AngryKame extends TimedBlasts
 
    construct(unit actor,vec3 pos, player owner, vec3 target, int level)
        super(actor,pos.addReals(0, 0, 700),owner,target,dummy2,originalScale)
        this.impactAoe = 500
        actor.setFlyHeight(700, 700.)
        this.flightOn = true
        this.dropOn = true
        this.offsetDistance = 0
        this.missileCol = colorA(150,150,0,255)
        this.speed = 25
        this.endScale = 1.5
        this.dustSFX = dustWave
        this.damage = 5000*level.toReal() + (GetHeroInt(actor, true)*20)
        this.finalSFX = nuclearExplosion
        this.trailOn = true
        this.dustOn = true
        this.pulseOn = true
        this.launchOn = false
        this.channelIndex = 20
        this.launchIndex = 10
        this.trailSFX = pinkBall
        this.trailSize = .6
        this.launchSFX = kameLaunch
        this.launchSize = 5
        this.trailCol = colorA(150,150,0,255)
        this.lowerRate = 300.
        this.realDropSpeed = -25
        this.destroyExplode = 3
        this.pulseSFX = windBlue
        this.pulseSize = 1.2
        this.pulseCol = colorA(150,150,0,255)
        effect x = AddSpecialEffectTarget(kamelightning, u, "origin")
        doAfter(1.5, () -> begin
            doAfter(1, () -> begin
                PlaySoundOnUnitBJ(gg_snd_angrykame, 70, this.u)
            end)
            startMissile()
            fx.setFx(pinkBall)
        end)
        doAfter(12, () -> begin
            DestroyEffectBJ(x)
        end)
 
 

goto line:
Compare with:
text copy window edit this code post new code