HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Combining JCast with JESP spells?

10-26-2010, 05:08 AM#1
rogueteddybear
I am pretty new to the whole JASS scene. I was looking around all the systems people have made and found JCast here:

http://www.wc3c.net/showthread.php?t...ight=cast+time

I also found the JESP standard and think I will try to use it. What I like the most about JCast is the casting time/spell cast progression and interruption. I was wondering if anyone has successfully combined JCast with a JESP spell. If they have, any advice or a test map? Perhaps JCast isn't the answer and I should consider something else?

If so, any pointers or help would be appreciated.
A simple link to a tutorial that I may have missed while searching would also be cool.
10-26-2010, 03:37 PM#2
Deaod
JESP is outdated and shouldnt be used anymore.
Your best chance is to ask Profet about a demo map, maybe he can show you some abilities he has made using JCast.
If that doesnt help you, i think youll have to tell me what exactly you want to do. What do you want to use JCast for?
10-27-2010, 01:08 AM#3
rogueteddybear
Hmmm i see. I have a demo map for JCast featuring about 5 spells so I have a good base to start from.

It just seemed to me that JESP was something to use since i saw it referenced a lot in the jass spells comments. Is there a better standard to follow?

My main reasons for using JCast is for the dynamic cast progression/channeling features along with interruption/channel push back. The override functionality is also nice.

I am currently trying to make JCast work with Shrapnade by Rising_Dusk from here:
http://www.wc3c.net/showthread.php?t=99501
10-27-2010, 10:21 AM#4
Deaod
Well, if you use vJass correctly, your code is automatically better than JASS with JESP. So JESP really became obsolete through people using vJass almost exclusively.

Just use vJass. If you want, you can post it here so either me or someone else can comment on it and see what could be improved.
10-28-2010, 04:42 AM#5
rogueteddybear
Alright. I modified the map that Rising_Dusk created to show off the spell I've been working with. I put in JCast and its dependencies. I got rid of "scope" in the spell and replaced it with "library" that has an initializer and some dependencies.

Some things I wasn't sure about:

- I changed the timer in the spell to be created using TimerUtils for the data storage aspect. I can't seem to start a Timer by giving it a non-static method inside a struct, so I made a static function called CallbackStart in the struct that looks in the timer for the data (the spell struct) and calls its non-static Callback. This seems excessive And there is probably a better way to do this?

- I am not sure if I am using vJass or not.

Thanks for the offer to comment on the code.

Here is the spell code:

Collapse JASS:
//***************************************************************************************************************
//*                                                                                                             *
//*                                         S H R A P N A D E                                                   *
//*                                            Actual Code                                                      *
//*                                               v1.07                                                         *
//*                                                                                                             *
//*                                          By: Rising_Dusk                                                    *
//*                                     Modified For Use with JCast                                             *
//***************************************************************************************************************

