HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

GetUnitCost

08-29-2009, 09:06 PM#1
Mr.Malte
Well, I made a map called 'Stampede Surviver'.
In that game you have to build barricades to be safe against stampedes.
And I added a sell functionality to all the units. For that I used a transmute-casting dummy.
The problem: The player wont get the full upograde costs back, when the unit upgrades.

This library solves that problem.
Actually I've been working on this for a while, at first I used a transmute dummy and Asynchronizer.
But I did more research to create this library.
I wanted to do more tests, but now Nestharus wants to get his GetUnitCost library approved, and I want to be a direct concurrency.

Requires Table:
http://www.wc3c.net/showthread.php?t=101246

Here is the code:

Collapse 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 temp == 0 then
            set temp = GetUnitCostSimple(GetUnitTypeId(u))
            set UnitData[u] = temp
        endif
        
        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
            if UnitData.exists(u) == false then
                set uc = GetUnitCostSimple(GetUnitTypeId(u))
                set UnitData[u] = uc
            endif
        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()
                call UnitData.flush(u)
            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

+ requires

Collapse JASS:
library CommonAIimports

    native GetUnitGoldCost takes integer unitid returns integer
    native GetUnitWoodCost takes integer unitid returns integer
    native GetUnitBuildTime takes integer unitid returns integer
    
endlibrary


constant native GetFoodMade takes integer unitId returns integer
constant native GetFoodUsed takes integer unitId returns integer

constant native GetUnitFoodMade takes unit whichUnit returns integer
constant native GetUnitFoodUsed takes unit whichUnit returns integer


This requires Table by Vexorian!

SellUnit:

Collapse 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 sound GoldAdded
        private sound LumberAdded
    endglobals
    
    function SellUnit takes unit who, real refundPercentage returns nothing
        local texttag t = CreateTextTag()
        local integer GoldCost = R2I(I2R(GetUnitCost(who,COST_GOLD))*refundPercentage)
        local integer LumberCost = R2I(I2R(GetUnitCost(who,COST_LUMBER))*refundPercentage)
        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()
        
            if GoldCost <= 0 then
                call SetSoundPosition(LumberAdded, x,y,100)
                call SetSoundVolume(LumberAdded, 127)
                call StartSound(LumberAdded)
            endif
            
            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
08-30-2009, 01:58 AM#2
Deaod
Collapse JASS:
library CommonAIimports

    native GetUnitGoldCost takes integer unitid returns integer
    native GetUnitWoodCost takes integer unitid returns integer
    native GetUnitBuildTime takes integer unitid returns integer
    
endlibrary


constant native GetFoodMade takes integer unitId returns integer
constant native GetFoodUsed takes integer unitId returns integer

constant native GetUnitFoodMade takes unit whichUnit returns integer
constant native GetUnitFoodUsed takes unit whichUnit returns integer
08-30-2009, 06:37 AM#3
Mr.Malte
Well, i can do Something against the name conflict.
And those functions return very strange things sometimes AND I explained, why I
made this system and what makes it useful/ better than the natives;
they don't care about upgrades. But that makes my foodcost detection useless, I'll change it later.
08-30-2009, 06:05 PM#4
Mr.Malte
Ok, and here is an additional thing that goes with this library:

