• 🏆 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!

Orianna Spellpack 1.0.3

Orianna the Lady of Clockwork Spellpack

Video

Introduction:

This is the third installment in my League of Legends spellpack series. Orianna took much longer than the others at over a thousand lines of code and unfortunately my lack of time delayed it further.

Notes:

  • I've created this pack with the intention that the abilities matched the real ones in league of legends as close as possible. I have not modified game constants to make level 18 stats match those of league of legends identically, I just aimed for values that were close enough by choosing good stats and stats per level, etc.
  • Since "One for All" mode exists right now which would allow me to accurately test the behaviour of Orianna in a multi-instanced environment, I want to note that I have not tested this to such an extent. I created this spellpack to behave in an MUI fashion that I believe to be ideal.
  • I've chosen not to follow exactly trivial code modification standards for his spells because this is designed to match Orianna with extreme accuracy, not designed for others to modify.
  • Yes, this is fully MUI. Try not to drool on your keyboard.

Description of Spells:

Clockwork Windup (Innate): Orianna’s autoattacks deal 10 / 18 / 26 / 34 / 42 / 50 (+ 15% intelligence) bonus magic damage on hit. Subsequent attacks on the same target within 4 seconds deal an additional 20% magic damage. This bonus stacks up to 2 times, dealing a maximum of 14 / 25.2 / 36.4 / 47.6 / 58.8 / 70 (+ 21% intelligence) magic damage.

Command: Attack (Active): Orianna commands the Ball to fly towards the target location, dealing magic damage to all enemies that the Ball passes through or that are in the target area. However, the Ball deals 10% less damage for each subsequent target hit down to a minimum of 40% damage done. After Command: Attack is used, the Ball remains behind at the target location.

Command: Dissonance (Active): Orianna commands the Ball to emit an electric pulse around its current location, dealing magic damage to enemies within 250 range and leaving an electric field on the area for 3 seconds that speeds up allies and slows enemies that walk over it. This effect diminishes to normal over 2 seconds after leaving the area.

Command: Protect (Passive/Active): Passive: The allied champion the Ball is currently attached to is granted bonus armor and magic resistance. Active: Orianna commands the Ball to fly to and attach onto an allied champion, dealing damage to enemies it passes through and shielding the allied champion for 4 seconds when it arrives.

Command: Shockwave (Active): Orianna commands the Ball to unleash a shockwave after 0.5 seconds, dealing magic damage to enemies within 400 range and flinging them into the air 350 range towards, and possibly over, the Ball.

Requirements:


* Yes I know it's deprecated. If anyone actually cares, I can change it to another lightweight library.

The Scripts:


