Dirac
22710180
- Reaction score
- 147
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 lagSummon Meat Grinder - [X]
Gathers the corpses of nearby fallen enemy units to use them as ammunition against any other.
And not a single drop of blood was wasted
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</u></u>
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:
v0.01
-Initial Release
v0.02
-Minor Tweaking to the map
-Increased efficiency by removing a boolean check performed every period
-Removed xefx as a library (was causing issues indexing corpses)
v0.03
-Minor Tweaking to the map
-Changed the way corpses move to increase efficiency
-Some changes to the code that also improve efficiency
v0.04
-The code is more legible
-Some changes to the code that also improve efficiency
v0.05
-Removed some not-needed configuration values
-Now the corpses fly accordingly to the floor's Z
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 bev0.06
-Changed struct names to Camel Case
-Triggers now use conditions