• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Making a Frozen Orb spell (Like diablo 2)

Status
Not open for further replies.
Level 4
Joined
Mar 14, 2009
Messages
98
So I had some spare time and thought, "Hey, you know what would be cool? Diablo 2's Frozen Orb spell in Warcraft 3." So I made one. I'm just having one problem, at the very end, the spell doesn't create missiles that shoot in every direction (like diablo 2's Frozen Orb does). I can't find the reason for the problem. Anyone wanna help out? +Rep to whomever can find the solution.

P.S. This includes a missile system (and attachment system) I've made just today. No vJASS, sorry. I haven't bothered to learn it yet.

JASS:
globals
    real MAX_MISSILE_MOVE_DISTANCE = 50
    unit array FrozenOrbMissiles[8192]
    timer array FrozenOrbTimers[8192]
    real array FrozenOrbSpeed[8192]
    real array FrozenOrbDistanceTraveled[8192]
    real array FrozenOrbDistanceMax[8192]
    real array FrozenOrbCollisionSize[8192]
    integer array FrozenOrbLevel[8192]
    integer array FrozenOrbSpell[8192]
    integer array FrozenOrbSpellOrderID[8192]
    boolean udg_GroupEmptyCheck
    player udg_CollisionPlayer
endglobals

function H2I takes handle H returns integer
    return H
    return 0
endfunction

function MainOrbFilter takes nothing returns boolean
    return IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
endfunction

function CollisionFilter takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), udg_CollisionPlayer)
endfunction

//GroupEmptyCheck and GroupEmptyCheck2 is for seeing if a group is empty.
function GroupEmptyCheck2 takes nothing returns nothing
    set udg_GroupEmptyCheck = false
endfunction

function GroupEmptyCheck takes group g returns boolean
    set udg_GroupEmptyCheck = true
    call ForGroup(g, function GroupEmptyCheck2)
    return udg_GroupEmptyCheck
endfunction

//This function simplifies making dummy units to cast spells.
function UnitCastSpellOnTarget takes player whichPlayer, widget whichUnit, integer whichSpell, integer spellLevel, integer orderID returns nothing
    local unit Caster = CreateUnit(whichPlayer, 'n000', GetWidgetX(whichUnit), GetWidgetY(whichUnit), 0)
    call SetUnitVertexColor(Caster, 255, 255, 255, 0)
    call UnitAddAbility(Caster, whichSpell)
    call SetUnitAbilityLevel(Caster, whichSpell, spellLevel)
    call UnitApplyTimedLife(Caster, 'BTLF', 0.50)
    call IssueTargetOrderById(Caster, orderID, whichUnit)
    set Caster = null
endfunction

//Timer function for the missile. It moves the missile a certain amount, then checks if 
//there is anything the missile should be hitting around it. If it does, then it creates a 
//dummy unit and casts a spell on it. If not, then it checks if the missile has traveled 
//its maximum distance. If the missile has traveled far enough, then it destroys the 
//missile and frees the "struct".
function SingleTargetMissileTimerFunction takes nothing returns nothing
    local integer index = ModuloInteger(H2I(GetExpiredTimer()), 8192)
    local group g = CreateGroup()
    local real CurrentX = GetUnitX(FrozenOrbMissiles[index])
    local real CurrentY = GetUnitY(FrozenOrbMissiles[index])
    local boolexpr filter
    if GetUnitTypeId(FrozenOrbMissiles[index]) == 'ball' then
    set filter = Filter(function MainOrbFilter)
    else
    set filter = Filter(function CollisionFilter)
    endif
    call SetUnitPosition(FrozenOrbMissiles[index], CurrentX+Cos(Deg2Rad(GetUnitFacing(FrozenOrbMissiles[index])))*FrozenOrbSpeed[index], CurrentY+Sin(Deg2Rad(GetUnitFacing(FrozenOrbMissiles[index])))*FrozenOrbSpeed[index])
    set FrozenOrbDistanceTraveled[index] = FrozenOrbDistanceTraveled[index] + FrozenOrbSpeed[index]
    set udg_CollisionPlayer = GetOwningPlayer(FrozenOrbMissiles[index])
    call GroupEnumUnitsInRange(g, CurrentX, CurrentY, FrozenOrbCollisionSize[index], filter)
    if GroupEmptyCheck(g) then
    if FrozenOrbDistanceTraveled[index] >= FrozenOrbDistanceMax[index] then
    call RemoveUnit(FrozenOrbMissiles[index])
    set FrozenOrbMissiles[index] = null
    call DestroyTimer(FrozenOrbTimers[index])
    set FrozenOrbTimers[index] = null
    endif
    else
    call UnitCastSpellOnTarget(GetOwningPlayer(FrozenOrbMissiles[index]), FirstOfGroup(g), FrozenOrbSpell[index], FrozenOrbLevel[index], FrozenOrbSpellOrderID[index] )
    call RemoveUnit(FrozenOrbMissiles[index])
    set FrozenOrbMissiles[index] = null
    call DestroyTimer(FrozenOrbTimers[index])
    set FrozenOrbTimers[index] = null
    endif
    call DestroyGroup(g)
    set g = null
    call DestroyBoolExpr(filter)
    set filter = null
