Spell Soul Slave

No_exit

Regular User (What is Custom User Title?)
Reaction score
40
Soul Slave

General information
Gui/Jass/vJass?: vJass
JESP?: I think so but correct me if I'm wrong.
Lags?: The first framedrop on high end pcs is around 70 souls on a single hero. Lag will really start to kick in from around 120 souls. Feedback on low end pc performance will be greatly appreciated.
MUI?: Yes but do read the above line again.
Type:: Aura I guess?
N-levels?: Yes


Functionality
Whenever a nearby unit (friendly or hostile) dies, the souls of this unit will be your slave (the amount of souls depends on the dying unit level).

The soul slaves are forced to follow your hero around and can only free themselves by suiciding on an enemy unit which deals damage depending on the ability level.


Pictures
Picture 1:
2afiek8.jpg


Picture 2:
8wh5s2.jpg


Picture 3:
11t7nfk.jpg


Soul Position
A lot of work has been made in the position of the soul to make it feel more natural and to make it look better.

Souls will orbit at a random radius. Souls that are further away from the hero will take a longer time to orbit around the hero (The orbit radius is randomly chosen and the direction is randomly chosen clockwise or counter clockwise). (See picture 1)

The relative position of the hero and the soul are not fixed, this means that if the hero moves the souls will be "dragged" towards this direction. (see Picture 2)

Whenever a leading soul acquires a target, it will speed up. If it loses a target, it will slow down again. All other souls will follow their previous soul. (See Picture 3)


Soul Color
The souls will vary their colors depending on what state they are in: birth (default: blue), normal(default: green) or angry(default: red). Transition between the colors is smoothly (see pictures 1 and 3).


