The Meat Grinder

Discussion in 'Spells' started by Dirac, Jul 6, 2011.

  1. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    The Meat Grinder
    A tower for your TD


    [​IMG]

    Well this is a very old project i submitted to a tower contest made a long time back, but i made it in GUI at that time, and it was buggy, inefficient and laggy. So here's the remake

    It's a very unique tower coded with the best intentions of not causing lag

    Evaluation as a tower
    Pros:
    -It's very efficient against creeps, since it's meant to deal a lot of AoE damage.
    -You can't just put it anywhere, so it adds some strategy to your TD
    -Cleans up the map of corpses
    Cons:
    -Wouldn't be as effective against bosses
    -May conflict with towers that use corpses (not itself obviously)

    Evaluation as something to implement to your map
    Pros:
    -Very unique
    -Has a nice eye-candy
    -It was hard coding this so it saves you the trouble of making a tower this complicated
    Cons:
    -Lags if they're more than 500 corpses TO PICK in the map at the same time (corpses outside the range of the tower won't cause lag)
    -Requires libraries (is this something bad?)

    The Code(Requires AIDS, TimerUtils, T32 and AutoFly)
    JASS:
    library MeatGrinder initializer start uses AIDS,T32,TimerUtils,AutoFly
    //====================================================//
    //                   MEAT GRINDER
    //====================================================//
    //                   made by Dirac
    
    //best used with default settings and tower model Slaughterhouse with "work" animation
    //if you want to prevent any unit in your map from being summoed as a corpse make sure to add it to MeatGrinder_AvoidGroup
    
        globals
    //main setup
            private constant integer DUMMY='e000' //raw code of the dummy
            private constant integer TOWER='u000' //raw code of the tower
    //damage and stats
            private constant real ANGLEGAP=35 //aperture of the angle it takes the corpse to adquire a target based on it's facing
            private constant real SPEED=950*T32_PERIOD //speed of the corpse when attacking
            private constant real NORMALSPEED=450*T32_PERIOD //speed of the corpse when spinning around tower
            private constant real SPIN=147.2*T32_PERIOD //angle rotation speed around tower
            private constant real DELAY=0.5 //the delay between summoning corpses
            private constant real COOLDOWN=0 //attack cooldown
            private constant real DMG=0
            private constant real AOE=270
            private constant real HPDMG=0.30 //corpses can deal a fraction of the unit's max life if wanted
            private constant real AOEMULT=1 //this multiplies the damage done to the target and deals that number to AoE targets.
            private constant real ADQUISITION=1200
            private constant real RANGE=800
            private constant real MAXCORPSES=6
    //stetics
            private constant real TOWERANGLE=90 //facing angle of the corpse towards the tower
            private constant real RADIUS=100 //radius the corpses rotate around
            private constant real HEIGHT=180 //initial height of the corpses
            private constant real CORPSESIZE=1.2 //size of each corpse (1.00=100%)
            private constant string CORPSEFX="Abilities\\Weapons\\MeatwagonMissile\\MeatwagonMissile.mdl" //corpse effect
            private constant string SUMMONFX="Abilities\\Spells\\Undead\\RaiseSkeletonWarrior\\RaiseSkeleton.mdl" //effect created when summons a corpse
        endglobals
    //====================================================//
    //                END CONFIGURATION
    //====================================================//
    
        globals
            private unit TempUnit
            private group Group=CreateGroup()
            private group TowerGroup=CreateGroup()
            public group AvoidGroup=CreateGroup() //units inside this group won't be summoned as corpses
        endglobals
        
        //===========TOWER STRUCT==========//
        private struct MeatGrinder extends array
            readonly real x
            readonly real y
            readonly real z
            real tick
            real cooldown
            integer corpsecount
            group corpsegroup
            
            private static method AIDS_filter takes unit u returns boolean
                return GetUnitTypeId(u)==TOWER
            endmethod
            
            private method periodic takes nothing returns nothing
                set .tick=RMaxBJ(.tick-T32_PERIOD,0.) //i use T32 here because i wan't this to be very exact, using TimerUtils would give me a I2S(DELAY) margin of error
                set .cooldown=.cooldown-T32_PERIOD
            endmethod
            
            implement T32x
            
            private method AIDS_onCreate takes nothing returns nothing
                set .corpsecount=0
                set .cooldown=0.
                set .tick=0.
                set .corpsegroup=CreateGroup()
                set .x=GetUnitX(.unit)
                set .y=GetUnitY(.unit)
                set .z=GetLocationZ(Location(.x,.y))
            endmethod
            
            private method AIDS_onDestroy takes nothing returns nothing
                set .corpsecount=0
                set .tick=0.
                call DestroyGroup(.corpsegroup)
            endmethod
    
            //! runtextmacro AIDS()
        endstruct
        
        //===========INDEX CORPSE==========//
        private struct CorpseIndex extends array
            integer data //this attaches every corpse their struct.
            //I made this to remove the corpses when the tower dies
            
            static method AIDS_filter takes unit u returns boolean
                return GetUnitTypeId(u)==DUMMY
            endmethod
            //! runtextmacro AIDS()
        endstruct
        
        //===========CORPSE STRUCT==========//
        private struct Corpse
            MeatGrinder tower
            readonly real towerX
            readonly real towerY
            readonly real distance
            private real selfdelay
            private real damage
            private real ux
            private real uy
            private real ua
            private player owner
            private unit thecorpse
            private unit target
            private unit dead
            private effect fx
            private static thistype currentdata
    
            method operator x takes nothing returns real
                return .ux
            endmethod
            method operator x= takes real a returns nothing
                call SetUnitX(.thecorpse,a)
                set .ux=a
            endmethod
            method operator y takes nothing returns real
                return .uy
            endmethod
            method operator y= takes real a returns nothing
                call SetUnitY(.thecorpse,a)
                set .uy=a
            endmethod
            method operator xyangle takes nothing returns real
                return .ua
            endmethod
            method operator xyangle= takes real a returns nothing
                call SetUnitFacing(.thecorpse,a*bj_RADTODEG)
                set .ua=a
            endmethod
    
            method destroy takes nothing returns nothing
                call DestroyEffect(.fx)
                call RemoveUnit(.thecorpse)
                set .target=null
                set .thecorpse=null
                call .stopPeriodic()
                call .deallocate()
            endmethod
    
            static method AddCorpse takes nothing returns boolean
                local unit filter=GetFilterUnit()
                local MeatGrinder this=MeatGrinder[TempUnit]
                if IsUnitAlly(TempUnit,GetOwningPlayer(filter))==false and IsUnitInGroup(filter,AvoidGroup)==false and this.corpsecount<MAXCORPSES and GetWidgetLife(filter)<=0 then
                    call thistype.create(GetUnitX(filter),GetUnitY(filter),DELAY,filter,this)
                endif
                set filter=null
                return false
            endmethod
    
            private static method SetTarget takes nothing returns boolean
                local unit filter=GetFilterUnit()
                local real x
                local real y
                local real a
                local real dif
    
                if GetWidgetLife(filter)>0 and IsUnitAlly(filter,currentdata.owner)!=true and currentdata.tower.cooldown<=0 and currentdata.selfdelay>=0.8 then
                    //first check if the unit is a valid target to then do the math
                    set x=GetUnitX(filter)
                    set y=GetUnitY(filter)
                    set a=Atan2(y-currentdata.y,x-currentdata.x)
                    set dif=(currentdata.xyangle-a)*bj_RADTODEG
                    if ((dif<=(ANGLEGAP/2) and dif>=-(ANGLEGAP/2)) or (dif<=360+(ANGLEGAP/2) and dif>=360-(ANGLEGAP/2)) or (dif<=-360+(ANGLEGAP/2) and dif>=-360-(ANGLEGAP/2))) then
                        set currentdata.target=filter
                        set currentdata.tower.cooldown=COOLDOWN
                    endif
                endif
                set filter=null
                return false
            endmethod
            
            private static method aoedmg takes nothing returns boolean
                local unit filter=GetFilterUnit()
                local real x=GetUnitX(filter)
                local real y=GetUnitY(filter)
                if GetUnitState(filter,UNIT_STATE_LIFE)>0 and IsUnitAlly(filter,currentdata.owner)!=true and currentdata.target!=filter then
                    call UnitDamageTarget(currentdata.tower.unit,filter,currentdata.damage*AOEMULT,true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
                endif
                set filter=null
                return false
            endmethod
    
            private method periodic takes nothing returns nothing
                local real a
                local real x
                local real y
                local real z
                local real dist
                local location l
                
                if GetUnitTypeId(.target)==0 then
                    set dist=SquareRoot((.towerX-.x)*(.towerX-.x)+(.towerY-.y)*(.towerY-.y))
                    set .selfdelay=RADIUS/dist
                    set a=Atan2(.towerY-.y,.towerX-.x)-90*((RADIUS)/(dist))*bj_DEGTORAD
                    set .currentdata=this
                    call GroupEnumUnitsInRange(Group,.x,.y,RANGE,function thistype.SetTarget)
                    set z=HEIGHT+.tower.z+(((HEIGHT+.tower.z)/(.distance-RADIUS))*(.distance-dist)-HEIGHT-.tower.z)
                    set x=.x+NORMALSPEED*Cos(a)
                    set y=.y+NORMALSPEED*Sin(a)
                else
                    set x=GetUnitX(.target)
                    set y=GetUnitY(.target)
                    set a=Atan2(y-.y,x-.x)
                    set .distance=SquareRoot((.towerX-x)*(.towerX-x)+(.towerY-y)*(.towerY-y))
                    set dist=SquareRoot((.x-towerX)*(.x-towerX)+(.y-towerY)*(.y-towerY))
                    if SquareRoot((.x-x)*(.x-x)+(.y-y)*(.y-y))<=SPEED+1 then
                        call UnitDamageTarget(.tower.unit,.target,.damage,true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
                        set .tower.corpsecount=.tower.corpsecount-1
                        if .tower.corpsecount==0 then //this add unused corpses nearby to the tower
                            set TempUnit=.tower.unit
                            call GroupEnumUnitsInRange(Group,.towerX,.towerY,ADQUISITION,Filter(function thistype.AddCorpse))
                        endif
                        set currentdata=this
                        call GroupEnumUnitsInRange(Group,x,y,AOE,function thistype.aoedmg)
                        call DestroyEffect(AddSpecialEffect(CORPSEFX,x,y))
                        call .destroy()
                        return
                    endif
                    set x=.x+SPEED*Cos(a)
                    set y=.y+SPEED*Sin(a)
                    set z=HEIGHT+.tower.z+(((HEIGHT+.tower.z)/(.distance-RADIUS))*(.distance-dist)-HEIGHT-.tower.z)
                endif
                set .xyangle=a
                set .x=x
                set .y=y
                set l=Location(.x,.y)
                call SetUnitFlyHeight(.thecorpse,z-GetLocationZ(l),0.)
                call RemoveLocation(l)
                set l=null
            endmethod
            
            implement T32x
            
            private static method summon takes nothing returns nothing
                local timer t=GetExpiredTimer()
                local thistype this=GetTimerData(t)
                
                if IsUnitInGroup(.tower.unit,TowerGroup) then
                    set .fx=AddSpecialEffectTarget(CORPSEFX,.thecorpse,"origin")
                    call DestroyEffect(AddSpecialEffect(SUMMONFX,.x,.y))
                    call .startPeriodic()
                else
                    call RemoveUnit(.thecorpse)
                endif
                call RemoveUnit(.dead)
                set .dead=null
                call ReleaseTimer(t)
            endmethod
            
            static method create takes real x,real y,real delay,unit whichUnit,MeatGrinder whichStruct returns thistype
                local thistype this=thistype.allocate()
                local real centerX=GetUnitX(whichStruct.unit)
                local real centerY=GetUnitY(whichStruct.unit)
                local real a=Atan2(centerY-y,centerX-x)
                local timer t=NewTimer()
                
                call SetTimerData(t,this)
                set whichStruct.tick=whichStruct.tick+delay
                set whichStruct.corpsecount=whichStruct.corpsecount+1
                call GroupAddUnit(AvoidGroup,whichUnit)
                call TimerStart(t,whichStruct.tick,false,function thistype.summon)
                set .distance=SquareRoot((centerX-x)*(centerX-x)+(centerY-y)*(centerY-y))
                set .towerX=centerX
                set .towerY=centerY
                set .target=null
                set .tower=whichStruct
                set .selfdelay=1.2
                set .dead=whichUnit
                set .damage=HPDMG*GetUnitState(whichUnit,UNIT_STATE_MAX_LIFE)+DMG
                set .owner=GetOwningPlayer(whichStruct.unit)
                set .thecorpse=CreateUnit(.owner,DUMMY,x,y,a*bj_RADTODEG)
                set CorpseIndex[.thecorpse].data=this
                set .ux=GetUnitX(.thecorpse)
                set .uy=GetUnitY(.thecorpse)
                call SetUnitScale(.thecorpse,CORPSESIZE,CORPSESIZE,CORPSESIZE)
                call GroupAddUnit(whichStruct.corpsegroup,.thecorpse)
                set t=null
                return this
            endmethod
        endstruct
    
    //===========STARTUP AND UNIT DEATH==========//
        private function range takes nothing returns nothing
            local unit filter=GetEnumUnit()
            local MeatGrinder this=MeatGrinder[filter]
            local real x1=GetUnitX(filter)
            local real y1=GetUnitY(filter)
            local real x2=GetUnitX(TempUnit)
            local real y2=GetUnitY(TempUnit)
            local real xy=SquareRoot((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))
            
            if xy<=ADQUISITION and IsUnitAlly(TempUnit,GetOwningPlayer(filter))==false and IsUnitInGroup(TempUnit,AvoidGroup)==false and this.corpsecount<MAXCORPSES then
                call Corpse.create(x2,y2,DELAY,TempUnit,this)
                return
            endif
            set filter=null
        endfunction
        
        private function DeleteCorpse takes nothing returns nothing
            local Corpse this=CorpseIndex[GetEnumUnit()].data
            call this.destroy()
        endfunction
    
        private function UnitDeath takes nothing returns boolean
            set TempUnit=GetTriggerUnit()
            call ForGroup(TowerGroup,function range)
            if GetUnitTypeId(TempUnit)==TOWER then
                call ForGroup(MeatGrinder[TempUnit].corpsegroup,function DeleteCorpse)
                call GroupRemoveUnit(TowerGroup,TempUnit)
            endif
            return false
        endfunction
    
        private function EnableTower takes nothing returns boolean
            local unit u=GetTriggerUnit()
            
            if GetUnitTypeId(u)==TOWER then
                call MeatGrinder[u].startPeriodic()
                call GroupAddUnit(TowerGroup,u)
                set TempUnit=u
                call GroupEnumUnitsInRange(Group,GetUnitX(u),GetUnitY(u),ADQUISITION,function Corpse.AddCorpse)
            endif
            set u=null
            return false
        endfunction
        
        private function InitTower takes nothing returns boolean
            local unit u=GetFilterUnit()
            
            if GetUnitTypeId(u)==TOWER then
                call MeatGrinder[u].startPeriodic()
                call GroupAddUnit(TowerGroup,u)
            endif
            set u=null
            return false
        endfunction
        
        private function start takes nothing returns nothing
            local trigger trig=CreateTrigger()
            local trigger construction=CreateTrigger()
            local integer i=0
            loop
                call TriggerRegisterPlayerUnitEvent(trig,Player(i),EVENT_PLAYER_UNIT_DEATH, null)
                call TriggerRegisterPlayerUnitEvent(construction,Player(i),EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null)
                set i=i+1
                exitwhen i==bj_MAX_PLAYER_SLOTS
            endloop
            call TriggerAddCondition(trig,function UnitDeath)
            call TriggerAddCondition(construction,function EnableTower)
            call GroupEnumUnitsInRect(Group,bj_mapInitialPlayableArea,function InitTower)
            set trig=null
        endfunction
    endlibrary


    Thanks to:
    Jesus4Lyf for his awesome libraries
    Vexorian for NG

    In this thread we discuss bugs, issues and efficiency

    If you wan't a custom-made version of this tower PM me and i'll find time to do it

    CHANGELOG:
    Warning: in the demo map the T32 period is set very high (0.09) to enhance efficiency, set it to 0.03125 if you want to see the tower the way it's meant to be


     
    • Like Like x 1
  2. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    Update
    I wan't to hear some feedback, maybe some suggestions? :D
     
  3. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    Another Update
     
  4. tooltiperror

    tooltiperror Super Moderator Staff Member

    Ratings:
    +233 / 0 / -0
    I like it. The code is fine as far as I can see.

    Approved.

    EDIT Moved back to T&R section as it could use some work.
     
    • Like Like x 1
  5. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    some work? where? how? i'll do it
     
  6. Bribe

    Bribe vJass errors are legion

    Ratings:
    +67 / 0 / -0
    This section needs the most work:
    JASS:
        private function getdmg takes unit tower returns real
            return (GetUnitAbilityLevel(tower,ABIL)*0.)+500.
        endfunction
        //this function determines the splash AoE
        private function getaoe takes unit tower returns real
            return (GetUnitAbilityLevel(tower,ABIL)*0.)+200.
        endfunction
        //this function determines how far it can summon corpses
        private function getadquisition takes unit tower returns real
            return (GetUnitAbilityLevel(tower,ABIL)*0.)+1000.
        endfunction
        //this function determines how far the tower can attack
        private function getrange takes unit tower returns real
            return (GetUnitAbilityLevel(tower,ABIL)*0.)+800.
        endfunction
        //this function determines how much corpses it can hold
        private function getcount takes unit tower returns integer
            return (GetUnitAbilityLevel(tower,ABIL)*0)+6
        endfunction


    You could pass "level" as a parameter rather than calling
    it every time. You also don't need to do "*0." the full level
    part should just be deleted and added only by the user if
    he wants it, you also don't need the parenthesis because
    programming math parses mul/div before add/sub.

    You also could multiply by 0 instead of by 0. because it will
    return a real number any way.
     
  7. tooltiperror

    tooltiperror Super Moderator Staff Member

    Ratings:
    +233 / 0 / -0
    And you have a lot of handles you don't null.

    Things named like addcorpse should be AddCorpse.

    Rather than do this:
    JASS:
                call UnitAddAbility(.thecorpse,CROW)
                call SetUnitFlyHeight(.thecorpse,z,0.)
                call UnitRemoveAbility(.thecorpse,CROW)
    Just add AutoFly as a requirement.

    JASS:
                if IsUnitInGroup(.tower.unit,TOWERGROUP)==true then

    You don't need "== true".

    JASS:
        globals
            private integer TEMPDATA
            private unit TEMPUNIT
            private group GROUP=CreateGroup()
            private group TOWERGROUP=CreateGroup()
            public group AVOIDGROUP=CreateGroup() //units inside this group won't be summoned as corpses
        endglobals

    These should be named in CamelCase, and "TEMPDATA" is a bad name anyways. What does it do?

    All struct names should be named in CamelCase also.

    in corpse (corpse -> Corpse) you use onDestroy. Just override "destroy" and add in .deallocate into it at the end.

    In start you use TriggerAddAction, just use Conditions, they're faster.
     
  8. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    >These should be named in CamelCase, and "TEMPDATA" is a bad name anyways. What does it do?
    It's a var i was using before, but replaced it and didn't realize it was still there

    >in corpse (corpse -> Corpse) you use onDestroy. Just override "destroy" and add in .deallocate into it at the end.
    I was thinking of maybe adding Alloc as an optional, so... should i change this?

    >In start you use TriggerAddAction, just use Conditions, they're faster.
    Conditions regarding event unit death are bugged, MUST use actions for this one, already tested this before

    >And you have a lot of handles you don't null.
    Is it bad when i don't null an unit and set it to another value?

    EDIT:
     
  9. tooltiperror

    tooltiperror Super Moderator Staff Member

    Ratings:
    +233 / 0 / -0
    >It's a var i was using before, but replaced it and didn't realize it was still there
    Alright.

    >I was thinking of maybe adding Alloc as an optional, so... should i change this?
    Doesn't matter, just don't use onDestroy and use .destroy...

    >Conditions regarding event unit death are bugged, MUST use actions for this one, already tested this before
    Interesting.

    >Is it bad when i don't null an unit and set it to another value?
    You have to null local variable handles or else their reference count will climb and make them slower to read/write (I think that's why) but you definitely have to null them.
     
  10. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    Update
    Is this ready for approval?
     
  11. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    Bump
     
  12. tooltiperror

    tooltiperror Super Moderator Staff Member

    Ratings:
    +233 / 0 / -0
    Make struct names correct. "corpseindex" => "CorpseIndex".

    Make it use TriggerAddCondition, it's more efficient and such, and then the trigger could hypothetically be used dynamically and such.

    That should be it.
     
  13. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    Done
     
  14. tooltiperror

    tooltiperror Super Moderator Staff Member

    Ratings:
    +233 / 0 / -0
    >Done
    >private struct corpseindex extends array

    oh yeah?
     
  15. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    You read the code before i actually edited the post lol, took me 30 secs to copy it from the editor :S
     
  16. tooltiperror

    tooltiperror Super Moderator Staff Member

    Ratings:
    +233 / 0 / -0
    Excellent.

    Approved.
     
  17. Sgqvur

    Sgqvur FullOfUltimateTruthsAndEt ernalPrinciples, i.e shi

    Ratings:
    +62 / 0 / -0
    tooltiperror:
    1. Excellent. Approved.

    1. The spell leaks 32 locations per second:

    Corpse.periodic(last line):
    call SetUnitFlyHeight(.thecorpse,z-GetLocationZ(Location(.x,.y)),0.)
     
  18. Dirac

    Dirac 22710180

    Ratings:
    +147 / 0 / -0
    This is correct, fixed it.

    IMO this tower needs some work because it's impossible to micro it. Update on that coming soon.

    EDIT: Forgot to mention:
    The development of this tower uses systems that are not available on this website (such as UnitIndexer) so i don't really know if i should update it at all.
     

Share This Page