library Shrapnade initializer InitA uses AbilityCore, TimerUtils
globals
    //*********************************************************
    //* These are the configuration constants for the spell
    //*
    //* AbilityID:           The ability ID that triggers the spell
    //* DummyUnitID:         The dummy unit's ID for who to attach effects to
    //* DamageLocal:         Where the DamageEffect gets played on a unit
    //* DamageEffect:        Which effect plays when you damage an enemy
    //* ExplodeEffect:       Which effect plays when a Shrapnade explodes
    //* DummyUnitEffect:     Which effect is attached to the dummy unit
    //* AttackType:          What attack type the damage dealt is of
    //* DamageType:          What damage type the damage dealt is of
    //* BaseSpeed:           Smaller numbers make it move faster (Normal: 25)
    //* MinSpeed:            Iteration speed minimum for fragmented shards
    //* MaxSpeed:            Iteration speed maximum for fragmented shards
    //* BaseDamage:          The damage the spell deals at level 1
    //* DamagePerLevel:      The damage each extra level makes the spell do
    //* Fragments:           Base number of fragments each spell cast breaks into
    //* FragmentsPerLevel:   How many extra fragments each level adds
    //* MinFragRange:        Minimum range fragments will target from initial blast
    //* MaxFragRange:        Maximum range fragments will target from initial blast
    //* BlastAreaOfEffect:   How big of an area the damage is dealt within
    //* InitialBounceHeight: The max height of the spell-cast bomb
    //* MinBounceHeight:     The minimum max height of the fragmented shards
    //* MaxBounceHeight:     The maximum max height of the fragmented shards
    //* MomentumAngle:       The angle spread in radians that fragments can have
    //*
    
    //private integer AbilityID                      = 'A003'
    private integer DummyUnitID                    = 'h000'
    private string DamageLocal                     = "chest"
    private string DamageEffect                    = "Abilities\\Weapons\\Rifle\\RifleImpact.mdl"
    private string ExplodeEffect                   = "Abilities\\Weapons\\Mortar\\MortarMissile.mdl"
    private string DummyUnitEffect                 = "Abilities\\Spells\\Other\\Transmute\\GoldBottleMissile.mdl"
    private attacktype AttackType                  = ATTACK_TYPE_NORMAL
    private damagetype DamageType                  = DAMAGE_TYPE_UNIVERSAL
    private real BaseSpeed                         = 20.
    private real MinSpeed                          = 15.
    private real MaxSpeed                          = 35.
    private real BaseDamage                        = 80.
    private real DamagePerLevel                    = 20.
    private integer Fragments                      = 1
    private integer FragmentsPerLevel              = 1
    private real MinFragRange                      = 100.
    private real MaxFragRange                      = 400.
    private real BlastAreaOfEffect                 = 200.
    private real InitialBounceHeight               = 250.
    private real MinBounceHeight                   = 100.
    private real MaxBounceHeight                   = 350.
    private real MomentumAngle                     = 1.04719
    
    //*********************************************************
    //* These are static constants used by the spell and shouldn't be changed
    //*
    //* Timer:              The timer that runs all of the effects for the spell
    //* Counter:            The counter for how many spell instances exist
    //* Nades:              The array of all struct instances that exist
    //* TimerInterval:      The interval for the timer that gets run.
    //* Boolexpr:           The boolean expression used for damage checking.
    //*
    private timer Timer                            
    private integer Counter                        = 0
    private integer array Nades
    private real TimerInterval                     = 0.04
    private boolexpr Boolexpr                      = null
    
    //* This is the trigger for reference outside of the scope if desired
    //public trigger Trig                            = CreateTrigger()
endglobals

function InitA takes nothing returns nothing
    call BJDebugMsg("LIBRARY INIT")
    set Timer = NewTimer()
endfunction

//*********************************************************
//* Support struct for the spell, I recommend leaving it alone
private struct nade

    unit Dummy
    unit Caster
    effect DummyEffect
    integer Level
    boolean isPrimary
    real Speed
    real MaxHeight
    real DistanceCheck
    real DistanceTarget
    real Angle
    
    static method create takes unit u, real x, real y, integer level, real angle, real TotalDist, real max, real speed, boolean primary returns nade
        local nade n = nade.allocate()
        set n.Caster = u
        set n.Dummy = CreateUnit(GetOwningPlayer(u), DummyUnitID, x, y, angle)
        set n.DummyEffect = AddSpecialEffectTarget(DummyUnitEffect, n.Dummy, "origin")
        set n.DistanceCheck = TotalDist
        set n.DistanceTarget = TotalDist
        set n.isPrimary = primary
        set n.MaxHeight = max
        set n.Speed = speed
        set n.Angle = angle
        set n.Level = level
        call SetUnitFacing(n.Dummy, angle*57.29583)
        if IsUnitType(n.Dummy, UNIT_TYPE_GROUND) then
            call UnitAddAbility(n.Dummy, 'Amrf')
            call UnitRemoveAbility(n.Dummy, 'Amrf')
        endif
        return n
    endmethod
    
    private method onDestroy takes nothing returns nothing
    
        call BJDebugMsg("ON DESTROY SHRAPNADE")
    
        call DestroyEffect(AddSpecialEffect(ExplodeEffect, GetUnitX(this.Dummy), GetUnitY(this.Dummy)))
        call DestroyEffect(this.DummyEffect)
        call KillUnit(this.Dummy)
    endmethod
