• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] Custom Projectiles

Mag you got ripped of. :L

Not really, everything here is much more expensive. :p
In the states, an iPhone 4S costs $400. Here, it costs $1850. (And people here see that as ok :O)
A car that costs $190000 here would cost about $60000 in the US :eek:

(3RD WORLD COUNTRIES FTW!!!)

1700$ lolwut? I bought my PC this january for 500$ (without monitor, mouse and keyboard):

-AMD Triple-core CPU, 3,2 Ghz
-2x2 Gbyte DDR3 RAM (Kingmax)
-nVidia GeForce FX GT430 1Gbyte VGA (Asus)
-Gigabyte Motherboard
-500W PSU RaptoxX
-1 TByte HDD (Samsung)

FFFFFFFFFFFFFFFUUUUUUUUUUU

My computer is still pretty fast though, I can run a PS2 emulator at a constant 60 FPS with no lag spikes (Which is pretty demanding)

The speed difference between 2 and 4 cores is ~20% >.>
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
This computer cost me less than $1000 almost a year ago. There is no excuse for having a computer that costs less than $500 unless you're a bum. What I should really do is send my code through the optimizer and then see what kind of performance I get. Usually that picks things up by almost double.

I encourage you to use this and tell me when you start to notice the FPS dropping (I'm not talking about 62-64 difference). Also please state your system specs.

And my is an old 3 year computer for around 750$ so what to expect? :p Tech these days are evolving so fast that if you buy a computer now, it will be on sale in a year.
 
Tech these days are evolving so fast that if you buy a computer now, it will be on sale in a year.

True.
My computer was the second fastest thing you can buy from Apple about a year ago.
Now, the slowest thing you can buy is at least twice as fast >.>

Uhh.. this reminds me of something you guys are going to facepalm:
My uncle bought a 12-core Mac Pro for $8000 >.<
They cost $5000 in the US :/
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
You guys are funny. Anybody want to try testing this out and print some screen shots of FPS/total projectiles? The "UI" should be enabled in the most recent test-map I really want to see how this performs on a variety of computers. The "UI" displays how many projectiles currently exist (which reminds me I need to change the text of the UI to show the proper version number lol).

Okay well I tried substituting real members in the projectile struct instead of vectors (while still using vectors to generate the x/y/z values) and it seems like there is absolutely no gain in performance. I'm testing on my good computer, which could support around 600 projectiles before the frame-rate dropped below 40 FPS (using vector members). Testing on the same computer, roughly the same 600 projectiles could be handled before the frame-rate dropped below 40 FPS (using real members).

It would be beneficial if other members posted screenshots of their performance in game with varying amounts of projectiles. I want to see the FPS and the # of projectiles that currently exist (try not to factor the video-card into the situation by off-screen mini-map clicking). Depending on how the system performs on different machines, I'll decide what needs to be done.

*New*
----
I was just playing around with both versions (with/without vectors) and it really seems like the vectors work better. The frame-rate drops less quickly.
 
Last edited:
Level 3
Joined
Mar 14, 2011
Messages
32
isValidUnitTargetUnit bugs.
I'm only using this at the moment

JASS:
method isValidTargetUnit takes unit u returns boolean
return u == .target
endmethod

This works fine with slow projectiles (600ish)
but if the projectile has 1500+ish speed, the filter sometimes return false (when it lands on the target)! very strange
and the in this case.. its solved when i move around a little (also reoccurs when i move around more)

Any solution?
I'm assuming the projectile got destroyed before the enum function was called so the target was null
 
Last edited:
Level 3
Joined
Mar 14, 2011
Messages
32
I tested this on your demo map
I didn't modify any library other than these two for testing

JASS:
library RangedAttack requires Projectile
    struct rangedattack extends projectile
    
        private real damage
        
        method isValidTargetUnit takes unit u returns boolean
            return u == target        
        endmethod
        
        method onUnitCollision takes unit u returns nothing
            if not destroyed then
                call UnitDamageTarget(source, u, damage, false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
                call destroy()
            endif
        endmethod
        
        static method create takes unit source, unit target, real damage, vector startVector, vector endVector, /*
                                                        */ real speed, real arc, string modelpath returns thistype
            local real      a   = Atan2(endVector.y-startVector.y, endVector.x-startVector.x)
            local thistype  d   = .allocate(CreateUnit(GetOwningPlayer(source), 'dumy', startVector.x, startVector.y, a*bj_RADTODEG))
            
            // Ranged data setup:
            set d.damage = damage
            call d.setModel(modelpath)
            
            // Projectile setup:
            set d.source                = source
            set d.collision             = 32
            
            set d.activeUnitCollision   = true
            set d.toRemove              = true
            set d.toDestroy             = true
            set d.activePitch           = true
            set d.activeRotation        = true
            set d.activeTargetFollow    = true
            set d.targetZOffset         = 60
            set d.target                = target
            
            call d.launch(startVector, endVector, speed, arc)
            
            return d
        endmethod
    endstruct

endlibrary

JASS:
    private function filter takes nothing returns boolean
        return GetSpellAbilityId() == 'A002'
    endfunction

    private function onOrder takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        local unit target = GetSpellTargetUnit()
        local real x = GetUnitX(target)
        local real y = GetUnitY(target)
        local vector a
        local vector b
        
        call MoveLocation(loc, GetUnitX(caster), GetUnitY(caster))
        set a = vector.create(GetUnitX(caster), GetUnitY(caster), GetLocationZ(loc) + GetUnitFlyHeight(caster) + 60) // 60 represents the height offset from caster.
        call MoveLocation(loc, x, y)
        set b = vector.create(x, y, GetLocationZ(loc) + GetUnitFlyHeight(target) + 45) // there is no height offset
        
        call rangedattack.create(caster, target, 100, a, b, 1500, 0.2, "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl")
        
        call a.destroy()
        call b.destroy()
        
        set target = null
        set caster = null
    endfunction

Is this going to be fixed?
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
I'm trying to do what you're saying you've done to cause this "bug" but nothing strange happens... the projectile is destroyed as it comes into contact with the unit target. I tested with the speed of 1500, but I am confident that this isn't fast enough to cause any bugs.

I just tried it with speed set to 2000, still there isn't any case where what you are describing happens. Can you provide any more information that will help me produce this "bug"?
 
Level 3
Joined
Mar 14, 2011
Messages
32
I've tried this on your demo map.
The projectile is destroyed, but the onUnitCollision function is sometimes not called (the higher the speed is, more often it 'misses')

I think it makes sense for a missile to "collide" with unit even if its speed is really high
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
I think it makes sense for a missile to "collide" with unit even if its speed is really high

Yea but at the cost of way more unit enumeration than normal. There is a point where the functionality of ridiculously fast moving projectiles does not outweigh the performance cost of having to enumerate through these exceptional cases.

I put a couple of messages in the unit-collision method:

JASS:
    method onUnitCollision takes unit u returns nothing
        if not (destroyed) then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 2, "Unit collision!")
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 2, "    "+GetUnitName(u))
            call UnitDamageTarget(source, u, damage, true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
            call destroy()
        endif
    endmethod

It is easier to see the problem when toDestroy is off, so that the projectile will not destroy itself when it reaches the end of its trajectory. What happens is the projectile will stall in the air briefly before touching the unit. What's strange is that there is only one way the projectile should stop suddenly, and that is upon being destroyed (which never happens).

Another possibility, perhaps, is that the onFinish method is executing before unit-collision has been detected. This would be due to the really fast projectiles resulting in a low time expectancy. Let's see...

Yup. That's what it is. The onFinish method is being executed before the collision is detected.

I do want to have a chance to defend myself here, though; what you've presented is a misuse of the target member. If you've got a projectile with a target then unit-collision should only be determining collision with non-target units. The target member was made primarily to be used in the onFinish method as a way of referencing a predetermined target. If the target is predetermined, then there is no reason to enumerate units looking for it.

JASS:
local integer i=0
local real n=7.0
local real x=1.0/n
loop
    exitwhen not (i<10)
    set x = x * (n + 1.0) - 1.0
    set i=i+1
endloop
call BJDebugMsg(R2S(x))

x = x * (n + 1.0) - 1.0
x = (1/n) * (n + 1.0) - 1.0
x = 1 + (1/n) - 1
x = 1/n

The values printed out as "x" are going to be far from this, even after only 10 iterations. That's almost worse than standard float numbers, can't imagine why they didn't use double types as real numbers.
 
Last edited:
Level 3
Joined
Mar 14, 2011
Messages
32
ah I see =)

