HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Criticism of Spell-Order Filter

12-07-2008, 12:05 PM#1
fX_
Made this for my map. Some of the spells in it can target only units of a particular Id; others require some other special qualification (x allies must be in range of the caster, etc.). It requires Vexorian's SimError for error message displaying, though this feature can be precluded.
Criticism?

**Updated

SpellOrderFilter
Collapse JASS:
library SpellOrderFilter initializer Init requires SimError

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    //  SpellOrderFilter
    //--------------------------------------------------------------------------------------------------------
    //      Enforces special specified targeting conditions/requirements for spell-casting.
    //  When a filtered spell order is issued, its validity for issuance is checked - it is filtered.
    //      Spell orders are described by their orderstring and their availability in the unit to which the
    //  orderstring is issued:
    //  A unit can effectively use only 1 spell that uses a particular orderstring. Therefore, issued
    //  orders will correspond to only 1 ability - whatever ability the ordered unit possesses that uses
    //  that orderstring. Thus, by associating orderstrings with ability Ids, the issued spell order can be
    //  determined and filtered.
    //      When the spell-order is identified, it is checed for validity in the instanciation as
    //  specified SpellOrderFilterFunc determines. If it is invalid, the unit will be paused-stopped-paused
    //  and an error message is displayed by SimError.
    //
    //  This code requires: SimError Library (by Vexorian @ [url]www.wc3campaigns.net[/url])
    //
    //////////////////////////////////////////////////////////////////////////////////////////////////////////

globals
    private string array gSTR_Order[8190] //Orderstring of the ability/spell.
    private integer array gINT_AbilityId[8190] //Ability Id of the ability/spell.
    private SpellOrderFilterFunc array gFILTER[8190] //Filter associated with the casting/ordering of the spell.
    private string array gSTR_Error[8190] //Error message displayed when spell-order instanciation is invalid.
    private integer gINT_CountFilter = 0 //Number of existent-operational filters.