endstruct



// Struct for the actual spell, 
// extends IJCast for JCast functionality
private struct spell extends IJCast

    //Ability's rawcode declaration (required for auto-generated spell's trigger)
    private static constant integer AbilityId = 'A004'


    //*********************************************************
    //* Some quick configuration functions
    
    //* This is the targeting BoolExpr for the spell (Who it hits)
    //* bj_meleeNearestMine references the spell caster
    private method Check takes nothing returns boolean
        return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(bj_meleeNearestMine)) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING) and GetWidgetLife(GetFilterUnit()) > 0.405 and GetUnitAbilityLevel(GetFilterUnit(), 'Avul') <= 0
    endmethod

    //* This just does the calculation for the spell's damage
    private method FinalDamage takes integer lvl returns real
        return BaseDamage+(DamagePerLevel*(lvl-1))
    endmethod

    //* This just does the calculation for the spell's fragmentation
    private method FragCount takes integer lvl returns integer
        return Fragments+(FragmentsPerLevel*(lvl-1))
    endmethod



    //******************************************************************************
    //* The spell code itself.

    private method Conditions takes nothing returns boolean
        return GetSpellAbilityId() == AbilityId
    endmethod

    //Im not sure why I need this, or if I do.
    //I couldn't find a way to make the timer call the regular Callback without
    //keeping it non-static. Is it a big deal if it is static or not in this case? I'm not sure really.
    public static method CallbackStart takes nothing returns nothing
        call spell(GetTimerData(GetExpiredTimer())).Callback()
    endmethod

    private method Callback takes nothing returns nothing
        local unit u
        local unit d
        local unit s
        local nade n = 0
        local nade m = 0
        local integer i = Counter - 1
        local integer j = 0
        local integer lvl
        local group g = CreateGroup()
        local real xi
        local real yi
        local real xf
        local real yf
        local real z
        local real di1
        local real di2
        
        //call BJDebugMsg("CALLBACK CALLLED: counter:"+I2S(Counter)+" i: "+I2S(i))
        
        loop
            exitwhen i < 0
            set n = Nades[i]
            set u = n.Caster
            set d = n.Dummy
            set di1 = n.DistanceCheck
            set di2 = n.DistanceTarget
            set lvl = n.Level
            set xi = GetUnitX(d)
            set yi = GetUnitY(d)
            
           // call BJDebugMsg("CALLBACK distance: di1:"+R2S(di1))
            
            if di1 <= 0 then
                set bj_meleeNearestMine = u
                call GroupEnumUnitsInRange(g, xi, yi, BlastAreaOfEffect, Boolexpr)
                
                loop
                    set s = FirstOfGroup(g)
                    exitwhen s == null
                    call DestroyEffect(AddSpecialEffectTarget(DamageEffect, s, DamageLocal))
                    call UnitDamageTarget(u, s, FinalDamage(lvl), false, false, AttackType, DamageType, null)
                    call GroupRemoveUnit(g, s)
                endloop
                
                call GroupClear(g)
                
                if n.isPrimary then
                    loop
                        exitwhen j >= FragCount(lvl)
                        set m = nade.create(u, xi, yi, lvl, GetRandomReal(n.Angle-MomentumAngle,n.Angle+MomentumAngle), GetRandomReal(MinFragRange,MaxFragRange), GetRandomReal(MinBounceHeight, MaxBounceHeight), GetRandomReal(MinSpeed, MaxSpeed), false)
                        set Nades[Counter] = m
                        set Counter = Counter + 1
                        set j = j + 1
                    endloop
                endif
                
                call n.destroy()
                set Counter = Counter - 1
                
                if Counter <= 0 then
                    call BJDebugMsg("PAUSE TIMER")
                    call PauseTimer(Timer)
                    set Counter = 0
                else
                    set Nades[i] = Nades[Counter]
                endif
            else
                set xf = xi + (di2/n.Speed) * Cos(n.Angle)
                set yf = yi + (di2/n.Speed) * Sin(n.Angle)
                set z = ((4*di1)/di2)*(1-(di1/di2))*n.MaxHeight
                set n.DistanceCheck = di1 - (di2/n.Speed)
                call SetUnitFacing(d, n.Angle*57.29583)
                call SetUnitFlyHeight(d, z, 0)
                call SetUnitX(d, xf)
                call SetUnitY(d, yf)
            endif
            set i = i - 1
        endloop
        
        call GroupClear(g)
        call DestroyGroup(g)
        set g = null
        set u = null
        set d = null
        set s = null
    endmethod

    private method Actions takes nothing returns nothing

        local unit u = GetCaster()
        local integer lvl = GetUnitAbilityLevel(u, AbilityId)
        local real xi = GetUnitX(u)
        local real yi = GetUnitY(u)
        local real xf = GetTargetX()
        local real yf = GetTargetY()
        local real an = Atan2(yf - yi, xf - xi)
        local real di = SquareRoot((xi-xf)*(xi-xf) + (yi-yf)*(yi-yf))
        local nade n = 0
        
        //call BJDebugMsg("ACTIONS NOW, level? "+I2S(lvl)+" of: "+I2S(AbilityId)+" VS: "+I2S('A001')+" or: "+I2S('A003') +" real: "+I2S(GetUnitAbilityLevel(u, 'A001'))+" | "+I2S(GetUnitAbilityLevel(u, 'A003')))
        //call BJDebugMsg("ACTIONS NOW, target: "+R2S(GetLocationX(l))+" "+R2S(GetLocationY(l))+" OR UNIT? "+GetUnitName(GetSpellTargetUnit()))
        
        if di == 0 then
            set di = 1
            set an = GetUnitFacing(u)
        endif
        set n = nade.create(u, xi, yi, lvl, an, di, InitialBounceHeight, BaseSpeed, true)
        if Counter == 0 then
            call SetTimerData(Timer, integer(this))
            call TimerStart(Timer, TimerInterval, true, function thistype.CallbackStart)
        endif
        set Nades[Counter] = n
        set Counter = Counter + 1
        
        set u = null
    endmethod

    //***********************************************
    //JCast Specific Functions
    //Overridden for more functionality, or comments

    //Define the spell's casting time as 1 second
    method GetCastTime takes nothing returns real
            return 1.
    endmethod

    //For now, require no specific conditions. Later could add something.
    method CanCast takes nothing returns boolean
        return true
    endmethod
    
    //Called when CanCast() returned FALSE.
    method OnFail takes nothing returns nothing
        call SimError(GetOwningPlayer(GetCaster()), "Please select a wounded unit")
    endmethod

    //Just play and animation when the cast starts.
    //Note: Don't forget to set the war3's spell 'Animation name' setting to "stand" if
    //      you don't want the spell anim to be played when CanCast() returns FALSE.
    method OnCastStart takes nothing returns nothing
        call SetUnitAnimation(GetCaster(), "stand channel")
        call Demo_CastbarCreate(GetCaster(), GetAbilityName(AbilityId))
    endmethod

    //Let's implement this method to update the casting bar.
    method OnCastUpdate takes real progress returns nothing
        //call BJDebugMsg("OnCastUpdate() progress: "+R2S(progress))
        call Demo_CastbarUpdate(GetCaster(), progress)
    endmethod

    //The cast stage is over, then reset the casting bar.
    method OnCastEnd takes nothing returns nothing
        call Demo_CastbarReset(GetCaster())
    endmethod
    
    method OnEnd takes nothing returns nothing
        call Demo_CastbarReset(GetCaster())
    endmethod

        
    //The cast is complete and successful (CanCast method is evaluated again when cast is done),
    //then it's time to perform spell effects.
    method OnEffect takes nothing returns nothing
        call Actions()
    endmethod
    
    //Not used right now
    //When hit by an attack, cast progress is reduced by 0.5 second (infinite pushback)
    method ApplyCastPushback takes real progress, real castTime, real pushCount returns real
        return RMaxBJ(0., progress - 0.5)
    endmethod
    
    
    //The module must be implemented after the declaration of OnCastUpdate and OnChannelUpdate, else these methods
    //won't be taken into consideration by the vJass compiler.
    implement JCastModule
        
