Spell Ultima

Sim

Forum Administrator
Staff member
Reaction score
534
Ever played Final Fantasy games?

Final Fantasy 3/6?

Well then, you should remember one spell at least: Ultima.

Here's a picture showing the spell.

Ultima_FFVI.gif


Well, I wanted to do that too, 9000 damage.

I ended up doing more. Whatever.

I present thee my version of Ultima: Warcraft Fantasy 3/6

ultimascreenshot.jpg


Ultima

Channels the very mist force from the surroundings into a sphere of pure energy, growing in size as the energy is gathered. When it reaches epic proportions, it collapses, leaving only ashes behind.

Here's the code:

JASS:
scope Ultima initializer Init

//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
//¤
//¤ ***************** 
//¤     - Ultima - 
//¤ *****************
//¤ KeyTimers version   
//¤
//¤ By: Daxtreme
//¤ 
//¤ --> How to implement in your map:
//¤                           
//¤     1. Copy the spell "Ultima" into your map.
//¤     2. Copy the unit named "Ultima" into your map.
//¤     3. Copy this trigger into your map.
//¤     4. Import the Invoke.mdx model into your map.
//¤     5. Import the "Textures\RibbonNE1_blue.blp" texture into your map. (Keep the path as is)
//¤     6. Import the "Textures\Blue_Glow2.blp" into your map. (Keep the path as is)
//¤     7. (Optional) Define as variables the warcraft3 sounds featured in the Sound Editor.
//¤        For this, you need to search the sounds by name and select "Use as sound" when found.
//¤     8. Copy the trigger named "KT" into your map.
//¤     9. Copy the trigger named "GT" into your map
//¤     10. Change the rawcodes in the configuration section so they fit with your map's.
//¤
//¤ --> How to customize it:
//¤
//¤     You can configure the spell using the few globals and functions just below. Change their values.
//¤     
//¤
//¤ CREDITS:
//¤
//¤     - JetFangInferno's CharmTarget.
//¤     - BuilderBob's sound edit.
//¤     - Jesus4Lyf's KeyTimers2 and GTrigger Events sytems.
//¤     - Tinki3's spell map test template.
//¤
//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
//
// ***      Configuration Section      ***
//
//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

globals

private constant integer RawCode = 'A001'   // The spell's rawcode in the editor
private constant integer dummyid = 'h000'   // The "ultima" unit's id
private constant real AreaEffect = 1000.    // Damage AoE of the spell at the end
private constant real dummylife = .25       // The dummy unit's life span. Keep this low (Below 1.5)
private constant real period = 0.01         // Defines the rate at which the sphere augments in proportions
                                              // I recommend keeping this very low if you don't want the sphere 
                                              // growing awkwardly as it is rather big at the end.
private constant real damage = 1000.        // Multiply this by the level of the spell for damage value.
                                              // Note that this spell wasn't meant to be balanced in a normal game.
private constant real duration = 5.         // In seconds. You must change this into the Object Editor too.
private constant real SphereIncrement = 1.  // Factor determining the sphere's growing speed. It is set to 1 meaning
                                              // that every (period), its size is set to
                                              // 100% + SphereIncrement * currentcount. In short, if period = 0.01,
                                              // the sphere increases in size by 1% every 0.01 second = 100 times a second.
private constant real NoReturnPoint = 1.  // Minimal duration needed before the spell gets out of control and finishes
                                              // unleashing, even if the caster no longer is casting.
                                              // Do not set this above 1., else the spell will never function.
                                              // Expects values as so 0 < NoReturnPoint <= 1, 0.5 meaning the spell
                                              // can be interrupted as long as the caster has been channeling
                                              // for 50% of the total duration. Exceeding this state will cause the spell
                                              // to trigger its end instantly when casting is stopped.
private constant attacktype AType = ATTACK_TYPE_NORMAL // Attack type. This one means "Spell".
private constant damagetype DType = DAMAGE_TYPE_UNIVERSAL // Damage type. This one means no damage reduction.
private filterfunc non_leaking_filter = null
private unit forFilter = null

endglobals

private function Damage takes unit u returns real
    return I2R(GetUnitAbilityLevel(u, RawCode)) * damage
endfunction

//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
//
// ***       Spell Code       ***
//
// --> I highly recommend editing the following only if you know what you're doing.
//
//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

private constant function DummyFilter takes nothing returns boolean
    return TRUE
endfunction

private function DeathConditions takes nothing returns boolean
    return GetUnitTypeId(GetTriggerUnit()) == dummyid
endfunction

private struct data
    unit die
    unit c
    integer lvl
    integer id
    real j = 0.
    real i = 0.
    real x
    real y
endstruct

private function FilterEnemies takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(forFilter))
endfunction

