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

Prediction - v1.0

  • Like
Reactions: Imadori
qol.png

Description

predictiong.png

Active: Using a process of complex calculations, the Mage predicts an area where the target unit will be in 2 seconds. If he is correct and the target is inside the area, it takes a large amount of damage. If the target is outside the area, it will get hit by a mana flare which burns mana, depending how far it was from the area. The greater the distance, the less mana is burned.

Level 1: 150AoE; 150 damage; up to 100 mana burned
Level 2: 175AoE; 250 damage; up to 150 mana burned
Level 3: 200AoE; 350 damage; up to 200 mana burned

Cooldown: 13 seconds.
Mana cost: 100/125/150.


Requirements

- JASS NewGen Pack
- TimerUtils by Vexorian
- xebasic, xefx, xemissile by Vexorian
- [OPTIONAL] SpellEffectEvent by Bribe

Links for the above systems can be found in the code.

The Code


JASS:
library Prediction /*
****************************************************************************************************
*
*   P R E D I C T I O N
*
*       ~ version 1.0
*       ~ coded by Mr_Bean
*
****************************************************************************************************
*
*    */ requires /*
*       ¯¯¯¯¯¯¯¯
*           */ TimerUtils,                  /* http://www.wc3c.net/showthread.php?t=101322
*           */ xemissile,                   /* http://www.wc3c.net/showthread.php?t=101150
*
*       Optional
*       ¯¯¯¯¯¯¯¯
*           */ optional SpellEffectEvent    /* http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193
*
****************************************************************************************************
*
*       Installation
*       ¯¯¯¯¯¯¯¯¯¯¯¯
*   - Make sure you've got (and installed properly) the above systems.
*   - Copy the custom ability. Set SPELL_ID (below) to the raw ID of this ability.
*   - Go through the configurables and change what you want to.
* 
****************************************************************************************************
*
*       Configuration
*       ¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
    globals
    
        //==================================================
        //=== RAW IDS
        private constant integer SPELL_ID       = 'A000'    // Raw ID of the ability.
        
        //==================================================
        //=== GENERAL
        private constant real    INITIAL_DELAY  = 0.03      // Delay between cast and prediction. Shouldn't need to be changed.
        
        //==================================================
        //=== CENTRE AREA EFFECT CONFIGURATION
        // This effect is created at the centre of the prediction area.
        private constant real    L_FX_SCALE     = 1.0       // Model scale.
        private constant real    L_FX_HEIGHT    = 25.0      // Height.
        
        // Model path:
        private constant string  INDICATOR_FX   = "Abilities\\Spells\\Items\\VampiricPotion\\VampPotionCaster.mdl"
        
        //==================================================
        //=== PERIMITER AREA EFFECT CONFIGURATION
        // Multiple of these effects are created around the perimeter of the prediction area.
        private constant integer NUM_EFFECTS    = 8         // Number of effects.
        private constant real    EFFECT_SCALE   = 1.0       // Model scale.
        private constant real    EFFECT_HEIGHT  = 25.0      // Height.
        
        // Model path:
        private constant string  INDICATOR_ART  = "Abilities\\Weapons\\BloodElfMissile\\BloodElfMissile.mdl"
        
        //==================================================
        //=== EXPLOSION CONFIGURATION
        
        // Created at prediction area when the time expires:
        private constant string  EXPLODE_EFFECT = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"
        
        // Created at prediction area when the target is in the area:
        private constant string  HIT_EFFECT     = "Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl"
        
        // Created at prediction area when the target is not in the area:
        private constant string  MISS_EFFECT    = "Abilities\\Spells\\Human\\Polymorph\\PolyMorphTarget.mdl"
        
        //==================================================
        //=== MANA MISSILE CONFIGURATION
        private constant real    MAX_DISTANCE   = 1000.0    // No mana is burned if the target is this far from the prediction area.
        private constant real    MISSILE_SCALE  = 1.0       // Scale.
        private constant real    MISSILE_SPEED  = 750.0     // Speed.
        private constant real    MISSILE_ARC    = 0.2       // Arc.
        private constant real    MISSILE_HEIGHT = 50.0      // Height.
        
        // Model:
        private constant string  MISSILE_ART    = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"
        
        // Effect created on target when mana is burned:
        private constant string  MANA_BURN_FX   = "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
        
        // Attachment point for the above effect:
        private constant string  BURN_FX_ATTACH = "origin"
        
        //==================================================
        //=== FLOATING TEXT CONFIGURATION
        private constant boolean DISPLAY_TEXT   = true      // Display floating text showing damage dealt?
        private constant real    TEXT_ANGLE     = 90.0      // Movement angle.
        private constant real    TEXT_SPEED     = 64.0      // Movement speed.
        private constant real    TEXT_OFFSET    = 50.0      // Height above target.
        private constant real    TEXT_DURATION  = 2.0       // Duration.
        private constant real    TEXT_FADEPOINT = 1.0       // Fade time.
        private constant real    TEXT_HEIGHT    = 0.023     // Size.
        
        // Colour of text when showing damage:
        private constant string  COLOUR_DAMAGE  = "|cffff0000"
        
        // Colour of text when showing mana burned:
        private constant string  COLOUR_MANA    = "|cff8000FF"
    endglobals
    
    //==================================================
    //=== PREDICTS TARGET'S POSITION IN THIS MANY SECONDS
    private function GetDelay takes integer level returns real
        return 2.0
    endfunction
    
    //==================================================
    //=== HIT AREA OF EFFECT
    private function GetArea takes integer level returns real
        return (25.0 * level) + 125.0
    endfunction
    
    //==================================================
    //=== DAMAGE DEALT ON HIT
    private function GetDamage takes integer level returns real
        return (100.0 * level) + 50.0
    endfunction
    
    //==================================================
    //=== MANA BURNED ON MISS
    private function GetManaBurn takes integer level returns real
        return 100.0 * level
    endfunction
    
    //==================================================
    //=== DAMAGE FUNCTION SETUP
    // This is if you want to change attack/damage types or if you use a custom
    // function for damage detection or something similar.
    private function DealDamage takes unit source, unit target, real amount returns nothing
        call UnitDamageTarget(source, target, amount, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
    endfunction
    
/***************************************************************************************************
*
*   END OF CONFIGURATION AND DOCUMENTATION
*
***************************************************************************************************/

    globals
        private constant real TEXT_X_VELOCITY = (Cos(TEXT_ANGLE * bj_DEGTORAD) * TEXT_SPEED) * 0.071 / 128.0
        private constant real TEXT_Y_VELOCITY = (Sin(TEXT_ANGLE * bj_DEGTORAD) * TEXT_SPEED) * 0.071 / 128.0
    endglobals
    
    static if (DISPLAY_TEXT) then
        private function DisplayText takes unit target, string text returns nothing
            local texttag tt
            
            if (IsVisibleToPlayer(GetUnitX(target), GetUnitY(target), GetLocalPlayer())) then
                set tt = CreateTextTag()
                call SetTextTagText(tt, text, TEXT_HEIGHT)
                call SetTextTagPosUnit(tt, target, TEXT_OFFSET)
                call SetTextTagVelocity(tt, TEXT_X_VELOCITY, TEXT_Y_VELOCITY)
                call SetTextTagPermanent(tt, false)
                call SetTextTagLifespan(tt, TEXT_DURATION)
                call SetTextTagFadepoint(tt, TEXT_FADEPOINT)
                call SetTextTagVisibility(tt, true)
                set tt = null
            endif
        endfunction
    endif

    private struct ManaMissile extends xemissile
        real amount
        
        private method onHit takes nothing returns nothing
            local real mana
            
            if (not IsUnitType(targetUnit, UNIT_TYPE_DEAD) /*
                */ and not IsUnitType(targetUnit, UNIT_TYPE_MAGIC_IMMUNE) /*
                */ and not IsUnitType(targetUnit, UNIT_TYPE_STRUCTURE)) then
                
                set mana = GetUnitState(targetUnit, UNIT_STATE_MANA)
                
                if (mana > 0.0) then
                    call SetUnitState(targetUnit, UNIT_STATE_MANA, mana - amount)
                    call DestroyEffect(AddSpecialEffectTarget(MANA_BURN_FX, targetUnit, BURN_FX_ATTACH))
                    static if (DISPLAY_TEXT) then
                        call DisplayText(targetUnit, COLOUR_MANA + "-" + I2S(R2I(amount)) + "|r")
                    endif
                endif
            endif
        endmethod
    
        static method create takes real sx, real sy, unit t, real amt returns thistype
            local thistype this = allocate(sx, sy, 0.0, GetUnitX(t), GetUnitY(t), MISSILE_HEIGHT)
            
            set amount = amt
            set targetUnit = t
            set fxpath = MISSILE_ART
            set scale = MISSILE_SCALE
            call launch(MISSILE_SPEED, MISSILE_ARC)
            
            return this
        endmethod
    
    endstruct
    
    // I had to write this because I can't set xefx objects to be
    // visible to only certain players.
    private struct Effect
        unit dummy
        effect fx
        
        method destroy takes nothing returns nothing
            call DestroyEffect(fx)
            set dummy = null
            set fx = null
            call deallocate()
        endmethod
        
        static method create takes player p, real x, real y, real s, real h, string e returns thistype
            local thistype this = allocate()
            
            set dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x, y, 0.0)
            call SetUnitScale(dummy, s, s, s)
            set fx = AddSpecialEffectTarget(e, dummy, "origin")
            call ShowUnit(dummy, IsPlayerAlly(GetLocalPlayer(), p))
            
            if (h != 0.0) then
                call UnitAddAbility(dummy, 'Amrf')
                call UnitRemoveAbility(dummy, 'Amrf')
                call SetUnitFlyHeight(dummy, h, 99999.0)
            endif
            
            return this
        endmethod
    
    endstruct
    
    private type fxList extends Effect array[NUM_EFFECTS]
    
    private struct Prediction extends array // I made it an array struct because I wanted to try it :)
        static constant real ANGLE_DIV = (2.0 * bj_PI) / NUM_EFFECTS
        //-----
        static integer numInstances = 0
        static integer numRecycles  = 0
        //-----
        static integer array recycled
        //-----
        unit    caster
        unit    target
        integer level
        real    tx
        real    ty
        real    area
        fxList  fx
        Effect  locFx
        //-----
        
        /**
         * Removes the area effects and cleans up.
         */
        private method destroy takes nothing returns nothing
            local integer i = 0
            
            loop
                exitwhen i == NUM_EFFECTS
                call fx[i].destroy()
                set i = i + 1
            endloop
            
            call fx.destroy()
            call locFx.destroy()
            set caster = null
            set target = null
            set recycled[numRecycles] = this
            set numRecycles = numRecycles + 1
        endmethod
        
        /**
         * Checks to see whether the target is in the area. Damages or burns
         * mana accordingly.
         */
        private static method explode takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local real newX = GetUnitX(target)
            local real newY = GetUnitY(target)
            local real distance = SquareRoot((newX - tx) * (newX - tx) + (newY - ty) * (newY - ty))
            
            call ReleaseTimer(GetExpiredTimer())
            call DestroyEffect(AddSpecialEffect(EXPLODE_EFFECT, tx, ty))
            
            // Target is in area:
            if (distance <= area) then
                call DealDamage(caster, target, GetDamage(level))
                call DestroyEffect(AddSpecialEffect(HIT_EFFECT, tx, ty))
                static if (DISPLAY_TEXT) then
                    call DisplayText(target, COLOUR_DAMAGE + I2S(R2I(GetDamage(level))) + "!|r")
                endif
            // Target is outside area:
            else
                // Target is within max distance:
                if (distance <= MAX_DISTANCE) then
                    call ManaMissile.create(tx, ty, target, ((MAX_DISTANCE - distance) / MAX_DISTANCE) * GetManaBurn(level))
                endif
                call DestroyEffect(AddSpecialEffect(MISS_EFFECT, tx, ty))
            endif
            
            call destroy()
        endmethod
        
        /**
         * Displays the estimated area.
         */
        private method displayArea takes nothing returns nothing
            local player owner = GetOwningPlayer(caster)
            local integer i = 0
            
            set fx = fxList.create()
            
            loop
                exitwhen i == NUM_EFFECTS
                set fx[i] = Effect.create(owner, tx + area * Cos(i * ANGLE_DIV), ty + area * Sin(i * ANGLE_DIV), /*
                    */ EFFECT_SCALE, EFFECT_HEIGHT, INDICATOR_ART)
                set i = i + 1
            endloop
            
            set locFx = Effect.create(owner, tx, ty, L_FX_SCALE, L_FX_HEIGHT, INDICATOR_FX)
                    
            set owner = null
        endmethod
        
        /**
         * Checks whether the target moved. Creates the estimated area accordingly.
         */
        private static method checkPosition takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local real newX
            local real newY
            local real angle
            local real distance
            
            set newX = GetUnitX(target)
            set newY = GetUnitY(target)
            
            // Target has moved:
            if (newX != tx and newY != ty) then
                set angle = Atan2(tx - newX, ty - newY)
                set distance = SquareRoot((newX - tx) * (newX - tx) + (newY - ty) * (newY - ty)) / INITIAL_DELAY * GetDelay(level)
                set tx = tx + distance * Cos(angle)
                set ty = ty + distance * Sin(angle)
            endif
            
            call displayArea()
            call TimerStart(GetExpiredTimer(), GetDelay(level), false, function thistype.explode)
        endmethod
        
        /**
         * Allocate a new instance and save the target's position. Start a timer to check
         * whether it is moving.
         */
        private static method create takes nothing returns thistype
            local thistype this
            
            if (numRecycles == 0) then
                set numInstances = numInstances + 1
                set this = numInstances
            else
                set numRecycles = numRecycles - 1
                set this = recycled[numRecycles]
            endif
            
            set caster = GetTriggerUnit()
            set level = GetUnitAbilityLevel(caster, SPELL_ID)
            set area = GetArea(level)
            set target = GetSpellTargetUnit()
            set tx = GetUnitX(target)
            set ty = GetUnitY(target)
            call TimerStart(NewTimerEx(this), INITIAL_DELAY, false, function thistype.checkPosition)
            
            return this
        endmethod
    
        static if (not LIBRARY_SpellEffectEvent) then
            private static method checkSpell takes nothing returns boolean
                if (GetSpellAbilityId() == SPELL_ID) then
                    call create()
                endif
                return false
            endmethod
        endif
        
        /**
         * Register the spell and preload effects.
         */
        private static method onInit takes nothing returns nothing
            static if (LIBRARY_SpellEffectEvent) then
                call RegisterSpellEffectEvent(SPELL_ID, function thistype.create)
            else
                local trigger t = CreateTrigger()
                call TriggerAddCondition(t, Condition(function thistype.checkSpell))
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            endif
            
            call Preload(INDICATOR_FX)
            call Preload(INDICATOR_ART)
            call Preload(EXPLODE_EFFECT)
            call Preload(HIT_EFFECT)
            call Preload(MISS_EFFECT)
            call Preload(MISSILE_ART)
            call Preload(MANA_BURN_FX)
        endmethod
    
    endstruct

endlibrary

Changelog

v1.0
- Initial release.

Credits

For Systems:
- Bribe
- Vexorian

Keywords:
mr_bean, prediction, mind, guess, predict
Contents

Prediction (Map)

Reviews
00:14, 23rd Jun 2013 PurgeandFire: Review: http://www.hiveworkshop.com/forums/2365603-post6.html Approved, though, since those updates are incredibly minor.

Moderator

M

Moderator

00:14, 23rd Jun 2013
PurgeandFire: Review:
http://www.hiveworkshop.com/forums/2365603-post6.html

Approved, though, since those updates are incredibly minor.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
JASS:
// I had to write this because I can't set xefx objects to be
// visible to only certain players.
You can do this by just setting fxpath differently when checking GetLocalPlayer.
Example:
JASS:
local xefx fx = xefx.create(0, 0, 0)
local string path

if GetLocalPlayer() == Player(0) then
    set path = "units\\human\\Peasant\\Peasant.mdl"
else
    set path = ""
endif

set fx.fxpath = path
 
Top