- Joined
- Sep 26, 2009
- Messages
- 9,534
You could also have done 1/32.00
1/32 = 0, how could it be....![]()
static method onCollide takes Missile missile, unit hit returns boolean
* All of them return a boolean, which determines the future of that Missile instance.
* - return true --> destroy the Missile.
* - return false --> keep the Missile flying.
That is actually pretty neat. So it basicly comes with an in-build knockback-feature now? Does it account for pathability on X/Y movement? If not, that should definitely be an input parameter here; I suggest three different types:Missile.createEx is new and takes a unit argument, so can shoot any unit like it
would be a Missile. Units which do not match the dummy type id are not recycled/removed.
True, didn't think of that. Anyways, great idea with the integrated "knockback light". It's a great addition when combined on a map with Knockback3D, as it has different input parameters and some additional functionality.It's an half-baked knockback feature. I have to compare Missile to approved Knockback
systems to see what is missing.
Basically one could use onPeriod as it runs each timer tick and do all required checks
you mentioned here.
This still doesn't explain why the spell works fine for several dozen casts, but then gradually gets more and more taxing. This is the typical behaviour one would expect from a major leak. But I checked the spell code and there aren't any leaks there (because, it's pretty simple).You need to set MissileRecycler's recycled indices allowed to a much higher number if you want insane missile counts like that. I think that is about as overboard as it gets, lol.
Yes. But even if not; just creating/killing units alone is not a sufficient explanation for the sudden FPS drop on consecutive casts, considering that tower defense maps with thousands of dying and spawning units are a thing.I guess the dummy id set in MissileRecycler matches to the one set in the Missile library.
Yes. But even if not; just creating/killing units alone is not a sufficient explanation for the sudden FPS drop on consecutive casts, considering that tower defense maps with thousands of dying and spawning units are a thing.
The dummy id matches. That's not the problem.Once you have MissileRecycler in your map, Missile wants to recycle Missile units.
If the dummy id doesn't match then the system does literally nothing and the units
just remain where they are.
I return "true" at the onFinish method, so they should be properly recycled, unless there is a bug with your system here. I'll check out the spell code again if I maybe made a mistake here, but I'm pretty sure I didn't.Another reason could be ( just thinking loud ), that you accumulate units on your screen ( 280 units). invisble and paused, but they are there.
Will do. Something is fishy about this. Btw I'm still on Missile 1.3; could that be the issue?Track how many units are created by MissileRecycler, it shouldn't be much more than 1-2 x 280.
readonly static constant real HIT_BOX = (2/3)
should rather be:readonly static constant real HIT_BOX = (2./3.)
Yes, it plays the sound if you use something else than WEAPON_TYPE_WHOKNOWS. Some weapon types don't work, though.Question - isn't weapontype always going to be the same thing whether it's null or not? Or does it actually play a sound when you use things like WEAPON_TYPE_LIGHT_CHOP?
private struct ChainLightning
static method onFinish takes Missile this returns boolean
local group g
local unit u
if IsAliveEnemy(this.target, GetOwningPlayer(this.source)) then
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile.mdl", this.target, "chest"))
call UnitDamageTargetEx(this.source, this.target, StatSpellpower[this.source]*2*this.scale*this.scale*GetRandomReal(0.9, 1.1), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_LIGHTNING, WEAPON_TYPE_WHOKNOWS)
if this.scale > 0.85 then //jump twice
set this.scale = this.scale * 0.9
set g = CreateGroup()
call GroupEnumUnitsInRange(g, GetUnitX(this.target), GetUnitY(this.target), 400, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
if IsAliveEnemy(u, GetOwningPlayer(this.source)) and IsUnitPet(u) == false and u != this.target and IsPointInCone(GetUnitX(this.target), GetUnitY(this.target), GetUnitX(u), GetUnitY(u), this.angle, 45*bj_DEGTORAD) then
set this.target = u //lock on a new target
call TimedL.QuickU2U("CLSB", this.source, this.target, 1.0, 40, 40, 1, 1, 1, 1, 1)
exitwhen true
endif
call GroupRemoveUnit(g, u)
endloop
call DestroyGroup(g)
set g = null
set u = null
return false //keep the missile flying
endif
endif
return true
endmethod
implement MissileStruct
static method fire takes unit cast, unit targ returns nothing
local Missile m
set m = Missile.createXYZ(GetUnitX(cast), GetUnitY(cast), 60, GetUnitX(targ), GetUnitY(targ), 60)
set m.speed=50
set m.model="Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile.mdl"
set m.height=0
set m.source=cast
set m.scale=1
set m.target = targ
call thistype.launch(m)
call TimedL.QuickU2U("CLPB", cast, targ, 1.0, 40, 40, 1, 1, 1, 1, 1)
endmethod
endstruct
I guess the issue is solved?EDIT:
Nevermind... I did a classic mistake here: not accounting for the case in which no valid target is present. In this case, the function still returns false and keeps the missile at the target, triggering onFinish again.
Yes, yes it is. I was just being an idiot.I guess the issue is solved?
* static method onCollide takes Missile missile, unit hit returns boolean
Errrm, he did?You forgot to explain what the returned boolean will do.JASS:* static method onCollide takes Missile missile, unit hit returns boolean
JASS:Important: * * All of them return a boolean, which determines the future of that Missile instance. * - return true --> destroy the Missile. * - return false --> keep the Missile flying.
Hey, what should I return on this method?
Jass:
static method onCollide takes Missile missile, unit hit returns boolean
I should you should add it to the document.
EDIT:
Damn, missed this:
Jass:
* All of them return a boolean, which determines the future of that Missile instance.
* - return true --> destroy the Missile.
* - return false --> keep the Missile flying.
Sorry
@Quilnez: Déjà vu?
Somewhere a bit earlier in the thread:
function IsPointInRectangle takes real ax, real ay, real bx, real by, real cx, real cy, real dx, real dy, real px, real py returns boolean
local real cross0 = (py-ay)*(bx-ax)-(px-ax)*(by-ay)
local real cross1 = (py-cy)*(ax-cx)-(px-cx)*(ay-cy)
local real cross4 = (py-dy)*(ax-dx)-(px-dx)*(ay-dy)
return ((cross0*cross1 >= 0) and (((py-by)*(cx-bx)-(px-bx)*(cy-by))*cross1 >= 0)) or ((cross0*cross4 >= 0) and (((py-by)*(dx-bx)-(px-bx)*(dy-by))*cross4 >= 0))
endfunction
I would absolutely recommend it. I recently coded a simple volley spell which actually suffered from heavy (and deadly for the players) imprecision because of that. I could slow the missiles down to fix it but it would look ugly.It's a corner case and Wietlol is totally right.
So basically a rectangle check has to be done, if the missile collision is lower than it's velocity.
if thistype.speed >= thistype.radius then
I'm not 100% sure if there is a small chance that the internal list of trigger conditions can lose their next node,
when using TriggerRemoveCondition the way I do it here. ( Nestharus? ).
TriggerRemoveCondition
on a trigger that is currently being evaluated and you happen to remove the condition that is currently being evaluated, any conditions beyond the current condition will not be run for that instance.// Handles the periodic timer callback and fires the trigger.
globals
private constant trigger CORE = CreateTrigger()
private constant timer CLOCK = CreateTimer()
private integer active = 0
private integer array instances
private Missile array missileStack
private boolexpr array expression
private triggercondition array condition
private integer array remove
endglobals
private function Fire takes nothing returns nothing
local integer i = remove[0]
set remove[0] = 0
loop
exitwhen i == 0
if (instances[i] == 0) and (condition[i] != null) then
call TriggerRemoveCondition(CORE, condition[i])
set condition[i] = null
set active = active - 1
endif
set i = remove[i]
endloop
if (active == 0) then
call PauseTimer(CLOCK)
else
call TriggerEvaluate(CORE)
endif
endfunction
private function MissileCreateExpression takes integer structId, code func returns nothing
set expression[structId] = Condition(func)
endfunction
// Start timer if not already running.
private function StartPeriodic takes integer structId returns nothing
if (instances[structId] == 0) then
if (condition[structId] == null) then
set condition[structId] = TriggerAddCondition(CORE, expression[structId])
set active = active + 1
endif
if (active == 0) then
call TimerStart(CLOCK, Missile_TIMER_TIMEOUT, true, function Fire)
endif
endif
set instances[structId] = instances[structId] + 1
endfunction
// And stops it.
private function StopPeriodic takes integer structId returns nothing
set instances[structId] = instances[structId] - 1
if (instances[structId] == 0) then
set remove[structId] = remove[0]
set remove[0] = structId
endif
endfunction
if (active == 0) then
call TimerStart(CLOCK, Missile_TIMER_TIMEOUT, true, function Fire)
endif
// Handles the periodic timer callback and fires the trigger.
globals
private constant trigger CORE = CreateTrigger()
private constant timer CLOCK = CreateTimer()
private integer active = 0
private integer array instances
private Missile array missileStack
private boolexpr array expression
private triggercondition array condition
private integer array remove
endglobals
private function Fire takes nothing returns nothing
local integer i = remove[0]
set remove[0] = 0
loop
exitwhen i == 0
if (instances[i] == 0) and (condition[i] != null) then
call TriggerRemoveCondition(CORE, condition[i])
set condition[i] = null
set active = active - 1
endif
set i = remove[i]
endloop
if (active == 0) then
call PauseTimer(CLOCK)
else
call TriggerEvaluate(CORE)
endif
endfunction
private function MissileCreateExpression takes integer structId, code func returns nothing
set expression[structId] = Condition(func)
endfunction
// Start timer if not already running.
private function StartPeriodic takes integer structId returns nothing
if (instances[structId] == 0) then
if (active == 0) then
call TimerStart(CLOCK, Missile_TIMER_TIMEOUT, true, function Fire)
endif
if (condition[structId] == null) then
set condition[structId] = TriggerAddCondition(CORE, expression[structId])
set active = active + 1
endif
endif
set instances[structId] = instances[structId] + 1
endfunction
private function StopPeriodic takes integer structId returns nothing
set instances[structId] = instances[structId] - 1
if (instances[structId] == 0) then
set remove[structId] = remove[0]
set remove[0] = structId
endif
endfunction
method flightTime2Speed takes real duration returns nothing
set speed = RMaxBJ(0.00000001, (origin.distance - distance)*Missile_TIMER_TIMEOUT/duration)
endmethod
// Detect the collision type ( internally each loop )
//
if (speed < collision) then// maybe also speed < collision*.5 for accuracity.
set collisionType = Missile_COLLISION_TYPE_CIRCLE
else
set collisionType = Missile_COLLISION_TYPE_RECTANGLE
endif
// Runs unit collision.
static if thistype.onCollide.exists then
if (this.allocated) and (0 != this.collision) then
set b = (this.collisionType == Missile_COLLISION_TYPE_RECTANGLE)
if b then
call this.groupEnumUnitsRectangle()
else
call GroupEnumUnitsInRange(GROUP, this.x, this.y, this.collision + Missile_MAXIMUM_COLLISION_SIZE, null)
endif
loop
set u = FirstOfGroup(GROUP)
exitwhen u == null
call GroupRemoveUnit(GROUP, u)
if ((IsUnitInRange(u, this.dummy, this.collision)) or (b)) then
// These units are passed in onCollide.
private static method enumUnits takes nothing returns boolean
local thistype this = thistype.temp
//
local unit u = GetFilterUnit()
local real a = x - xPrev
local real b = y - yPrev
local real s = (a*(GetUnitX(u) - xPrev) + b*(GetUnitY(u)- yPrev))/(a*a + b*b)
local boolean is
if(s < 0) then
set s = 0.
elseif(s > 1) then
set s = 1.
endif
set is = IsUnitInRangeXY(u, xPrev + s*a, yPrev + s*b, collision)
set u = null
return is
endmethod
private method prepareRectRectangle takes nothing returns nothing
local real x1 = xPrev// x of the previous loop
local real y1 = yPrev// y of the previous loop
local real x2 = x
local real y2 = y
local real d = collision + Missile_MAXIMUM_COLLISION_SIZE
// What is min what is max ...
if(x1 < x2) then
if( y1 < y2) then
call SetRect(RECT, x1 - d, y1 - d, x2 + d, y2 + d)
else
call SetRect(RECT, x1 - d, y2 - d, x2 + d, y1 + d)
endif
else
if( y1 < y2) then
call SetRect(RECT, x2 - d, y1 - d, x1 + d, y2 + d)
else
call SetRect(RECT, x2 - d, y2 - d, x1 + d, y1 + d)
endif
endif
endmethod
method groupEnumUnitsRectangle takes nothing returns nothing
call prepareRectRectangle()
set thistype.temp = this
call GroupEnumUnitsInRect(GROUP, RECT, Filter(function thistype.enumUnits))
endmethod