Spell Splitting Skull

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
Splitting Skull

I was bored so I made this spell. It is MUI but you cannot cast more than 1 instance per unit because it attaches data to the caster.

The spell requires: xebasic, xefx, xecollider, xepreload, GroupUtils, PruneGroup

Description (Taken from tooltip):
Creates a skull speeding at a target unit splitting into more skulls as it hits the targets, damage is based on how many units it has bounced on and therefore weakened.

2ytti7b.jpg








JASS:
scope SplittingSkull initializer init 
// requires xebasic, xefx, xecollider, xepreload, GroupUtils, PruneGroup
/*=============================================================
*    
*              Splitting Skull by Gwypaas
*                        v 1.2
*
*
*
*   Changelog
*
* v1.0 Initial release
* v1.1 Fixed some small things
* v1.2 Can now use any number of instances on the same unit, also added a model that is displayed 
       when the target is damaged. (Can be turned off). Does not use any unit indexer anymore.
*
*==============================================================*/




private keyword data
globals
    private constant integer SPELL_ID = 'A000' // The ability ID
    private constant string MODEL_PATH = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilMissile.mdl" // Model Path
    private constant string ON_HIT_MODEL_PATH = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl" // The model used when a target is hit by the spell.
    private constant boolean SHOW_ON_HIT_MODEL = true // If the spell should create any on hit effect or not
    private constant real HIT_MODEL_SCALE = .75
    private constant real INITIAL_SPEED = 300 // The speed every new projectile starts with
    private constant real ACCELERATION = 200 // The projectiles accelaration
    private constant real MAX_SPEED = 1500 // The max speed a projectile can reach
    private constant real MAX_BOUNCE_RANGE = 500 // The highest possible range it can fire a new missile to when splitting
    private constant real EXPIRATION_TIME = 100 // How long time it will be able to follow a unit before removed.
    private constant real INITIAL_SCALE = 1.2 // The initial scale of the projectile
    private constant real PROJECTILE_Z = 50 // The fly height the projectile uses.
    private constant real ANGLE_SPEED = 1 // How fast the projectile can turn when homing in radians. WARNING CAN NOT BE 0!
    private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL // The attack type the spell uses.
    private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL// The damage type the spell uses.
    private constant weapontype WEAPON_TYPE = null// The weapon type the spell uses.
            

    // Private globals DO NOT TOUCH
    private data tmpdata
endglobals

// Use this function to specify the damage beased on lvl and the current bounce.
private constant function Damage takes integer lvl, integer bounce returns real
    return (100*lvl)/(bounce*.5)
endfunction

// The amount of bounces based on lvl
private constant function Bounces takes integer lvl returns integer
    return 4
endfunction

// The amount of units the projectile can split into upon a hit based on the current bounce.
private constant function Splits takes integer lvl, integer bouce returns integer
    return 3
endfunction

// The speed that the splits will start with
private constant function SplitSpeed takes integer bounce, real currSpeed returns real
    return 300.
endfunction

// The scale based on the current bounce the splits should have
private constant function NewScale takes integer bounce returns real
    return 1.
endfunction

// The units the splits can target.
private function bounceFilter takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(tmpdata.sowner)) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false
endfunction



 // Function used to get new targets, modified so it wont select units that already has been targetted
private function LowDistFitnessAndRight takes unit u returns real
        local real x
        local real y
        if IsUnitInGroup(u,tmpdata.targUnits.tUnits) == true then
            return 10000000
        endif
        set x = GetUnitX(u)-tmpdata.x
        set y = GetUnitY(u)-tmpdata.y
 
        return -x*x-y*y
endfunction

private struct targettedUnits
    integer projCount
    group tUnits
    
    static method create takes nothing returns thistype
        local thistype tmp = .allocate()
        set tmp.projCount = 0
        set tmp.tUnits = NewGroup()
        return tmp
    endmethod
    
    method onDestroy takes nothing returns nothing
        call ReleaseGroup(.tUnits)
        set .tUnits = null
    endmethod
endstruct