Code
JASS:
scope ness initializer Init
globals
    //-----------------
    //----- Setup -----
    //-----------------
    //These variables are a bridge between the map and the (vJass) code.
    
    private constant integer SPELL_ID = 'A001' //Spell ID
    private constant integer SOUL_ID = 'e001' //Soul ID
    
    
    //-------------------------
    //----- Configurables -----
    //-------------------------
    //These variables can be changed to adapt the ability for your needs.
    
    //Damage Configurables.
    private constant real DAMAGE_OFFSET = 0. //Basic damage a soul does.
    private constant real DAMAGE_PERLEVEL = 16. //Damage that is added for every level.
    private constant attacktype DAMAGE_ATTACKTYPE = ATTACK_TYPE_MAGIC //The attacktype that the damage does.
    private constant damagetype DAMAGE_DAMAGETYPE = DAMAGE_TYPE_DEATH //The weapontype that the damage does.
    private constant weapontype DAMAGE_WEAPONTYPE = WEAPON_TYPE_WHOKNOWS //The weapontype that the damage does.
    
    //Spawn Configurables.
    private constant real SPAWN_AMOUNT_OFFSET = .8 //Amount of souls that spawn. (on average)
    private constant real SPAWN_AMOUNT_PERABILLEVEL = .0 //Amount of extra souls you get per ability level increase. (on average)
    private constant real SPAWN_AMOUNT_PERUNITLEVEL = .6 //Amount of extra souls you get per unit level of the dying unit. (on average)
    private constant real SPAWN_RADIUS = 800. //The maximum distance at which a unit will be coverted to souls.
    private constant real SPAWN_INTERVAL = .32 //Time between the spawning of souls from one dying unit.
    private constant real SPAWN_ILLNESS = 1.4 //Interval in which souls will not acquire any targets. MUST BE BIGGER THEN SPAWN_INTERVAL TO AVOID SPAWNING TO STOP, I warned you.
    
    //Position Configurables.
    private constant real MAIN_INTERVAL = .035 //Interval at which the souls will rotate around the caster.
    private constant real SPEED_ROTATE_NORMAL = 10. //Desired speed of the soul (per tick)
    private constant real SPEED_ROTATE_VICTIM = 20. //Desired speed of the soul when they have a victim (per tick).
    private constant real SPEED_CATCHUP_SPEED_PERC = .11 //Has to be between 0. and 1. where 0. would mean it would never go to the desired location and 1. would mean it would go there instantly.
    private constant real SPEED_CATCHUP_RADIUS_PERC = .09 //Has to be between 0. and 1. where 0. would mean it would never go to the desired location and 1. would mean it would go there instantly.
    private constant real SPEED_CATCHUP_COLOR_PERC = .14 //Has to be between 0. and 1. where 0. would mean it would never go to the desired color and 1. would mean it would go there instantly.
    private constant real DES_DIST = 48. //Desired distance between souls
    private constant real RADIUS_MIN = 90. //(Desired) minimum distance between the caster and the soul
    private constant real RADIUS_MAX = 230. //(Desired) maximum distance between the caster and the soul
    private constant real CLOCKWISE_CHANCE = .5 //1. is always rotate clockwise, 0. is always counter clockwise, .3 is 30% clockwise and 70% counter clockwise.
    
    //Color Configurables.
    private constant integer COLOR_OPAQUE = 224 //The opaque level of the souls color.
    //Color that the soul will spawn with. (All values are between 0 and 255)
    private constant integer COLOR_SPAWN_RED = 0 
    private constant integer COLOR_SPAWN_GREEN = 0
    private constant integer COLOR_SPAWN_BLUE = 255
    //Color that the soul will try to maintain when it has no victim. (All values are between 0 and 255)
    private constant integer COLOR_NORMAL_RED = 0 
    private constant integer COLOR_NORMAL_GREEN = 255
    private constant integer COLOR_NORMAL_BLUE = 0
    //Color that the soul will try to maintain when it has a victim. (All values are between 0 and 255)
    private constant integer COLOR_ANGRY_RED = 255
    private constant integer COLOR_ANGRY_GREEN = 0
    private constant integer COLOR_ANGRY_BLUE = 0
    
    //Other Victim Configurables.
    private constant integer VICTIM_RANGE = 222 //Range in which the soul will acquire a victim.
    private constant integer VICTIM_DAMAGE_RANGE = 90 //Range in which the soul will need to be in order to suicide and damage the victim.
    private constant integer VICTIM_FOLLOW_RANGE = 1200 //Range from which the soul will stop chasing the victim and follow the normal path again. (Between the Caster and the Victim, so not the soul).
    
    //Death configurables.
    private constant real DEATH_SCALE = .3 //The size that the death animation will be.

    
endglobals

//----------------------------------
//----- Configurable functions -----
//----------------------------------

//This function will determine if the dying unit was a valid one (and that you need to spawn new souls).
//This is an important function if you have other dummy units that die.
private function IsValidUnit takes nothing returns boolean
    return GetUnitTypeId(GetDyingUnit()) != SOUL_ID and IsUnitType(GetDyingUnit(), UNIT_TYPE_STRUCTURE) == false
endfunction
    
//The damage function (because I know you want to).
private constant function CalculateDamage takes integer level returns real
    return  (level * DAMAGE_PERLEVEL) + DAMAGE_OFFSET
endfunction

//The amount of souls that spawns function (because I know you want to).
private function SpawnAmount takes unit dyingUnit, integer lvlSpell returns integer
    local real target = SPAWN_AMOUNT_OFFSET + GetUnitLevel(dyingUnit) * SPAWN_AMOUNT_PERUNITLEVEL + lvlSpell * SPAWN_AMOUNT_PERABILLEVEL
    local integer effective = R2I(target)
    if target - effective < GetRandomReal(0.,1.) then //for example if target is 5.3 then you will have 5 spawns + 30% chance to have 1 more.
        return effective + 1
    endif
    return effective
endfunction

//The desired Radius. (Used for the desired distance between the soul and the hero.)
private function GetDesiredRadius takes nothing returns real
    return GetRandomReal(RADIUS_MIN,RADIUS_MAX)
endfunction

