[snippet]RangedAccuracy

notes
This is currently unstable. When the firing unit moves, it'll screw up the accuracy stuff. Attack indexing is being worked on so that this will work right 100% of the time. Until then, you can just see the concept of what's going to be done in the demo map ^_-.
I'm going to update the accuracy chance to hit formula to be a bit better. the current one works, but I was never satisfied with it.


[/quote]

After working with plateaus, I decided it would be interesting to make plateaus act like cliffs do in regards to missing. When a unit on a cliff level attacks a unit on a higher cliff level, the unit has a large**** chance to miss.

This uses accuracy, z level, and 3d distance to determine a unit's chance to hit. A unit at a higher z level has a greater chance to hit while a unit at a lower z level has a much lower chance to hit.

There is always a chance to miss, even if the accuracy is 70000 and the 3D distance is 1 (chance to miss in that situation is probably .0001%).

This, CliffBound, and SlopeSpeed, should drastically increase strategy for wc3.

You will need to tinker with the ACCURACY_RATE and Z_RATE values to get it just right. My current ACCURACY_RATE value might be a bit high... with the mass Z, the yellow unit only hits a little more than 50% of the time using .025.. .0125 would probably work well for ACCURACY_RATE.

For Z_RATE, .8 to .9 seem to work well.

You can also change those values to totally different things and just decrease/increase accuracy norms for the units. Increases will make it more difficult to improve units and decreases will make it easier to increase units. I tried to use norms that were just the unit attack ranges, but norms like 3500-9000 could be used with only 700 attack range for ORPGs or something to make classes more defined (ranged gear adds lots of accuracy, melee gear adds more armor, etc).

All sorts of things can be done with this ; ).

JASS:
library RangedAccuracy uses /*
    */UnitIndexer //http://www.hiveworkshop.com/forums/jass-functions-413/unit-indexer-172090/
    
    globals
        //how much 3D distance effects hitting (greater distance is less chance to hit)
        //greater value makes distance weight larger
        //exponential
        private constant real ACCURACY_RATE = .025
        
        //how much change in z effects hitting (positive increases chance, negative decreases)
        //greater value makes z weight larger
        //exponential
        private constant real Z_RATE = .8
    endglobals
    
    private function AccuracyFilter takes unit u returns boolean
        return true
    endfunction
    
//save, close map, then open map, comment the below, then save
//! external ObjectMerger w3a AIlz RAAn anam "Life Bonus" ansf "(Ranged Accuracy)" Ilif 1 900000 aite 0
    