private struct data extends xecollider
    unit sowner
    integer currBounce 
    targettedUnits targUnits
    
    method dataTerminate takes nothing returns nothing
    
        static if (not SHOW_ON_HIT_MODE) then    
            set .scale = HIT_MODEL_SCALE 
            call .flash(ON_HIT_MODEL_PATH)
        endif
        
        set targUnits.projCount = targUnits.projCount - 1
        if .targUnits.projCount <= 0 then
            call .targUnits.destroy()
        endif
        set .sowner = null
        call .terminate()
    endmethod
    
    method loopControl takes nothing returns nothing 
        if GetWidgetLife(.targetUnit) <= .405 then
            call .dataTerminate()
        endif
    endmethod
    
    method onUnitHit takes unit target returns nothing
        local data d
        local integer i = 0
        local unit u

        local integer lvl
        
        
        if target == .targetUnit then
            set lvl = GetUnitAbilityLevel(sowner, SPELL_ID)
            set .currBounce = .currBounce + 1
            
            call UnitDamageTarget(sowner, .targetUnit, Damage(lvl, currBounce) , false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
            
            
            if .currBounce <= Bounces(lvl) then
                set tmpdata = this
                
                call GroupEnumUnitsInRange(ENUM_GROUP, .x,.y, MAX_BOUNCE_RANGE, function bounceFilter)
                call PruneGroup(ENUM_GROUP, LowDistFitnessAndRight, Splits(lvl, .currBounce), NO_FITNESS_LIMIT)

                set u = FirstOfGroup(ENUM_GROUP)
                loop
                    exitwhen u == null
                    if IsUnitInGroup(u, .targUnits.tUnits) == false then
                        
                        set d = data.create(.x,.y,  Atan2((GetUnitY(u)- .y), (GetUnitX(u) - .x)))
                        set d.targUnits = .targUnits
                        set .targUnits.projCount = .targUnits.projCount + 1
                                                
                        call GroupAddUnit(d.targUnits.tUnits, u)
                        set d.sowner = .sowner
                        set d.currBounce = .currBounce
                                
                        set d.fxpath = MODEL_PATH
                        set d.targetUnit = u
                        set d.angleSpeed = ANGLE_SPEED
                        set d.owner = .owner
                        
                        set d.expirationTime = EXPIRATION_TIME
                        
 
                        
                        set d.speed = SplitSpeed(.currBounce, .speed)
                        set d.acceleration = ACCELERATION 
                        set d.maxSpeed = MAX_SPEED
                                
                        set d.z = PROJECTILE_Z
                        set d.scale = NewScale(.currBounce)
                        
                        
                    endif
                    
                    call GroupRemoveUnit(ENUM_GROUP, u)
                    set u = null
                    set u = FirstOfGroup(ENUM_GROUP)
                endloop
                call GroupClear(ENUM_GROUP)
            endif
            
            call .dataTerminate()
        endif
        
        set u = null
    endmethod
endstruct



function Splitting_Skull_Cast takes unit caster, unit target returns nothing
    local real x = GetUnitX(caster)
    local real y = GetUnitY(caster)

    local data d = data.create(x,y,  Atan2((GetUnitY(target)- y), (GetUnitX(target) - x)))
    set d.targUnits = targettedUnits.create()
    set d.targUnits.projCount = 1
    
    
    set d.sowner = caster
    set d.currBounce = 0
    
    set d.fxpath = MODEL_PATH
    set d.targetUnit = target
    set d.angleSpeed = ANGLE_SPEED
    set d.owner = GetOwningPlayer(caster)
    set d.expirationTime = EXPIRATION_TIME
    set d.speed = INITIAL_SPEED
    set d.acceleration = ACCELERATION 
    set d.maxSpeed = MAX_SPEED
    set d.z = PROJECTILE_Z
    set d.scale = INITIAL_SCALE
        
    call GroupAddUnit(d.targUnits.tUnits, target)

    set caster = null
    set target = null
endfunction

private function cond takes nothing returns boolean
    if GetSpellAbilityId() == SPELL_ID then
        call Splitting_Skull_Cast(GetTriggerUnit(), GetSpellTargetUnit())
    endif
    return false
endfunction

private function init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(trig, function cond)
    
    call XE_PreloadAbility(SPELL_ID)
endfunction
endscope


Btw, I LOOOOVE that structs can act like poiners <3 <3 <3 <3
 

Attachments

  • Splitting_Skull.w3x
    64.1 KB · Views: 311

Kenny

Back for now.
Reaction score
202
It is MUI but you cannot cast more than 1 instance per unit because it attaches data to the caster.

Lolwut?

Attacktype and damagetype should probably be configurable also.

JASS:
.
    method loopControl takes nothing returns nothing 
        local integer id
        
        if GetWidgetLife(.targetUnit) &lt;= .405 then
            set id = GetUnitId(.sowner)
            set data.projectileCount[id] = data.projectileCount[id] - 1
            if data.projectileCount[id] &lt;= 0 then
                call GroupClear(.dmgedUnits)
                call ReleaseGroup(.dmgedUnits)
                set .dmgedUnits = null
            endif
            set .sowner = null
            call terminate() //  &lt;--- Here.
        endif
    endmethod


terminate() should be: this.terminate() or .terminate().

Also, I'd rather see the speed of the projectiles in units per second, not units per interval.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
Most spells only attaches stuff to timers meaning you can cast any number of instances with 1 unit.

Attacktype and damagetype should probably be configurable also.
Ill fix it if more people complain since normal spells dont have attack types.


terminate() should be: this.terminate() or .terminate().
Read vJASS manual.

Also, I'd rather see the speed of the projectiles in units per second, not units per interval.
Yeah I move the unit with 300 units per .025 seconds while increasing it with 200 per interval, try using that with a spell that you can see without slowing wc3 down a couple of hundred times.
 

Kenny

Back for now.
Reaction score
202
Most spells only attaches stuff to timers meaning you can cast any number of instances with 1 unit.

I don't care about other spells, I am just pointing out that what you said is not correct.

Ill fix it if more people complain since normal spells dont have attack types.

Almost all custom spells that use UnitDamageTarget() have attack/damage/weapon types configurable. It is just standard procedure these days. It will only take a couple of seconds to fix.

Read vJASS manual.

I am aware of what the vJass manual says, but if that is going to be your reasoning behind leaving the . out of that one line, then you have issues. Every other struct member/method in your script (including the other terminate) uses the . syntax.

I'm just saying it looks crap.

Yeah I move the unit with 300 units per .025 seconds while increasing it with 200 per interval, try using that with a spell that you can see without slowing wc3 down a couple of hundred times.

Although I don't really understand what you are saying.. I'm going to go ahead and presume that that really doesn't address the problem.

What I meant is: change the INITIAL_SPEED and other speed variables in the spell to use units per second instead of units per interval. Again, that is common practice these days.

(Missiles and such in normal wc3 spells use units per second, so should custom spells. Plus it means the speed variables wont be reliant on the periodic interval.)

I suggest you spend all of approximately 5 minutes updating this so that it follows some standards that are expected of spells of this quality.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
I don't care about other spells, I am just pointing out that what you said is not correct.
Well, most maps use Blizzard made spells and you can never configure such stuff in them so trying to balance a spell using hem is utterly useless and should be done using the damage function instead.

Almost all custom spells that use UnitDamageTarget() have attack/damage/weapon types configurable. It is just standard procedure these days. It will only take a couple of seconds to fix.
And the people who ever will bother to change them will understand how because they know JASS well enough.

I am aware of what the vJass manual says, but if that is going to be your reasoning behind leaving the . out of that one line, then you have issues. Every other struct member/method in your script (including the other terminate) uses the . syntax.

I'm just saying it looks crap.
I've left it out on more parts if you care to really look it through anyways. I like not using it, sometimes I feel it is easier to not use it and therefore I dont. Got it?


Although I don't really understand what you are saying.. I'm going to go ahead and presume that that really doesn't address the problem.
Because the problem only exists in your head?



What I meant is: change the INITIAL_SPEED and other speed variables in the spell to use units per second instead of units per interval. Again, that is common practice these days. (Missiles and such in normal wc3 spells use units per second, so should custom spells. Plus it means the speed variables wont be reliant on the periodic interval.)
Before you make such statements maybe go and check out he method I use for moving them, XE. I actually linked it in my post to make it easier for people like you to find it and give correct reviews.


I suggest you spend all of approximately 5 minutes updating this so that it follows some standards that are expected of spells of this quality.
It does already follow the quality standards, you've had nothing negative more than stuff made up by your imagination and some small things in the code that doesn't even matter.
 

Kenny

Back for now.
Reaction score
202
And the people who ever will bother to change them will understand how because they know JASS well enough.

I actually change them all the time, and it is a hastle to look through the scripts of various spells just to do so. I only suggested making them configurable, it isn't a big deal.

I've left it out on more parts if you care to really look it through anyways. I like not using it, sometimes I feel it is easier to not use it and therefore I dont. Got it?

Sorry, I must be blind. I can only see you not using . syntax twice, and in both situations you used . syntax previously when refering to the same variables and methods.

Again I am only suggesting this for the sake of consistency. If you do it to one thing, you should do it to all, this mix and match stuff is pretty ugly. And it wil only take a couple of seconds to make it consistent anyway, a . here and there and you're sweet.

Before you make such statements maybe go and check out he method I use for moving them, XE. I actually linked it in my post to make it easier for people like you to find it and give correct reviews.

I notice that you updated your first post to include higher speeds for the speed variables. I am guessing that the lower speeds I saw were just for testing or something? It was a misread on my part. Seeing such low values for speed lead me to assume you were using units/int not units/sec, as 50 units per second is shit slow. :p

Oh and I just realised that you deleted some of the . infront of the struct members (dmgedunits) when you updated your first post, sneaky. If you refer to my post (post 4) you can see that: set .dmgedunits = null clearly has a . infront of it.

Tryin to be sneaky are we? hahaha. :p
 

Executor

I see you
Reaction score
57
I don't get your concept. You use structs and methods but also functions.. thats no OOP.

JASS:
struct Object
    // Config
    static constant real          DMG_PER_LVL = 40.
    static constant integer       BNC_PER_LVL = 2 
    static constant string        EFFECT      = &quot;someModel.mdl&quot;
    static          integer array someA 
    
    static method InitConfig takes nothing returns nothing
        set .someA[0] =  3
        set .someA[1] =  2
        set .someA[2] = -5
    endmethod
    
    static method Damage takes integer lvl returns real
        return lvl * DMG_PER_LVL
    endmethod
    static method Bounces takes integer lvl returns integer
        return lvl * BNC_PER_LVL
    endmethod
    // End Config
    
    static trigger someT = CreateTrigger()
    
    private real x
    private real y
    
    method move takes real x, real y returns nothing
        set .x = x
        set .y = y
    endmethod
    
    static method someM takes nothing returns boolean
        call KillUnit(GetTriggerUnit())
        return true
    endmethod
    
    static method onInit takes nothing returns nothing
        call .InitConfig()
        call TriggerAddCondition(.someT, Condition( function thistype.someM))
    endmethod
endstruct


You can put everything in a struct, this makes your code cleaner and easier to read.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
@Kenny!

Yes the lower speeds was so I could get a nice screenshot, thought I uploaded the code before I modified it... OMG I didn't do that ;)


@Executor

I know what OOP is and using private constant static methods are just ugly and since a static method == a function, there is no difference at all, static methods cannot use instance specefic members or anything like that, sure you maybe doesn't need to type the struct name before you use a static variable but that's no win when you are forced to type "private static constant method" infront of everything. I've coded an entire spell in a struct before just to try it and sure it works, looks pretty nice but I dont really like the syntax. And it is not easier to read, I think that its easier for users to have some system that's used in all spells and that is global block + constant functions to configure it.

Also, if you dont get my "concept" then you dont know how spells are made when you submit them. Maybe check out the vJass spells section on wc3c before you open your mouth again :)
 

