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='u5IN' //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=290 //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
//Dummy Units
and GetUnitTypeId(filter,'h01E')==false and GetUnitTypeId(filter,'e002')==false and GetUnitTypeId(filter,'h00J')==false and GetUnitTypeId(filter,'h00P')==false
and GetUnitTypeId(filter,'h01J')==false and GetUnitTypeId(filter,'e003')==false and GetUnitTypeId(filter,'h00T')==false and GetUnitTypeId(filter,'h013')==false
and GetUnitTypeId(filter,'e1LL')==false and GetUnitTypeId(filter,'e007')==false and GetUnitTypeId(filter,'h00Y')==false and GetUnitTypeId(filter,'h012')==false
and GetUnitTypeId(filter,'e009')==false and GetUnitTypeId(filter,'e000')==false and GetUnitTypeId(filter,'h00V')==false and GetUnitTypeId(filter,'h4CQ')==false
and GetUnitTypeId(filter,'e008')==false and GetUnitTypeId(filter,'e004')==false and GetUnitTypeId(filter,'h003')==false and GetUnitTypeId(filter,'h5G4')==false
and GetUnitTypeId(filter,'h5G3')==false and GetUnitTypeId(filter,'h7P1')==false and GetUnitTypeId(filter,'h5UD')==false and GetUnitTypeId(filter,'h7P2')==false
and GetUnitTypeId(filter,'h7P3')==false and GetUnitTypeId(filter,'h8UD')==false and GetUnitTypeId(filter,'h5G0')==false and GetUnitTypeId(filter,'h4UF')==false
and GetUnitTypeId(filter,'h01K')==false and GetUnitTypeId(filter,'hL7X')==false and GetUnitTypeId(filter,'h01B')==false and GetUnitTypeId(filter,'h01C')==false
and GetUnitTypeId(filter,'h00F')==false and GetUnitTypeId(filter,'h3T2')==false and GetUnitTypeId(filter,'h3T0')==false and GetUnitTypeId(filter,'h3T1')==false
and GetUnitTypeId(filter,'h00Z')==false and GetUnitTypeId(filter,'h0B9')==false and GetUnitTypeId(filter,'h001')==false and GetUnitTypeId(filter,'h00M')==false
and GetUnitTypeId(filter,'h00N')==false and GetUnitTypeId(filter,'h018')==false and GetUnitTypeId(filter,'h00O')==false and GetUnitTypeId(filter,'h006')==false
and GetUnitTypeId(filter,'h008')==false and GetUnitTypeId(filter,'h00H')==false and GetUnitTypeId(filter,'h011')==false and GetUnitTypeId(filter,'h00Q')==false
and GetUnitTypeId(filter,'h00R')==false and GetUnitTypeId(filter,'h00S')==false and GetUnitTypeId(filter,'h016')==false and GetUnitTypeId(filter,'h00D')==false
and GetUnitTypeId(filter,'h00L')==false and GetUnitTypeId(filter,'h00X')==false and GetUnitTypeId(filter,'h00W')==false and GetUnitTypeId(filter,'h01A')==false
and GetUnitTypeId(filter,'h01F')==false and GetUnitTypeId(filter,'hLC2')==false and GetUnitTypeId(filter,'h01H')==false and GetUnitTypeId(filter,'h01D')==false
and GetUnitTypeId(filter,'h004')==false and GetUnitTypeId(filter,'o00C')==false and GetUnitTypeId(filter,'oK97')==false and GetUnitTypeId(filter,'dumy')==false
and GetUnitTypeId(filter,'dmy2')==false and GetUnitTypeId(filter,'drks')==false and GetUnitTypeId(filter,'n014')==false and GetUnitTypeId(filter,'n00D')==false
and GetUnitTypeId(filter,'n006')==false and GetUnitTypeId(filter,'n00U')==false and GetUnitTypeId(filter,'n00I')==false and GetUnitTypeId(filter,'n00N')==false
and GetUnitTypeId(filter,'n012')==false and GetUnitTypeId(filter,'n018')==false and GetUnitTypeId(filter,'n019')==false and GetUnitTypeId(filter,'n017')==false
and GetUnitTypeId(filter,'n00X')==false and GetUnitTypeId(filter,'n00Z')==false and GetUnitTypeId(filter,'n003')==false and GetUnitTypeId(filter,'n00J')==false
and GetUnitTypeId(filter,'n016')==false and GetUnitTypeId(filter,'n015')==false and GetUnitTypeId(filter,'n010')==false and GetUnitTypeId(filter,'n00K')==false
and GetUnitTypeId(filter,'n00L')==false and GetUnitTypeId(filter,'n00Y')==false and GetUnitTypeId(filter,'n00G')==false and GetUnitTypeId(filter,'n00O')==false
and GetUnitTypeId(filter,'n00C')==false and GetUnitTypeId(filter,'n00M')==false and GetUnitTypeId(filter,'n00P')==false and GetUnitTypeId(filter,'n011')==false
and GetUnitTypeId(filter,'n00R')==false and GetUnitTypeId(filter,'n005')==false and GetUnitTypeId(filter,'n001')==false and GetUnitTypeId(filter,'n007')==false
and GetUnitTypeId(filter,'u003')==false
//Special Units
and GetUnitTypeId(filter,'uJ5P')==false and GetUnitTypeId(filter,'N00Q')==false
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
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
call SetUnitFlyHeight(.thecorpse,z-GetLocationZ(Location(.x,.y)),0.)
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