endglobals

    function EnforceSpellOrderFilter takes string orderstring, integer abilityId, SpellOrderFilterFunc filter, string error returns boolean
        local integer INT_Index = 0
        
        //First, check if the spell-order is already registered. Do not create more than 1 index for
        //a spell-order.
        loop
            exitwhen INT_Index == gINT_CountFilter
            if gSTR_Order[INT_Index] == orderstring and gINT_AbilityId[INT_Index] == abilityId then
                return false
            endif
            set INT_Index = INT_Index + 1
        endloop

        //Index new object.
        set gSTR_Order[gINT_CountFilter] = orderstring
        set gINT_AbilityId[gINT_CountFilter] = abilityId
        set gFILTER[gINT_CountFilter] = filter
        set gSTR_Error[gINT_CountFilter] = error
        set gINT_CountFilter = gINT_CountFilter + 1

        return true
    endfunction

    function UnenforceSpellOrderFilter takes string orderstring, integer abilityId returns boolean
        local integer INT_Index = 0

        //Find the spell-order matching the query.
        loop
            exitwhen INT_Index == gINT_CountFilter
            //De-index the spell-order matching the query.
            if gSTR_Order[INT_Index] == orderstring and gINT_AbilityId[INT_Index] == abilityId then
                set gINT_CountFilter = gINT_CountFilter - 1
                set gSTR_Order[INT_Index] = gSTR_Order[gINT_CountFilter]
                set gINT_AbilityId[INT_Index] = gINT_AbilityId[gINT_CountFilter]
                set gSTR_Error[INT_Index] = gSTR_Error[gINT_CountFilter]
                //Flush.
                set gSTR_Order[INT_Index] = null
                set gSTR_Error[INT_Index] = null
                return true
            endif
            set INT_Index = INT_Index + 1
        endloop

        return false
    endfunction

    function interface SpellOrderFilterFunc takes unit caster, unit target, real x, real y returns boolean

    private function FilterSpellOrder takes nothing returns boolean
        local string STR_Order = OrderId2String(GetIssuedOrderId())
        local unit U_Agent = GetTriggerUnit()
        local unit U_Target = null
        local real R_XTarget = 0.00
        local real R_YTarget = 0.00
        local integer INT_Index = 0

        set U_Target = GetOrderTargetUnit()
        set R_XTarget = GetOrderPointX()
        set R_YTarget = GetOrderPointY()

        //Find a spell-order index that matches the issued orderstring. Then check if the unit has the ability associated
        //with the orderstring that constitute the spell order. If it has these - if it was issued the particular query spell-order -
        //validate it.
        loop
            exitwhen INT_Index == gINT_CountFilter
            //If invalid, stop the unit and display the error message.
            if gSTR_Order[INT_Index] == STR_Order and GetUnitAbilityLevel(U_Agent, gINT_AbilityId[INT_Index]) > 0 and gFILTER[INT_Index].evaluate(U_Agent, U_Target, R_XTarget, R_YTarget) == false then
                call PauseUnit(U_Agent, true)
                call IssueImmediateOrder(U_Agent, "stop")
                call PauseUnit(U_Agent, false)
                call SimError(GetOwningPlayer(U_Agent), gSTR_Error[INT_Index])
                return true
            endif
            set INT_Index = INT_Index + 1
        endloop

        //Flush data.
        set STR_Order = null
        set U_Agent = null
        set U_Target = null

        return false
    endfunction

    private function Init takes nothing returns nothing
        local trigger TRIG = CreateTrigger()

        //Setup order-catching trigger.
        call TriggerRegisterAnyUnitEventBJ(TRIG, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        call TriggerRegisterAnyUnitEventBJ(TRIG, EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER)
        call TriggerRegisterAnyUnitEventBJ(TRIG, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        call TriggerAddCondition(TRIG, Condition(function FilterSpellOrder))
        
        set TRIG = null
    endfunction
    
endlibrary

SimError
http://wc3campaigns.net/showthread.php?t=101260

Example:
This is the spell that prompted production of this system.
Floraspring: Creates a Verdant Hand at the target point or upgrades the target Verdant Hand.
Based-off 'Channel'.

Obviously, it can target only points and Verdant Hands. Point-targeting is not a problem, but restricting target options to units of the 'Verdant Hand' unit-type requires triggering.

Collapse JASS:
scope Floraspring

globals
    private constant string gSTR_OrderFloraspring = "ward"
    private constant integer gINT_AbilityIdFloraspring = 'A004'
    private constant string gSTR_Error = "Must target one of your Verdant Hands that have not been fully-grown."
    public constant integer gINT_UnitIdVerdantHand = 'o001'
    private constant integer gINT_AbilityIdVerdantHandAttribute1 = 'A00E'
    private constant integer gINT_AbilityIdVerdantHandAttribute2 = 'A00D'
endglobals

    private function IsFloraspringCast takes nothing returns boolean
        return GetSpellAbilityId() == gINT_AbilityIdFloraspring
    endfunction

    private function FloraspringCast takes nothing returns nothing
        local unit U_Caster = GetTriggerUnit()
        local unit U_Target = GetSpellTargetUnit()
        local location LOC_Target = GetSpellTargetLoc()
        local integer INT_Level

        if U_Target == null then
            //Create a new Verdant Hand if the target is a point.
            set U_Target = CreateUnit(GetOwningPlayer(U_Caster), gINT_UnitIdVerdantHand, GetLocationX(LOC_Target), GetLocationY(LOC_Target), GetUnitFacing(U_Caster) + 180.00)
            call SetUnitAnimation(U_Target, "birth")
            call PolledWait(0.25)
            call SetUnitAnimation(U_Target, "stand")
        else
            //Upgrade the target Verdant Hand if the target is a Verdant Hand.
            set INT_Level = GetUnitAbilityLevel(U_Target, gINT_AbilityIdVerdantHandAttribute1) + 1
            call SetUnitAbilityLevel(U_Target, gINT_AbilityIdVerdantHandAttribute1, INT_Level)
            call SetUnitAbilityLevel(U_Target, gINT_AbilityIdVerdantHandAttribute2, INT_Level)
            call SetUnitScale(U_Target, 0.50 + (INT_Level - 1) * 0.25, 0.00, 0.00)
        endif

        set U_Caster = null
        set U_Target = null
        call RemoveLocation(LOC_Target)
        set LOC_Target = null
    endfunction

    private function OrderFilter takes unit caster, unit target, real x, real y returns boolean
        if (target != null and (GetUnitTypeId(target) != gINT_UnitIdVerdantHand or GetOwningPlayer(target) != GetOwningPlayer(caster))) or GetUnitAbilityLevel(target, gINT_AbilityIdVerdantHandAttribute1) == 3 then
            return false
        endif

        return true
    endfunction
    
    public function Init takes nothing returns nothing
        local trigger TRIG_Cast = CreateTrigger()

        call EnforceSpellOrderFilter(gSTR_OrderFloraspring, gINT_AbilityIdFloraspring, SpellOrderFilterFunc.OrderFilter, gSTR_Error)
        call TriggerRegisterAnyUnitEventBJ(TRIG_Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(TRIG_Cast, Condition(function IsFloraspringCast))
        call TriggerAddAction(TRIG_Cast, function FloraspringCast)
    endfunction

endscope
12-07-2008, 01:39 PM#2
Anitarf
In the Unenforce function you don't need to loop through the rest of the array, you could just store the values from the last array spot to the spot of the removed function.

You needn't loop through the rest of the list once you find an order filter that matches the issued order.

If you store more than a couple of orders this way, using gamecache would be faster than looping through arrays.

You have some odd variable naming conventions.

Users should call the SimError function from their own order filters, for example what if an order has multiple conditions under wich it gets cacneled, in that case users might want different error messages for each of them, whih is something your system doesn't allow but if the users called SimErr themselves before returning they could do that.

The order filter function in your spell example always returns false?
12-07-2008, 02:00 PM#3
fX_
I don't know anything about gamecache and it seems like a complication.
As for the thing on SimError, I thought of implementing some general error message, but your suggestion works well - maybe even better since it gives a more elaborate message.

Ya my example doesn't work. Tested it just now.

Did changes. I'll update soon.

p.s. As for the conventions, they're easier for me to understand. Abbreviation of variable type + "_" + variable name. A "g" before the variable type denotes that it is a global variable. In structs, I do "int"+"variableName = "intVariableName".

thx
12-07-2008, 10:41 PM#4
Zerzax
Globals conventionally are all caps with I forget the name for "_" as spaces, locals without caps. It's obviously up to you, but the more conventional often the easier for people to read your scripts.
12-07-2008, 11:08 PM#5
Anitarf
In Jass, only constant globals are conventionally all caps.