Executor

I see you
Reaction score
57
Also, if you dont get my "concept" then you dont know how spell are made when you submit them. Maybe check out the vJass spells section on wc3c before you open your mouth again :)

"how spelll are made when you submit them" .. sry but I don't get what you're trying to say.

Do you mean how the vJass converts to Jass or what pls?
I know the preprocessing mechanics exactly and I know that methods are just functions my dear.
All I wanted to say is that your code wouldnt look so unclear and ugly, if you would have used a standard.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
"how spelll are made when you submit them" .. sry but I don't get what you're trying to say.
Sorry for not wording it 100% corect. Let me rephrase it: Then you don't know the process behind making a spell ready to be released for the community as a resource that should be used by users who have no idea how the spell works in the inner part and only can configure the globals and tweak the constant functions to fit their needs.



I know the preprocessing mechanics exactly and I know that methods are just functions my dear.
You can't put a == between a method and a function because a method gets an extra integer which is the struct id and they are used in very different ways when you code.

You can put a == between a static method and a function though.

All I wanted to say is that your code wouldnt look so unclear and ugly, if you would have used a standard.
You just said that the entire vJass spells section on Wc3c is "unclear and ugly", I don't think you have a fucking clue about what you are talking about.
 

Romek

Super Moderator
Reaction score
963
> Creates a skull speeding
Overstatement. Those wisps could probably outrun that skull. :p