Should I lower the timer period just in case i want a really fast moving projectile without predetermined target?
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Well what I was getting as it that you can just move your code from onUnitCollision to the onFinish method. This method uses an expected time to calculate when it should be executed based on the projectile's velocity and such. The time expectancy is re-adjusted when the target unit moves (if target-following is enabled).

This means that no matter how fast your projectiles move (unless they're moving across enormous distances in splits of seconds) the onFinish method will still catch them when they should have hit the target.

If you're only using the target as a desired "target" then this would be your best bet.

Just try it out in the test-map. Instead of having isValidUnitTarget and onUnitCollision simply use onFinish.

JASS:
struct rangedattack extends projectile
    private real    damage
    private real    collisionHeight
    
    method onFinish takes nothing returns nothing
        call UnitDamageTarget(source, target, damage, true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
        call destroy( )
    endmethod
    
    static method create takes unit source, unit target, real damage, vector startVector, vector endVector, /*
                                                    */ real speed, real arc, string modelpath returns thistype
        local real      a   = Atan2(endVector.y-startVector.y, endVector.x-startVector.x)
        local thistype  d   = .allocate(CreateUnit(GetOwningPlayer(source), 'dumy', startVector.x, startVector.y, a*bj_RADTODEG))
        
        // Ranged data setup:
        set d.damage        = damage
        call d.setModel(modelpath)
        
        // Projectile setup:
        set d.source                = source
        set d.collision             = 32
        set d.collisionHeight       = 128
        
        set d.activeUnitCollision   = true
        set d.toRemove              = true
        set d.toDestroy             = false
        set d.activePitch           = true
        set d.activeRotation        = true
        set d.activeTargetFollow    = true
        set d.targetZOffset         = 60
        set d.target                = target
        
        call d.launch(startVector, endVector, speed, arc)
        
        return d
    endmethod
endstruct

As far as I can tell it still works as intended even at 5000.00 speed. I didn't need to lower my timer frequency either. This is why I made onFinish the way I did. And actually you don't even need unit collision for projectiles that only hit their targets. You could set that field to false as well.
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
Well I've actually got a version 2.2.2 that I'm working on where only 1 hash-table is used. I'm going to check out this destructible-collision bug but other than that what issues are you referring to?

when I change the rect used by the projectile code to be just bj_mapinitial blahblah, it works...

Can you post the code where this occurs? The rect you are referring to is initialized at point (0, 0) with a width/height of the maximum possible collision. This rect is moved around and re-sized depending on the projectile that is being checked. I'm pretty sure that I tested this when I implemented the feature though and there were no issues with the rect.

Would you be able to post some code or a test-map where this does not work?

----
I just used the destructible collision. Make sure you've enabled it, projectileVariable.enableDestCollision = true. Make sure that your projectiles are simply not missing the destructibles either try using a relatively high collision size.
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
Just today I checked to see if it was working and it is. I know because I have a spell that shoots projectiles and knocks down trees. I made it just recently, earlier today.

Nobody ever posts any code when they have problems, so if there really is a problem I'll need a far more specific description of what the problem is. I can't really just go by, "your destructable collision doesn't work can you fix it?". If he isn't setting enableDestCollision = true then it won't fire destructable collision events. If the collision size is too small the destructables may not be detected nearby. If... I could go on. Code is not as ambiguous as a description of an error.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
With 100 projectiles or with 100 units?

My computer can only handle a few dozen of his projectiles, but it can handle 100 homing projectiles on a much lighter missile system which I wrote. I am not releasing that system yet because it's not ready, but Anitarf's XE Missile is extremely lightweight comparitively (again though it's a lighter system, doesn't have as many features).
 
Level 14
Joined
Sep 28, 2011
Messages
968
It is normal to have a not overpowered computer.But generally the ones who does not have a powerfull computer does not say anywhere.I have a computer with windows XP and a NVIDIA GeForce4 TI 4200 grafic card and A AMD Athlon(tm) processor
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I have no idea why your system should be running worse than mine, when my computer looks like this:

Intel Atom single-core 1.6GHz
1GB RAM
No graphics card

Are you talking about units currently visible on the screen, or just units in general? Your computer shouldn't even be able to support the WarChasers map, a game built for 128MB RAM, 16MB graphics and 400MHz processor machines.

Maybe your computer has a virus.
 
It is when units move that it become hard to calculate

Use this:

JASS:
library Test

    private function Add takes nothing returns boolean
        local vector v1 = vector.create(-2000,-2000,64)
        local vector v2 = vector.create(2000,2000,64)
        local integer i = S2I(GetEventPlayerChatString())
        local integer j = 0
        local projectile temp
        loop
            exitwhen j == i
            call projectile.create(CreateUnit(Player(0), 'dumy', 0, 0, 0)).launch(v1, v2, 85, 0)
            set j = j + 1
        endloop
        return false
    endfunction
    
    private struct Inits
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterPlayerChatEvent(t, Player(0), "", false)
            call TriggerAddCondition(t, Condition(function Add))
            set t = null
        endmethod
    endstruct

endlibrary

In the chat, type in 100 for 100 projectiles.
They'll be created and move reaaaal slow.
It makes it easier to test the system.

edit
lol My old test was very inaccurate (in FPS)
So I did a new one.
5 FPS with 700 projectiles :(
 
JASS:
struct rangedattack extends projectile
        static trigger rangedtrigger = CreateTrigger()
        private real    damage
        private real    collisionHeight
        static location tempLoc = Location(0,0)
        private integer element
        
        method isValidTargetUnit takes unit u returns boolean
            local boolean inRange   = false 
            call MoveLocation(tempLoc, GetUnitX(u), GetUnitY(u))
            if (absoluteReal(GetLocationZ(thistype.tempLoc)+GetUnitFlyHeight(u) - z) < .collisionHeight) then
                set inRange = true
            endif
            return (u != source) and GetWidgetLife(u) >= .405 and inRange
        endmethod
    
        method onUnitCollision takes unit u returns nothing
            if not (destroyed) then
                call Damage.Unit(.source, u, .damage, .element)
                call destroy()
            endif
        endmethod
        
        method isValidTargetDest takes destructable d returns boolean
            call DisplayTextToPlayer(Player(0), 0.0, 0.0, "Hit") //for debugging
            return true
        endmethod
        
        method onDestCollision takes destructable d returns nothing
            if (not (destroyed)) then
                if not IsDestructableInvulnerable(d) then
                    call SetDestructableLife(d, GetDestructableLife(d) - .damage)
                endif
                call destroy()
            endif
        endmethod
        
        static method create takes unit source, unit target, real damage, vector startVector, vector endVector, /*
                                                    */ real speed, real arc, string modelpath, integer element returns thistype
            local real      a   = Atan2(endVector.y-startVector.y, endVector.x-startVector.x)
            local thistype  d   = .allocate(CreateUnit(GetOwningPlayer(source), 'dumy', startVector.x, startVector.y, a*bj_RADTODEG))
        
            // Ranged data setup:
            set d.damage        = damage
            call d.setModel(modelpath)
        
            // Projectile setup:
            set d.source                = source
            set d.collision             = 32
            set d.collisionHeight       = 128
            
            set d.element               = element
            set d.activeUnitCollision   = true
            set d.activeDestCollision   = true
            set d.toRemove              = false
            set d.toKill                = true
            set d.toDestroy             = true
            set d.activePitch           = true
            set d.activeRotation        = true
            set d.activeTargetFollow    = false
            set d.targetZOffset         = 60
            set d.target                = target
        
            call d.launch(startVector, endVector, speed, arc)
        
            return d
        endmethod
endstruct

then this is the default lines on the projectile system, which when I use, doesn't work, the debug line on the filter above doesn't show, and the projectile isn't destroyed when it hits destructables
JASS:
if (dat.activeDestCollision) and dat != 0 then // Destructable Collision
                    set projRef = dat
                    call SetRect(rct, 0, 0, dat.PROP__collision, dat.PROP__collision)
                    call MoveRectTo(rct, x, y)
                    call EnumDestructablesInRect(rct, FUNC__enumDests, null)
                endif

and when I change the above lines into this, the destructable hit works totally fine
JASS:
if (dat.activeDestCollision) and dat != 0 then // Destructable Collision
                    set projRef = dat
                    call EnumDestructablesInRect(bj_mapInitialPlayableArea, FUNC__enumDests, null)
                endif
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
I think I may be using the wrong calculation for the size of the rect. The rect should be 2×collision instead of simply collision, because that is the radius in which to check. Try doing this instead and see if that helps:

JASS:
call SetRect(rct, 0, 0, 2*dat.PROP__collision, 2*dat.PROP__collision)

That should work.

Thank you for pointing that out, when I tested it the only thing I noticed was that it seemed like I needed a larger collision size to detect the destructables, but it may be because of this.

I'm worried though that the destructable collision will still be a little wonky. I remember destructables being enumerated kind of strangely, but mathematically you should not get any more results expanding the enumeration to a global scale than a rectangle who's dimensions are 2*collision.

If you've got a circle of radius 2×collision then at any given degree or radian measure the point that is drawn on any point in the circumference will be within the bounds of that rectangle.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
There really isn't any problem with using 4*collision because in the end it will still filter out destructables that are not within the collision radius. The difference is how many destructables the function has to go through to determine the same thing. I think I'll look into it and see what the best option is. I've got some other material added too that I wanted to release so perhaps I will make it in the update.

I just tested it using:
JASS:
call SetRect(rct, x-2*dat.PROP__collision, y-2*dat.PROP__collision, x+2*dat.PROP__collision, y+2*dat.PROP__collision)

This should give me a rectangle that has a length/width both equal to 4 times the collision of the projectile. I don't really see why the result would change because in the filter function I simply check to see if its within the rectangle but not within the circle (on a corner or something). The fact that I've got to extend the search for destructables by 4 times to get accurate results means that the enumerations within the collision×collision rectangle are missing some destructables that should technically be in range.

By the way, the above code also allows me to not have to move the rect after I set it. There really is no point in it, so it's not a bad idea. Though it may be better to move a rectangle around with the maximum collision detection possible (so that they do not have to be constantly re-sized).
 
By the way, the above code also allows me to not have to move the rect after I set it. There really is no point in it, so it's not a bad idea. Though it may be better to move a rectangle around with the maximum collision detection possible (so that they do not have to be constantly re-sized).

--> yeah, I tested that too, and it also works really fine that way...
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
But how does it work when there are destructables at the tip of collision on a large collision object? For example, right now our collision is 32 or so and that's the dimensions of the rectangle. If the dimensions of the rectangle are 192 or something and the collision of the projectile is 192, it may not detect destructables that should have been hit by the large collision projectile.

So it may be better to stick to a multiple of the projectile's collision. I tried expanding the rectangle by 4 times (like you mentioned) and that seemed to work a little better. It's really hard to test whether or not they should have been hit at low collision ranges.
 
Level 9
Joined
Mar 6, 2012
Messages
64
I modified it to support the new special effects native. Removed the projectile art interface and merged it withthe main lib. Note that I only modified it to use the new natives, added some variables and did not delete any unused variables.

Also I made a modified version which makes the hitbox and movement in 3d perspective instead of 2d, made the performance slightly worse tho. If anyone wants it, you can pm me.
 

Attachments

  • Projectile v2.2.0.w3x
    92.6 KB · Views: 56
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I modified it to support the new special effects native. Removed the projectile art interface and merged it the the main lib. Note that I only modified it to use the new natives, added some variables and did not delete any unused variables.

Also I made a modified version which makes the hitbox and movement in 3d perspective instead of 2d, made the performance slightly worse tho. If anyone wants it, you can pm me.
IIRC Berb (then Berbanog) originally had a 3D hit box but he deleted it as per request due to desync risks.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
@Bribe Hmm do you know what exactly are the reasons for those desync issues? As far as I know calculating hitboxes are just a bunch of calculations.
Because he would take 3d height into consideration (an asynchronous thing) of whether to launch the onImpact event, which would both destroy the projectile as well as cause damage to the target.
 
Level 9
Joined
Mar 6, 2012
Messages
64
Because he would take 3d height into consideration (an asynchronous thing) of whether to launch the onImpact event, which would both destroy the projectile as well as cause damage to the target.
I see. I actually just found out that GetLocationZ may cause desync issues due to terrain deformations and fog of war. Just a quick question, does that apply to the new native blzgetunitz function? What if I save the boolean for every local player and and compare them such that a single false would cause the necessary action for all players? Though it might cost a bit slower performance. Also, thanks for the info.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I see. I actually just found out that GetLocationZ may cause desync issues due to terrain deformations and fog of war. Just a quick question, does that apply to the new native blzgetunitz function? What if I save the boolean for every local player and and compare them such that a single false would cause the necessary action for all players? Though it might cost a bit slower performance. Also, thanks for the info.
You don't want to wait for synchronization before calling an onImpact function. The sync will take far longer than the missile will require.

BlzGetUnitZ will have the same problem.
 
Level 9
Joined
Mar 6, 2012
Messages
64
You don't want to wait for synchronization before calling an onImpact function. The sync will take far longer than the missile will require.

BlzGetUnitZ will have the same problem.
Hmm it seems that the only way to avoid desyncs is to avoid terrain deformations and anything related huh. Thanks a lot, this has been very helpful.
 
Top