//The position update you do compared to your desired position. (This will determine the amount of space between 
private constant function CatchupSpeed takes real current_distance, real desired_distance returns real
        return (1.-SPEED_CATCHUP_SPEED_PERC) * current_distance + SPEED_CATCHUP_SPEED_PERC * desired_distance 
endfunction

//The radius update you do compared to your desired radius.
private constant function CatchupRadius takes real current_distance, real desired_distance returns real
        return (1.-SPEED_CATCHUP_RADIUS_PERC) * current_distance + SPEED_CATCHUP_RADIUS_PERC * desired_distance 
endfunction

//The color update you do compared to your desired color.
private constant function CatchupColor takes real current_distance, real desired_distance returns real
        return (1.-SPEED_CATCHUP_COLOR_PERC) * current_distance + SPEED_CATCHUP_COLOR_PERC * desired_distance 
endfunction

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!! Don't change anything past here unless if you know what you are doing !!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
private keyword Soul
globals
    //---------------------------
    //----- Other variables -----
    //---------------------------
    private trigger gg_trg_maintimer = CreateTrigger() //Main trigger.
    private trigger gg_trg_dyingunit = CreateTrigger() //Dying unit trigger.
    private Soul array souls //Contains the first soul of the chain.
    private integer maxsouls = -1 //Max amount of souls.
    private timer main_timer = CreateTimer()
    
    //-----------------
    //----- Cache -----
    //-----------------
    //These variables are kept to (hopefully) speed up the spell.
    private boolexpr cache_filteralive
    private group cache_group = CreateGroup()
    private player cache_player
    
endglobals

//Filter functions.
private function IsFilterAlive takes nothing returns boolean
    return IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false
endfunction



private struct Soul
    //All souls have
    unit u //The soul itself.
    unit caster //The one it's circling around.
    Soul nxt //Next soul in the chain.
    Soul pre //Previous soul in the chain.
    real sign // Will determine if something is clockwise or not.
    real radius //Distance between this soul and the caster.
    real speed //Speed of the unit
    real red //Color of the soul (red)
    real green // ... (green)
    real blue // ... (blue)
    real illness //Amount of time that this wisp will not try get a target.
    
    //Only the first souls in the chain need
    unit victim //Sees if the soul aquired a victim to suicide on.
    real desRadius //Desired radius.
    
    //All souls except the ones that are first in their chain need
    real dist //Distance between this soul and the previous soul.
    
    //Only the last souls in the chain need
    integer spawnAmount //Amount of souls still to spawn behind you.
    real spawnTime //Amount of time for the next soul to spawn.
    real xCreate //Where this unit is created (and the next one will be created).
    real yCreate //Where this unit is created (and the next one will be created).

    static method create takes unit c, Soul p, real s_x, real s_y, integer extraAmount returns Soul
        local real c_x = GetUnitX(c)
        local real c_y = GetUnitY(c)
        
        local real dx = s_x - c_x
        local real dy = s_y - c_y
        
        local real angle
        
        local real radius = SquareRoot(dx * dx + dy * dy)
        
        local Soul d = Soul.allocate()
        set d.u = CreateUnit(GetOwningPlayer(c), SOUL_ID, s_x, s_y, 270.)
        call SetUnitColor(d.u, PLAYER_COLOR_LIGHT_GRAY)
        set d.caster = c
        
        if radius < 10. then
            set radius = 10.
        endif
        
        set d.radius = radius
        set d.speed = 0
        
        set d.red = COLOR_SPAWN_RED
        set d.green = COLOR_SPAWN_GREEN
        set d.blue = COLOR_SPAWN_BLUE
        call SetUnitVertexColor(d.u, R2I(d.red), R2I(d.green), R2I(d.blue), COLOR_OPAQUE)
        
        set d.victim = null
        set d.xCreate = s_x
        set d.yCreate = s_y
 
        set d.illness = SPAWN_ILLNESS
        
        set d.nxt = 0 //This will be set by the next one.
        set d.pre = p
        
        if p == 0 then //Checking if we have a previous or not.
            set d.dist = 0.
            set d.desRadius = GetDesiredRadius()
            
            //Setting spawn amount and time.
            set d.spawnAmount = extraAmount - 1
            set d.spawnTime = SPAWN_INTERVAL
            if (GetRandomReal(0.,1.) <= CLOCKWISE_CHANCE) then
                set d.sign = -1
            else
                set d.sign = 1
            endif
            set maxsouls = maxsouls + 1 //Updating the soul max.
            set souls[maxsouls] = d
        else
            set p.nxt = d
            set d.sign = p.sign
            
            
            set d.desRadius = p.desRadius
            
            //We steal data from previous Soul.
            set d.spawnAmount = d.spawnAmount + p.spawnAmount
            set p.spawnAmount = 0
            set d.spawnTime = p.spawnTime
            set p.spawnTime = 0
            
            //Get first distance to previous
            //set d.dist = d.distanceToPrevious()
            set angle = d.sign * (Atan2(GetUnitY(p.u) - c_y, GetUnitX(p.u) - c_x) - Atan2(s_y - c_y, s_x - c_x))
            if angle < 0 then
                set angle = angle + 2 * bj_PI
            endif
            set d.dist = angle * radius
        endif
        
        return d
    endmethod
    