JASS:
                call GroupClear(.dmgedUnits)
                call ReleaseGroup(.dmgedUnits)
                set dmgedUnits = null

ReleaseGroup clears the group, and since the group is recycled, it doesn't need nulling.
In fact, I personally don't think struct members need nulling, though that's nothing major anyway.

JASS:
private function LowDistFitnessAndRight takes unit u returns real
        local real x = GetUnitX(u)-tmpdata.x
        local real y = GetUnitY(u)-tmpdata.y
        if IsUnitInGroup(u,tmpdata.dmgedUnits) == true then
            return 10000000
        endif
        return -x*x-y*y
endfunction

You could've modified this so that it doesn't aimlessly set those locals if the if statement is true.

JASS:
set u = null
set u = FirstOfGroup(ENUM_GROUP)

Local agents only need to be nulled when they're not being used anymore.
Also, a local unit that's only used in a FoG loop doesn't need to be nulled after the loop, as it's already null by then ([ljass]exitwhen u == null[/ljass]).


Also I agree that your coding/naming conventions are a bit weird. But since everything's private anyway, that doesn't matter.
There's no need to get so angry about something so simple though. =|

Edit: So quit the bickering, everyone.
Also, stop referring everything to wc3c. You're on Thehelper.net now. o_O
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
Overstatement. Those wisps could probably outrun that skull.
Fuck... I hate changing stuff to get a nice view of it and then forget to change it back... fixed like soon.