private function Timer takes nothing returns boolean
    local data d = KT_GetData()
    local group g
    local unit targ
    local unit dummy
    local real advancement = ((d.i * period) * (1. / period)) / (duration * (1. / period))
    
// END OF SPELL
    
    if ( (GetUnitCurrentOrder(d.c) != d.id) and (advancement < NoReturnPoint ) ) then
        set dummy = CreateUnit(GetOwningPlayer(d.c), dummyid, d.x, d.y, 0)
        call UnitApplyTimedLife(dummy, 'BTLF', 1.5)   // 1.5: Optimal visual effect time
        call SetUnitScalePercent(dummy, 105. + (SphereIncrement * d.i), 105. + (SphereIncrement * d.i), 105. + (SphereIncrement * d.i))
        call data.destroy(d)
        set dummy = null
        set d.c = null
        return true
    elseif ( (GetUnitCurrentOrder(d.c) != d.id) and (advancement >= NoReturnPoint ) ) then
        set dummy = CreateUnit(GetOwningPlayer(d.c), dummyid, d.x, d.y, 0)
        call UnitApplyTimedLife(dummy, 'BTLF', .25)   // 0.25: Optimal visual effect time
        call SetUnitScalePercent(dummy, 10000., 10000., 10000.) 
        set g = CreateGroup()
        set forFilter = d.c
        call GroupEnumUnitsInRange(g, d.x, d.y, AreaEffect, Condition(function FilterEnemies))
        loop
            set targ = FirstOfGroup(g)
            exitwhen targ == null
            call UnitDamageTarget(d.c, targ, Damage(d.c), true, false, AType, DType, WEAPON_TYPE_WHOKNOWS)            
            call GroupRemoveUnit(g, targ)
        endloop
        call SetSoundPosition(gg_snd_LightningBolt, d.x, d.y, 0.)
        call SetSoundVolume(gg_snd_LightningBolt, PercentToInt(100., 127))
        if (gg_snd_LightningBolt != null) then
            call StartSound(gg_snd_LightningBolt)
        endif
        call DestroyGroup(g)
        call data.destroy(d)
        set d.c = null
        set dummy = null
        set g = null
        set targ = null
        return true
    endif
    
// SPHERE INCREASING IN SIZE

    set d.i = d.i + 1.
    set dummy = CreateUnit(GetOwningPlayer(d.c), dummyid, d.x, d.y, 0)
    call UnitApplyTimedLife(dummy, 'BTLF', dummylife)
    call SetUnitScalePercent(dummy, 100.00 + (SphereIncrement * d.i), 100.00 + (SphereIncrement * d.i), 100.00 + (SphereIncrement * d.i)) 
    set dummy = null
    return false
endfunction

private function Actions takes nothing returns boolean
    local data d = data.create()
 
    set d.c = GetTriggerUnit()
    set d.x = GetUnitX(d.c)
    set d.y = GetUnitY(d.c)
    set d.id = GetUnitCurrentOrder(d.c)
    call SetSoundPosition(gg_snd_DarkSummoningTarget1, d.x, d.y, 0.)
    call SetSoundVolume(gg_snd_DarkSummoningTarget1, PercentToInt(100., 127))
    if (gg_snd_DarkSummoningTarget1 != null) then
        call StartSound(gg_snd_DarkSummoningTarget1)
    endif
    set d.lvl = GetUnitAbilityLevel(d.c, RawCode)
    call KT_Add(function Timer, d, period)
    return false
endfunction

private function DeathCallBack takes nothing returns boolean
    local data d = KT_GetData()
    
    if d.j > .1 / period then // 0.1: optimal remove timer.
        call RemoveUnit(d.die)
        set d.die = null
        return true
    endif
    set d.j = d.j + 1.
    return false
endfunction

private function DeathActions takes nothing returns nothing
    local data d = data.create()
    
    set d.die = GetTriggerUnit()
    call KT_Add(function DeathCallBack, d, period)
endfunction

//===========================================================================

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    local integer index = 0
    set non_leaking_filter = Filter( function DummyFilter )
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(index), EVENT_PLAYER_UNIT_DEATH, non_leaking_filter)
        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
    call TriggerAddCondition( t, Condition( function DeathConditions ) )
    call TriggerAddAction( t, function DeathActions )
    set t = null
    call GT_AddActionToStartsEffectEvent(RawCode, function Actions)
endfunction

endscope


Uses KeyTimers2 timer system. :p I assure you that the original model is a lot different: http://www.hiveworkshop.com/forums/resource.php?t=49971&prev=search=charmtarget&d=list&r=20

Tell me what you think!

EDIT: Updated to version 2! Many changes, you can look at them in post #14.
EDIT2: Updated!
 

Attachments

  • Ultima v2.w3x
    81.5 KB · Views: 567

