• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Trigger] Help with a Spellsystem: Basic Spells, Friendly Fire, Attribute based, Modes

Status
Not open for further replies.
Level 3
Joined
Jan 7, 2010
Messages
37
Hi guys.

I'm looking for advice.

I'm trying to figure out how to modify the basic attack/healing spells in a way they:

1.) work on every target (friend or foe)[friendly fire/ foe healing]
2.) gain extra dmg through hero attributes
3.) they have modified versions

1.) work on every target (friend or foe)
example:
Storm Bolt works on friends
Holy Light heals enemies
Chain Lightning jumps on friends and foes alike
Thunder Clap damages and slows everyone around
...

I know some spells can be modified in the editor by putting the "hit enemy/friend" checkbox to true (Storm Bolt). Others (i.e. Thunder Clap) don't work that way, so I tried triggers - works so far.


2.) gain damage through attributes
example:
Storm Bolt deals 30 base dmg + 0.5 x Hero Strenght

The problem I have here is, I don't know how to get a "on hit" event with projectiles. The damage would hit the target, before the projectile arrived.


3.) they have modified versions
example:
Fan of Knives deals FLAT AoE dmg, nothing but damage
Thunder Clap deals AoE dmg and SLOWS
War Stomp deals AoE dmg and STUNS

I want to have based on a basic ability multiple versions.
Core abilites would be:
Projectile (i.e. Storm Bolt)
Projectile AoE (i.e. Shockwave)
Instant Ranged (i.e. Frost Nova / Holy Light)
Instant AoE (i.e. Fan of Knives)
Instant AoE Ranged (i.e. Flame Strike)
Channeling Ranged (i.e. Life Drain)
Channeling AoE (i.e. Starfall)
Channeling AoE Ranged (i.e. Blizzard)
Jumping (i.e. Chain Lightning / Healing Wave)

Modifiers:
BASIC,FLAT,CRIT,SLOW,ROOT,STUN,DOT/HOT,HEAL,LL,ML

In the end, there would be 10 versions of each basic ability I named above.



How I would approach this:
Creating a basic mui spell via trigger, using a dummy spell, for each category.
Creating an extra trigger for every modifier, launching "on hit"(?)/starts effect of ability, that handles the targets and damage/healing.
Some effects would be modified via triggers, others in the editor.


Has there been a system like this? Did I miss something? Is this even doable in GUI? What tips/hints can you give me?
 
Level 4
Joined
Jan 27, 2010
Messages
133
Regarding the "missile on hit" things.

You import an invisible model (see attached file) with the attachment point "origin". Make a flying unit with the model, then via trigger create an instance of that unit and add desired missile as special effect. Run a timer that executes every 0.03 seconds, and make the unit move.

You can technically do it in GUI, but if you want it to be MUI you're going to have to do a lot of manual array work. If you use jassNewGenPack (vJASS), there are plenty of options. I myself use this homegrown system:
JASS:
library ME initializer Init needs xefx, optional IsUnitInvisibleEx

// =============================================================================================
//   ABOUT
// =============================================================================================
/*
 * @Creator: Themerion
 * @Version: 2
 *
*/
// =============================================================================================
//   CONFIG
// =============================================================================================

globals
    private constant real Z_OFFSET=64
    private constant real PERIOD=0.03
    
  // If the target moves further than this IN AN INSTANT, the missile will miss.
    private constant real DEFAULT_HOMING_DISTANCE = 200
endglobals
    
// =============================================================================================
//   DOCUMENTATION
// =============================================================================================

// The effect is not created until you send the missile.

/* MEMissile.create( real x, real y, player owner, string fx )
*/

/* m.sendToUnit( target, speed )
 * m.sendToPoint( x, y, speed )
*/
 
// For storing struct data

/* m.setData( integer )
 * m.getData( integer )
*/

// Event-callbacks:

/*  m.onHit( callbackFunction )
 *  m.onMiss( callbackFunction )
 *  m.onCollide( collideCallbackFunction, filterFunction, collisionRadius )
*/
 
/* interface callbackFunction takes integer data
 * interface collideCallbackFunction takes integer data, unit collidedUnit returns boolean // (boolean = stop after collision?)
 *
 *  // uses GetFilterUnit() and global MEData -> data assigned to missile.
 * interface filterFunction takes nothing returns boolean // uses GetFilterUnit()
*/