endstruct



private function MainTick takes nothing returns nothing
    // This function is called (1 / MAIN_TIMER) times per second.
    // With the default values this means about 30 times per second.
    // This function is made to be very efficient (at the cost of readability/modifiability/reusability)
    // Also if you don't like maths then this function might not be your favorit one.
    
    local integer s = 0
    local Soul mainSoul //First of the chain.
    local Soul curSoul //Current Soul in the chain.
    
    local real cx
    local real cy 

    local real sx
    local real sy

    local real vx
    local real vy

    local real dx
    local real dy
    local real radius
    local real d

    local real angle
    local real angle_pre
    local real angle_diff

    loop //MainLoop over all leading souls.
        exitwhen s > maxsouls
        set mainSoul = souls<s>
        
        if IsUnitType(mainSoul.caster, UNIT_TYPE_DEAD) == true then
            loop //Kill the chain(all except the last)
                call KillUnit(mainSoul.u) //Kill all soul units in the chain. (inclusive of the first and last)
                exitwhen mainSoul.nxt == 0
                set mainSoul = mainSoul.nxt
                call mainSoul.pre.destroy() //You can&#039;t just destroy a soul and then ask the next, hence this weird way of doing it.
            endloop
            
            call mainSoul.destroy() //Destroy the last
            set souls<s> = souls[maxsouls] //Update the soul array.
            set maxsouls = maxsouls - 1
        elseif IsUnitType(mainSoul.u, UNIT_TYPE_DEAD) == true then
            //Need to remove but we need to get a new leader for the chain if needed.
            
            if mainSoul.nxt == 0 then
                set souls<s> = souls[maxsouls]
                set maxsouls = maxsouls - 1
            else
                //We get all the data from the leader to the new soul.
                set curSoul = mainSoul.nxt
                //Keep in mind that a lot of these values are calculated and set only by the first in the soulchain.
                //So we gain efficiency by not having to assign these values every main timer tick.
                set curSoul.pre = 0
                set curSoul.victim = mainSoul.victim
                set curSoul.desRadius = mainSoul.desRadius
                set curSoul.sign = curSoul.sign
                set souls<s> = curSoul //Bow down to the new leader.
            endif
            call mainSoul.destroy()
        else //A good soul.

            //Getting Coordinates and distances.
            //Caster coordinates.
            set cx = GetUnitX(mainSoul.caster)
            set cy = GetUnitY(mainSoul.caster)
            //Soul coordinates.
            set sx = GetUnitX(mainSoul.u)
            set sy = GetUnitY(mainSoul.u)
            //Distance between caster and soul.
            set dx = cx - sx
            set dy = cy - sy
            set radius = SquareRoot(dx * dx + dy * dy) //Current distance between the caster and the soul.
            if radius &lt; 10. then
                set radius = 10. //To avoid divisions by 0. Why 10? This way we hope to avoid to come in this situation again too soon.
            endif

            //Updating the victim.
            //Clearing old target (if needed)
            if mainSoul.victim != null and IsUnitType(mainSoul.victim, UNIT_TYPE_DEAD) == true then
                set mainSoul.victim = null
            endif
            //Finding new target (if needed)
            if mainSoul.victim == null and mainSoul.illness &lt;= 0 then //Can&#039;t use elseif due to actions in the previous if
                set cache_player = GetOwningPlayer(mainSoul.caster)
                call GroupClear(cache_group)
                call GroupEnumUnitsInRange(cache_group, sx, sy, VICTIM_RANGE , cache_filteralive)
                loop
                    set mainSoul.victim = FirstOfGroup(cache_group)
                    exitwhen mainSoul.victim == null or IsUnitEnemy(mainSoul.victim, cache_player)
                    call GroupRemoveUnit(cache_group, mainSoul.victim)
                endloop
                //We don&#039; use the group elsewhere so we clean it up immediatly.
            elseif mainSoul.illness &gt; 0 then
                set mainSoul.illness = mainSoul.illness - MAIN_INTERVAL //This can cause illness to drop under 0 but we don&#039;t really care.
            endif
            //Final update
            if mainSoul.victim == null then
                set mainSoul.speed = CatchupSpeed(mainSoul.speed, SPEED_ROTATE_NORMAL)
                set mainSoul.radius = CatchupRadius(radius, mainSoul.desRadius) //New Radius
                set angle = Atan2(sy - cy, sx - cx) + mainSoul.sign * mainSoul.speed / mainSoul.radius //New angle
                if mainSoul.illness &lt;= 0. then
                    set mainSoul.red = CatchupColor(mainSoul.red, COLOR_NORMAL_RED)
                    set mainSoul.blue = CatchupColor(mainSoul.blue, COLOR_NORMAL_BLUE)
                    set mainSoul.green = CatchupColor(mainSoul.green, COLOR_NORMAL_GREEN)
                    call SetUnitVertexColor(mainSoul.u, R2I(mainSoul.red), R2I(mainSoul.green), R2I(mainSoul.blue), COLOR_OPAQUE)
                endif
            else
                set vx = GetUnitX(mainSoul.victim)
                set vy = GetUnitY(mainSoul.victim)
            
                set dx = vx - cx
                set dy = vy - cy
                set d = SquareRoot(dx * dx + dy * dy) //Distance between victim and caster
                set mainSoul.radius = CatchupRadius(mainSoul.radius, d)
                if d &gt; VICTIM_FOLLOW_RANGE then
                    //out of range, abort
                    set angle = Atan2(sy - cy, sx - cx) + mainSoul.sign * mainSoul.speed / mainSoul.radius
                    set mainSoul.speed = CatchupSpeed(mainSoul.speed, SPEED_ROTATE_NORMAL)
                    set mainSoul.victim = null
                else
                    //set angle = Atan2(vy - cy, vx - cx) + angle
                    //if angle &lt; 0 then
                    //    set angle = angle + 2 * bj_PI
                    //endif
                    set mainSoul.speed = CatchupSpeed(mainSoul.speed, SPEED_ROTATE_VICTIM)
                    set angle = Atan2(sy - cy, sx - cx) + mainSoul.sign * mainSoul.speed / mainSoul.radius
                    set dx = vx - sx
                    set dy = vy - sy
                    set d = SquareRoot(dx * dx + dy * dy) //Distance between victim and the soul
                    if d &lt; VICTIM_DAMAGE_RANGE then
                        call UnitDamageTarget(mainSoul.caster, mainSoul.victim, CalculateDamage(GetUnitAbilityLevel(mainSoul.caster, SPELL_ID)), false, false, DAMAGE_ATTACKTYPE, DAMAGE_DAMAGETYPE, DAMAGE_WEAPONTYPE) 
                        call SetUnitScale(mainSoul.u,DEATH_SCALE,DEATH_SCALE,DEATH_SCALE)
                        call KillUnit(mainSoul.u)
                    endif
                endif
                set mainSoul.red = CatchupColor(mainSoul.red, COLOR_ANGRY_RED)
                set mainSoul.blue = CatchupColor(mainSoul.blue, COLOR_ANGRY_BLUE)
                set mainSoul.green = CatchupColor(mainSoul.green, COLOR_ANGRY_GREEN)
                call SetUnitVertexColor(mainSoul.u, R2I(mainSoul.red), R2I(mainSoul.green), R2I(mainSoul.blue), COLOR_OPAQUE)
            endif
            call SetUnitX(mainSoul.u, cx + mainSoul.radius * Cos(angle)) //New x coord
            call SetUnitY(mainSoul.u, cy + mainSoul.radius * Sin(angle)) //New y coord
        
            
            set curSoul = mainSoul
            set angle_pre = angle
            //update rest of chain too
            loop
                exitwhen curSoul.nxt == 0 //Note that you aren&#039;t allowed to do circular soul chains.
                set curSoul = curSoul.nxt
                if curSoul.illness &gt; 0 then
                    set curSoul.illness = curSoul.illness - MAIN_INTERVAL
                else
                    set curSoul.red = CatchupColor(curSoul.red, curSoul.pre.red)
                    set curSoul.green = CatchupColor(curSoul.green, curSoul.pre.green)
                    set curSoul.blue = CatchupColor(curSoul.blue, curSoul.pre.blue)
                    call SetUnitVertexColor(curSoul.u, R2I(curSoul.red), R2I(curSoul.green), R2I(curSoul.blue), COLOR_OPAQUE)
                endif

                set sx = GetUnitX(curSoul.u)
                set sy = GetUnitY(curSoul.u)

                set curSoul.radius = CatchupRadius(curSoul.radius, curSoul.pre.radius)
                set curSoul.speed = CatchupSpeed(curSoul.speed, curSoul.pre.speed)
                set angle = Atan2(sy - cy, sx - cx) + mainSoul.sign * (curSoul.speed + curSoul.dist - CatchupSpeed(curSoul.dist, DES_DIST)) / curSoul.radius //New angle
                
                call SetUnitX(curSoul.u, cx + curSoul.radius * Cos(angle)) //New x coord
                call SetUnitY(curSoul.u, cy + curSoul.radius * Sin(angle)) //New y coord
                
                set angle_diff = curSoul.sign * (angle_pre - angle)
                if angle_diff &lt; 0 then
                    set angle_diff = angle_diff + 2 * bj_PI
                endif
                set curSoul.dist = angle_diff * curSoul.radius
                set angle_pre = angle
            endloop
            
            //Extra commands for the last soul.
            if curSoul.spawnAmount &gt; 0 then
                set curSoul.spawnTime = curSoul.spawnTime - MAIN_INTERVAL
                if curSoul.spawnTime &lt;= 0 then
                    set curSoul.spawnAmount = curSoul.spawnAmount - 1
                    set curSoul.spawnTime = curSoul.spawnTime + SPAWN_INTERVAL
                    call Soul.create(mainSoul.caster, curSoul, mainSoul.xCreate, mainSoul.yCreate, 0)
                endif
            endif
            
            //To the next soul chain.
            set s = s + 1
        endif //end of good soul.
    endloop
    if maxsouls &lt; 0 then
        call PauseTimer(main_timer)
    endif