ReleaseGroup clears the group, and since the group is recycled, it doesn't need nulling.
In fact, I personally don't think struct members need nulling, though that's nothing major anyway.
I null everything that I will not use anymore to prevent any leaks, and it's not like it will cause any lagg or "less" speed anyways.

Also I agree that your coding/naming conventions are a bit weird. But since everything's private anyway, that doesn't matter.
Like? I have 3 "private" variables, some globals with nice descriptions, some constant functiosn with nice descriptions and the rest is just XE variables used by me.
 

Executor

I see you
Reaction score
57
Like? I have 3 "private" variables, some globals with nice descriptions, some constant functiosn with nice descriptions and the rest is just XE variables used by me.


naming convention ex.:

"u1" => "target" or "t"

coding convention ex.:

JASS:
...  
local data d = data.create(x,y,  Atan2((GetUnitY(u1)- y), (GetUnitX(u1) - x)))
    
set d.dmgedUnits = NewGroup()
    
set d.sowner = u
set d.currBounce = 0
...


You break OOP (in some kind) by initializing the struct from "outside" and not using the create method with no cognizable reason. Furthermore by doing so you have +1 function call.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
@Executor
> Naming stuff
I always use thoose variable names so they are not ugly for me :)


Now lets see next problem, if I moved the initializing part into the create method then that would require a ton of function arguments and then I would have some ugly ifs to make it initialize different depending on if it is the "main" projectiel or any of the children. It would also be slower (for all the speed freaks here)
 

Viikuna

No Marlo no game.
Reaction score
265
Couldnt you just use 2d array for that number of skulls thingy?

It would be cool if this was multi instanceable per unit.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top