Spell Arcane Alchemy

Hatebreeder

So many apples
Reaction score
381
Hey there,
I have been playing around with Unit Indexing lately, speaking of PUI (Perfect Unit Indexing) and came up with this little Spell.

Describtion:
Whenever one casts a Spell, one extracts the very essence of the Spell and creates an "Arcane Component". Each component can be fused together to form a deadly substance, though, there is a chance of 50% that it "reduces", reducing the total damage of the substance, but also has a 50% chance of "oxidizing", increasing the total damage of the substance sufficiently.
Requirements:

- New Gen Editor
- TT (Timer Ticker)
-PUI (Perfect Unit Indexing)
-SimError (Vex's Error Snipplet )
(all these (excluding New Gen) can be found in the demo map)

Screenshots:
AlchemyOrbOne.jpg

AlchemyOrbTwo.jpg

AlchemyOrbThree.jpg

AlchemyOrbFour.jpg

AlchemyOrbFive.jpg

AlchemyOrbBlast.jpg
Code:
JASS:
scope ArcaneAlchemy initializer Init
//******************************************************************
//*                    Arcane Alchemy                              *
//*                 by Hatebreeder                                 *
//******************************************************************

globals
    private constant integer ID     = 'A000' //Raw ID of the Passive Skill
    private constant integer SUB_ID = 'A001' //Raw ID of the Target Skill
    private constant integer DUMMY  = 'u001' //Raw ID of the Dummy Unit
    private constant integer EDGES  = 5      //Max. allowed Components
    
    private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC    //Attacktype
    private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC    //Damagetype
    private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS //Weapontype
    
    private constant real BAIL      = 120.   //Distance from Dummy to Caster
    private constant real ANGLES    = 360.   //360 Degrees (CAN be changed, but leave it as it is for a good result)
    private constant string SFX     = "Abilities\\Spells\\Human\\Polymorph\\PolyMorphDoneGround.mdl" //Impact Effect
    private constant string ERRORMSG= "Unable to cast. No Compounds found." //Error message
    
    private Data array UserData              //Global struct array 
endglobals
struct Data
    private method Rotation_Speed takes nothing returns real  //Dummy Rotation Speed
        return 5.
    endmethod
        
    private method Chance takes nothing returns integer       //Chance of Reduction // Counter Part for Oxidation
        return 50
    endmethod
    
    private method Reduce takes nothing returns real          //Reduction
        return .25 - (.05 * .Level)
    endmethod
    
    private method Empower takes nothing returns real         //Oxidation
        return .15 + (.05 * .Level)
    endmethod
    
    private method Damage takes nothing returns real          //Damage per Compound
        return (30. + (5. * .Level)) * .Max
    endmethod
        
    private method Move_Speed takes nothing returns real      //Move Speed of Missle
        return 17.
    endmethod
    
    private method Scale takes nothing returns real           //Scale of Missle
        return (.85 * .Max)
    endmethod
//===========================================================================
        unit Caster
        unit Target
        unit Missle
        unit array Dummy [EDGES]
        real array Angles[EDGES]
        real Distance
        integer Level
        integer Max
        boolean IsCast
        boolean IsActive
        
            static method create takes unit c returns Data
                local integer Pui = GetUnitIndex(c)
                local Data this = Data.allocate()
                local trigger OnCast  = CreateTrigger()
                local trigger SubCast = CreateTrigger()
                local trigger CheckCast = CreateTrigger()
                
                set .Caster   = c
                set .Max      = 0
                set .IsCast   = false
                set .IsActive = false

                call TriggerRegisterUnitEvent(OnCast,.Caster,EVENT_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(OnCast,Condition(function Data.OnCast_Conditions))
                call TriggerAddAction(OnCast,function Data.OnCast_Actions)
                
                call TriggerRegisterUnitEvent(CheckCast,.Caster,EVENT_UNIT_SPELL_CAST)
                call TriggerAddCondition(CheckCast,Condition(function Data.SubCast_Conditions))
                call TriggerAddAction(CheckCast,function Data.CheckCast_Actions)
                
                call TriggerRegisterUnitEvent(SubCast,.Caster,EVENT_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(SubCast,Condition(function Data.SubCast_Conditions))
                call TriggerAddAction(SubCast,function Data.SubCast_Actions) 
                
                set OnCast  = null
                set CheckCast = null
                set SubCast = null
                
                set UserData[Pui] = this
                
                    return this
            endmethod
            
        static method OnCast_Conditions takes nothing returns boolean
            return GetSpellAbilityId() != SUB_ID
        endmethod
        
        static method OnCast_Timer takes nothing returns boolean
            local Data this = TT_GetData()
            local real CasterX = GetUnitX(.Caster)
            local real CasterY = GetUnitY(.Caster)
            local real X
            local real Y
            local integer Count = 0
            
            if .Distance <= BAIL then
                set .Distance = .Distance + .Rotation_Speed()
            endif

            if IsUnitType(.Caster,UNIT_TYPE_DEAD) == true then
                loop
                    exitwhen Count >= .Max
                        set Count = Count + 1
                            call KillUnit(.Dummy[Count])
                endloop
                    set .Max = 0
                    set .IsActive = false
                        return true
            else
                loop
                    exitwhen Count >= .Max
                        set Count = Count + 1
                        set .Angles[Count] = .Angles[Count] + .Rotation_Speed() * bj_DEGTORAD
                        set X              = CasterX + .Distance * Cos(.Angles[Count])
                        set Y              = CasterY + .Distance * Sin(.Angles[Count])
                        call SetUnitX(.Dummy[Count],X)
                        call SetUnitY(.Dummy[Count],Y)
                        call SetUnitFacing(.Dummy[Count],.Angles[Count] * bj_RADTODEG)
                endloop
                
            if .IsCast == true then
                return true
            endif
        endif
        return false
        endmethod

        static method OnCast_Actions takes nothing returns nothing
            local Data this = UserData[GetUnitIndex(GetTriggerUnit())]
            local real CasterX
            local real CasterY
            local real X
            local real Y
            local integer Count = 0
            
                if .IsCast == false and .Max < EDGES then
                    set .Max = .Max + 1
                    set .Distance = 0
                    set .Level = GetUnitAbilityLevel(.Caster,ID)
                    set CasterX = GetUnitX(.Caster)
                    set CasterY = GetUnitY(.Caster)
                        
                        loop
                            exitwhen Count >= .Max
                                set Count = Count + 1
                                set .Angles[Count] = Count * ANGLES/.Max * bj_DEGTORAD
                                    if Count == .Max then
                                        set X              = CasterX + .Distance * Cos(.Angles[Count])
                                        set Y              = CasterY + .Distance * Sin(.Angles[Count])
                                        set .Dummy[Count]  = CreateUnit(GetOwningPlayer(.Caster),DUMMY,X,Y,.Angles[Count] * bj_RADTODEG)
                                    endif
                        endloop
                
                    if .IsActive == false then
                        call TT_Start(function Data.OnCast_Timer,this)
                        set .IsActive = true
                    endif
                endif
        endmethod
        
        static method SubCast_Conditions takes nothing returns boolean
            return GetSpellAbilityId() == SUB_ID
        endmethod
        
        static method SubCast_Timer takes nothing returns boolean
            local Data this = TT_GetData()
            local real DX = GetUnitX(.Missle)
            local real DY = GetUnitY(.Missle)
            local real TX = GetUnitX(.Target)
            local real TY = GetUnitY(.Target)
            local real Angle = Atan2(TY - DY,TX - DX)
            local real X  = DX + .Move_Speed() * Cos(Angle)
            local real Y  = DY + .Move_Speed() * Sin(Angle)
            local real DistX = TX - DX
            local real DistY = TY - DY
            local real Dist = SquareRoot(DistX * DistX + DistY * DistY)
            
            call SetUnitX(.Missle,X)
            call SetUnitY(.Missle,Y)
            call SetUnitFacing(.Missle,Angle * bj_RADTODEG)
            
                if Dist <= .Move_Speed() then
                    call KillUnit(.Missle)
                    set .IsActive = false
                    set .IsCast = false
                    call DestroyEffect(AddSpecialEffect(SFX,X,Y))
                        if GetRandomInt(1,100) <= .Chance() then
                            call UnitDamageTarget(.Caster,.Target,.Damage() - ( .Damage() * .Reduce() ),true,true,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
                        else
                            call UnitDamageTarget(.Caster,.Target,.Damage() + ( .Damage() * .Empower() ),true,true,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
                        endif
                    set .Max = 0
                        return true
                endif
            return false
        endmethod
        
        static method SubCast_Actions takes nothing returns nothing
            local Data this = UserData[GetUnitIndex(GetTriggerUnit())]
            local real CasterX = GetUnitX(.Caster)
            local real CasterY = GetUnitY(.Caster)
            local real TargetX
            local real TargetY
            local real X
            local real Y
            local real Angle
            local integer Count = 0
            
                set .IsCast = true
                set .Target = GetSpellTargetUnit()
                set TargetX = GetUnitX(.Target)
                set TargetY = GetUnitY(.Target)
                set Angle   = Atan2(TargetY - CasterY,TargetX - CasterX)
                set X       = CasterX + BAIL * Cos(Angle)
                set Y       = CasterY + BAIL * Sin(Angle)
                    
                    loop
                        exitwhen Count >= .Max
                            set Count = Count + 1
                                call KillUnit(.Dummy[Count])
                    endloop
                
                set .Missle = CreateUnit(GetOwningPlayer(.Caster),DUMMY,X,Y,Angle)
                call SetUnitScale(.Missle,.Scale(),.Scale(),.Scale())
                
                call TT_Start(function Data.SubCast_Timer,this)
        endmethod
        
        static method CheckCast_Actions takes nothing returns nothing
            local Data this = UserData[GetUnitIndex(GetTriggerUnit())]
                if .Max == 0 then
                    call IssueImmediateOrder(.Caster,"stop")
                    call SimError(GetOwningPlayer(.Caster),ERRORMSG)
                endif
        endmethod
endstruct
    
private function Conditions takes nothing returns boolean
local unit Learning = GetLearningUnit()
local integer Pui   = GetUnitIndex(Learning)

    if GetLearnedSkill() == ID and GetUnitAbilityLevel(Learning,ID) == 1 then
        set UserData[Pui] = UserData[Pui].create(Learning)
        call UnitAddAbility(Learning,SUB_ID)
    endif
    if GetLearnedSkill() == ID and GetUnitAbilityLevel(Learning,ID) >=  1 then
        call SetUnitAbilityLevel(Learning,SUB_ID,GetUnitAbilityLevel(Learning,ID))
    endif
    
    set Learning = null
    
    return false
endfunction

//===========================================================================
private function Init takes nothing returns nothing
    local trigger Trig = CreateTrigger(  )
    call Preload(SFX)
    call TriggerRegisterAnyUnitEventBJ( Trig, EVENT_PLAYER_HERO_SKILL )
    call TriggerAddCondition( Trig, Condition( function Conditions ) )
    set Trig = null
endfunction
endscope

As usual, i don't care about credits, but don't claim this as your own.

Any comments?
 

Attachments

  • ArcaneAlchemy.w3x
    66.6 KB · Views: 270

Executor

I see you
Reaction score
57
Hey hm i like the idea but its lacking of the realization.

My critic points:
- New orbs just "plop up" in the orb circle and all the others are replaced in that instant.
- Orb creation bugs after death
- The 'fire' ability lets all orbs hide and lets "plop up" a new greater orb flying to the target
 

Komaqtion

You can change this now in User CP.
Reaction score
469
I'd suggest maybe creating the orbs inside the caster, and smoothly move them out in the circly (Though, might be too much work :S)...

Also, make the other orbs smootlhy correct their position to the other orbs, so when a new orb is created, then kinda "wait" to that the proportions of the circle is still the same.

(I mean that all the orbs have the same distance to the ones around them ;))
 

Hatebreeder

So many apples
Reaction score
381
Hey hm i like the idea but its lacking of the realization.

My critic points:
- New orbs just "plop up" in the orb circle and all the others are replaced in that instant.
- Orb creation bugs after death
- The 'fire' ability lets all orbs hide and lets "plop up" a new greater orb flying to the target

Kay , i will try to make the creation a bit smoother with the idea of Komaqtion.
I tried to "unify" the Orbs, but, it was very inaccurate... so, i just destroyed orbs and created the new orb, scaling it.

I will fix the Bug immediatly.

EDIT:
-Fixed the Bug.
-Made the Orb Creation smooth.

updated Code and Demo Map

In the future i will try to make a kind of formation trigger, but for now, since it works perfectly, i will update to v2 once i got the formation running smooth and accurate.
 
Reaction score
91
Well, the other Blood Mage didn't receive the "orb releasing spell" when I spammed the "orb gathering spell" for him (forgot the names). It was like this - I toyed around with one of the Blood Mage, released everything and when I tried to do the same with the other one nothing happened. I wanted to gather some orbs by casting the spell but.. no effect.
 

Hatebreeder

So many apples
Reaction score
381
Well, the other Blood Mage didn't receive the "orb releasing spell" when I spammed the "orb gathering spell" for him (forgot the names). It was like this - I toyed around with one of the Blood Mage, released everything and when I tried to do the same with the other one nothing happened. I wanted to gather some orbs by casting the spell but.. no effect.

I fixed that as well.
Instead of trying to maintain the Orbs after death, i simply reset the orbs at death.

<<<updated code an demo map.>>>

thanks for the review. + REP
 

Lobster

Old Fogey ofthe site
Reaction score
90
So basically its a system thats a critical hit, a normal hit, or a poor hit? Sounds cool.
Please correct me if im wrong though
 

Hatebreeder

So many apples
Reaction score
381
So basically its a system thats a critical hit, a normal hit, or a poor hit? Sounds cool.
Please correct me if im wrong though

Yeah ^^
Just that it's a Spell, not an attack, which is then again dependant on other spells.
 

BlackRose

Forum User
Reaction score
239
I though the dummy spell was the actual spell and was like.... "WTF, what the hell kind of spells is this?".
Then I actually learn the skill and haha D:

I don't like how the orbs have to 'teleport' back into the middle and then start orbiting instead of just the new orb moving into flow with the rest.

Also, for such a big orb, it looks like it should damage enemy units that are in the way rather than just the big orb aiming at a target.

Maybe you should make EDGES a configurable function.
And what is ".Max" in Scale and Damage?
 

Hatebreeder

So many apples
Reaction score
381
I though the dummy spell was the actual spell and was like.... "WTF, what the hell kind of spells is this?".
Then I actually learn the skill and haha D:

I don't like how the orbs have to 'teleport' back into the middle and then start orbiting instead of just the new orb moving into flow with the rest.

Also, for such a big orb, it looks like it should damage enemy units that are in the way rather than just the big orb aiming at a target.

Maybe you should make EDGES a configurable function.
And what is ".Max" in Scale and Damage?

This teleport thingy was the smoothest i could make it, withoug having to start annother timer and make everything overcomplicated. So it will stay as it is right now.

There is something called "scale" in the methods, adjust that to your needs (since i actually like this sinlge target action, i won't change it.).

EDGES is configurable. it defines the max. amoun of Orbs, and so on. I just used the tern edges, since it marks the amount of Edges, if you see it as a static shape.

".Max", is the Current amount of Orbs, and the limitation at the same time (Limitation is based on EDGES).
 

Azlier

Old World Ghost
Reaction score
461
Simple. You can't. Why are you using an array when a linked list can suffice?
 

Azlier

Old World Ghost
Reaction score
461
You do if you want edges to be a configurable function.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top