endfunction

//This function takes a whole bunch of stuff. Most of it is pretty self-explanatory, 
//boolean followRange checks if the missile should stop where the spell was cast or if it 
//should go with maxRange. The ability stuff is for what happens when the missile hits 
//something.
function LaunchSingleTargetMissileAtPoint takes player missileOwner, integer missileType, real speed, location origin, location destination, real maxRange, boolean followRange, real collisionSize, integer abilityToCast, integer abilityLevel, integer abilityOrderID returns unit
    local integer index
    local timer Temp
    loop
    set Temp = CreateTimer()
    exitwhen FrozenOrbTimers[ModuloInteger(H2I(Temp), 8192)] == null
    call DestroyTimer(Temp)
    endloop
    set index = ModuloInteger(H2I(Temp), 8192)
    set FrozenOrbTimers[index] = Temp
    set FrozenOrbMissiles[index] = CreateUnit(missileOwner, missileType, GetLocationX(origin), GetLocationY(origin), AngleBetweenPoints(origin, destination))
    call SetUnitPathing(FrozenOrbMissiles[index], false)
    call SetUnitPosition(FrozenOrbMissiles[index], GetLocationX(origin), GetLocationY(origin))
    call SetUnitFacing(FrozenOrbMissiles[index], AngleBetweenPoints(origin, destination))
    if followRange then
    set FrozenOrbDistanceMax[index] = maxRange
    else
    set FrozenOrbDistanceMax[index] = DistanceBetweenPoints(origin, destination)
    endif
    set FrozenOrbDistanceTraveled[index] = 0
    set FrozenOrbCollisionSize[index] = collisionSize
    set FrozenOrbLevel[index] = abilityLevel
    set FrozenOrbSpell[index] = abilityToCast
    set FrozenOrbSpellOrderID[index] = abilityOrderID
    if speed/25 < MAX_MISSILE_MOVE_DISTANCE then
    set FrozenOrbSpeed[index] = speed/25
    call TimerStart(FrozenOrbTimers[index], 0.04, true, function SingleTargetMissileTimerFunction)
    else
    set FrozenOrbSpeed[index] = 50
    call TimerStart(FrozenOrbTimers[index], 50/speed, true, function SingleTargetMissileTimerFunction)
    endif
    set Temp = null
    set origin = null
    set destination = null
    set missileOwner = null
    return FrozenOrbMissiles[index]
endfunction

JASS:
globals
    integer FROZEN_ORB_MAIN_ID = 'ball'
    integer FROZEN_ORB_SHARD_ID = 'shrd'
    integer FROZEN_ORB_SHARD_SPELL = 'ewan'
    real FROZEN_ORB_SHARD_COLLISION_SIZE = 64
    real FROZEN_ORB_MAIN_COLLISION_SIZE = 64
    real FROZEN_ORB_MAIN_SPEED = 500
    real FROZEN_ORB_SHARD_SPEED = 700
    real FROZEN_ORB_FINAL_SHARD_SPEED = 900
    real FROZEN_ORB_MAIN_RANGE = 1000
    real FROZEN_ORB_SHARD_RANGE = 1200
    integer FROZEN_ORB_SHARD_SPELL_ORDER_ID = 852230
    unit array FrozenOrbMain[8192]
    timer array FrozenOrbMainTimers[8192]
    real array FrozenOrbMainAngle[8192]
    integer array FrozenOrbCastedLevel[8192]
    location array FrozenOrbLastLoc[8192]
endglobals