Archideas

Active Member
Reaction score
32
Nice, I like it alot. But you should've done the Ultima from FFIX instead, it's way cooler. ;P
 

Romek

Super Moderator
Reaction score
963
Damn you! I always wanted to make a spell based on Ultima, called Ultima. :D

You're using both KT2 and TU? I think you could just use one of them (Probably TU for the precise wait).

> "return (TRUE)"
return true

I'll review it in more detail later, once I've graded a few more submissions. :)

Edit: Just tried it out in game. You could make it channeling, so it stops when you move, instead of what you're currently doing.
 

UndeadDragon

Super Moderator
Reaction score
447
Ultima has to be one of the best FF spells :thup:
 

Sim

Forum Administrator
Staff member
Reaction score
534
> Edit: Just tried it out in game. You could make it channeling, so it stops when you move, instead of what you're currently doing.

Ultima said:
Ultima is not an interruptable spell. :) Its power is beyond control.

:rolleyes: I see your concern, but Ultima is Ultima.

Regarding timer systems, I think I'll make 2 versions to see if there's a difference.
 

Romek

Super Moderator
Reaction score
963
> I see your concern, but Ultima is Ultima.
What if the user themselves decide to move, because they want to cancel the spell themselves?
Also remember, this is Warcraft, not FF. Active spells are either channeling or instant. :)
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Awesome Ultima is awesome! :D

And that FF6 clip sure was nostalgic.
 

wraithseeker

Tired.
Reaction score
122
I like the effect but you could just use TimerUtils only instead of Key timers 2.

Instead of pausing the unit, give the unit some sort of a command that blocks orders.

Pausing units is very very ugly and do not do that unless it is absolutely needed.

JASS:
private constant function DummyFilter takes nothing returns boolean
    return (TRUE)
endfunction


to

JASS:
private constant function DummyFilter takes nothing returns boolean
    return TRUE
endfunction


You do not need d.dummy as a struct, use a local unit instead.

Merge the two structs together, you don't need 2.

Also, you do not need to null struct members as they are just integers.

Do just d.destroy() and don't go a longg way.

Any reason for you to use a global GUI variable sound? Take a look at SimError, Vexorian now doesn't use a global variable but instead create it manually.

For d.c, why are you nulling it every callback, you don't even need to :p

Also why are you using GT? It is not stable as of currently since It isn't approved yet.


JASS:
private function DeathCallBack takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local deathdata d = GetTimerData(t)
    
    call RemoveUnit(d.die)
endfunction


to

JASS:
private function DeathCallBack takes nothing returns nothing
    local deathdata d = deathdata(GetTimerData(GetExpiredTimer()))
    call RemoveUnit(d.die)
endfunction


You also do not need d.x and d.y, use locals instead.


Other then that the code is good.

I hope you will take my review and change them well.
 
Reaction score
91
> Also, you do not need to null struct members as they are just integers.
What? You've got things wrong. The struct is an integer, not its members - the members just get redeclared when the struct gets destroyed (not used any more). I agree on the part that it doesn't need nulling.

JASS:

local deathdata d = deathdata(GetTimerData(GetExpiredTimer()))

Typecasting isn't really needed here. And he's going to leak the timer.
 

D.V.D

Make a wish
Reaction score
73
The spell is kinds laggy:(. Maybe make the units in a bigger period of time.
 

Sim

Forum Administrator
Staff member
Reaction score
534
> And that FF6 clip sure was nostalgic.

Yea, sure was!

Thanks for the numerous feedbacks guys, really appreciated. However...

> Also remember, this is Warcraft, not FF. Active spells are either channeling or instant.
> Pausing units is very very ugly and do not do that unless it is absolutely needed.

I understand your concerns, but this isn't how the spell is designed. FF or not, it is intended that the caster may not move/be interrupted during the casting. Also, if I may, you said yourself "unless it is absolutely needed". Well, it is. Once started, the orb is expected to reach epic proportions and explode, no matter what happens. It isn't meant to be balanced in a normal game, remember. The caster is paused especially to prevent him from instant-canceling and walking away.

Spells can also be a mix between channeling and instant. "Channel" exists for that and provides a solution!

> I like the effect but you could just use TimerUtils only instead of Key timers 2.

I will post 2 versions of the spell. One using KT2 and TU and one using only TU.

> You do not need d.dummy as a struct, use a local unit instead.

Indeed.

> Merge the two structs together, you don't need 2.

Will do.

> Also, you do not need to null struct members as they are just integers.
> I agree on the part that it doesn't need nulling.

http://www.hiveworkshop.com/forums/1042989-post22.html

Although many would say "but that's just n leaks, where n is the number of global variables" I would answer "well, that's n less leaks to worry about". Although the leaks are constantly re-used, why keep them around? Because = null is a slow command? It's worth nulling for.

> Any reason for you to use a global GUI variable sound? Take a look at SimError, Vexorian now doesn't use a global variable but instead create it manually.

Thanks for sharing!

> For d.c, why are you nulling it every callback, you don't even need to

Well, personally, I see the nulling inside the end callback loop, which happens onDestroy...?

> Also why are you using GT? It is not stable as of currently since It isn't approved yet.

Approved or not, it needs testing. I'm using it in every spell I make and it works. I used KT before it was approved and it never failed me.

Proove the system wrong and I will stop using it. For now, though, I see it as a better way of using events that's perfectly logic as to how it works.

> You also do not need d.x and d.y, use locals instead.

It is there for safety. In case the caster *ever* gets moved, somehow, so the final sphere still appears where he was standing when casting the spell. I want his original location saved, not his current. Knockback, anyone?

> Typecasting isn't really needed here. And he's going to leak the timer.

Yup.

Thanks for the many comments! I hope you liked the spell!

EDIT: Mind explaining how SimError might work? I understand it takes a sound from label but wouldn't that be a global variable, in fact?
 

Romek

Super Moderator
Reaction score
963
How about adding a constant boolean, so the user may decide whether or not the spell should be interruptible? I personally would want it to function as any normal channeling spell.
 

Sim

Forum Administrator
Staff member
Reaction score
534
Well then, the explosion would only occur if the spell reaches its final stage. :)

