local types = require('openmw.types')
local core = require('openmw.core')
local I = require('openmw.interfaces')
local self = require('openmw.self')
local util = require('openmw.util')

I.AnimationController.addTextKeyHandler('spellcast', function(groupname, key)

    if key.sub(key, #key - 6) == 'release' then
        
      if types.Actor.getSelectedSpell(self).id == "fireball" then
        
        local effect = core.magic.effects.records[core.magic.EFFECT_TYPE.FireDamage].areaStatic
        local model = types.Static.records[effect].model
        
        for i= 1 , 50 do     
          
          local x = 100 * math.cos(math.rad(45)*i)
          local y = 100 * math.sin(math.rad(45)*i)

          local trans = util.transform
          local rotate = trans.rotateZ(self.rotation:getYaw()) * util.vector3(x,20*i,y)
          
          local pos = self.position + util.vector3(0,0,100) + rotate  
           
          core.sendGlobalEvent('SpawnVfx', {model = model, position = pos })
          
        end
      end
    end
end)

cylindrical coordinates

randomised spiral

local types = require('openmw.types')
local core = require('openmw.core')
local I = require('openmw.interfaces')
local self = require('openmw.self')
local util = require('openmw.util')
local time = require('openmw_aux.time')

I.AnimationController.addTextKeyHandler('spellcast', function(groupname, key)

    if key.sub(key, #key - 6) == 'release' then
        
      if types.Actor.getSelectedSpell(self).id == "fireball" then
        
        local effect = core.magic.effects.records[core.magic.EFFECT_TYPE.FireDamage].areaStatic
        local model = types.Static.records[effect].model
        
     local t = 0
        
     local stop = time.runRepeatedly(function() 
        t = t + 1
        for i= 1 , 50 do     
          
          local R = 10 + 3*i
          local theta = i*t 
          
          local x = R * math.cos(theta)
          local y = R * math.sin(theta)

          local pos = self.position + util.vector3(x,y,0)  
           
          core.sendGlobalEvent('SpawnVfx', {model = model, position = pos })
        end
        end, 1 * time.second)
      end
    end
end)

simple tornado

     local stop = time.runRepeatedly(function() 
        t = t + 1
        for i= 1 , 100 do     
          
          local R = 10 + 3*i
          local theta = i + t*15*3.14/360 
          
          local x = R * math.cos(theta)
          local y = R * math.sin(theta)
          local z = theta*i*0.1

          local pos = self.position + util.vector3(x,y,z)  
           
          core.sendGlobalEvent('SpawnVfx', {model = model, position = pos })
        end
        end, 0.2 * time.second)