Collapse 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
08-30-2009, 06:35 PM#5
Vexorian
I think Deaod was trying to ask, why use this instead of the cost natives?
08-30-2009, 07:39 PM#6
Deaod
Nah, its just that he could have used those natives internally. I know his library does something which those natives dont (adding up the upgrade costs). Still, i think you could simplify your code by using these natives.
08-30-2009, 07:58 PM#7
Mr.Malte
Hmm, but I don't have access to them.
Do I have to import them somehow?
08-31-2009, 04:43 PM#8
Mr.Malte
Ok, I can use the natives now.
But the problem is; They don't work with heroes.
So why should I use them instead of this?
09-01-2009, 01:47 AM#9
Nestharus
If unit type is not hero, then use native?
if unit type is hero, then don't use native?
09-02-2009, 08:34 PM#10
Mr.Malte
Ok. updated.
Changed all the complained things.
09-04-2009, 02:19 PM#11
Mr.Malte
Update.
Now you can decide for which unit the gold cost function (including upgrades)
shall be available.
For example you can set it to 'no dummies'
The filter is found in line 67.
09-04-2009, 06:37 PM#12
Rising_Dusk
  • Link to Table in your first post towards the top. Don't just mention at the very bottom that it requires Table. That requirement is important.
  • Please create an ObjectMerger call for your dummy unit, do not create an object editor dependency.
  • I dislike your API, it should be like this.
    Collapse JASS:
    function GetUnitGoldCost takes unit u, boolean includeUpgrades returns integer
    function GetUnitLumberCost takes unit u, boolean includeUpgrades returns integer
    function GetUnitTypeGoldCost takes integer unitType returns integer
    function GetUnitTypeLumberCost takes integer unitType returns integer

    The names for GetUnitTypeGoldCost() and GetUnitTypeLumberCost() are consistent with Blizzard's Unit API in GetUnitTypeId() and so forth. This will make them more intuitive. (It is also the same # of letters to type as your previous ById names) If you're wondering why I don't like the ...ById names, it is because it meshes with Blizzard's Order API (IssueImmediateOrderById, etc.) and that can confuse users.

    I also disapprove of the returning of a struct in this case. I feel that you can use your struct internally, but that the functions should just return integers. This again makes them as intuitive as possible.

    You do not need 7 user functions in your system. You only need to provide the user with four functions, and they need to be as I said above. Anything else is superfluous. Speed is a nonissue here, because you are not going to be getting a unit's gold cost a million times. Even if you are, because you use Table it will be O(1) anyways. (That means it's fast enough)
  • Your SellUnit library add-on is cool. I see that being a very useful library for tower defense mapmakers. In it, I think it will be cluttered if you play both gold sounds and lumber sounds at once. I think you should only play the lumber sound if the gold sound was not played.
If you fix these things, I will approve this.
09-04-2009, 08:01 PM#13
Mr.Malte
First of all: Thanks for reviewing this :)

Quote:
# Link to Table in your first post towards the top. Don't just mention at the very bottom that it requires Table. That requirement is important.
# Please create an ObjectMerger call for your dummy unit, do not create an object editor dependency.

Ok.
I don't know, hoiw object mergers work, but Ill find out.

Quote:
Collapse JASS:
function GetUnitGoldCost takes unit u, boolean includeUpgrades returns integer
function GetUnitLumberCost takes unit u, boolean includeUpgrades returns integer

I disagree with this API. includeUpgrade should always be true, when you use these functions. Otherwise you could use GetUnitTypeGoldCost(GetUnitTypeId(u)).
Also, I can't name a function GetUnitGoldCost, because it would come to a name conflict with the natives.

I will keep it as GetUnitTotalGoldCost, because it is always total (with the upgrades)

Quote:
function GetUnitTypeGoldCost takes integer unitType returns integer
function GetUnitTypeLumberCost takes integer unitType returns integer

That's fine.
´The rest is fine, too.
I'll upload the new version today or tomorrow.
09-04-2009, 08:15 PM#14
Rising_Dusk
Quote:
Originally Posted by Mr.Malte
includeUpgrade should always be true, when you use these functions. Otherwise you could use GetUnitTypeGoldCost(GetUnitTypeId(u)).
No, I can see many cases where I would not want to include the upgrades. This is especially true for the target genre of this library, tower defenses, where one might want to "downgrade" their towers and get refunded only the upgrade's cost.
Quote:
Originally Posted by Mr.Malte
Also, I can't name a function GetUnitGoldCost, because it would come to a name conflict with the natives.
Hrm. Then I would approach it like this:
Collapse JASS:
globals
    constant integer COST_GOLD = 1
    constant integer COST_LUMBER = 2
endglobals

function GetUnitCost takes unit u, integer costType, boolean includeUpgrades returns integer
function GetUnitTypeCost takes integer unitType, integer costType returns integer
This further simplifies the API. Additionally, GetUnitTypeCost should always return the non-upgraded cost because you do not know how exactly a unit has been upgraded. There may be two units that each cost different amounts that upgrade to the same new unit type. I find this incredibly important to be able to distinguish between upgrade costs and total costs. The library's value is severely cut without access to the includeUpgrades boolean.
09-04-2009, 09:03 PM#15
Mr.Malte
If you only want the upgrade cost, you can do this:

Collapse JASS:
GetUnitTypeCost(GetUnitTypeId(UNIT))

This would be GetUnitCost, but just return the cost of this thing.
Because GetUnitTypeCost does always ignore upgrades.

I updated the code.
I did not only change the things you complained, I made various changes.