JASS:
// This is the main Orianna module which contains most of the code
// that handles Orianna's abilities. We depend on DamageType because
// Shield depends on DamageType, and is necessary to implement
// Command: Protect.
// Note: This library contains four (4) customizable sections
library Orianna requires DamageType
    // InitShim is a small adapter used to prevent a specific
    // instantiation issue.
    private module InitShim
        //The only implemented method necessary for the adapter
        private static method onInit takes nothing returns nothing
            call i()
        endmethod
    endmodule
    
    // Throw handler is a private module used to throw units with
    // a specific pattern which we desire to behave like Command:
    // Shockwave in League of Legends. Hence it is only used by
    // Command: Shockwave
    private struct ThrowHandler
        private unit u
        private real dX
        private real dY
        private real dZ
        
        private static thistype array DB
        private static integer dbIndex=-1
        private static timer time=CreateTimer()
        
        
        //*********************************************************
        // This section to be modified by the user
        //*********************************************************
        
        // The clock period determines the frame rate of the
        // throwing algorithm. A value between 1./20. and 1./60. is
        // recommended.
        private static constant real CLOCK_PERIOD     =1./30.
        
        // The rate in units per second at which thrown units 
        // accelerate towards the ground.
        private static constant real GRAVITY          =200.
        
        // Below this height, units are considered back on the
        // ground. Choose something below 10. or you'll have visual
        // abnormalities.
        private static constant real GROUND_HEIGHT_MAX=5.
        
        //*********************************************************
        // End customizable section
        //*********************************************************
        
        
        // ThrowHandler.p is the periodic function ThrowHandler
        // uses to handle multiple units simultaneously.
        private static method p takes nothing returns nothing
            local thistype t
            local integer i=0
            loop
                exitwhen i>dbIndex
                set t=DB[i]
                call SetUnitFlyHeight(t.u,GetUnitFlyHeight(t.u)+t.dZ,0.)
                set t.dZ=t.dZ-GRAVITY*CLOCK_PERIOD
                call SetUnitX(t.u,GetUnitX(t.u)+t.dX)
                call SetUnitY(t.u,GetUnitY(t.u)+t.dY)
                if GetUnitFlyHeight(t.u)<GROUND_HEIGHT_MAX and t.dZ<0. then
                    call t.destroy()
                    set DB[i]=DB[dbIndex]
                    set dbIndex=dbIndex-1
                    set i=i-1
                    if dbIndex==-1 then
                        call PauseTimer(time)
                    endif
                endif
                set i=i+1
            endloop
        endmethod
        
        // This is the public method used for imparting a throw vector on
        // a unit. This is the only public interface with the struct.
        public static method add takes unit u, real dx, real dy, real dz returns nothing
            local thistype tempDat=thistype.create()
            set tempDat.u=u
            set tempDat.dX=dx*CLOCK_PERIOD
            set tempDat.dY=dy*CLOCK_PERIOD
            set tempDat.dZ=dz*CLOCK_PERIOD
            set dbIndex=dbIndex+1
            set DB[dbIndex]=tempDat
            if UnitAddAbility(u,'Arav') then
                call UnitRemoveAbility(u,'Arav')
            endif
            if dbIndex==0 then
                call TimerStart(time,CLOCK_PERIOD,true,function thistype.p)
            endif
        endmethod
    endstruct
    
    // HasteHandler is a private struct used to increase the speed of
    // allied units in a specific fashion implemented by Command:
    // Distortion.
    private struct HasteHandler
        
        
        //**********************************************************
        // Customizable Section
        //**********************************************************
        
        // The full haste duration units are imparted after leaving
        // the distortion field in seconds.
        private static constant real HASTE_DURATION=2.
        
        // The clock period allows the user to control how accurate
        // the slowing effect is. Recommend a value between 1./2. and
        // 1./60.
        private static constant real CLOCK_PERIOD=1./5.
        
        // The rawcode ID of the dummy caster unit
        private static constant integer DUMMY_CASTER_ID='h004'
        
        // The rawcode ID of the haste ability
        private static constant integer HASTE_ABILITY_ID='A007'
        
        // The raw string order ID of the haste ability
        private static constant string HASTE_STRING="bloodlust"
        //**********************************************************
        // End Customizable Section
        //**********************************************************
        
        
        private static thistype array DB
        private static integer dbIndex=-1
        private static HandleTable fromU
        private static group grp=CreateGroup()
        private static timer clock=CreateTimer()
        private static unit caster
        
        private unit who
        private integer level=0
        private real timeLeft=HASTE_DURATION
        
        // The periodic method used to control the hasting of all
        // units simultaneously
        private static method periodic takes nothing returns nothing
            local thistype tempDat
            local integer index=0
            loop
                exitwhen index>dbIndex
                set tempDat=DB[index]
                set tempDat.timeLeft=tempDat.timeLeft-CLOCK_PERIOD
                call SetUnitAbilityLevel(thistype.caster,thistype.HASTE_ABILITY_ID,R2I(tempDat.level*tempDat.timeLeft/thistype.HASTE_DURATION)+1)
                call IssueTargetOrder(thistype.caster,HASTE_STRING,tempDat.who)
                if tempDat.timeLeft<=0. then
                    call fromU.flush(tempDat.who)
                    call tempDat.destroy()
                    set DB[index]=DB[dbIndex]
                    set thistype.dbIndex=thistype.dbIndex-1
                    if dbIndex==-1 then
                        call PauseTimer(thistype.clock)
                    endif
                endif
                set index=index+1
            endloop
        endmethod
        
        // This is the public method used to *set* a unit's haste
        // value. This is important because we need to be able to
        // over-write a unit's existing haste value if it steps
        // back onto the distortion ring.
        public static method setUnit takes unit u, integer z returns nothing
            local thistype tempDat
            if fromU.exists(u) then
                set tempDat=fromU[u]
                set tempDat.level=z
                set tempDat.timeLeft=HASTE_DURATION
            else
                set tempDat=thistype.create()
                set tempDat.who=u
                set tempDat.level=z
                set fromU[u]=tempDat
                set dbIndex=dbIndex+1
                set DB[dbIndex]=tempDat
                if dbIndex==0 then
                    call TimerStart(clock,CLOCK_PERIOD,true,function thistype.periodic)
                endif
            endif
            call SetUnitAbilityLevel(thistype.caster,HASTE_ABILITY_ID,z)
            call IssueTargetOrder(thistype.caster,HASTE_STRING,tempDat.who)
        endmethod
        
        // The initialization function called in the InitShim which
        // creates a static dummy
        private static method i takes nothing returns nothing
            set thistype.fromU=HandleTable.create()
            set thistype.caster=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),DUMMY_CASTER_ID,0.,0.,0.)
            call UnitAddAbility(thistype.caster,HASTE_ABILITY_ID)
        endmethod
        
        //calls i() on initialization
        implement InitShim
    endstruct
    
    // SlowHandler is a private interface for handling the specific
    // slow procedure used in Command: Distortion
    private struct SlowHandler
    
    
        //***********************************************************
        // Customizable Section
        //***********************************************************
        
        // The duration of slows after leaving the distortion field
        private static constant real SLOW_DURATION=2.
        
        // The clock period for handling the slow timings. I suggest
        // a value between 1./2. and 1./30.
        private static constant real CLOCK_PERIOD=1./5.
        
        // The unit ID of the dummy caster
        private static constant integer DUMMY_CASTER_ID='h004'
        
        // The ability ID of slow
        private static constant integer SLOW_ABILITY_ID='A006'
        
        // The order ID for the slow ability
        private static constant string SLOW_ORDER="slow"
        
        //***********************************************************
        // End Customizable Section
        //***********************************************************
        
        
        private static thistype array DB
        private static integer dbIndex=-1
        private static HandleTable fromU
        private static group grp=CreateGroup()
        private static timer clock=CreateTimer()
        private static unit caster
        
        private unit who
        private integer level=0
        private real timeLeft=SLOW_DURATION
        
        // Periodic function used to handle all the slow effects
        // simultaneously.
        private static method periodic takes nothing returns nothing
            local thistype tempDat
            local integer index=0
            loop
                exitwhen index>dbIndex
                set tempDat=DB[index]
                set tempDat.timeLeft=tempDat.timeLeft-CLOCK_PERIOD
                call SetUnitAbilityLevel(thistype.caster,thistype.SLOW_ABILITY_ID,R2I(tempDat.level*tempDat.timeLeft/thistype.SLOW_DURATION)+1)
                call IssueTargetOrder(thistype.caster,SLOW_ORDER,tempDat.who)
                if tempDat.timeLeft<=0. then
                    call fromU.flush(tempDat.who)
                    call tempDat.destroy()
                    set DB[index]=DB[dbIndex]
                    set thistype.dbIndex=thistype.dbIndex-1
                    if dbIndex==-1 then
                        call PauseTimer(thistype.clock)
                    endif
                endif
                set index=index+1
            endloop
        endmethod
        
        // The only public interface available for the SlowHandler.
        // Allows us to set the slow level of a unit arbitrarily.
        public static method setUnit takes unit u, integer z returns nothing
            local thistype tempDat
            if fromU.exists(u) then
                set tempDat=fromU[u]
                set tempDat.level=z
                set tempDat.timeLeft=SLOW_DURATION
            else
                set tempDat=thistype.create()
                set tempDat.who=u
                set tempDat.level=z
                set fromU[u]=tempDat
                set dbIndex=dbIndex+1
                set DB[dbIndex]=tempDat
                if dbIndex==0 then
                    call TimerStart(clock,CLOCK_PERIOD,true,function thistype.periodic)
                endif
            endif
            call SetUnitAbilityLevel(thistype.caster,SLOW_ABILITY_ID,z)
            call IssueTargetOrder(thistype.caster,"slow",tempDat.who)
        endmethod
        
        // Initialization function called by InitShim
        private static method i takes nothing returns nothing
            set thistype.fromU=HandleTable.create()
            set thistype.caster=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),DUMMY_CASTER_ID,0.,0.,0.)
            call UnitAddAbility(thistype.caster,SLOW_ABILITY_ID)
        endmethod
        
        implement InitShim
    endstruct
    
    // Essentially a sound in stuct form - necessary due
    // to the implementation specifics
    private struct soundShim
        sound s
    endstruct
    
    // Struct which overrides the library title. Here is
    // the real contents of Orianna.
    struct Orianna
        private static group grp=CreateGroup()
        private static integer dbIndex=-1
        private static timer clock=CreateTimer()
        private static thistype array DB
        private static HandleTable fromOri
        private static HandleTable preloadTable
        
        
        //**************************************************************
        // Customizable Section
        //**************************************************************
        
        // The unit rawcode for the dummy which points to an Orianna
        // instance's ball
        private static constant integer ARROW_ID='h001'
        
        // Unit rawcode for a ball
        private static constant integer BALL_ID='h000'
        
        // Unit rawcode for the Command: Disonnance indicator
        private static constant integer DISONNANCE_INDICATOR_ID='h003'
        
        // Unit rawcode for the small stand below an immobile ball
        private static constant integer STAND_INDICATOR_ID='h002'
        
        // Unit rawcode for the circle that appears while Command:
        // Shockwave is charging
        private static constant integer SHOCKWAVE_INDICATOR_ID='h005'
        
        // Unit rawcode for the dummy electric balls that appear
        // while casting Command: Shockwave
        private static constant integer SHOCKWAVE_ORB_ID='h006'
        
        // Opacity value for the ball (Out of 255)
        private static constant integer BALL_OPACITY=200
        
        // Opacity value for the disonnance indicator (Out of 255)
        private static constant integer DISONNANCE_INDICATOR_OPACITY=155
        
        // Disonnance indicator Red concentration (Out of 255)
        private static constant integer DISONNANCE_INDICATOR_RED=55
        
        // Disonnance indicator Green concentration (Out of 255)
        private static constant integer DISONNANCE_INDICATOR_GREEN=165
        
        // Disonnance indicator Blue concentration (Out of 255)
        private static constant integer DISONNANCE_INDICATOR_BLUE=55
        
        // How far away should Orianna's ball pointer be from here
        // in game units
        private static constant real ARROW_HOVER_DISTANCE=100.
        
        // Model scale of the ball pointer
        private static constant real ARROW_SCALE=.5
        
        // Fly height of the ball pointer
        private static constant real ARROW_FLY_HEIGHT=50.
        
        // Distance to the ball at which the ball pointer is hidden
        private static constant real ARROW_MIN_RANGE=150.
        
        // Maximum distance at which the arrow is green (close)
        private static constant real ARROW_GREEN_RANGE=650.
        
        // Maximum distance at which the arrow is yellow (far)
        private static constant real ARROW_YELLOW_RANGE=925.
        
        // The radius around the ball which damages enemies while moving
        private static constant real BALL_ATTACK_DAMAGE_RADIUS=100.
        
        // The sound pitch of the ball's begin move sound
        private static constant real BALL_MOVE_START_SOUND_PITCH=1.25
        
        // The sound pitch of the ball's hit sound
        private static constant real BALL_MOVE_HIT_SOUND_PITCH=.5
        
        // The sound pitch of the ball's end move sound
        private static constant real BALL_MOVE_END_SOUND_PITCH=.75
        
        // The flying height a ball rests at while on a unit
        private static constant real BALL_OVERHEAD_HEIGHT=150.
        
        // The flyight height a ball rests at while on the ground
        private static constant real BALL_GROUND_HEIGHT=50.
        
        // The animation scale (speed) used by a ball by default
        private static constant real BALL_TIME_SCALE=1./10.
        
        // The animation scale (speed) used by a ball on the ground
        private static constant real BALL_TIME_SCALE_GROUND=1./5.
        
        // The maximum range a ball can be away from Orianna while
        // on the ground
        private static constant real BALL_LEASH_RANGE_GROUND=1125.
        
        // The maximum range a ball can be away from Orianna while
        // attached to another unit
        private static constant real BALL_LEASH_RANGE_ONUNIT=1225.
        
        // How close must Orianna get to the ball to pick it up
        private static constant real BALL_PICKUP_RANGE=64.
        
        // How fast should the ball move while under the effects of
        // Command: Attack or Command: Shockwave
        private static constant real BALL_VELOCITY=1200.
        
        // How accurate should the timing clock be for Orianna?
        // Recommend value between 1./27. and 1./80.
        private static constant real CLOCK_PERIOD=1./90.
        
        // How long is too long between attacks to retain Orianna's
        // innate?
        private static constant real CLOCKWORK_WINDUP_TIMEOUT=4.
        
        // How much flat damage should Command: Attack deal?
        private static constant real COMMAND_ATTACK_FLAT_DAMAGE=30.
        
        // How much damage per level should Command: Attack deal?
        private static constant real COMMAND_ATTACK_DAMAGE_PER_LEVEL=30.
        
        // What multiplier should be used for Command: Attack
        private static constant real COMMAND_ATTACK_INTELLIGENCE_MULTIPLIER=.5
        
        // What is the minimum damage multiplier a unit can take from
        // Command: Attack?
        private static constant real COMMAND_ATTACK_MINIMUM_DAMAGE_MULTIPLIER=.4
        
        // How much should Command: Attack's damage be reduced on
        // subsequent hit?
        private static constant real COMMAND_ATTACK_DAMAGE_REDUCTION=.1
        
        // Modle scale of the disonnance indicator
        private static constant real DISONNANCE_INDICATOR_SCALE=2.5
        
        // Fly height of the active disonnance indicator
        private static constant real DISONNANCE_INDICATOR_HEIGHT=25.
        
        // Radius of the active disonnance effect
        private static constant real DISONNANCE_RADIUS=190.
        
        // Flat damage of Command: Disonnance
        private static constant real DISONNANCE_FLAT_DAMAGE=25.
        
        // Additional damage per level of Command: Disonnance
        private static constant real DISONNANCE_DAMAGE_PER_LEVEL=45.
        
        // Multiplier for intelligence based damage on Command:
        // Disonnance
        private static constant real DISONNANCE_INTELLIGENCE_MULTIPLIER=.7
        
        // Duration of Command: Disonnance's Active Effect
        private static constant real DISONNANCE_ACTIVE_DURATION=3.
        
        // The duration at the end of Disonnance's effect during which
        // the Disonnance indicator should fade away
        private static constant real DISONNANCE_INDICATOR_FADE_PERIOD=.25
        
        // Fly height off the ball when attached to an allied unit
        private static constant real PROTECT_ATTACH_RANGE=100.
        
        // Flying height given to units who are "hidden" in the top
        // left of the map
        private static constant real DUMMY_HIDDEN_HEIGHT=2000.
        
        // Model scale of "hidden" units
        private static constant real DUMMY_HIDDEN_SCALE=1./100.
        
        // X value of the map's top left corner (should be inside the
        // map boundary)
        private static constant real MAP_TOP_LEFT_X=-7680.
        
        // Y value
        private static constant real MAP_TOP_LEFT_Y=5630.
        
        // model scale of the dummy indicator for a ball on the ground
        private static constant real STAND_INDICATOR_SCALE=.4
        
        // Duration in seconds of the shield imparted by Command:
        // Protect
        private static constant real SHIELD_DURATION=4.
        
        // Flat shield value of Command: Protect
        private static constant real SHIELD_FLAT_AMOUNT=40.
        
        // Flat damage value of Command: Protect
        private static constant real SHIELD_FLAT_DAMAGE=30.
        
        // Damage per level of Command: Protect
        private static constant real SHIELD_DAMAGE_PER_LEVEL=30.
        
        // Additional intelligence based damage multiplier for
        // Command: Protect
        private static constant real SHIELD_BONUS_DAMAGE_MULTIPLIER=.3
        
        // Shield value per level of Command: Protect
        private static constant real SHIELD_AMOUNT_PER_LEVEL=40.
        
        // Bonus Shield per intelligence of Command: Protect
        private static constant real SHIELD_BONUS_MULTIPLIER=.4
        
        // Model scale of the Command: Shockwave indicator
        private static constant real SHOCK_INDIC_SCALE=8.
        
        // Radius of Command: Shockwave's effect
        private static constant real SHOCK_RADIUS=300.
        
        // Flying height of the Shockwave Indicator
        private static constant real SHOCK_INDIC_HEIGHT=-100.
        
        // Duration of Command: Shockwave's windup animation
        private static constant real SHOCK_WINDUP_DURATION=.5
        
        // Rotational velocity of shockwave orbs during the
        // windup animation
        private static constant real SHOCK_WINDUP_ORB_SPEED=3.*bj_PI
        
        // The phase difference between the shockwave orbs
        private static constant real SHOCK_ORB_PHASE_DIFFERENCE=2.*bj_PI/3.
        
        // Model scale of shockwave orbs
        private static constant real SHOCK_ORB_SCALE=1.25
        
        // XY velocity at which units are thrown under the
        // effect of Command: Shockwave
        private static constant real THROW_VELOCITY=500.
        
        // Multiplier for the initial vertical velocity
        private static constant real THROW_VERT_MULTIPLIER=3.
        
        // Spawned effect when an ability hits a unit
        private static constant string ABILITY_HIT_EFFECT="Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl"
        
        // Model file for the dummy that rests on the ball while it
        // remains on the ground
        private static constant string BALL_GROUND_EFFECT="buildings\\other\\CircleOfPower\\CircleOfPower.mdl"
        
        // Sound played when the ball begins moving
        private static constant string BALL_MOVE_START_SOUND="Abilities\\Spells\\Orc\\Ensnare\\EnsnareMissile.wav"
        
        // Sound played when the ball finishes moving
        private static constant string BALL_MOVE_END_SOUND="Abilities\\Weapons\\ShadowHunterMissile\\HeroShadowhunterMissileHit1.wav"
        
        // Model displayed when disonnance occurs
        private static constant string DISONNANCE_EFFECT="Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl"
        
        // Model displayed when disonnacne hits a unit
        private static constant string DISONNANCE_HIT_EFFECT="Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodFootman.mdl"
        
        // Attachment point for the effect that occurs when a unit
        // is hit by Command: Dissonance
        private static constant string DISONNANCE_EFFECT_TARGET="chest"
        
        // Model used when Command: Protect shields a unit
        private static constant string SHIELD_FX="Abilities\\Spells\\Human\\ManaShield\\ManaShieldCaster.mdl"
        
        // Orianna Raw ID
        public static constant integer ID='E000'
        
        // Volume passed for max volume (sounds)
        public static constant integer SOUND_MAX_VOLUME=127
        
        // Fade rate passed for max fade rate (sounds)
        public static constant integer SOUND_MAX_FADERATE=12700
        
        // Ball state values (change this if you want to avoid
        // using identical values within other interfaces... I guess)
        public static constant integer BALL_STATE_DEAD='d'
        public static constant integer BALL_STATE_HOME='h'
        public static constant integer BALL_STATE_ATTACK='a'
        public static constant integer BALL_STATE_GROUND='g'
        public static constant integer BALL_STATE_TARGET='t'
        public static constant integer BALL_STATE_ONUNIT='o'
        public static constant integer BALL_STATE_SHOCKWAVE='s'
        
        // Ability raw ID for command: attack
        public static constant integer COMMAND_ATTACK_ID='A002'
        
        // Ability raw ID for the PASSIVE (disabled) Command: attack
        public static constant integer COMMAND_ATTACK_DIS_ID='A004'
        
        // Ability IDs for the active and passive Command: Disonnance
        public static constant integer COMMAND_DISONNANCE_ID='A005'
        public static constant integer COMMAND_DISSONANCE_DIS_ID='A008'
        
        // Ability IDs for the active and passive Command: Protect
        public static constant integer COMMAND_PROTECT_ID='A009'
        public static constant integer COMMAND_PROTECT_DIS_ID='A00A'
        
        // Ability ID for Command: Protect's passive effect
        public static constant integer PROTECT_PASSIVE_ID='A00D'
        
        //  Ability IDs for the active and passiev Command: Shockwave
        public static constant integer COMMAND_SHOCKWAVE_ID='A00B'
        public static constant integer COMMAND_SHOCKWAVE_DIS_ID='A00C'
        
        // Method ID passed. This probably won't have much use unless
        // you decide to implement a second, different version of
        // Orianna's innate (why?)
        public static constant integer METHOD_INCREMENT_AND_COUNT='a'
        
        //**************************************************************
        // End Customizable Section
        //**************************************************************
        
        private group alreadyHit
        private unit ori
        private unit ball
        private unit arrow
        private unit shockwaveIndicator
        private unit shockwaveOrb1
        private unit shockwaveOrb2
        private unit shockwaveOrb3
        private unit standIndicator
        private unit disonnanceIndicator
        private unit target
        private unit pTarget
        private unit protectBonusUnit=null
        private integer attackCount=0
        private integer state=0
        private integer steps=0
        private integer attackRevertLevel=0
        private integer dissonanceRevertLevel=0
        private integer protectRevertLevel=0
        private integer shockwaveRevertLevel=0
        private real counterTimeout=0.
        private real targetX=0.
        private real targetY=0.
        private real damage=0.
        private real disonnanceTimeLeft=0.
        private real delX=0.
        private real delY=0.
        private real delZ=0.
        private real minDamage=0.
        private real shieldAmount=0.
        
        // Method used to passify (disable) Orianna's abilities - occurs
        // when the ball already has some action.
        private method passifyAbilities takes nothing returns nothing
            set this.attackRevertLevel=GetUnitAbilityLevel(ori,COMMAND_ATTACK_ID)
            set this.dissonanceRevertLevel=GetUnitAbilityLevel(ori,COMMAND_DISONNANCE_ID)
            set this.protectRevertLevel=GetUnitAbilityLevel(ori,COMMAND_PROTECT_ID)
            set this.shockwaveRevertLevel=GetUnitAbilityLevel(ori,COMMAND_SHOCKWAVE_ID)
            if GetUnitAbilityLevel(this.ori,COMMAND_ATTACK_DIS_ID)>0 then
                call SetUnitAbilityLevel(this.ori,COMMAND_ATTACK_ID,5)
                call IncUnitAbilityLevel(this.ori,COMMAND_ATTACK_ID)
                call UnitAddAbility(this.ori,COMMAND_ATTACK_DIS_ID)
            endif
            if GetUnitAbilityLevel(this.ori,COMMAND_DISONNANCE_ID)>0 then
                call SetUnitAbilityLevel(this.ori,COMMAND_DISONNANCE_ID,5)
                call IncUnitAbilityLevel(this.ori,COMMAND_DISONNANCE_ID)
                call UnitAddAbility(this.ori,COMMAND_DISSONANCE_DIS_ID)
            endif
            if GetUnitAbilityLevel(this.ori,COMMAND_PROTECT_ID)>0 then
                call SetUnitAbilityLevel(this.ori,COMMAND_PROTECT_ID,5)
                call IncUnitAbilityLevel(this.ori,COMMAND_PROTECT_ID)
                call UnitAddAbility(this.ori,COMMAND_PROTECT_DIS_ID)
            endif
            if GetUnitAbilityLevel(this.ori,COMMAND_SHOCKWAVE_ID)>0 then
                call SetUnitAbilityLevel(this.ori,COMMAND_SHOCKWAVE_ID,3)
                call IncUnitAbilityLevel(this.ori,COMMAND_SHOCKWAVE_ID)
                call UnitAddAbility(this.ori,COMMAND_SHOCKWAVE_DIS_ID)
            endif
        endmethod
        
        // Method used to re-activate Orianna's active abilities
        private method actifyAbilities takes nothing returns nothing
            call UnitRemoveAbility(this.ori,COMMAND_ATTACK_DIS_ID)
            call SetUnitAbilityLevel(this.ori,COMMAND_ATTACK_ID,this.attackRevertLevel)
            call UnitRemoveAbility(this.ori,COMMAND_DISSONANCE_DIS_ID)
            call SetUnitAbilityLevel(this.ori,COMMAND_DISONNANCE_ID,this.dissonanceRevertLevel)
            call UnitRemoveAbility(this.ori,COMMAND_PROTECT_DIS_ID)
            call SetUnitAbilityLevel(this.ori,COMMAND_PROTECT_ID,this.protectRevertLevel)
            call UnitRemoveAbility(this.ori,COMMAND_SHOCKWAVE_DIS_ID)
            call SetUnitAbilityLevel(this.ori,COMMAND_SHOCKWAVE_ID,this.shockwaveRevertLevel)
        endmethod
        
        // Public method called when a client wants to Command: Attack a location
        public static method attack takes unit ori, real x, real y returns nothing
            local thistype tempDat=fromOri[ori]
            local real dist
            local real bx=GetUnitX(tempDat.ball)
            local real by=GetUnitY(tempDat.ball)
            local real theta=Atan2(y-by,x-bx)
            local sound snd=CreateSound(BALL_MOVE_START_SOUND,false,true,true,SOUND_MAX_FADERATE,SOUND_MAX_FADERATE,"")
            call SetSoundVolume(snd,SOUND_MAX_VOLUME)
            call SetSoundPosition(snd,bx,by,BALL_GROUND_HEIGHT)
            call SetSoundPitch(snd,BALL_MOVE_START_SOUND_PITCH)
            call StartSound(snd)
            call KillSoundWhenDone(snd)
            call GroupClear(tempDat.alreadyHit)
            if tempDat.state==BALL_STATE_GROUND then
                call SetUnitX(tempDat.standIndicator,MAP_TOP_LEFT_X)
                call SetUnitY(tempDat.standIndicator,MAP_TOP_LEFT_Y)
                call SetUnitScale(tempDat.standIndicator,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            endif
            set tempDat.state=BALL_STATE_ATTACK
            set tempDat.targetX=x
            set tempDat.targetY=y
            set dist=SquareRoot((x-bx)*(x-bx)+(y-by)*(y-by))
            set tempDat.steps=R2I(dist/(BALL_VELOCITY*CLOCK_PERIOD))
            set tempDat.delX=BALL_VELOCITY*CLOCK_PERIOD*Cos(theta)
            set tempDat.delY=BALL_VELOCITY*CLOCK_PERIOD*Sin(theta)
            set tempDat.delZ=(BALL_GROUND_HEIGHT-GetUnitFlyHeight(tempDat.ball))/tempDat.steps
            set tempDat.damage=COMMAND_ATTACK_FLAT_DAMAGE + COMMAND_ATTACK_DAMAGE_PER_LEVEL*tempDat.attackRevertLevel + COMMAND_ATTACK_INTELLIGENCE_MULTIPLIER*GetHeroInt(ori,true)
            set tempDat.minDamage=tempDat.damage*COMMAND_ATTACK_MINIMUM_DAMAGE_MULTIPLIER
            if tempDat.protectBonusUnit!=null then
                call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                set tempDat.protectBonusUnit=null
            endif
            call tempDat.passifyAbilities()
            set snd=null
        endmethod
        
        // Public method used when an Orianna should cast Command: Disonnance
        public static method disonnance takes unit ori returns nothing
            local thistype tempDat=fromOri[ori]
            local real cX=GetUnitX(tempDat.ball)
            local real cY=GetUnitY(tempDat.ball)
            local real damage=DISONNANCE_FLAT_DAMAGE+DISONNANCE_DAMAGE_PER_LEVEL*GetUnitAbilityLevel(tempDat.ori,COMMAND_DISONNANCE_ID)+GetHeroInt(tempDat.ori,true)*DISONNANCE_INTELLIGENCE_MULTIPLIER
            local unit FoG
            set tempDat.disonnanceTimeLeft=DISONNANCE_ACTIVE_DURATION
            call SetUnitX(tempDat.disonnanceIndicator,cX)
            call SetUnitY(tempDat.disonnanceIndicator,cY)
            call SetUnitScale(tempDat.disonnanceIndicator,DISONNANCE_INDICATOR_SCALE,DISONNANCE_INDICATOR_SCALE,DISONNANCE_INDICATOR_SCALE)
            call DestroyEffect(AddSpecialEffect(DISONNANCE_EFFECT,cX,cY))
            call GroupEnumUnitsInRange(thistype.grp,cX,cY,DISONNANCE_RADIUS,null)
            loop
                set FoG=FirstOfGroup(thistype.grp)
                exitwhen FoG==null
                if IsUnitEnemy(tempDat.ori,GetOwningPlayer(FoG)) and UnitAlive(FoG) then
                    call DamageType.dealCodeDamage(tempDat.ori,FoG,damage)
                    call DestroyEffect(AddSpecialEffectTarget(DISONNANCE_HIT_EFFECT,FoG,DISONNANCE_EFFECT_TARGET))
                endif
                call GroupRemoveUnit(thistype.grp,FoG)
            endloop
        endmethod
        
        // Public method used when an orianna should Command: Protect an allied unit
        public static method protect takes unit ori, unit target returns nothing
            local thistype tempDat=fromOri[ori]
            local real int=GetHeroInt(ori,true)
            set tempDat.pTarget=target
            call SetUnitTimeScale(tempDat.ball,BALL_TIME_SCALE)
            call SetUnitFlyHeight(tempDat.ball,BALL_OVERHEAD_HEIGHT,0.)
            call SetUnitX(tempDat.standIndicator,MAP_TOP_LEFT_X)
            call SetUnitY(tempDat.standIndicator,MAP_TOP_LEFT_Y)
            call SetUnitScale(tempDat.standIndicator,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            call GroupClear(tempDat.alreadyHit)
            set tempDat.state=BALL_STATE_TARGET
            set tempDat.shieldAmount=SHIELD_FLAT_AMOUNT+SHIELD_AMOUNT_PER_LEVEL*tempDat.protectRevertLevel+SHIELD_BONUS_MULTIPLIER*int
            set tempDat.damage=SHIELD_FLAT_DAMAGE+SHIELD_DAMAGE_PER_LEVEL*tempDat.protectRevertLevel+SHIELD_BONUS_DAMAGE_MULTIPLIER*int
            if tempDat.protectBonusUnit!=null then
                call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                set tempDat.protectBonusUnit=null
            endif
            call tempDat.passifyAbilities()
        endmethod
        
        // Public method used when an Orianna should Command: Shockwave
        public static method shockwave takes unit ori returns nothing
            local thistype tempDat=fromOri[ori]
            local real bX=GetUnitX(tempDat.ball)
            local real bY=GetUnitY(tempDat.ball)
            call SetUnitTimeScale(tempDat.ball,BALL_TIME_SCALE)
            call SetUnitFlyHeight(tempDat.ball,BALL_GROUND_HEIGHT,0.)
            set tempDat.steps=0
            set tempDat.state=BALL_STATE_SHOCKWAVE
            call SetUnitX(tempDat.shockwaveIndicator,bX)
            call SetUnitY(tempDat.shockwaveIndicator,bY)
            call SetUnitScale(tempDat.shockwaveIndicator,SHOCK_INDIC_SCALE,SHOCK_INDIC_SCALE,SHOCK_INDIC_SCALE)
            call SetUnitFlyHeight(tempDat.shockwaveIndicator,SHOCK_INDIC_HEIGHT,0.)
            call SetUnitX(tempDat.shockwaveOrb1,bX+SHOCK_RADIUS*Cos(0.*SHOCK_ORB_PHASE_DIFFERENCE))
            call SetUnitY(tempDat.shockwaveOrb1,bY+SHOCK_RADIUS*Sin(0.*SHOCK_ORB_PHASE_DIFFERENCE))
            call SetUnitScale(tempDat.shockwaveOrb1,SHOCK_ORB_SCALE,SHOCK_ORB_SCALE,SHOCK_ORB_SCALE)
            call SetUnitX(tempDat.shockwaveOrb2,bX+SHOCK_RADIUS*Cos(1.*SHOCK_ORB_PHASE_DIFFERENCE))
            call SetUnitY(tempDat.shockwaveOrb2,bY+SHOCK_RADIUS*Sin(1.*SHOCK_ORB_PHASE_DIFFERENCE))
            call SetUnitScale(tempDat.shockwaveOrb2,SHOCK_ORB_SCALE,SHOCK_ORB_SCALE,SHOCK_ORB_SCALE)
            call SetUnitX(tempDat.shockwaveOrb3,bX+SHOCK_RADIUS*Cos(2.*SHOCK_ORB_PHASE_DIFFERENCE))
            call SetUnitY(tempDat.shockwaveOrb3,bY+SHOCK_RADIUS*Sin(2.*SHOCK_ORB_PHASE_DIFFERENCE))
            call SetUnitScale(tempDat.shockwaveOrb3,SHOCK_ORB_SCALE,SHOCK_ORB_SCALE,SHOCK_ORB_SCALE)
            if tempDat.protectBonusUnit!=null then
                call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                set tempDat.protectBonusUnit=null
            endif
        endmethod
        
        // Method used to get and increment the hitcount for an Orianna;
        // used by her innate
        public static method hitCount takes unit ori, unit targ, integer meth returns integer
            local thistype tempDat
            if meth==METHOD_INCREMENT_AND_COUNT then
                set tempDat=thistype.fromOri[ori]
                if tempDat.target==targ then
                    set tempDat.attackCount=tempDat.attackCount+1
                else
                    set tempDat.target=targ
                    set tempDat.attackCount=1
                endif
                set tempDat.counterTimeout=CLOCKWORK_WINDUP_TIMEOUT
                return tempDat.attackCount
            endif
            return 0
        endmethod
        
        // Method used for preloading sounds after a timer
        private static method preloadSoundAfter takes nothing returns nothing
            local timer time=GetExpiredTimer()
            local soundShim shim=thistype.preloadTable[time]
            call StartSound(shim.s)
            call KillSoundWhenDone(shim.s)
            call shim.destroy()
            set time=null
        endmethod
        
        // Method used to preload sounds
        private static method preloadSound takes string s returns nothing
            local timer time=CreateTimer()
            local sound snd=CreateSound(s,false,false,true,SOUND_MAX_FADERATE,SOUND_MAX_FADERATE,"")
            local soundShim shim=soundShim.create()
            set shim.s=snd
            set thistype.preloadTable[time]=shim
            call SetSoundVolume(snd,1)
            call TimerStart(time,0.,false,function thistype.preloadSoundAfter)
            set time=null
            set snd=null
        endmethod
        
        // Periodic method that handles all the dynamic movement and timings
        // of Orianna's basic effects... take a deep breath.
        private static method periodic takes nothing returns nothing
            local integer index=0
            local thistype tempDat
            local real ballX
            local real ballY
            local real dx
            local real dy
            local real pX
            local real pY
            local real pAng
            local real sid
            local real phase
            local real uX
            local real uY
            local real dist2
            local real theta
            local sound snd
            local unit FoG
            loop
                exitwhen index>dbIndex
                set tempDat=DB[index]
                set ballX=GetUnitX(tempDat.ball)
                set ballY=GetUnitY(tempDat.ball)
                set dx=ballX-GetUnitX(tempDat.ori)
                set dy=ballY-GetUnitY(tempDat.ori)
                set dist2=dx*dx+dy*dy
                // Some logic for Clockwork Windup
                if tempDat.counterTimeout>0. then
                    set tempDat.counterTimeout=tempDat.counterTimeout-CLOCK_PERIOD
                    if tempDat.counterTimeout<=0. then
                        set tempDat.target=null
                    endif
                endif
                // Some logic for disonnance's effect
                if tempDat.disonnanceTimeLeft>0. then
                    set tempDat.disonnanceTimeLeft=tempDat.disonnanceTimeLeft-CLOCK_PERIOD
                    // 36 is between an arbitrary value and a constant. It's just a 
                    // tenth of a circle.
                    call SetUnitFacingTimed(tempDat.disonnanceIndicator,GetUnitFacing(tempDat.disonnanceIndicator)-36.,2.)
                    if tempDat.disonnanceTimeLeft<DISONNANCE_INDICATOR_FADE_PERIOD then
                        call SetUnitVertexColor(tempDat.disonnanceIndicator,DISONNANCE_INDICATOR_RED,DISONNANCE_INDICATOR_GREEN,DISONNANCE_INDICATOR_BLUE,R2I(DISONNANCE_INDICATOR_OPACITY*tempDat.disonnanceTimeLeft/DISONNANCE_INDICATOR_FADE_PERIOD))
                    endif
                    call GroupEnumUnitsInRange(thistype.grp,GetUnitX(tempDat.disonnanceIndicator),GetUnitY(tempDat.disonnanceIndicator),thistype.DISONNANCE_RADIUS,null)
                    loop
                        set FoG=FirstOfGroup(grp)
                        exitwhen FoG==null
                        if IsUnitEnemy(FoG,GetOwningPlayer(tempDat.ori)) then
                            call SlowHandler.setUnit(FoG,GetUnitAbilityLevel(tempDat.ori,thistype.COMMAND_DISONNANCE_ID)+3)
                        else
                            call HasteHandler.setUnit(FoG,GetUnitAbilityLevel(tempDat.ori,thistype.COMMAND_DISONNANCE_ID)+3)
                        endif
                        call GroupRemoveUnit(grp,FoG)
                    endloop
                    if tempDat.disonnanceTimeLeft<=0. then
                        set tempDat.disonnanceTimeLeft=0.
                        call SetUnitVertexColor(tempDat.disonnanceIndicator,DISONNANCE_INDICATOR_RED,DISONNANCE_INDICATOR_GREEN,DISONNANCE_INDICATOR_BLUE,DISONNANCE_INDICATOR_OPACITY)
                        call SetUnitX(tempDat.disonnanceIndicator,MAP_TOP_LEFT_X)
                        call SetUnitY(tempDat.disonnanceIndicator,MAP_TOP_LEFT_Y)
                        call SetUnitScale(tempDat.disonnanceIndicator,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
                    endif
                endif
                // Some logic for when a ball is attached to a unit
                if tempDat.state==BALL_STATE_ONUNIT then
                    call SetUnitX(tempDat.ball,GetUnitX(tempDat.pTarget))
                    call SetUnitY(tempDat.ball,GetUnitY(tempDat.pTarget))
                    if dist2>BALL_LEASH_RANGE_ONUNIT*BALL_LEASH_RANGE_ONUNIT then
                        set tempDat.state=BALL_STATE_HOME
                        call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                        set tempDat.protectBonusUnit=tempDat.ori
                        call UnitAddAbility(tempDat.ori,PROTECT_PASSIVE_ID)
                    endif
                endif
                // Some logic while the the ball has been issued Command:
                // Attack
                if tempDat.state==BALL_STATE_ATTACK then
                    set uX=GetUnitX(tempDat.ball)+tempDat.delX
                    set uY=GetUnitY(tempDat.ball)+tempDat.delY
                    call SetUnitX(tempDat.ball,uX)
                    call SetUnitY(tempDat.ball,uY)
                    call SetUnitFlyHeight(tempDat.ball,GetUnitFlyHeight(tempDat.ball)+tempDat.delZ,0.)
                    set tempDat.steps=tempDat.steps-1
                    call GroupEnumUnitsInRange(grp,uX,uY,BALL_ATTACK_DAMAGE_RADIUS,null)
                    loop
                        set FoG=FirstOfGroup(grp)
                        exitwhen FoG==null
                        if IsUnitEnemy(tempDat.ori,GetOwningPlayer(FoG)) and (not IsUnitInGroup(FoG,tempDat.alreadyHit)) then
                            call DamageType.dealCodeDamage(tempDat.ori,FoG,tempDat.damage)
                            call DestroyEffect(AddSpecialEffectTarget(ABILITY_HIT_EFFECT,FoG,"chest"))
                            set tempDat.damage=tempDat.damage*(1.-COMMAND_ATTACK_DAMAGE_REDUCTION)
                            call GroupAddUnit(tempDat.alreadyHit,FoG)
                            if tempDat.damage<tempDat.minDamage then
                                set tempDat.damage=tempDat.minDamage
                            endif
                        endif
                        call GroupRemoveUnit(grp,FoG)
                    endloop
                    if tempDat.steps<1 then
                        call SetUnitX(tempDat.ball,tempDat.targetX)
                        call SetUnitY(tempDat.ball,tempDat.targetY)
                        call SetUnitFlyHeight(tempDat.ball,BALL_GROUND_HEIGHT,0.)
                        call SetUnitTimeScale(tempDat.ball,BALL_TIME_SCALE_GROUND)
                        call SetUnitX(tempDat.standIndicator,tempDat.targetX)
                        call SetUnitY(tempDat.standIndicator,tempDat.targetY)
                        call SetUnitScale(tempDat.standIndicator,STAND_INDICATOR_SCALE,STAND_INDICATOR_SCALE,STAND_INDICATOR_SCALE)
                        set snd=CreateSound(BALL_MOVE_END_SOUND,false,true,true,12700,12700,"")
                        call tempDat.actifyAbilities()
                        call SetSoundVolume(snd,127)
                        call SetSoundPitch(snd,BALL_MOVE_END_SOUND_PITCH)
                        call SetSoundPosition(snd,tempDat.targetX,tempDat.targetY,BALL_GROUND_HEIGHT)
                        call StartSound(snd)
                        call KillSoundWhenDone(snd)
                        set tempDat.state=BALL_STATE_GROUND
                    endif
                // Some logic while the ball has been issued Command:
                // Shockwave
                elseif tempDat.state==BALL_STATE_SHOCKWAVE then
                    set tempDat.steps=tempDat.steps+1
                    set sid=SHOCK_RADIUS*(-1.*CLOCK_PERIOD*tempDat.steps/SHOCK_WINDUP_DURATION+1)
                    set phase=tempDat.steps*CLOCK_PERIOD*SHOCK_WINDUP_ORB_SPEED
                    if GetUnitAbilityLevel(tempDat.ori,COMMAND_SHOCKWAVE_DIS_ID)==0 then
                        call tempDat.passifyAbilities()
                    endif
                    call SetUnitX(tempDat.shockwaveOrb1,ballX+sid*Cos(2*bj_PI+phase))
                    call SetUnitY(tempDat.shockwaveOrb1,ballY+sid*Sin(2*bj_PI+phase))
                    call SetUnitX(tempDat.shockwaveOrb2,ballX+sid*Cos(2.*bj_PI/3.+phase))
                    call SetUnitY(tempDat.shockwaveOrb2,ballY+sid*Sin(2.*bj_PI/3.+phase))
                    call SetUnitX(tempDat.shockwaveOrb3,ballX+sid*Cos(4.*bj_PI/3.+phase))
                    call SetUnitY(tempDat.shockwaveOrb3,ballY+sid*Sin(4.*bj_PI/3.+phase))
                    if tempDat.steps>SHOCK_WINDUP_DURATION/CLOCK_PERIOD then
                        set tempDat.state=BALL_STATE_GROUND
                        call GroupEnumUnitsInRange(grp,GetUnitX(tempDat.shockwaveIndicator),GetUnitY(tempDat.shockwaveIndicator),250.,null)
                        call tempDat.actifyAbilities()
                        loop
                            set FoG=FirstOfGroup(grp)
                            exitwhen FoG==null
                            set pY=75.+75.*GetUnitAbilityLevel(tempDat.ori,COMMAND_SHOCKWAVE_ID)+0.7*GetHeroInt(tempDat.ori,true)
                            if IsUnitEnemy(tempDat.ball,GetOwningPlayer(FoG)) and UnitAlive(FoG) then
                                set pX=Atan2(GetUnitY(tempDat.ball)-GetUnitY(FoG),GetUnitX(tempDat.ball)-GetUnitX(FoG))
                                call DamageType.dealCodeDamage(tempDat.ori,FoG,pY)
                                call ThrowHandler.add(FoG,THROW_VELOCITY*Cos(pX),THROW_VELOCITY*Sin(pX),THROW_VELOCITY*THROW_VERT_MULTIPLIER)
                            endif
                            call GroupRemoveUnit(grp,FoG)
                        endloop
                        call SetUnitX(tempDat.shockwaveOrb1,MAP_TOP_LEFT_X)
                        call SetUnitY(tempDat.shockwaveOrb1,MAP_TOP_LEFT_Y)
                        call SetUnitX(tempDat.shockwaveOrb2,MAP_TOP_LEFT_X)
                        call SetUnitY(tempDat.shockwaveOrb2,MAP_TOP_LEFT_Y)
                        call SetUnitX(tempDat.shockwaveOrb3,MAP_TOP_LEFT_X)
                        call SetUnitY(tempDat.shockwaveOrb3,MAP_TOP_LEFT_Y)
                        call SetUnitX(tempDat.shockwaveIndicator,MAP_TOP_LEFT_X)
                        call SetUnitY(tempDat.shockwaveIndicator,MAP_TOP_LEFT_Y)
                    endif
                // Some logic while the ball has been issued Command:
                // Protect
                elseif tempDat.state==BALL_STATE_TARGET then
                    set pX=GetUnitX(tempDat.pTarget)-ballX
                    set pY=GetUnitY(tempDat.pTarget)-ballY
                    set pAng=Atan2(pY,pX)
                    set uX=ballX+thistype.BALL_VELOCITY*thistype.CLOCK_PERIOD*Cos(pAng)
                    set uY=ballY+thistype.BALL_VELOCITY*thistype.CLOCK_PERIOD*Sin(pAng)
                    call SetUnitX(tempDat.ball,uX)
                    call SetUnitY(tempDat.ball,uY)
                    call GroupEnumUnitsInRange(grp,uX,uY,BALL_ATTACK_DAMAGE_RADIUS,null)
                    loop
                        set FoG=FirstOfGroup(grp)
                        exitwhen FoG==null
                        if IsUnitEnemy(tempDat.ori,GetOwningPlayer(FoG)) and (not IsUnitInGroup(FoG,tempDat.alreadyHit)) then
                            call DamageType.dealCodeDamage(tempDat.ori,FoG,tempDat.damage)
                            call DestroyEffect(AddSpecialEffectTarget(ABILITY_HIT_EFFECT,FoG,"chest"))
                            call GroupAddUnit(tempDat.alreadyHit,FoG)
                        endif
                        call GroupRemoveUnit(grp,FoG)
                    endloop
                    if (pX*pX+pY*pY)<PROTECT_ATTACH_RANGE*PROTECT_ATTACH_RANGE then
                        set tempDat.state=BALL_STATE_ONUNIT
                        call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                        set tempDat.protectBonusUnit=tempDat.pTarget
                        call UnitAddAbility(tempDat.pTarget,PROTECT_PASSIVE_ID)
                        call Shield.add(tempDat.pTarget,tempDat.shieldAmount,SHIELD_DURATION,SHIELD_FX,"origin")
                        call tempDat.actifyAbilities()
                    endif
                // Other stuff for when Orianna is alive
                elseif UnitAlive(tempDat.ori) then
                    if tempDat.state==BALL_STATE_DEAD then
                        set tempDat.state=BALL_STATE_HOME
                        call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                        set tempDat.protectBonusUnit=tempDat.ori
                        call UnitAddAbility(tempDat.ori,PROTECT_PASSIVE_ID)
                        call SetUnitFlyHeight(tempDat.ball,BALL_OVERHEAD_HEIGHT,0.)
                        call SetUnitVertexColor(tempDat.ball,255,255,255,255)
                        call SetUnitOwner(tempDat.ball,GetOwningPlayer(tempDat.ori),false)
                        call SetUnitPosition(tempDat.ball,GetUnitX(tempDat.ori),GetUnitY(tempDat.ori))
                    elseif tempDat.state==BALL_STATE_GROUND then
                        if dist2>BALL_LEASH_RANGE_GROUND*BALL_LEASH_RANGE_GROUND or dist2<BALL_PICKUP_RANGE*BALL_PICKUP_RANGE then
                            call SetUnitTimeScale(tempDat.ball,BALL_TIME_SCALE)
                            call SetUnitFlyHeight(tempDat.ball,BALL_OVERHEAD_HEIGHT,0.)
                            call SetUnitX(tempDat.standIndicator,MAP_TOP_LEFT_X)
                            call SetUnitY(tempDat.standIndicator,MAP_TOP_LEFT_Y)
                            call SetUnitScale(tempDat.standIndicator,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
                            set tempDat.state=BALL_STATE_HOME
                            call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                            set tempDat.protectBonusUnit=tempDat.ori
                            call UnitAddAbility(tempDat.ori,PROTECT_PASSIVE_ID)
                        endif
                    elseif tempDat.state==BALL_STATE_HOME then
                        call SetUnitX(tempDat.ball,GetUnitX(tempDat.ori))
                        call SetUnitY(tempDat.ball,GetUnitY(tempDat.ori))
                    endif
                // ... and when she's dead
                elseif not UnitAlive(tempDat.ori) then
                    call SetUnitX(tempDat.ball,MAP_TOP_LEFT_X)
                    call SetUnitY(tempDat.ball,MAP_TOP_LEFT_Y)
                    call SetUnitFlyHeight(tempDat.ball,DUMMY_HIDDEN_HEIGHT,0.)
                    call SetUnitVertexColor(tempDat.ball,255,255,255,0)
                    call SetUnitOwner(tempDat.ball,Player(PLAYER_NEUTRAL_PASSIVE),false)
                    if tempDat.state==BALL_STATE_GROUND then
                        call SetUnitX(tempDat.standIndicator,MAP_TOP_LEFT_X)
                        call SetUnitY(tempDat.standIndicator,MAP_TOP_LEFT_Y)
                        call SetUnitScale(tempDat.standIndicator,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
                    endif
                    set tempDat.state=BALL_STATE_DEAD
                    call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                    set tempDat.protectBonusUnit=null
                endif
                // When to hide the indicator
                if tempDat.state==BALL_STATE_HOME or tempDat.state==BALL_STATE_DEAD or dist2<ARROW_MIN_RANGE*ARROW_MIN_RANGE then
                    call SetUnitX(tempDat.arrow,MAP_TOP_LEFT_X)
                    call SetUnitY(tempDat.arrow,MAP_TOP_LEFT_Y)
                    call SetUnitFlyHeight(tempDat.arrow,DUMMY_HIDDEN_HEIGHT,0.)
                    call SetUnitScale(tempDat.arrow,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
                // When to move the indicator
                else
                    set theta=Atan2(dy,dx)
                    call SetUnitX(tempDat.arrow,GetUnitX(tempDat.ori)+ARROW_HOVER_DISTANCE*Cos(theta))
                    call SetUnitY(tempDat.arrow,GetUnitY(tempDat.ori)+ARROW_HOVER_DISTANCE*Sin(theta))
                    if GetLocalPlayer()==GetOwningPlayer(tempDat.ori) then
                        call SetUnitFlyHeight(tempDat.arrow,ARROW_FLY_HEIGHT,0.)
                        call SetUnitScale(tempDat.arrow,ARROW_SCALE,ARROW_SCALE,ARROW_SCALE)
                    endif
                    if dist2<ARROW_GREEN_RANGE*ARROW_GREEN_RANGE then
                        call SetUnitColor(tempDat.arrow,PLAYER_COLOR_GREEN)
                    elseif dist2<ARROW_YELLOW_RANGE*ARROW_YELLOW_RANGE then
                        call SetUnitColor(tempDat.arrow,PLAYER_COLOR_YELLOW)
                    else
                        call SetUnitColor(tempDat.arrow,PLAYER_COLOR_RED)
                    endif
                endif
                //Last check if Orianna exists so we can properly destroy her otherwise
                if GetUnitTypeId(tempDat.ori)==0 then
                    call DestroyGroup(tempDat.alreadyHit)
                    call RemoveUnit(tempDat.ball)
                    call RemoveUnit(tempDat.arrow)
                    call RemoveUnit(tempDat.shockwaveIndicator)
                    call RemoveUnit(tempDat.shockwaveOrb1)
                    call RemoveUnit(tempDat.shockwaveOrb2)
                    call RemoveUnit(tempDat.shockwaveOrb3)
                    call RemoveUnit(tempDat.standIndicator)
                    call RemoveUnit(tempDat.disonnanceIndicator)
                    if tempDat.protectBonusUnit!=null then
                        call UnitRemoveAbility(tempDat.protectBonusUnit,PROTECT_PASSIVE_ID)
                        set tempDat.protectBonusUnit=null
                    endif
                    call tempDat.destroy()
                    set DB[index]=DB[dbIndex]
                    set dbIndex=dbIndex-1
                    set index=index-1
                endif
                set index=index+1
            endloop
            set snd=null
        endmethod
        
        // Initialization function for an Orianna instance
        private static method initialize takes unit u returns nothing
            local player owner=GetOwningPlayer(u)
            local thistype tempDat=thistype.create()
            set tempDat.ori=u
            set tempDat.ball=CreateUnit(owner,BALL_ID,0.,0.,270.)
            set tempDat.arrow=CreateUnit(owner,ARROW_ID,MAP_TOP_LEFT_X,MAP_TOP_LEFT_Y,270.)
            set tempDat.standIndicator=CreateUnit(owner,STAND_INDICATOR_ID,MAP_TOP_LEFT_X,MAP_TOP_LEFT_Y,270.)
            set tempDat.disonnanceIndicator=CreateUnit(owner,DISONNANCE_INDICATOR_ID,MAP_TOP_LEFT_X,MAP_TOP_LEFT_Y,270.)
            set tempDat.shockwaveIndicator=CreateUnit(owner,SHOCKWAVE_INDICATOR_ID,MAP_TOP_LEFT_X,MAP_TOP_LEFT_Y,270.)
            set tempDat.shockwaveOrb1=CreateUnit(owner,SHOCKWAVE_ORB_ID,MAP_TOP_LEFT_X,MAP_TOP_LEFT_Y,270.)
            set tempDat.shockwaveOrb2=CreateUnit(owner,SHOCKWAVE_ORB_ID,MAP_TOP_LEFT_X,MAP_TOP_LEFT_Y,270.)
            set tempDat.shockwaveOrb3=CreateUnit(owner,SHOCKWAVE_ORB_ID,MAP_TOP_LEFT_X,MAP_TOP_LEFT_Y,270.)
            call SetUnitScale(tempDat.shockwaveIndicator,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            call SetUnitScale(tempDat.shockwaveOrb1,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            call SetUnitScale(tempDat.shockwaveOrb2,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            call SetUnitScale(tempDat.shockwaveOrb3,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            call SetUnitScale(tempDat.disonnanceIndicator,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            call SetUnitVertexColor(tempDat.disonnanceIndicator,DISONNANCE_INDICATOR_RED,DISONNANCE_INDICATOR_GREEN,DISONNANCE_INDICATOR_BLUE,DISONNANCE_INDICATOR_OPACITY)
            call SetUnitScale(tempDat.standIndicator,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            call SetUnitColor(tempDat.arrow,PLAYER_COLOR_GREEN)
            call SetUnitScale(tempDat.arrow,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE,DUMMY_HIDDEN_SCALE)
            set thistype.fromOri[u]=tempDat
            set tempDat.target=null
            set tempDat.alreadyHit=CreateGroup()
            call UnitAddAbility(tempDat.ball,'Arav')
            call UnitRemoveAbility(tempDat.ball,'Arav')
            call UnitAddAbility(tempDat.disonnanceIndicator,'Arav')
            call UnitRemoveAbility(tempDat.disonnanceIndicator,'Arav')
            call UnitAddAbility(tempDat.shockwaveIndicator,'Arav')
            call UnitRemoveAbility(tempDat.shockwaveIndicator,'Arav')
            call SetUnitFlyHeight(tempDat.disonnanceIndicator,DISONNANCE_INDICATOR_HEIGHT,0.)
            call SetUnitX(tempDat.ball,GetUnitX(u))
            call SetUnitY(tempDat.ball,GetUnitY(u))
            call SetUnitTimeScale(tempDat.ball,BALL_TIME_SCALE)
            call SetUnitFlyHeight(tempDat.ball,BALL_OVERHEAD_HEIGHT,0.)
            call SetUnitVertexColor(tempDat.ball,255,255,255,BALL_OPACITY)
            set dbIndex=dbIndex+1
            set DB[dbIndex]=tempDat
            if dbIndex==0 then
                call TimerStart(clock,CLOCK_PERIOD,true,function thistype.periodic)
            endif
            set tempDat.state=BALL_STATE_HOME
        endmethod
        
        // Auxillary function used for getting new Orianna
        // instances
        private static method c takes nothing returns boolean
            local unit tU=GetTriggerUnit()
            if GetUnitTypeId(tU)==ID then
                call thistype.initialize(tU)
            endif
            set tU=null
            return false
        endmethod
        
        // Initialization method called by the InitShim
        private static method i takes nothing returns nothing
            local unit FoG
            local region reg=CreateRegion()
            local trigger t=CreateTrigger()
            set thistype.fromOri=HandleTable.create()
            set thistype.preloadTable=HandleTable.create()
            call thistype.preloadSound(BALL_MOVE_START_SOUND)
            call thistype.preloadSound(BALL_MOVE_END_SOUND)
            call RegionAddRect(reg,bj_mapInitialPlayableArea)
            call TriggerRegisterEnterRegion(t,reg,null)
            call TriggerAddCondition(t,Condition(function thistype.c))
            set reg=null
            set t=null
            call GroupEnumUnitsInRect(grp,bj_mapInitialPlayableArea,null)
            loop
                set FoG=FirstOfGroup(grp)
                exitwhen FoG==null
                if GetUnitTypeId(FoG)==thistype.ID then
                    call thistype.initialize(FoG)
                endif
                call GroupRemoveUnit(grp,FoG)
            endloop
        endmethod
        
        implement InitShim
    endstruct
endlibrary



JASS:
// System for handling Orianna's Innate. Note: Contains
// one (1) customizable section
scope clockworkWindup initializer i
    globals
    
    
        //*********************************************************
        // Customizable Section
        //*********************************************************
        
        // How many levels to wait before incrementing the damage
        // bonus
        private constant integer LEVEL_SKIP_REQUIREMENT=3
        
        // Flat magic damage added to basic attacks
        private constant real FLAT_BONUS=10.
        
        // Incremental magic damage added to basic attacks
        private constant real INCR_BONUS=8.
        
        // Intelligence based magic damage added to basic attacks
        private constant real INT_BONUS=.15
        
        // Multiplier on second hit for bonus magic damage
        private constant real HIT_TWO_BONUS=.2
        
        // Bonus magic damage on third and consecutive hits
        private constant real HIT_THREE_BONUS=.4
        
        // Model displayed on attack
        private constant string HIT_ANIMATION="Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl"
        
        // Model displayed on subsequent attacks
        private constant string HIT_ANIMATION_2="Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodSorceress.mdl"
        
        //*********************************************************
        // End Customizable Section
        //*********************************************************
        
        
    endglobals
    
    // The handler
    private function handler takes nothing returns nothing
        local integer count
        local real damage
        local real damage2=0.
        local unit dS=GetEventDamageSource()
        local unit tU=GetTriggerUnit()
        if DamageType.get()==DamageType.ATTACK and GetUnitTypeId(dS)==Orianna.ID then
            set damage=FLAT_BONUS + INCR_BONUS*R2I((GetHeroLevel(dS)-1)/LEVEL_SKIP_REQUIREMENT) + INT_BONUS*GetHeroInt(dS,true)
            set count=Orianna.hitCount(dS,tU,Orianna.METHOD_INCREMENT_AND_COUNT)
            if count==2 then
                set damage2=(GetEventDamage()+damage)*HIT_TWO_BONUS
            elseif count>2 then
                set damage2=(GetEventDamage()+damage)*HIT_THREE_BONUS
            endif
            call DamageType.dealCodeDamage(dS,tU,damage+damage2)
            call DestroyEffect(AddSpecialEffectTarget(HIT_ANIMATION,tU,"chest"))
            if count>1 then
                call DestroyEffect(AddSpecialEffectTarget(HIT_ANIMATION_2,tU,"chest"))
            endif
        endif
        set tU=null
        set dS=null
    endfunction
    
    // Initialization function which adds a handler to
    // recognize Orianna's basic attacks
    private function i takes nothing returns nothing
        call StructuredDD.addHandler(function handler)
    endfunction
endscope



JASS:
// Interface for Command: Attack. Essentially just an adapter
// for the primary Orianna interface
scope commandAttack initializer i
    // Registered function on cast
    private function c takes nothing returns boolean
        if GetSpellAbilityId()==Orianna.COMMAND_ATTACK_ID then
            call Orianna.attack(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
        endif
        return false
    endfunction
    
    // Initialization function
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope



JASS:
// Interface for Command: Disonnance; much like the 
// commandAttack one
scope commandDissonance initializer i
    //Registered Function on cast
    private function c takes nothing returns boolean
        if GetSpellAbilityId()==Orianna.COMMAND_DISONNANCE_ID then
            call Orianna.disonnance(GetTriggerUnit())
        endif
        return false
    endfunction
    
    // Initialization Function
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope



JASS:
//Interface for Command: Protect which speaks to Orianna library
scope commandProtect initializer i
    // Function registered on cast
    private function c takes nothing returns boolean
        if GetSpellAbilityId()==Orianna.COMMAND_PROTECT_ID then
            call Orianna.protect(GetTriggerUnit(),GetSpellTargetUnit())
        endif
        return false
    endfunction
    
    // Initialization function
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope



JASS:
// Interface for Command: Shockwave
scope commandShockwave initializer i
    // Function registered on cast
    private function c takes nothing returns boolean
        if GetSpellAbilityId()==Orianna.COMMAND_SHOCKWAVE_ID then
            call Orianna.shockwave(GetTriggerUnit())
        endif
        return false
    endfunction
    
    // Initialization function
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope


Credits:
  • league of legends/riot games for creating and designing the entirety of Orianna (none of this spell pack is original content).
  • Anitarf for being partially responsible for the IsTerrainWalkable classic snippet.
  • Vexorian, MindworX, PitzerMike, others for the creation of Table, JassHelper, and JNGP. I would never have created these without them.

Installation:
  1. Make sure you have Jass Newgen Pack or JassHelper to properly use the vJass used in this code
  2. Please copy all object data from this map to yours
  3. Copy all triggers from Orianna Spellpack (folder) to your map
  4. Please verify and update all values within CUSTOMIZE sections, which can be found in both Orianna and clockworkWindup, ensuring correct rawcodes
  5. Make sure you have copies of Table, StructuredDD, DamageType, Shield, and IsTerrainWalkable installed in your map
  6. Create an Orianna unit in game or in the editor and play!

Change Log:
  • 2014.04.25 - Fixed a critical, unbelievable bug... finally.
  • 2014.01.16 - Updated to improve performance (lol, like it needed it)
  • 2013.12.01 - Initial upload to spells section.

Keywords:
league of legends, lol, orianna, lady of clockwork, shockwave
Contents

Orianna 1.0.3 (Map)

Reviews
12:41, 1st May 2014 BPower: Excellent coding and visuals. Now with the cooldown issue beeing fixed the spellpack ready for approval. In my eyes 5/5 seems to be an appropriate rating.

Moderator

M

Moderator

12:41, 1st May 2014
BPower:
Excellent coding and visuals. Now with the cooldown issue beeing fixed the spellpack ready for approval. In my eyes 5/5 seems to be an appropriate rating.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Cool spell pack and testmap. It's fun to cast and experiment with the spells. :)
I should add, that I never played League of Legends, so the concept was completly new to me.
I also liked the idea to rip, what you called Interface for command, out of the core Orianna library. You could use SpellEffectEvent here, but that's totally optional and only my opinion.

What I don't like is the fact that you are using Table 3.1 by Vexorian. I know it is more lightweight than bribe's is, but the thing is that so many resources on THW are using bribe's Table. Most users simply won't understand why JassHelper throws an error and how to fix it. Probably 80% or (worse) use GUI only, if they create resources on their own. Maybe you could add something like static if Table.flush2D.exists then and make it compatible to both Table and NewTable.

I only scanned the code quickly, but you could use a player variable --> tempDat.owner. This would erase lots of GetOwningPlayer(Fog/tempDat.ball) calls.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
Cool spell pack and testmap. It's fun to cast and experiment with the spells. :)
I should add, that I never played League of Legends, so the concept was completly new to me.
I also liked the idea to rip, what you called Interface for command, out of the core Orianna library. You could use SpellEffectEvent here, but that's totally optional and only my opinion.

What I don't like is the fact that you are using Table 3.1 by Vexorian. I know it is more lightweight than bribe's is, but the thing is that so many resources on THW are using bribe's Table. Most users simply won't understand why JassHelper throws an error and how to fix it. Probably 80% or (worse) use GUI only, if they create resources on their own. Maybe you could add something like static if Table.flush2D.exists then and make it compatible to both Table and NewTable.

Wait, you're saying Bribe's API is not compatible with Vexorian's? That's hilarious. I'll have to look into it.

I only scanned the code quickly, but you could use a player variable --> tempDat.owner. This would erase lots of GetOwningPlayer(Fog/tempDat.ball) calls.

Yeah you're right, I considered drawing a karnough map and simplifying the script perfectly but.. good god it's so much code. I'll look into this before the next update.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
Bribe made an BCTable for backwards compatibility NewTable

Yeah, that's absolutely not OK. I'll not be updating my code to play nicely with a library whose API is so poorly designed.

It's a huge shame because Bribe's library is actually better, and it's too late to revert his API. The only solution I can see is an entirely different library being produced in the future.
 
Really cool spell.. made fun to test it. Also nice test commands :thumbs_up:

I also think you could add another special effect to Shockwave, looks a bit empty.. and I don't recognize much that my enemy is really affected by the spell.
Maybe you could add a little stun or something like this, after the units and got back on ground.

What do you think about the idea to put all Orianna spells into a spell book? Then you additionally could add some more unique of your spell ideas, and hero would even have some place for possible other abilites.

I really think this spell would wonderful fit too many RPG maps.. good job, man 5/5:wink:
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
I like this ;)
Still coding very well I have to admit (even if I don't like struct and dynamic indexing because it generates a bit too much coding, nothing really serious anyway i'm not a speed freak) !
I found the idea to put everything in the library a bit confusing at the begginning because when I wanted to see the code I was a bit lost at the begginning.

Anyway cool spellpacks and you really deserve a 5/5 +rep from me :))
Suggestions : add some other effects on the ultimate :p
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Lovely spellpack and I couldn't see anything wrong inside the code by now.
The outlined comments and variable names make the whole script very readable,
allthough much is shared over many functions. As expected the coding is very good.

It is the small things that makes Orianna unique like the scaling, rotation and fading effects you've added to the objects.
However I got the feeling the ultimate could use a specialeffect, either on landing or on lifting the unit into the air.

I have to admit, that in this special case it is more likely to detect bugs ingame, than reading them out of the code and so did I.
The protect ability isn't using the 9 seconds cooldown mentioned in the tooltip.

I'll already made up my mind to give the Orianna pack the highest rating I'm authorized to, which is 5/5. However I would like to go through it a few more times, before finally approving it.

Out of respect for the immense time it takes to create a working spellpack I'll list everything that stands out to me into this table.
Some stuff will be personal preferences, others about efficiency and some about the limitations of the wc3 engine.
You don't have to reply on the posted content, unless I ask explicitly.


- You could add a player variable as member of the Orianna struct, althought the ball owner is variable it would still save you alot of native GetOwningPlayer calls for the orianna unit.
Sometimes you are using IsUnitEnemy(FoG, GetOwner(.ori)) or IsUnitEnemy( .ori, GetOwner(FoG)). ---> IsUnitEnemy(FoG, tempDat.owner)

- When preloading sounds, you should null the sound member of the shim struct once it is done.

- keyword public is not required for public access. All struct members are public by default, if not specified as private.

- You can only scale units in x axis, passing in y and z axis will not affect the unit in any way. It doesn't hurt though.

- local real int=GetHeroInt(ori,true) Some will find it neat others don't. I think it depends very much on the context and I don't see a problem here. (Edit: Interesting my JassHelper lists int as keyword for a type, probably an error in my settings.)

- I'm pretty sure this will desync if the arrow would be selectable or have collision size. It doesn't, hence should work as intended.
JASS:
if GetLocalPlayer()==GetOwningPlayer(tempDat.ori) then
    call SetUnitFlyHeight(tempDat.arrow,ARROW_FLY_HEIGHT,0.)
    call SetUnitScale(tempDat.arrow,ARROW_SCALE,ARROW_SCALE,ARROW_SCALE)
endif
- You do calculate 2*pi/3 and 2*pi in the configuration block, but in another context. You could use them here aswell, but I don't recommend it due to the extend of the whole Orianna script. The code might lose readability, for a gain of a nanosecond.
JASS:
call SetUnitY(tempDat.shockwaveOrb1,ballY+sid*Sin(2*bj_PI+phase))
call SetUnitX(tempDat.shockwaveOrb2,ballX+sid*Cos(2.*bj_PI/3.+phase))
- The following check doesn't make much sense to me, unless you are stopping the clock, if it was the last existing Orianna.
JASS:
 //Last check if Orianna exists so we can properly destroy her otherwise
if GetUnitTypeId(tempDat.ori)==0 then
 
Last edited:

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
However I got the feeling the ultimate could use a specialeffect, either on landing or on lifting the unit into the air.

I just imitated league of legends, you're not the first to say this but I'm not sure what to tell you.

The protect ability isn't using the 9 seconds cooldown mentioned in the tooltip.

Nice catch, I didn't notice that. Indeed it is strange because this:

JASS:
        // Method used to passify (disable) Orianna's abilities - occurs
        // when the ball already has some action.
        private method passifyAbilities takes nothing returns nothing
            set this.attackRevertLevel=GetUnitAbilityLevel(ori,COMMAND_ATTACK_ID)
            set this.dissonanceRevertLevel=GetUnitAbilityLevel(ori,COMMAND_DISONNANCE_ID)
            set this.protectRevertLevel=GetUnitAbilityLevel(ori,COMMAND_PROTECT_ID)
            set this.shockwaveRevertLevel=GetUnitAbilityLevel(ori,COMMAND_SHOCKWAVE_ID)
            if GetUnitAbilityLevel(this.ori,COMMAND_ATTACK_DIS_ID)>0 then
                call SetUnitAbilityLevel(this.ori,COMMAND_ATTACK_ID,5)
                call IncUnitAbilityLevel(this.ori,COMMAND_ATTACK_ID)
                call UnitAddAbility(this.ori,COMMAND_ATTACK_DIS_ID)
            endif
            if GetUnitAbilityLevel(this.ori,COMMAND_DISONNANCE_ID)>0 then
                call SetUnitAbilityLevel(this.ori,COMMAND_DISONNANCE_ID,5)
                call IncUnitAbilityLevel(this.ori,COMMAND_DISONNANCE_ID)
                call UnitAddAbility(this.ori,COMMAND_DISSONANCE_DIS_ID)
            endif
            if GetUnitAbilityLevel(this.ori,COMMAND_PROTECT_ID)>0 then
                call SetUnitAbilityLevel(this.ori,COMMAND_PROTECT_ID,5)
                //call IncUnitAbilityLevel(this.ori,COMMAND_PROTECT_ID)
                call UnitAddAbility(this.ori,COMMAND_PROTECT_DIS_ID)
            endif
            if GetUnitAbilityLevel(this.ori,COMMAND_SHOCKWAVE_ID)>0 then
                call SetUnitAbilityLevel(this.ori,COMMAND_SHOCKWAVE_ID,3)
                call IncUnitAbilityLevel(this.ori,COMMAND_SHOCKWAVE_ID)
                call UnitAddAbility(this.ori,COMMAND_SHOCKWAVE_DIS_ID)
            endif
        endmethod

fixes the problem (but creates a visual abnormality).

I don't know what to do. I'm using the same base ability and the same technique to hide the ability without resetting cooldown, and yet it's getting reset. Any thoughts would be appreciated.

You could add a player variable as member of the Orianna struct, althought the ball owner is variable it would still save you alot of native GetOwningPlayer calls for the orianna unit.
Sometimes you are using IsUnitEnemy(FoG, GetOwner(.ori)) or IsUnitEnemy( .ori, GetOwner(FoG)). ---> IsUnitEnemy(FoG, tempDat.owner)

Yes, someone else mentioned this as well. I've just updated that on my local version; will upload soon.

When preloading sounds, you should null the sound member of the shim struct once it is done.

I disagree. I know you're aware that struct instances are global arrays, and will tell me that nulling them is useful anyway since in theory the maximum instance count is only attained once, consider:

JASS:
globals
    sound array sounds
endglobals

for x in [0,5] do 
    sounds[x] = CreateSound() 
    sounds[x] = DestroySound()

// now you have an array list of size 8 whose members are:
// destroyed sound * 6, NaV, NaV

vs

JASS:
globals
    sound array sounds
endglobals

for x in [0,5] do 
    sounds[x] = CreateSound()
    sounds[x] = DestroySound()
    sounds[x] = null

// now you have an array list of size 8 whose members are:
// null * 6, NaV, NaV

One option spends 6 jass lines to reduce the handle counter by 6 (not even a real leak), the other increases the handle counter by 6. Both options have an instantiated array list in memory.

local real int=GetHeroInt(ori,true) Some will find it neat others don't. I think it depends very much on the context and I don't see a problem here. (Edit: Interesting my JassHelper lists int as keyword for a type, probably an error in my settings.)

It's just meant to be a copy of league of legends.

- I'm pretty sure this will desync if the arrow would be selectable or have collision size. It doesn't, hence should work as intended.
JASS:
if GetLocalPlayer()==GetOwningPlayer(tempDat.ori) then
    call SetUnitFlyHeight(tempDat.arrow,ARROW_FLY_HEIGHT,0.)
    call SetUnitScale(tempDat.arrow,ARROW_SCALE,ARROW_SCALE,ARROW_SCALE)
endif

I want to respond to this so that others see that I recognize the importance of this. I have not tested this script in multiplayer and therefore cannot confirm or deny that it desyncs. It shouldn't, but it's something to keep an eye out for.

- The following check doesn't make much sense to me, unless you are stopping the clock, if it was the last existing Orianna.
JASS:
 //Last check if Orianna exists so we can properly destroy her otherwise
if GetUnitTypeId(tempDat.ori)==0 then
[/SIZE][/hidden]

If an Orianna is removed from the game, its typeId will be set to 0 - this check is important to clear all the otherwise leaking units.

Thanks for the detailed check. I know there's a lot to it...
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
You got me wrong with local integer int :). In my editor the "int" is highlighted, like for instance local unit unit
About the sounds, I agree.
I want to respond to this so that others see that I recognize the importance of this. I have not tested this script in multiplayer and therefore cannot confirm or deny that it desyncs. It shouldn't, but it's something to keep an eye out for.
There is a tutorial for GetLocalPlayer() somewhere on THW. The author mentioned that even SetUnitPosition can be handled locally if the unit is unselectable and without collision. I never run that test by myself.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
may i ask when the next spell pack is going to be released? ^_^ im pretty excited for it

Good question... http://leagueoflegends.wikia.com/wiki/Draven will be the next champion I make, but I sort of got side tracked and spent 30+ hours building a test map (10+ unique abilities and an AI) for no reason. Haven't opened the map in awhile, and my semester just started.

In other words, not for awhile.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
  • In struct ThrowHandler, HasteHandler and SlowHandler you should null the unit member, once you destroy the instance.
  • call IssueTargetOrder(thistype.caster,"slow",tempDat.who)In SlowHandler you save "slow" into a string variable.
Im my eyes everything else looks very good. I hope Maker has an idea why the IncUnitAbilityLevel doesn't work for the protect ability ....
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
  • In struct ThrowHandler, HasteHandler and SlowHandler you should null the unit member, once you destroy the instance.
  • call IssueTargetOrder(thistype.caster,"slow",tempDat.who)In SlowHandler you save "slow" into a string variable.
Im my eyes everything else looks very good. I hope Maker has an idea why the IncUnitAbilityLevel doesn't work for the protect ability ....

I'm thinking I'll just remove the SLOW_ID constant, and use "slow" directly. Any objection to that?
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
Indeed. I also don't know what the problem could be. I've tried:

  • Adjusting casting and follow through times of all abilities
  • Adjusting casting animation time for Orianna
  • Adjusting order ID/base id of abilities
  • Verifying the op-limit is never reached
  • Verifying the passify/actify methods are called just once each, when necessary

Here's a link to the support thread, for anyone seeing this:

http://www.hiveworkshop.com/forums/...es-incunitabilitylevel-reset-cooldown-247417/
 
Level 6
Joined
Jan 4, 2014
Messages
227
nice spells and well coded, good job :)
do you have a link for an orianna model for warcraft 3 ? or maybe you know some one who can make it ??
 
Level 13
Joined
Aug 19, 2014
Messages
1,111
You're really good doing lol spells huh, I really like the 1st skill. Its make me happy just moving it.
 
Level 2
Joined
Oct 30, 2013
Messages
9
This is great. I will surely add this to my map and credits to you aswell. Hoping to see more but i doubt it
 
Top