/*
struct RangedAccuracy
    //accuracy of a given unit (overwritten whenever mass accuracy methods called)
    real accuracy
    
    //offset accuracy of a specific unit
    real accuracyOffset
    
    //did the unit miss on its last attack?
    readonly boolean missed

    //sets accuracy for all units of a type
    static method setUnitTypeAccuracy takes integer unitType, real accuracy returns nothing
    
    //sets accuracy for all units of a type owned by a player
    static method setPlayerUnitTypeAccuracy takes integer unitType, integer playerId, real accuracy returns nothing
*/
    
    struct RangedAccuracy extends array
        public real accuracy
        public real accuracyOffset
        public boolean missedx
        
        private static constant integer NULL_PLAYER = -20
        
        private static hashtable unitTypeAccuracy = InitHashtable()
        
        private static real uaccuracy
        private static group ug
        private static group mg
        private static player op
        private static integer muid
        private static location loc = Location(0, 0)
        
        private trigger damage
        private static boolexpr dc
        
        private static integer removeCount = 0
        private static unit array remove
        private static timer rem = CreateTimer()
        private integer owner
        
        public method operator missed takes nothing returns boolean
            return missedx
        endmethod
        
        private static method updateAccuracy takes nothing returns nothing
            set RangedAccuracy(GetUnitUserData(GetEnumUnit())).accuracy = uaccuracy
        endmethod
        
        private static method moveUnit takes nothing returns nothing
            if (GetUnitTypeId(GetEnumUnit()) == muid and GetOwningPlayer(GetEnumUnit()) == op) then
                call GroupRemoveUnit(ug, GetEnumUnit())
                call GroupAddUnit(mg, GetEnumUnit())
            endif
        endmethod
        
        public static method setUnitTypeAccuracy takes integer unitType, real accuracy returns nothing
            call SaveReal(unitTypeAccuracy, NULL_PLAYER, unitType, accuracy)
            if (not HaveSavedHandle(unitTypeAccuracy, -NULL_PLAYER, unitType)) then
                call SaveGroupHandle(unitTypeAccuracy, -NULL_PLAYER, unitType, CreateGroup())
            else
                set uaccuracy = accuracy
                call ForGroup(LoadGroupHandle(unitTypeAccuracy, -NULL_PLAYER, unitType), function thistype.updateAccuracy)
            endif
        endmethod
        
        public static method setPlayerUnitTypeAccuracy takes integer unitType, integer playerId, real accuracy returns nothing
            call SaveReal(unitTypeAccuracy, playerId, unitType, accuracy)
            if (not HaveSavedHandle(unitTypeAccuracy, -playerId, unitType)) then
                set ug = LoadGroupHandle(unitTypeAccuracy, -NULL_PLAYER, unitType)
                set mg = CreateGroup()
                set op = Player(playerId)
                set muid = unitType
                call SaveGroupHandle(unitTypeAccuracy, -playerId, unitType, mg)
                if (ug != null) then
                    call ForGroup(ug, function thistype.moveUnit)
                endif
            else
                set uaccuracy = accuracy
                call ForGroup(LoadGroupHandle(unitTypeAccuracy, -playerId, unitType), function thistype.updateAccuracy)
            endif
        endmethod
        
        private static method onIndex takes nothing returns boolean
            local thistype this
            local integer pid
            local integer uid
            local group g
            
            if (AccuracyFilter(GetIndexedUnit())) then
                set this = GetIndexedUnitId()
                set pid = GetPlayerId(GetOwningPlayer(GetIndexedUnit()))
                set uid = GetUnitTypeId(GetIndexedUnit())
                
                set accuracy = LoadReal(unitTypeAccuracy, pid, uid)
                if (accuracy == 0) then
                    set accuracy = LoadReal(unitTypeAccuracy, NULL_PLAYER, uid)
                    set g = LoadGroupHandle(unitTypeAccuracy, -NULL_PLAYER, uid)
                    if (g == null) then
                        set g = CreateGroup()
                        call SaveGroupHandle(unitTypeAccuracy, -NULL_PLAYER, uid, g)
                    endif
                else
                    set g = LoadGroupHandle(unitTypeAccuracy, -pid, uid)
                    if (g == null) then
                        set g = CreateGroup()
                        call SaveGroupHandle(unitTypeAccuracy, -pid, uid, g)
                    endif
                endif
                call GroupAddUnit(g, GetIndexedUnit())
                
                set damage = CreateTrigger()
                call TriggerRegisterUnitEvent(damage, GetIndexedUnit(), EVENT_UNIT_DAMAGED)
                call TriggerAddCondition(damage, dc)
                set missedx = false
                set g = null
                set owner = pid
            endif
            return false
        endmethod
        
        private static method onDeindex takes nothing returns boolean
            local thistype this = GetIndexedUnitId()
            local integer pid
            local integer uid
            local group g
            
            if (damage != null) then
                set pid = GetPlayerId(GetOwningPlayer(GetIndexedUnit()))
                set uid = GetUnitTypeId(GetIndexedUnit())
                set g = LoadGroupHandle(unitTypeAccuracy, -pid, uid)
                
                if (g == null) then
                    set g = LoadGroupHandle(unitTypeAccuracy, -NULL_PLAYER, uid)
                endif
                call GroupRemoveUnit(g, GetIndexedUnit())
                set accuracy = 0
                call DestroyTrigger(damage)
                set damage = null
                set g = null
            endif
            return false
        endmethod
        
        private static method removec takes nothing returns nothing
            loop
                exitwhen removeCount == 0
                set removeCount = removeCount - 1
                call UnitRemoveAbility(remove[removeCount], 'RAAn')
            endloop
        endmethod
        
        private static method onAttack takes nothing returns boolean
            local unit attacked = GetTriggerUnit()
            local unit attacker = GetEventDamageSource()
            local thistype this = GetUnitUserData(attacker)
            local real x1
            local real x2
            local real y1
            local real y2
            local real xd
            local real yd
            local real zd
            local real z1
            local real z2
            local real d2
            local real d3
            local integer pos
            local real chanceToHit
            local real ac
            
            if (damage != null) then
                set x1 = GetUnitX(attacked)
                set x2 = GetUnitX(attacker)
                set y1 = GetUnitY(attacked)
                set y2 = GetUnitY(attacker)
                set xd = x2-x1
                set yd = y2-y1
                set ac = accuracy+accuracyOffset
                set pos = 1
                
                call MoveLocation(loc, x1, y1)
                set z1 = GetLocationZ(loc)+GetUnitFlyHeight(attacked)
                call MoveLocation(loc, x2, y2)
                set z2 = GetLocationZ(loc)+GetUnitFlyHeight(attacker)
                set zd = z2-z1
                set d2 = SquareRoot(xd*xd+yd*yd)
                set d3 = SquareRoot(d2*d2+zd*zd)-ac
                if (d3 < 0) then
                    set d3 = 0
                endif
                if (zd < 0) then
                    set pos = -1
                endif
                
                set chanceToHit = Pow(d3, 1-ACCURACY_RATE)-Pow(zd, Z_RATE)*pos
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, GetPlayerName(GetOwningPlayer(attacker)) + ": " + R2S(Pow(d3, 1-ACCURACY_RATE))+":"+R2S(Pow(zd, Z_RATE)*pos))
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, R2S(ac) + "<" + R2S(chanceToHit))
                if (GetRandomReal(0, ac) < chanceToHit) then
                    if (GetWidgetLife(attacked) <= .405) then
                        call UnitAddAbility(attacked, 'RAAn')
                        set remove[removeCount] = attacked
                        set removeCount = removeCount + 1
                        call TimerStart(rem, 0, false, function thistype.removec)
                    endif
                    call SetWidgetLife(attacked, GetWidgetLife(attacked)+GetEventDamage())
                    set missedx = true
                else
                    set missedx = false
                endif
                
                set attacked = null
                set attacker = null
            endif
            return false
        endmethod
        
        private static method onOwnerChange takes nothing returns boolean
            local thistype this = GetUnitUserData(GetTriggerUnit())
            local integer pid
            local integer uid
            local group g
            
            if (damage != null) then
                set pid = GetPlayerId(GetTriggerPlayer())
                set uid = GetUnitTypeId(GetIndexedUnit())
                
                set g = LoadGroupHandle(unitTypeAccuracy, -owner, uid)
                if (g == null) then
                    set g = LoadGroupHandle(unitTypeAccuracy, -NULL_PLAYER, uid)
                endif
                call GroupRemoveUnit(g, GetIndexedUnit())
                
                set g = LoadGroupHandle(unitTypeAccuracy, -pid, uid)
                if (g == null) then
                    set g = LoadGroupHandle(unitTypeAccuracy, -NULL_PLAYER, uid)
                    set accuracy = LoadReal(unitTypeAccuracy, NULL_PLAYER, uid)
                else
                    set accuracy = LoadReal(unitTypeAccuracy, pid, uid)
                endif
                
                call GroupAddUnit(g, GetTriggerUnit())
                set owner = pid
                set g = null
            endif
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            local integer i = 16
            local trigger t = CreateTrigger()
            call OnUnitIndex(Condition(function thistype.onIndex))
            call OnUnitDeindex(Condition(function thistype.onDeindex))
            set dc = Condition(function thistype.onAttack)
            loop
                set i = i - 1
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_CHANGE_OWNER, null)
                exitwhen i == 0
            endloop
            call TriggerAddCondition(t, Condition(function thistype.onOwnerChange))
        endmethod
    endstruct