// Tweaks for missing:

/* boolean m.targetInvisible   // if a unit goes invisible, undetected, will the missile still hit?
 * 
 * m.setHomingDistance( real ) // if a unit blinks/teleports/(is in instantly moved) HOMING_DISTANCE
*/                               // range or more, the missile will miss.

// ===================================================================================================
globals
    integer MEData

    private MEMissile m
    private MEMissile first
    private boolean running
    private timer loopTimer
    
    private group tempGroup
    private boolexpr collideFilter
    private boolean collisionStop
endglobals

function interface MECallback takes integer data returns nothing
function interface MECollideCallback takes integer data, unit hit returns boolean
function interface MECollideEval takes nothing returns boolean

private function CollideCheck takes nothing returns boolean
    if m.collideEval.evaluate() and IsUnitInGroup(GetFilterUnit(),m.collideGroup)==false then
        call GroupAddUnit(m.collideGroup,GetFilterUnit())
        set collisionStop = collisionStop or m.collideCB.evaluate(m.data,GetFilterUnit())
    endif
    return false
endfunction

private function Loop takes nothing returns nothing
    local MEMissile prev=0
    
    local real dx
    local real dy
    local real dz
    local real angle
    local real distance
    local integer ticksLeft
    
    local real newX
    local real newY

    local real movedX
    local real movedY
    
    local boolean seekUnit
    local boolean isInvisible
    
    set m=first
    
    loop
        exitwhen m==0
// --------------------------------------------
  // UNIT TARGET
  
    set seekUnit = (m.target!=null and m.missed==false)
    if seekUnit then
    
        static if (LIBRARY_IsUnitInvisibleEx) then
            set isInvisible = IsUnitInvisibleEx(m.target)
        else
            set isInvisible = IsUnitInvisible(m.target,Player(PLAYER_NEUTRAL_PASSIVE))
        endif
        
        if GetUnitTypeId(m.target)==0 or GetWidgetLife(m.target)<.405 or (m.targetInvisible==false and isInvisible and IsUnitVisible(m.target,m.owner)==false) then
          // Unit is no longer appropriate target.
          // Will target unit's last current position.
            set m.missed=true
            set seekUnit=false
        else
            
            // How far has the target moved since last update?
            set movedX = m.target_x - GetUnitX(m.target)
            set movedY = m.target_y - GetUnitY(m.target)
                
            if movedX*movedX + movedY*movedY > m.homingDistanceSquared then
                // If the target has blinked / run away
                    set m.missed=true
                    set seekUnit=false
            else
                // Unit is still appropriate target.
                    set dx=GetUnitX(m.target)-m.x
                    set dy=GetUnitY(m.target)-m.y
        
                // Store unit's current position.
                    set m.target_x=GetUnitX(m.target)
                    set m.target_y=GetUnitY(m.target)
            endif
        endif
    endif

  // POINT TARGET
  // Unit target can become point target, so need separate if's (else is not an option here).
    if not seekUnit then
        set dx=m.target_x-m.x
        set dy=m.target_y-m.y
    endif
    
    set distance = SquareRoot(dx*dx+dy*dy)
    
    set angle=Atan2(dy,dx)
    set newX=m.x+Cos(angle)*m.speed*PERIOD
    set newY=m.y+Sin(angle)*m.speed*PERIOD
        
  // xefx...
    set m.xyangle=angle
    set m.x=newX
    set m.y=newY
    
    if seekUnit then
    // Adjust for fly height
        set ticksLeft = R2I(distance/(m.speed*PERIOD))+1 // floor
        set dz = GetUnitFlyHeight(m.target) - m.z
        set m.z = m.z + dz/I2R(ticksLeft)
    endif
    
    // --- <Collision> ---
    set collisionStop = false
    set MEData = m.data
    if m.collideCB!=0 then
        call GroupEnumUnitsInRange(tempGroup, newX, newY, m.collideRadius, collideFilter)
    endif
    // --- </Collision> ---
    
    if distance<50 or collisionStop then
      // Target reached!
      
    // --- <Do callbacks> ---
        if not collisionStop then
            if m.missed then
                if m.missCB!=0 then
                    call m.missCB.execute(m.data)
                endif
            else
                if m.endCB!=0 then
                    call m.endCB.execute(m.data)
                endif
            endif
        endif
    // --- </Do callbacks> ---
        
    // --- <List stuff> ---
    //  Remove missile from list
        if m==first then
            set first=m.next
            call m.destroy()
            set m=first
        else
            set prev.next=m.next
            call m.destroy()
            set m=prev.next
        endif
        
        if first==0 then
            set running=false
            call PauseTimer(loopTimer)
        endif
    // --- </list stuff> ---
    else
      // Target not reached. Continue loop.
        set prev=m
        set m=m.next
    endif
