HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

UnitData System

11-05-2009, 08:45 PM#1
Mr.Malte
Well, I already made GetUnitCost.
And I am planning to make a very nice system, but for that I need GetUnitDamage. And for GetUnitDamage I need GetUnitAttackType and GetUnitArmorType.
But all the systems have a similar structure, I have to do the same safety checkings, got a dummy owner etc.
Shall I put all of those into one big script and call it UnitData?
Or would it cause too much code bloat (would have like 800 lines maybe).

GetUnitDamage is actually finished and works very very fine, but it has some bad limitations listed in the contra list.
I decided to remake the system, but for that I need the other systems.

Collapse JASS:
library GetUnitDamage initializer init requires Table
//===========================================================================
// Information: 
//==============
//
//  Getting a units damage is complicated and never 100% accurrate. This is only system
//  I know that is able to detect a units damage. And knowing a units damage can be very
//  very useful. My example is the Manrate system (coming soon). A system that allows dynamic
//  attack cooldowns. But you can't increase a units attack speed by more than 500% and you'd
//  need some research to get the right values, so I have to simulate attacks.
//
//===========================================================================
// Implementation:
// ===============
// 
//  1. Make a new trigger and convert it to custom text.
//  2. Insert the whole code.
//  3. Make sure you have the JassNewGen: [url]http://www.wc3c.net/showthread.php?t=90999[/url]
//  4. Download Table and implement it (like the GetUnitDamage system):
//     [url]http://www.wc3c.net/showthread.php?t=101246[/url]
//  5. Import the 'GUDD' dummy 
//  6. Import the 'GUDA' ability
//  7. Make the rawcodes of the dummy/ability fit to the constants.
//  8. Filter units you don't want to get the damage of with the UserFilter.
//  9. Save the map
//
//===========================================================================
// FAQ:
// ===
//
//  1. How does this system work accurately?
//      Each time a new unit type enters the map, several clones are made of
//      it in the upper right corner that attack a dummy unit with no armor.
//      The damage is saved to a struct. If all clones have attacked, the struct
//      can be used and the units are killed. Of course everything is invisible,
//      nobody will notice.
//
//  2. How does this handle heroes? Their damages depend on their levels.
//      This system doesn't support heroes at all.
//
//  3. What if 'new' units have a damage bonus?
//      The clones are just units of the same type of the 'new' unit.
//      So it will just detect the basic damage of a unit, if you didn't
//      add the bonuses by an aura.
//
//===========================================================================
// Functions: 
//===========
//
// GetUnitDamage(unit,type*)        : Returns the damage of a unit
// GetUnitdamage_Debug()            : Use this if the system doesn't work correctly. It will put out portential bugs.
//
// * type can be DAMAGE_ITEM_MAX_DAMAGE, DAMAGE_ITEM_MIN_DAMAGE or DAMAGE_ITEM_AVERAGE_DAMAGE
//
//===========================================================================
// Pros:
// =====
//
//  - This is the only system that can get a units damage accurately. And that
//    can be very useful
//
//===========================================================================
// Contras:
// =======
//
//  - This will increase the handle id by number of different unit types * ATTACK_COUNT
//  - The system only works correctly if a unit of the type has been longer than cooldown
//    of the unit / 5 seconds on the map.
//    ( Example: My Unit has a cooldown of 1.00. If a unit of that types enters the map the first
//    time, the system will register the damage of its unit type after 0.2 seconds. You can avoid that
//    by preplacing the unit)
//  - This uses some performance each time a unit enters the map or is damaged.
//  - Doesn't support heroes (can be added if highly desired)
//  - This can't handle units with attacks that only allow specific targets
//    (like only structures)
//
//===========================================================================

    globals
        constant integer DAMAGE_ITEM_MAX_DAMAGE = 0
        constant integer DAMAGE_ITEM_MIN_DAMAGE = 1
        constant integer DAMAGE_ITEM_AVERAGE_DAMAGE = 2
        private constant integer PLAYER_ID = 12
        private constant integer DUMMY_ID  = 'GUDD'
        private constant integer ABILITY_ID = 'GUDA'
        // Higher number = more accurate, slower, use more handle IDs
        private constant integer ATTACK_COUNT = 25
        private constant real MAX_TOLERANCE_VALUE = 2.5
    endglobals
    
    private function UserFilter takes unit u returns boolean
        return (GetUnitAbilityLevel(u,'Aloc') == 0)
    endfunction
    
    // DO NOT EDIT BELOW THIS LINE
    globals
        private trigger unitEnters = CreateTrigger()
        private trigger unitDamaged = CreateTrigger()
        private Table UnitDamage
        private Table GhostUnitDamage
        private real maxX
        private real maxY
        private unit dummy = null
        private player dummyOwner = Player(PLAYER_ID)
    endglobals
    
    struct UnitDamageStruct
        integer instance = 0
        real averageDamage = 0.
        real maxDamage = 0.
        real minDamage = 0.
    endstruct
    
    public function Debug takes nothing returns nothing
        // Has some flaws, but it's unimportant: Only for debugging purpose.
        local group g = CreateGroup()
        local unit u = null
        local integer count = 0
        
        if ATTACK_COUNT == 0 then
            call BJDebugMsg("GetUnitDamage Debug: ATTACK_COUNT is set to 0.")
        endif
        
        if GetPlayerId(dummyOwner) < 12 then
            call BJDebugMsg("GetUnitDamage Debug: Invalid dummyOwner number. Please chose a number below 12.")
        endif
        
        call GroupEnumUnitsOfPlayer(g,dummyOwner,null)
        loop
            set u = FirstOfGroup(g)
            exitwhen u == null
            if GetUnitTypeId(u) == DUMMY_ID then
                set count = count + 1
                call BJDebugMsg("GetUnitDamage Debug: Got too many dummy units. Please report this bug.")
                call GroupClear(g)
                if GetUnitName(u) != "GUDD (GetUnitDamageDummy)" then
                    call BJDebugMsg("GetUnitDamage Debug: You didn't import the dummy unit correctly.")
                endif
            endif
            call GroupRemoveUnit(g,u)
        endloop
        if count == 0 then
            call BJDebugMsg("GetUnitDamage Debug: Didn't find dummy unit.")
        endif
        call DestroyGroup(g)
        
    endfunction

    function GetUnitDamage takes unit u, integer damageItem returns real
        local UnitDamageStruct UD
        
        debug if IsUnitType(u,UNIT_TYPE_HERO) then
        debug    call BJDebugMsg("GetUnitDamage: Used hero.")
        debug    return 0.
        debug elseif u == null then 
        debug    call BJDebugMsg("GetUnitDamage: Used null unit.")
        debug    return 0.
        debug endif
        
        set UD = UnitDamage[GetUnitTypeId(u)]
        if UD == null then
            debug call BJDebugMsg("GetUnitDamage: Tried to get damage that's not registered yet.")
        endif
        //call BJDebugMsg("ID of struct: "+I2S(UD))
        if damageItem == DAMAGE_ITEM_MAX_DAMAGE then
            return UD.maxDamage
        elseif damageItem == DAMAGE_ITEM_MIN_DAMAGE then
            return UD.minDamage
        elseif damageItem == DAMAGE_ITEM_AVERAGE_DAMAGE then
            return UD.averageDamage
        else
            debug call BJDebugMsg("GetUnitDamage: Used invalid damage item")
            return 0.
        endif
    endfunction
    
    private function DefineUnitTypeDamage takes integer ID returns nothing
        local UnitDamageStruct UD = UnitDamage.create()
        local integer i = 0
        local unit u
        set UD.minDamage = 10000000.
        set GhostUnitDamage[ID] = UD
        loop
            set i = i + 1
            exitwhen i > ATTACK_COUNT
            set u = CreateUnit(dummyOwner,ID,maxX,maxY,0.)
            call SetUnitPathing(u,false)
            call UnitAddAbility(u,ABILITY_ID)
            call IssueTargetOrder(u,"attack",dummy)
            call ShowUnit(u,false)
            call UnitApplyTimedLife(u,'BTLF',MAX_TOLERANCE_VALUE)
        endloop
    endfunction
    
    private function returnFlag takes nothing returns boolean
        return UserFilter(GetFilterUnit())
    endfunction
    
    private function onEnter takes nothing returns nothing
        if GetOwningPlayer(GetTriggerUnit()) != dummyOwner then
            call TriggerRegisterUnitEvent(unitDamaged,GetTriggerUnit(),EVENT_UNIT_DAMAGED)
            if UnitDamage.exists(GetUnitTypeId(GetTriggerUnit())) == false then
                call DefineUnitTypeDamage(GetUnitTypeId(GetTriggerUnit()))
            endif
        endif
    endfunction
    
    // Well, I COULD require a damage system, but I have to use all units that
    // enter the map or are in the map in the beginning anyways.
    // Just a few lines of code.
    private function onDamage takes nothing returns nothing
        local integer ID = GetUnitTypeId(GetEventDamageSource())
        local real damage
        local UnitDamageStruct UD
        if GetOwningPlayer(GetEventDamageSource()) == dummyOwner then
            call RemoveUnit(GetEventDamageSource())
            if UnitDamage.exists(ID) == false then
                // We keep the Ghost Struct in reference until all of the mirror dummies
                // attack the damage-getter dummy.
                
                set damage = GetEventDamage()
                set UD = GhostUnitDamage[ID]
                if damage > UD.maxDamage then
                    set UD.maxDamage = damage
                endif
                if damage < UD.minDamage then
                    set UD.minDamage = damage
                endif
                set UD.averageDamage = UD.averageDamage + damage
                
                set UD.instance = UD.instance + 1
                if UD.instance == ATTACK_COUNT then
                    // Attack thing finished. Now UD is a 'real' registered struct.
                    set UnitDamage[ID] = UD
                    call GhostUnitDamage.flush(ID)
                    set UD.averageDamage = UD.averageDamage / I2R(ATTACK_COUNT)
                endif
            endif
        endif
    endfunction
    
    private function init takes nothing returns nothing
        local integer index = 0
        local group g = CreateGroup() // Catch also initial units.
        local unit u
        local boolexpr condition = Condition(function returnFlag)
        local UnitDamageStruct uc
    
        set UnitDamage = Table.create()
        set GhostUnitDamage = Table.create()
        // ==========================================================================
        
        set maxX = GetRectMaxX(bj_mapInitialPlayableArea)-500
        set maxY = GetRectMaxY(bj_mapInitialPlayableArea)-500
        set dummy = CreateUnit(dummyOwner,DUMMY_ID,maxX,maxY,270)
    
        // ==========================================================================
    
        call TriggerRegisterEnterRectSimple(unitEnters,bj_mapInitialPlayableArea)
        call TriggerAddAction(unitEnters,function onEnter)
        call TriggerAddAction(unitDamaged,function onDamage)
        
        // ==========================================================================
        
        call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,condition)
        loop
            set u = FirstOfGroup(g)
            exitwhen u == null
            if UnitDamage.exists(GetUnitTypeId(u)) == false then
                call DefineUnitTypeDamage(GetUnitTypeId(u))
            endif
            call TriggerRegisterUnitEvent(unitDamaged,u,EVENT_UNIT_DAMAGED)
            call GroupRemoveUnit(g,u)
        endloop
        
        call DestroyBoolExpr(condition)
        call DestroyGroup(g)
        //call BJDebugMsg("init function successful")
    endfunction
endlibrary
11-06-2009, 06:48 AM#2
Tot
this can be done faster with gmsi at the price that you've to execute the script everytime you changed something in the oe
11-06-2009, 11:00 AM#3
Viikuna-
Im actually just trying to learn how to do that stuff.

Is there any good tutorial about how to do that for lazy guys like me who would happily skip this read GMSI manual part?
11-06-2009, 11:34 AM#4
Tot
Quote:
Originally Posted by Viikuna-
Im actually just trying to learn how to do that stuff.

Is there any good tutorial about how to do that for lazy guys like me who would happily skip this read GMSI manual part?

Don't know if there is any tutorial flying around
I've learned it via TnE