endstruct

endlibrary

Attached Files
File type: zipShrapNadeJCast.zip (115.7 KB)
10-28-2010, 02:07 PM#6
Deaod
Quote:
- I am not sure if I am using vJass or not.
You are.

Quote:
This seems excessive And there is probably a better way to do this?
Actually, what you tried to do doesnt work. Just reverse what you changed and youll have the most efficient way to do it.
10-29-2010, 02:17 AM#7
rogueteddybear
If I reverse my changes I end up with something like this:

Collapse JASS:

globals
     //...
     private timer Timer = CreateTimer()
     //...
endglobals


struct spell extends IJcast

     private method Callback takes nothing returns nothing
          //...
     endmethod

     private method Actions takes nothing returns nothing
          //...
          TimerStart(Timer, TimerInterval, true,  function thistype.Callback)
     endmethod

endstruct


This leads to a compile error telling me:
Quote:
Callback is not a static method of Shrapnade_spell that takes nothing

I didn't want to make callback static because it uses other non-static methods in the spell struct. When I first started, the working spell didn't have its functions inside a struct, but in a scope. I got rid of that and put everything in the struct so they can extend from IJCast and use its methods.

There any way to make this work?
10-29-2010, 08:30 PM#8
Anitarf
Non-static methods implicitly take the struct instance as an argument; as such, they can not be used as timer callbacks since those must take no arguments. Typically, this is resolved by making the timer callback a static method that then adopts a struct instance:

Collapse JASS:
struct example
    timer t
    static method callback takes nothing returns nothing
        local example this = example(GetTimerData(GetExpiredTimer())) //this line makes our static method behave like a regular method
        call ReleaseTimer(.t)
    endmethod

    method runtimer takes real time returns nothing
        set .t=NewTimer()
        call SetTimerData(.t, this)
        call TimerStart(.t, time, false, function example.callback)
    endmethod
endstruct
10-29-2010, 09:21 PM#9
rogueteddybear
Thank you, i will try this. By just looking at it im sure it will work fine.
10-30-2010, 03:21 AM#10
Deaod
thats what he already does.

the problem is not with the timer. Im pretty sure Dusk used a stack initially, but rogueteddybear changed it to try and make it use one timer per instance. The only reason why this still works is because the Callback method (which is not static in the code he posted) was static before and still works as if it was static (meaning that it doesnt make use of the local variable "this"). As i said, just reverse whatever you changed about that method and youll be just fine. You dont need TimerUtils to do what you want.
10-30-2010, 11:07 AM#11
Anitarf
Ah, I see, you have this weird combination of two approaches like you couldn't really decide which one to use: a timer per instance or one timer running all instances. It seems you initially had a setup where one timer ran all the instances, but then you attached a specific instance to the timer even though you still have a functional stack in the background and the timer callback is in fact still processing all the instances on the stack, not just the instance attached to it.

I would recommend you use TimedLoop, it will handle the periodic timer management for you so you can simplify your code and avoid this confusion.