endfunction

private function DyingUnit takes nothing returns nothing
    //Input
    local unit dying = GetDyingUnit()
    //Other locals
    local real x = GetUnitX(dying)
    local real y = GetUnitY(dying)
    local group g = CreateGroup()
    local unit u
    local integer lvl
    
    call GroupEnumUnitsInRange(g, x, y, SPAWN_RADIUS, cache_filteralive)
    loop
        set u = FirstOfGroup(g)
        exitwhen u == null
        set lvl = GetUnitAbilityLevel(u, SPELL_ID)
        if lvl &gt; 0 then
            if maxsouls &lt; 0 then //No souls were currently active.
                call TimerStart(main_timer, MAIN_INTERVAL, true, function MainTick) //Lights, camera, action!
            endif
            call Soul.create(u, 0, x, y, SpawnAmount(dying, lvl)) 
        endif
        call GroupRemoveUnit(g, u)
    endloop
    
    //cleanup
    call DestroyGroup(g)
    set g = null
    //u is already null.
endfunction

private function Init takes nothing returns nothing
    //Cache inits
    set cache_filteralive = Condition(function IsFilterAlive)
    
    //Triggers
    //Trigger: A unit dies.
    call TriggerRegisterAnyUnitEventBJ(gg_trg_dyingunit, EVENT_PLAYER_UNIT_DEATH)
    call TriggerAddCondition(gg_trg_dyingunit, Condition(function IsValidUnit))
    call TriggerAddAction(gg_trg_dyingunit, function DyingUnit)