EDIT: I have a request for anybody. Could someone edit out Invoke.mdx (The spell's model) so it doesn't do this sound? I can live with it, but if it was to disappear, it would be great. :) I don't have the programs. It's the charm sound.
 

Sim

Forum Administrator
Staff member
Reaction score
534
New version released!

- Code changes. Sounds are still global though.
- Now only uses KeyTimers2 as timer system.
- Now channeling. Even features an "advancement" option.

Enjoy!
 

Builder Bob

Live free or don't
Reaction score
249
Very nice spell

It was very impressive while building, but didn't it vanish a bit too quickly when it exploded? Would be cool to add a white screen for players who are looking in it's location, sort of like a flash bang effect. You could use a cinematic filter.

EDIT: I have a request for anybody. Could someone edit out Invoke.mdx (The spell's model) so it doesn't do this sound? I can live with it, but if it was to disappear, it would be great. :) I don't have the programs. It's the charm sound.

Here you go
 

Attachments

  • Invoke.mdx
    18.5 KB · Views: 345

Sim

Forum Administrator
Staff member
Reaction score
534
> Would be cool to add a white screen for players who are looking in it's location, sort of like a flash bang effect.

Isn't it doing exactly that at the end?

> Here you go

Cool, thanks! I'm going to test that in a few. :)

EDIT: Oh yea, I see what you mean. I'm just creating a huge white dummy, but I guess I could use a cinematic filter too!
 

Jesus4Lyf

Good Idea™
Reaction score
397
Yo, haven't studied the code much, but a couple of points...

Firstly, looks pretty awesome. :D

>Would be cool to add a white screen for players who are looking in it's location, sort of like a flash bang effect. You could use a cinematic filter.

Cinematic filters that don't apply to all players cause desynchs. Correct me if I'm wrong.

You're using KT2 verson 1.1! Naughty. Replacing it with 1.5 directly causes no problems. :D

Try to change the period from 0.01 seconds to 0.03125 or something else. 0.01 is too fast - it lags on my computer (code lag). As a rule of thumb, don't use any periods under 0.025 - these will inevitably lag on a slower computer with any timer system. I recommend 0.03125 (32 executions per second, and KT2 does support it as of version 1.5). Changing the period to 0.03125 seemed to fix all lag for me, but the thing didn't grow to the same size. So see what you can do. Having a few standard periods you try to reuse in all your spells is a good idea. I never go below 0.03125. :thup:

JASS:
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(index), EVENT_PLAYER_UNIT_DEATH, non_leaking_filter)
        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop

Ewwwww! I should add this to GTrigger. :D (You've done it the best way possible, until then. :))

I'll try to get GTrigger upgraded and approved within the next few days.
 

Azlier

Old World Ghost
Reaction score
461
>Cinematic filters that don't apply to all players cause desynchs. Correct me if I'm wrong.

They don't. GUI ones do, because they destroy and create timers. But properly done cinematic filters work just fine for a single player.
 

Sim

Forum Administrator
Staff member
Reaction score
534
> Ewwwww! I should add this to GTrigger. (You've done it the best way possible, until then. )

These were exactly my thoughts. :rolleyes:

The problem with changing the period to something such as 0.03125, is that the sphere will grow awkwardly. It is much more fluid with a 0.01 period. And there's quite a difference.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top