// --------------------------------------------
    endloop
endfunction

struct MEMissile
    delegate xefx fx
    
    real speed
    unit target
    real target_x
    real target_y
    boolean missed
    
    string fxprel
    
    real homingDistanceSquared
    boolean targetInvisible
    
    integer data
    
    MEMissile next
    
    MECallback endCB
    MECallback missCB
    group collideGroup
    real collideRadius
    MECollideEval collideEval
    MECollideCallback collideCB
    
    // Does not create the xefx (since we want to be able to automatically calculate the angle).
    static method create takes real x, real y, player owner, string fxprel returns MEMissile
        local MEMissile mem = MEMissile.allocate()
    
    // Set default values
        set mem.endCB = 0
        set mem.missCB = 0
        set mem.collideCB = 0
        set mem.homingDistanceSquared = (DEFAULT_HOMING_DISTANCE)*(DEFAULT_HOMING_DISTANCE)
        set mem.targetInvisible = false
        
        if owner!=null then
            set mem.owner = owner
        else
            set mem.owner = Player(PLAYER_NEUTRAL_PASSIVE)
        endif
        
    // Temporarily use target_x/y to store the location where the missile will be created.
        set mem.target_x=x
        set mem.target_y=y
        
    // Temporarily store the wanted effect-string.
        set mem.fxprel=fxprel
        
        return mem
    endmethod
    
    method setData takes integer data returns nothing
        set .data=data
    endmethod
    
    method getData takes nothing returns integer
        return .data
    endmethod
    
    method onEnd takes MECallback cb returns nothing
        set .endCB = cb
    endmethod
    
    method onMiss takes MECallback cb returns nothing
        set .missCB = cb
    endmethod
    
    method onCollide takes MECollideCallback cb, MECollideEval eval, real radius returns nothing
        set .collideEval = eval
        set .collideRadius = radius
        if .collideGroup != null then
            call GroupClear(.collideGroup)
        else
            set .collideGroup = CreateGroup()
        endif
        set .collideCB = cb
    endmethod

    method setHomingDistance takes real distance returns nothing
        set .homingDistanceSquared = distance*distance
    endmethod
    
    private method send takes nothing returns nothing
        set .fx.z=Z_OFFSET
        set .fx.fxpath=.fxprel
        set .fxprel=null
    
        if running==false then
            set running=true
            call TimerStart(loopTimer,PERIOD,true,function Loop)
            set .next=0
        else
            set .next=first
        endif
        set first=this
    endmethod
    
    method sendToUnit takes unit target, real speed returns nothing
        set .fx=xefx.create(.target_x,.target_y,Atan2(GetUnitY(target)-.target_y,GetUnitX(target)-.target_x))
        
        set .target_x=GetUnitX(target)
        set .target_y=GetUnitY(target)
        
        set .target=target
        set .speed=speed
        
        call .send()
    endmethod
    
    method sendToPoint takes real x, real y, real speed returns nothing
        set .fx=xefx.create(.target_x,.target_y,Atan2(x-.target_y,y-.target_x))
        
        set .target_x=x
        set .target_y=y
        
        set .target=null
        set .speed=speed
        
        call .send()
    endmethod
    
    method onDestroy takes nothing returns nothing
        set .target=null
        call .fx.destroy()
    endmethod
endstruct

private function Init takes nothing returns nothing
    set first=0
    set running=false
    set loopTimer=CreateTimer()
    
    set tempGroup = CreateGroup()
    set collideFilter = Condition(function CollideCheck)
endfunction

endlibrary
 

Attachments

  • dummy.mdx
    34.2 KB · Views: 46
Status
Not open for further replies.
Top