endfunction

endscope</s></s></s></s>



Confused or still not convinced:
Try the demomap ;).


Comments
- This is my first vJass so any comments(bugs, improvements, remarks, ideas, lovemessages, hatemessages ...) are greatly appreciated :thup:.
- If this spell rapes low end pcs then please tell me so so I can further optimize the code.
- In the Soul Struct, There is not really a need to store the previous soul in a chain. This is in due to early designs that allowed all non-leading souls to acquire targets too and leave their chains. I dropped this idea to get better performance.
- If there is a lot of interest, I might change the spell such that you can activate and deactivate and the soul spawning takes a bit of mana everytime.
- Enjoy the skill as much I enjoyed making it ;).


Changelog
13 May 2009: Released v1
14 May 2009: Made functions private/Timer no longer runs idle/Improved a boolexpr (Thanks to Vestras/kenny!/kenny!)
15 May 2009: Made death animation scale configurable/Made Soul struct private/Checks to see if a unit is dead now use InUnitType instead of GetUnitState/Rewrote and simplified init/removed onDestroy. (Thanks to BlackRose/Vestras/Romek/kenny!+Romek/Romek)
 

Attachments

  • [Spell]No_exit_Soul_Slave.w3x
    48.8 KB · Views: 247

Builder Bob

Live free or don't
Reaction score
249
Now that's a fun spell! I can see you've spent a lot of time putting all sorts of details into it. I especially like how the souls change colors and modify their speed depending on what they're doing. All the subtle details really gave the impression these souls didn't enjoy being your slaves and would rather just crush themselves into the nearest possible release.