endlibrary

Demo
JASS:
struct tester extends array
    private static method onInit takes nothing returns nothing
        call RangedAccuracy.setUnitTypeAccuracy('earc', 600)
        call RangedAccuracy.setUnitTypeAccuracy('ebal', 1150)
    endmethod
endstruct
 

Attachments

  • test map.w3x
    23.9 KB · Views: 121
Last edited:
if I shoot a really slow missile and immediatly start climbing a hill will I have a greater chance to hit?

I would like to have a floating text which reads "MISS" or something everytime a unit misses

your heal method won't work for all cases (e.g. 10 max hp and 20 dmg)
adding bonus hp and removing it after unit took dmg will solve this problem
see attached testmap (not optimized, just an example)
 

Attachments

  • Block.w3m
    12.5 KB · Views: 225
Last edited:
->if I shoot a really slow missile and immediatly start climbing a hill will I have a greater chance to hit?

I can fix that, but you're going to have to give me some time to think about how. A simple stack of attacks isn't going to work =).

->I would like to have a floating text which reads "MISS" or something everytime a unit misses

missed var is in there so you can do stuff like that : )

edit
Realized I forgot to write the owner change code too O_O.


edit
For now I fixed the damage stuff as well as owner changing

Now I still need to fix on attack, and for that I will certainly have to ponder ; ).

A design would be similar to this
Code:
onAttack: queue the attack (last)
onDamage: pop the attack (first)

Only problem is sometimes, in cases of ranged attacks, the attack never damages. I'd have to somehow determine whether an attack is valid or not (pop off multiple), and that's what I must ponder over.

Tx for feedback mate : )

edit
I figured out how to do it. I'll have the update done in about 8 hours as I need to do some stuff ; ).

edit
You didn't need to attach that map, I know how to properly do the damage stuff : P. I was just being a bit lazy.
 
Last edited:
Top