function Trig_Frozen_Orb_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'FOrb'
endfunction
//This one is the action for the timer created whenever the spell is cast. It makes more 
//missiles come out from the Main Orb. Basically, it checks if the missile is still alive. If it 
//is, then it shoots another missile from the Main Orb. If it's not then it's supposed to 
//shoot several missiles in all directions from where the Main Orb last was, then clear 
//the "structs" related to the Main Orb. The else action works perfectly fine, but the 
//missiles don't appear. That's the problem.
function FrozenOrbShards takes nothing returns nothing
    local integer index = ModuloInteger(H2I(GetExpiredTimer()), 8192)
    local location origin
    local location destination
    if GetUnitState(FrozenOrbMain[index], UNIT_STATE_LIFE) > 0.405 then
    set origin = GetUnitLoc(FrozenOrbMain[index])
    set destination = Location(GetLocationX(origin)+Cos(FrozenOrbMainAngle[index])*900, GetLocationY(origin)+Sin(FrozenOrbMainAngle[index])*900)
    call LaunchSingleTargetMissileAtPoint(GetOwningPlayer(FrozenOrbMain[index]), FROZEN_ORB_SHARD_ID , FROZEN_ORB_SHARD_SPEED, origin, destination, FROZEN_ORB_SHARD_RANGE, true, FROZEN_ORB_SHARD_COLLISION_SIZE, FROZEN_ORB_SHARD_SPELL, FrozenOrbCastedLevel[index], FROZEN_ORB_SHARD_SPELL_ORDER_ID)
    call RemoveLocation(destination)
    set FrozenOrbMainAngle[index] = FrozenOrbMainAngle[index] + GetRandomReal(0.85, 0.15)
    if FrozenOrbLastLoc[index] != null then
    call RemoveLocation(FrozenOrbLastLoc[index])
    endif
    set FrozenOrbLastLoc[index] = origin
    else
    set FrozenOrbMainAngle[index] = 0
    set origin = FrozenOrbLastLoc[index]
    loop
    set destination = Location(GetLocationX(origin)+Cos(FrozenOrbMainAngle[index])*900, GetLocationY(origin)+Sin(FrozenOrbMainAngle[index])*900)
    call LaunchSingleTargetMissileAtPoint(GetOwningPlayer(FrozenOrbMain[index]), FROZEN_ORB_SHARD_ID , FROZEN_ORB_FINAL_SHARD_SPEED, origin, destination, FROZEN_ORB_SHARD_RANGE, true, FROZEN_ORB_SHARD_COLLISION_SIZE, FROZEN_ORB_SHARD_SPELL, FrozenOrbCastedLevel[index], FROZEN_ORB_SHARD_SPELL_ORDER_ID)
    call RemoveLocation(destination)
    set FrozenOrbMainAngle[index] = FrozenOrbMainAngle[index] + 0.52
    exitwhen FrozenOrbMainAngle[index] >= 6.24
    endloop
    call RemoveLocation(FrozenOrbLastLoc[index])
    call DestroyTimer(GetExpiredTimer())
    set FrozenOrbMain[index] = null
    set FrozenOrbMainTimers[index] = null
    set FrozenOrbLastLoc[index] = null
    set FrozenOrbMainAngle[index] = 0
    endif
    set origin = null
    set destination = null
endfunction
//This initializes the "struct" for the Main Orb and creates the Main Orb, which is itself a 
//missile.
function Trig_Frozen_Orb_Actions takes nothing returns nothing
    local location origin = GetUnitLoc(GetTriggerUnit())
    local location destination = GetSpellTargetLoc()
    local timer Temp
    local integer index
    loop
    set Temp = CreateTimer()
    exitwhen FrozenOrbMainTimers[ModuloInteger(H2I(Temp), 8192)] == null
    call DestroyTimer(Temp)
    endloop
    set index = ModuloInteger(H2I(Temp), 8192)
    set FrozenOrbMainTimers[index] = Temp
    set FrozenOrbMain[index] = LaunchSingleTargetMissileAtPoint(GetTriggerPlayer(), FROZEN_ORB_MAIN_ID, FROZEN_ORB_MAIN_SPEED, origin, destination, FROZEN_ORB_MAIN_RANGE, true, FROZEN_ORB_MAIN_COLLISION_SIZE, 0, 0, 0)
    set FrozenOrbMainAngle[index] = GetRandomReal(0, bj_PI*2)
    set FrozenOrbCastedLevel[index] = GetUnitAbilityLevel(GetTriggerUnit(), GetSpellAbilityId())
    call TimerStart(FrozenOrbMainTimers[index], 0.08, true, function FrozenOrbShards)
    call RemoveLocation(origin)
    call RemoveLocation(destination)
    set Temp = null
    set origin = null
    set destination = null
endfunction

//===========================================================================
function InitTrig_Frozen_Orb takes nothing returns nothing
    set gg_trg_Frozen_Orb = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Frozen_Orb, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_Frozen_Orb, Condition(function Trig_Frozen_Orb_Conditions))
    call TriggerAddAction( gg_trg_Frozen_Orb, function Trig_Frozen_Orb_Actions )
endfunction

Attached is the test map I made. It's not much.
 

Attachments

  • Frozen Orb.w3x
    20.1 KB · Views: 53
Last edited:
Level 4
Joined
Mar 14, 2009
Messages
98
Because I won't fix the easy to fix things until I fix that one with the final explosion of cold bolts thingy. That's also why the tooltip isn't nice, and why the sorceress is called "Default String". Still, a pretty cool name for a sorceress. I need halp. :(
 
Status
Not open for further replies.
Top