Another thing I enjoy about this spell is how simple it is for the player. It doesn't have a 10 mile long tooltip describing all the cool stuff it can do.

The spell had a good feel to it. There were no apparent bugs after testing it for a good while. Top quality! Keep it up!
 

Kenny

Back for now.
Reaction score
202
Haven't tested it yet, but from what i can see if the pictures and in your script, it looks impressive. As Builder_Bob said, i think it is the attention to detail that will set this spell apart from the rest :D.

Apart from making your functions and stuff private, like Vestras said. These:

JASS:
local boolexpr truefilter = Condition(function True)
local boolexpr dyingunit = Condition(function IsValidUnit)


Can be made into global boolexpr, therefore there will be no need to destroy them or whatever.

Also, I may have missed it, but it looks like your running a high frequency timer even when there are no spell instances to run. Maybe try to fix this? No one likes a timer thats running but doing nothing.

+rep for the sweet spell! Nice work, keep it up.
 

No_exit

Regular User (What is Custom User Title?)
Reaction score
40
A shitload of your functions should be private. Your struct too. Why have a public create method?

- Made a shitload of functions private. When I first tried to make the constant functions to be private, I had trouble since I putted "constant private" instead of "private constant" and forgot about it later, lol.
- The struct has not been made private because the global Soul array souls seems to protest when I make the struct private.
- The create method has to be public since else you can't create a new soul (ok you probably can do Soul.allocate() and then change it's members and drop the entire create method but I doubt that it is a nice solution, except probably for efficiency.).


