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

GetUnitCost

Level 11
Joined
Nov 4, 2007
Messages
337
Well, the library description should do it.

JASS:
library GetUnitCost initializer Init requires Table, CommonAIimports
//===========================================================================
// Information: 
//==============
//
//  This library allows you to get the cost of units in the following devices:
// Gold, Wood, Food, FoodIncrement. Normally this is not possible. This is useful
// for example, if you want to make a towerdefense and you want a sell button which gives
// 75% of the gold and wood back you payed for it. But when the tower is an upgrade, so the
// tower itself costs 100 gold plus 100 gold upgrade, and you'd use a dummy to cast Transmute on
// the unit, the player would only get 75% gold back, not the gold for all the upgrades.
// If you used this library, you'd be able to give him 150 instead of 75 gold back.
//
//===========================================================================
// Implementation: 
//================
//
//   1. Make a new trigger, convert it to custom text 
//      and replace all the code in the trigger by this.
//   2. Give credits to Mr.Malte (:P) and use the system.
//===========================================================================
// Functions: 
//===========
//
// GetUnitCost(unit,type*)          : Returns the total gold cost of a unit (upgrades included)
// GetUnitTypeCost(unitId,type*)    : Returns, how much a unit with ID X would cost. Note:
//                                   This ignores upgrade gold costs, so when you just want the cost of
//                                   THIS building/unit, you can use GetUnitTypeCost(GetUnitTypeId(#),#)
//
// * type can be COST_GOLD or COST_LUMBER
//
//===========================================================================
// Features: 
//==========
// 1. Gets the cost of Units including its previous forms (when upgraded)
// 2. Works for units and structures
// 3. Uses a smart way to detect gold and wood
// 4. Destroys UnitCost structs automatically, even when the units are removed.
//
//===========================================================================
    globals
        private constant integer PLAYER_ID = 14
        private constant integer DUMMY_ID  = 'hpea'
        private constant integer RESOURCE_LIMIT = 100000
        private constant boolean CLONE = true
    endglobals
    
    globals
        private trigger unitEnters = CreateTrigger()
        private trigger unitDies = CreateTrigger()
        private trigger unitUpgrades = CreateTrigger()
        private HandleTable UnitData
        private Table RawcodeData
        private real maxX
        private real maxY
        private unit dummy = null
        private player dummyOwner = Player(PLAYER_ID)
    endglobals
    
    // return true: Save the unit's cost.
    // return false: Don't save the unit's cost.
    private function UserFilter takes unit u returns boolean
        return ( GetUnitAbilityLevel(u,'Aloc') == 0 )
    endfunction
    
    struct UnitCost
        integer Lumber
        integer Gold
        UnitCost Link = 0
        
        method increaseBy takes UnitCost inc returns nothing
            set .Lumber = .Lumber + inc.Lumber
            set .Gold = .Gold + inc.Gold
            call inc.destroy()
            
            // And now synchronize the Clone.
            // Destroying and recreating would change its ID, so..
            if CLONE then
                if .Link != 0 then
                    set .Link.Gold = .Gold
                    set .Link.Lumber = .Lumber
                endif
            endif
        endmethod
        
        // We want to give the user the UnitCost struct, but we dont want him to access it
        // and change its data. So we make a 'Parastruct'
        method Clone takes nothing returns UnitCost
            if CLONE then
                if .Link == 0 then
                    set .Link = UnitCost.create()
                endif
                // Always restore the data to be safe.
                set .Link.Gold = .Gold
                set .Link.Lumber = .Lumber
                set .Link.Link = -1 // -1 means: This is just a clone.
                return .Link
            else
                return this
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            if .Link > 0 then
                call .Link.destroy()
            endif
        endmethod
    endstruct
    
    private function GetUnitCostSimple takes integer ID returns UnitCost
        local UnitCost u = UnitCost.create()
        
        //if RawcodeData.exists(ID) then
        //    return RawcodeData[ID]
        //endif
        if IsUnitIdType(ID,UNIT_TYPE_HERO) then
            
            call SetPlayerState(dummyOwner, PLAYER_STATE_RESOURCE_GOLD, RESOURCE_LIMIT)
            call SetPlayerState(dummyOwner, PLAYER_STATE_RESOURCE_LUMBER, RESOURCE_LIMIT)
            call SetPlayerState(dummyOwner, PLAYER_STATE_RESOURCE_FOOD_USED,0)
            call AddUnitToStock(dummy, ID, 1, 1)
            call IssueNeutralImmediateOrderById(dummyOwner, dummy, ID)
            call RemoveUnitFromStock(dummy,ID)
            
            set u.Gold = RESOURCE_LIMIT - GetPlayerState(dummyOwner,PLAYER_STATE_RESOURCE_GOLD)
            set u.Lumber = RESOURCE_LIMIT - GetPlayerState(dummyOwner,PLAYER_STATE_RESOURCE_LUMBER)
        else
            set u.Gold = GetUnitGoldCost(ID)
            set u.Lumber = GetUnitWoodCost(ID)
        endif
        //set RawcodeData[ID] = u
        
        return u
    endfunction
    
    // ======================================================================
    // ============== USER FUNCTIONS ==============
    // ======================================================================
    
    globals
        constant integer COST_GOLD = 0
        constant integer COST_LUMBER = 1
    endglobals
    
    function GetUnitCost takes unit u, integer cost returns integer
        local UnitCost temp = UnitData[u]
        if cost == COST_GOLD then
            return temp.Gold
        elseif cost == COST_LUMBER then
            return temp.Lumber
        else
            debug call BJDebugMsg("GetUnitCost: Used undefined cost-type")
        endif
        return 0
    endfunction
    
    function GetUnitTypeCost takes integer unitId, integer cost returns integer
        local UnitCost temp = GetUnitCostSimple(unitId)
        if cost == COST_GOLD then
            return temp.Gold
        elseif cost == COST_LUMBER then
            return temp.Lumber
        else
            debug call BJDebugMsg("GetUnitCost: Used undefined cost-type")
        endif
        return 0
    endfunction
    
    // ======================================================================
    // ======================================================================
    // ======================================================================
    
    private function onEnter takes nothing returns nothing
        local UnitCost uc
        local unit u = GetTriggerUnit()
        
        // Kill dummies.
        if GetOwningPlayer(u) == dummyOwner then
            call RemoveUnit(u)
            return
        endif
        
        // Actually it would make sense to only save the cost to the unit,
        // when it has been upgraded. But we can't detect the unit type it was
        // before, because the unit ID already changed when the onUpgrade trigger
        // fired.
        
        if UserFilter(u) then
            set uc = GetUnitCostSimple(GetUnitTypeId(u))
            set UnitData[u] = uc
        endif
    endfunction
    
    private function onUpgrade takes nothing returns nothing
        local UnitCost uc = GetUnitCostSimple(GetUnitTypeId(GetTriggerUnit()))
        local UnitCost ucPrev = UnitData[GetTriggerUnit()]
        call ucPrev.increaseBy(uc)
    endfunction
    
    private function Destruct takes unit u returns nothing
        local UnitCost uc
        
        if GetOwningPlayer(u) != dummyOwner then
            set uc = UnitData[u]
            if uc != null then
                call uc.destroy()
            endif
        endif
        
    endfunction
    
    private function onDie takes nothing returns nothing
        call Destruct(GetTriggerUnit())
    endfunction
    
    hook RemoveUnit Destruct
    
    private function returnFlag takes nothing returns boolean
        return UserFilter(GetFilterUnit())
    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 UnitCost uc
        
        set UnitData = HandleTable.create()
        set RawcodeData = Table.create()
        // ==========================================================================
        
        set dummy = CreateUnit(dummyOwner,DUMMY_ID,GetRectMaxX(bj_mapInitialPlayableArea),GetRectMaxY(bj_mapInitialPlayableArea),270)
        call UnitAddAbility(dummy,'Asud')
        call UnitAddAbility(dummy,'Aloc')
        call SetUnitAcquireRange(dummy,0.)
        call ShowUnit(dummy,false)
        call SetUnitInvulnerable(dummy,true)
    
        // ==========================================================================
    
        call SetPlayerState(dummyOwner, PLAYER_STATE_RESOURCE_FOOD_CAP,300)
        call TriggerRegisterEnterRectSimple(unitEnters,bj_mapInitialPlayableArea)
        loop
            call TriggerRegisterPlayerUnitEvent(unitUpgrades, Player(index), EVENT_PLAYER_UNIT_UPGRADE_START, condition)
            call TriggerRegisterPlayerUnitEvent(unitDies, Player(index), EVENT_PLAYER_UNIT_DEATH, condition)
            set index = index + 1
            exitwhen index == bj_MAX_PLAYER_SLOTS
        endloop
        call TriggerAddAction(unitEnters,function onEnter)
        call TriggerAddAction(unitUpgrades,function onUpgrade)
        call TriggerAddAction(unitDies,function onDie)
        
        // ==========================================================================
        
        call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,condition)
        loop
            set u = FirstOfGroup(g)
            exitwhen u == null
            set uc = GetUnitCostSimple(GetUnitTypeId(u))
            set UnitData[u] = uc
            call GroupRemoveUnit(g,u)
        endloop
        
        call DestroyBoolExpr(condition)
        call DestroyGroup(g)
    endfunction