Haven't tested it yet, but from what i can see if the pictures and in your script, it looks impressive. As Builder_Bob said, i think it is the attention to detail that will set this spell apart from the rest :D.

Apart from making your functions and stuff private, like Vestras said. These:

JASS:

local boolexpr truefilter = Condition(function True)
local boolexpr dyingunit = Condition(function IsValidUnit)


Can be made into global boolexpr, therefore there will be no need to destroy them or whatever.

Also, I may have missed it, but it looks like your running a high frequency timer even when there are no spell instances to run. Maybe try to fix this? No one likes a timer thats running but doing nothing.

+rep for the sweet spell! Nice work, keep it up.

- I didn't make the boolexpr global since they only run once (at map initialization) and there is no need to keep them in a global variable after. I did however notice another boolexpr problem, so sort of thanks for that :D.
- Fixed the timer but it seems that after doing a PauseTimer and then using ResumeTimer, the timer seems to have forgotten that it was a periodic one so now I use a Timerstart instead, I'm not sure if this is the nicest solution tho.

Thanks for the feedback and also the one from builder bob which I didn't quote but was a pleasure to read :).
 

BlackRose

Forum User
Reaction score
239
Damn, thats sexy. I like the way it spins and then it spins faster to catch up with the others :)

But maybe the effect on suicide should be more prominent? I changed the model from Wisp to Necronmancer missile..... the thing that needs most improvement is the dummy model IMO.

But your spell?

OFFTOPIC: Thanks for "Random Rep lol".
 

Vestras

Retired
Reaction score
249
> - The struct has not been made private because the global Soul array souls seems to protest when I make the struct private.

Then go something like...:

JASS:
private keyword Soul // Your struct name 

// Globals block with your struct array
globals
      private Soul array souls
      // ...
endglobals

// Your struct
private struct Soul
      // ...
endstruct
 

Romek

Super Moderator
Reaction score
964

IsUnitType has to be compared to true or false, or it may bug.

JASS:
private function IsFilterAlive takes nothing returns boolean
    return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) &gt; 0
endfunction

return IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == true

> struct Soul
private struct Soul

JASS:
method onDestroy takes nothing returns nothing
        if GetUnitState(.u, UNIT_STATE_LIFE) &gt; 0 then
            call KillUnit(.u)
        endif
    endmethod

IsUnitType(.u, UNIT_TYPE_DEAD) == true

Or, you could try removing the if altogether, and have just KillUnit(). I don't think killing a unit twice will hurt anyone. "Except the unit that was killed twice" - Uber

All of the local boolexpr things in the Init function are useless. Saving functions calls at Map Init is pretty useless.
 

No_exit

Regular User (What is Custom User Title?)
Reaction score
40
Repsonse to Vestras: Made the soul struct private, thanks for the help.

Repsonse to BlackRose:- I made the scale of the death animation bigger and it's configurable now so it's not too much and not too little. (This will only happen if it was a suicide, not when the caster dies.)
-Offtopic answer: I knew you'd earn it in the end :p.

Repsonse to Romek:
- changed all GetUnitstate(x,UNIT_STATE_LIFE) in GetUnitType(x, UNIT_TYPE_DEAD). I assume that these give different results only if a faulty script increases the life state of a unit even tho it was already dead.
- I removed the onDestroy method since I already kill the unit in the timer itself.
- The init has been rewritten. I'm used to loop my TriggerRegister function only over the first players because I know the skill will only be used by them. In this spell however there is only a death event trigger so there will normally be no need for a restriction to the players so I removed it.

Thanks for all the feedback once again ;).
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1
  • The Helper The Helper:
    New recipe is another summer dessert Berry and Peach Cheesecake - https://www.thehelper.net/threads/recipe-berry-and-peach-cheesecake.194169/

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top