endlibrary
 

Attachments

  • GetUnitCost.w3x
    31.6 KB · Views: 188
Last edited:
Level 11
Joined
Nov 4, 2007
Messages
337
That's true, but those AI natives sometimes bug up and give the wrong cost.
And they don't catch upgrades;
As explained in the system descriptions, this does.

When a unit costs 100 gold and upgrades to a unit that costs 150 gold, my function will return 250 goldcost, the native only 150.
 
Level 11
Joined
Nov 4, 2007
Messages
337
Ok, and here is an additional thing that goes with this library:

JASS:
library SellUnit initializer Init requires GetUnitCost
//===========================================================================
// Information: 
//==============
//
//  This library gives you a function that allows you to Sell Units to their owner.
//  When you let a dummy cast transmute onto the unit, you've got the problem that
//  only the costs for THIS unit are cought, so when I've got a unit that costs
//  200 gold and upgrade it for 100 gold, and I want to refund 75% of the cost
//  to the owner, He'd only get 75 gold.
//  With this, he'd get the full 225 gold.
//
//===========================================================================
    globals
        private constant real RESOURCE_REFUND_PERCENTAGE = 0.75
    endglobals
    
    globals
        private sound GoldAdded
        private sound LumberAdded
    endglobals
    
    function SellUnit takes unit who returns nothing
        local texttag t = CreateTextTag()
        local integer GoldCost = R2I(I2R(GetUnitGoldCost(who))*RESOURCE_REFUND_PERCENTAGE)
        local integer LumberCost = R2I(I2R(GetUnitLumberCost(who))*RESOURCE_REFUND_PERCENTAGE)
        local real x = GetUnitX(who)
        local real y = GetUnitY(who)
        
        call RemoveUnit(who)
        call SetPlayerState(GetOwningPlayer(who),PLAYER_STATE_RESOURCE_GOLD,GetPlayerState(GetOwningPlayer(who),PLAYER_STATE_RESOURCE_GOLD)+GoldCost)
        call SetPlayerState(GetOwningPlayer(who),PLAYER_STATE_RESOURCE_LUMBER,GetPlayerState(GetOwningPlayer(who),PLAYER_STATE_RESOURCE_LUMBER)+LumberCost)
        
        if GoldCost > 0 then
            call DestroyEffect(AddSpecialEffect("UI\\Feedback\\GoldCredit\\GoldCredit.mdl",x,y))
            
            call SetSoundPosition(GoldAdded,x,y,100.)
            call SetSoundVolume(GoldAdded, 127)
            call StartSound(GoldAdded)
            
            call SetTextTagText(t, "+"+I2S(GoldCost), 0.023)
            call SetTextTagPos(t, x,y+30.,0.04)
            call SetTextTagColor(t, 255, 204, 51, 255)
            call SetTextTagVelocityBJ(t,64.,90.)
            call SetTextTagVisibility(t, (GetLocalPlayer() == GetOwningPlayer(who)))
            call SetTextTagFadepoint(t, 2)
            call SetTextTagLifespan(t, 2.5)
            call SetTextTagPermanent(t, false)
        endif
        
        if LumberCost > 0 then
        set t = CreateTextTag()
        
            call SetSoundPosition(LumberAdded, x,y,100)
            call SetSoundVolume(LumberAdded, 127)
            call StartSound(LumberAdded)
            
            call SetTextTagText(t, "|cff006C00+"+I2S(LumberCost)+"|r", 0.023)
            call SetTextTagPos(t, x,y,0.04)
            call SetTextTagVelocityBJ(t,64.,90.)
            call SetTextTagVisibility(t, (GetLocalPlayer() == GetOwningPlayer(who)))
            call SetTextTagFadepoint(t, 2)
            call SetTextTagLifespan(t, 2.5)
            call SetTextTagPermanent(t, false)
        endif
        
        set t = null
    endfunction
    
    private function Init takes nothing returns nothing
        set GoldAdded = CreateSound("Abilities\\Spells\\Other\\Transmute\\AlchemistTransmuteDeath1.wav" , false , true , true , 10 , 10 , "CombatSoundsEAX")
        call SetSoundParamsFromLabel(GoldAdded , "TransmuteMissileImpact")
        call SetSoundDuration(GoldAdded , 1601)
        
        set LumberAdded = CreateSound("Abilities\\Spells\\Items\\ResourceItems\\BundleOfLumber.wav" , false , true , true , 10 , 10 , "SpellsEAX")
        call SetSoundParamsFromLabel(LumberAdded , "ReceiveLumber")
        call SetSoundDuration(LumberAdded , 1347)
    endfunction

endlibrary
 
Level 13
Joined
Mar 4, 2009
Messages
1,156
btw i got gold cost on another way (a bit noobish xD)

  • add 100000 gold to natural passive
  • add 100000 lumber to natural passive
  • Neutral Building - Add (Unit-type of (SOME UNIT)) to COUNTER 0065 <gen> with 1 in stock and a max stock of 1
  • Neutral Building - Add (Unit-type of (SOME UNIT)) to COUNTER 0065 <gen> with 1 in stock and a max stock of 1
  • Neutral Building - Remove (Unit-type of (SOME UNIT)) from COUNTER 0065 <gen>
  • Game - Display to (All players) the text: (GOLD + (String((100000 - (Neutral Passive Current gold)))))
  • Game - Display to (All players) the text: (LUMBER + (String((100000 - (Neutral Passive Current lumber)))))
  • remove sold unit
    • Events
      • Unit - COUNTER 0065 <gen> Sells a unit
    • Conditions
    • Actions
      • Unit - Remove (Sold unit) from the game
i also saw a trigger that get gold cost of unit in just one line

something like

GetUnitTotalGoldCost unit typeled......
 
Top