So basically:
- Unit comes in range of a projectile.
- If the unit is not in the group, it is added to the group and an onUnitImpact() method is run.
- If the unit is in the group, it is ignored.
It is your usual damage group scenario so that a unit does not get damaged twice. I've done it 1,000,000 times before. However, this time it doesn't want to work for some reason.
Here's the script:
The trouble area:
The [ljass]"WTF!"[/ljass] debug message runs twice for the same unit.
Can anyone spot the problem?
In the test map, just pick one of the blood mages, and target another unit with Hurl Boulder. You should see three debug messages. One for the caster and two for the target (WHICH SHOULDN'T HAPPEN!).
EDIT:
Sometimes the debug message will show up multiple (two) times for a unit and sometimes it wont show up at all, and rarely it will just work as it should. This is really weird, I am beginning to think I missed something very obvious.
Meh, figured it out, don't worry. It was something stupid after all.
- Unit comes in range of a projectile.
- If the unit is not in the group, it is added to the group and an onUnitImpact() method is run.
- If the unit is in the group, it is ignored.
It is your usual damage group scenario so that a unit does not get damaged twice. I've done it 1,000,000 times before. However, this time it doesn't want to work for some reason.
Here's the script:
JASS:
library Projectile initializer Init requires AIDS, T32, Event, Vector, AutoFly
//-----------------------------------------------------------------------\\
// ____ _ _ _ _ \\
// | _ \ _ __ ___ (_) ___ ___| |_(_) | ___ \\
// | |_) | '__/ _ \| |/ _ \/ __| __| | |/ _ \ \\
// | __/| | | (_) | | __/ (__| |_| | | __/ \\
// |_| |_| \___// |\___|\___|\__|_|_|\___| \\
// |__/ By kenny! v1.0.0 \\
// \\
//-----------------------------------------------------------------------\\
// \\
// To do list: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - Re-write method operators. [99% done] \\
// - Attempt to add vertical collision. [99% done] \\
// - Add method for resetting velocities. [99% done] \\
// - Re-write ProjInterface. [99% done] \\
// - Should global collision event run first? [50% done] \\
// \\
//-----------------------------------------------------------------------\\
// \\
// What is Projectile? \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Projectile is a basic projectile system for simple and advanced \\
// custom spells. This system provides users with a simple interface \\
// that gives great control over projectiles in a map. Users can: \\
// \\
// - Create normal Projectiles using the simplest of methods \\
// available. \\
// - Create more advanced projectiles by extending structs and \\
// using bonus features. \\
// - Group Projectiles in range of XYZ coordinates (not available \\
// in most similar systems). \\
// - Register triggers to execute when two projectiles collide and \\
// do stuff with them. \\
// \\
// How to implement: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Simply create a new trigger object called Projectile, go to: \\
// 'Edit -> Convert to Custom Text', and replace everything that's \\
// there with this script. \\
// \\
// Make sure you implement all the required systems for this system. \\
// That includes: AIDS, T32, Event, Vector and AutoFly. \\
// \\
// Save the map, close it, reopen it, then delete the "!" from the \\
// start of the next line (so "external" lines up with this line): \\
// external ObjectMerger w3u ushd proj unam "Projectile Dummy" uabi Aloc uble 0 ucbs 0 ucpt 0 umxp 0 umxr 0 umdl "dummy.mdl" uimz 0 ulpz 0 uprw 1 ushu "" uspa "" umvs 522 umvr 3 ucol 0 ufle 0000 usnd "" ufoo 0 ubba 0 ubdi 0 ubsi 0 uhom 0001 usid 1 usin 1 utyp "" upgr "" utip "" utub ""
// \\
// After you have done that, you are ready to use this system. Enjoy. \\
// \\
// Projectile API and usage: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Methods available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// These methods are available to users who wish to use the most \\
// basic of interfaces. \\
// \\
// - Projectile.create(xPos,yPos,zPos,facing) --> Projectile \\
// \\
// The above will create a projectile at a desired location. The \\
// first three agruments in the create method refer to the x, y and \\
// z coordinates where you want the projectile to be created. The \\
// last of the arguments refers to which direction (in radians) you \\
// want the projectile to face initially. \\
// \\
// - .project(xPos,yPos,zPos,speed) --> Nothing \\
// \\
// The above will start the projectile moving towards a desired \\
// location. The first three arguments refer to the x, y and z \\
// coordinates of where you would like the projectile to travel to. \\
// The last argument refers to the speed at which you want the \\
// projectile to move. The speed is in units per second. \\
// \\
// - .modifyTargeting(xPos,yPos,zPos) --> Nothing \\
// \\
// The above will determine the new target coordinates of the \\
// projectile, thus changing its trajectory accordingly. The three \\
// arguments refer to the new x, y and z coordinates of the wanted \\
// target location. \\
// \\
// - .modifySpeed(speed) --> Nothing \\
// \\
// The above method will modify the speed of the projectile to what \\
// the user has specified. The argument is the new speed that has \\
// to be in units per second. Users can use other struct members to \\
// retrieve original, previous and current speeds. \\
// \\
// - .isUnitInHeightRange(whichUnit) --> Boolean \\
// \\
// The above is somewhat of a bonus feature that users can make use \\
// of when the projectile hits a unit. The boolean returned by this \\
// method tells us if the unit is within collision distance of the \\
// projectile in regards to unit and projectile height. This method \\
// makes simple vertical collision easier for users. The one unit \\
// argument refers to the unit you want to check. \\
// \\
// - .terminate() --> Nothing \\
// \\
// The above method will destroy a projectile and all data that is \\
// associated with it. This can be called at any time while a \\
// projectile is alive, and multiple calls accidently will not \\
// damage anything. \\
// \\
// Bonus methods available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// These methods are available to users who wish to use a more \\
// advanced interface by extending structs. \\
// \\
// - .onStart() --> Nothing \\
// \\
// Users can implement this into a struct and use it to do extra \\
// stuff when the projectile starts to move such as adding effects. \\
// \\
// - .onLoop() --> Nothing \\
// \\
// Users can implement this into a struct if they wish to do extra \\
// stuff each iteration of the periodic timer. \\
// \\
// - .onFinish() --> Nothing \\
// \\
// Users can implement this into a struct if they want to do stuff \\
// when the projectile is destroyed. This will always fire. \\
// \\
// - .onLandImpact() --> Nothing \\
// \\
// Users can implement this into a struct if they want to do stuff \\
// when the projectile hits the ground. This may not fire depending \\
// on whether or not a projectile is terminated early or if it is \\
// designed to not hit the ground. \\
// \\
// - .onProjImpact() --> Nothing \\
// \\
// Users can implement this into a struct if they wish to do stuff \\
// when two projectiles collide. This will only fire if the \\
// projectile is collideable (see below). There is also an event \\
// for projectile collision (also see below). \\
// \\
// - .onUnitImpact(whichUnit) --> Nothing \\
// \\
// Users can implement this into a struct if they wish to do stuff \\
// when a projectile hits a unit. The unit argument in this metod \\
// refers to the unit that has been hit by the projectile. This \\
// can be used in conjunction with .isUnitInHeightRange(). \\
// \\
// Public members available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// These members are available for users to freely change and modify \\
// at any time throughout the projectiles life span. However, unless \\
// you fully understand how they work, it is recommended that you \\
// set them only after you create the projectile and no where else. \\
// \\
// - .caster --> The casting unit of a projectile spell. Can \\
// be null if the projectile is just for effect. \\
// The caster is not automatically the owner. \\
// - .owner --> The owning player of the projectile, and \\
// commonly of the casting unit as well. This \\
// must be set separately to the caster. \\
// - .timedLife --> Gives the projectile a life span. By \\
// default projectiles an unlimited life span. \\
// Timed life should be in seconds. \\
// - .gravity --> All projectiles are normally under the effect \\
// of default gravity. Users can set a custom \\
// gravity for individual projectiles if needed. \\
// - .scaleSize --> Sets the scale size of the projectile. Due to \\
// warcraft limitations this can only be set and \\
// not retrieved. \\
// - .unitCollision --> The radius in which a unit has to be to be \\
// considered a target. This does not have to be \\
// defined, as it will be set to default. \\
// - .projCollision --> The radius in which a projectile has to be \\
// to be considered a target. This does not have \\
// to be defined, as it will be set to default. \\
// - .pauseProj --> Users can pause all movement of a proj. \\
// This still allows .onLoop() and projectile \\
// collision. By default this is false. \\
// - .collideable --> Users can specify if a projectile is able \\
// to collide with other projectiles. By default \\
// this is false. \\
// - .effectPath --> Users can specify what the projectile looks \\
// like using this member. The effect path can \\
// even be retrieved for special effect usage. \\
// \\
// Readonly members available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Unlike the above members, these are not meant to be changed at any \\
// time throughout the duration of the projectile. Therefore, they \\
// are readonly, meaning that you can access them but not change \\
// them. \\
// \\
// - .proj --> Refers to the projectile unit itself. Can be \\
// useful for attaching effects amongst other \\
// things. \\
// - .theta --> Refers to the current XY facing angle of the \\
// projectile. Does not include Z coordinates. \\
// Can be useful for some projectile spells. \\
// - .phi --> Refers to the current XYZ facing angle of the \\
// projectile. Includes Z coordinates as well. \\
// Can be useful for some projectile spells. \\
// - .pos --> Refers to the current position of the \\
// projectile. Users can access: .pos.x / .pos.y \\
// / .pos.z. This is a vector struct \\
// - .vel --> Refers to the current velocity of the \\
// projectile. Users can access: .vel.x / .vel.y \\
// / .vel.z. This is a vector struct. \\
// - .start --> Refers to the starting coordinates of the \\
// projectile. Users can access: .start.x / \\
// .start.y / .start.z. This is a vector struct. \\
// - .target --> Refers to the target coordinates of the \\
// projectile. Users can access: .target.x / \\
// .target.y / .target.z. Again a vector struct. \\
// - .currentSpeed --> Returns the current speed of a given \\
// projectile. Returned speed is in units per \\
// second. \\
// - .previousSpeed --> Returns the previous speed of a projectile, \\
// before its speed was modified. Returned speed \\
// is in units per second. \\
// - .originalSpeed --> Returns the original speed of a projectile, \\
// even if its speed has been modified multiple \\
// times. Returned speed is in units per second. \\
// - .isTerminated --> Returns true if the projectile instance has \\
// been terminated. Can be of use in quite rare \\
// circumstances. \\
// \\
// The static method operator: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - Projectile[whichUnit] --> Projectile \\
// \\
// The static method operator is for unit to Projectile typcasting. \\
// Mainly for use when GroupEnumProjectiles... is used (see below). \\
// Users specify a unit and it will return the associated \\
// projectile struct, if there is any. \\
// \\
// Functions available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - GroupEnumProjectilesInRange(whichGroup,xPos,yPos,zPos,radius) \\
// \\
// Adds all projectiles within a certain radius of a location \\
// specified by the users to a group also specified by the user. \\
// Units added to the group can then be typecasted into a \\
// Projectile struct using the static method operator. \\
// \\
// The first argument is the group which users want to add the \\
// projectiles too. The next three arguments are the x, y and z \\
// coordinates that users want to check. The last argument is the \\
// radius in which units need to be to be added to the group. \\
// \\
// - RegisterProjectileCollisionEvent(whichTrigger) \\
// \\
// Registers a specified trigger for projectile collision events. \\
// The trigger will then fire whenever a collision between \\
// projectiles occurs. Users are then given other functions to \\
// retrieve both of the projectiles that collided, so that they \\
// can do something special. The argument refers to the trigger \\
// that users want registered. \\
// \\
// - GetFirstCollisionProj() \\
// \\
// Retrieves the first projectile from the most recent collision \\
// event. Users can then use all the methods, operators and members \\
// from the struct to do something with the projectile. \\
// \\
// - GetSecondCollisionProj() \\
// \\
// Same as the above function. This will retrieve the other \\
// projectile that collided in the most recent collision event. \\
// Users can then do stuff with the retrieved struct. \\
// \\
// Basic usage: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// function Actions takes nothing returns nothing \\
// local unit caster = GetTriggerUnit() \\
// local real castx = GetUnitX(caster) \\
// local real casty = GetUnitY(caster) \\
// local real targx = GetSpellTargetX() \\
// local real targy = GetSpellTargetY() \\
// local real angle = Atan2((targy-casty),(targx-castx)) \\
// local Projectile p = 0 \\
// \\
// set p = Projectile.create(castx,casty,50.00,angle) \\
// \\
// set p.caster = caster \\
// set p.owner = GetOwningPlayer(p.caster) \\
// set p.effectPath = "Something.mdl" \\
// set p.scaleSize = 1.00 \\
// \\
// call p.project(targx,targy,0.00,1000.00) \\
// \\
// set caster = null \\
// endfunction \\
// \\
// More advanced usage: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// private struct Proj extends Projectile \\
// \\
// unit target = null \\
// \\
// method onUnitImpact takes unit whichUnit returns nothing \\
// if IsUnitEnemy(whichUnit,this.owner) then \\
// if this.isUnitInHeightRange(whichUnit) then \\
// call UnitDamageTarget(...) \\
// endif \\
// endif \\
// endmethod \\
// \\
// method onProjImpact takes nothing returns nothing \\
// call this.terminate() \\
// endmethod \\
// \\
// method onStart takes nothing returns nothing \\
// call DestroyEffect(AddSpecialEffect(...)) \\
// endmethod \\
// \\
// method onLoop takes nothing returns nothing \\
// local real x = GetUnitX(this.target) \\
// local real y = GetUnitY(this.target) \\
// local real z = GetUnitFlyHeight(this.target) \\
// \\
// call this.modifyTargeting(x,y,z) \\
// endmethod \\
// \\
// method onFinish takes nothing returns nothing \\
// call DestroyEffect(AddSpecialEffect(...)) \\
// endmethod \\
// \\
// endstruct \\
// \\
// function Actions takes nothing returns nothing \\
// local unit caster = GetTriggerUnit() \\
// local unit target = GetSpellTargetUnit() \\
// local real castx = GetUnitX(caster) \\
// local real casty = GetUnitY(caster) \\
// local real targx = GetUnitX(target) \\
// local real targy = GetUnitY(target) \\
// local real angle = Atan2((targy-casty),(targx-castx)) \\
// local Proj p = 0 \\
// \\
// set p = Proj.create(castx,casty,50.00,angle) \\
// \\
// set p.caster = caster \\
// set p.target = target \\
// set p.owner = GetOwningPlayer(p.caster) \\
// set p.effectPath = "Something.mdl" \\
// set p.scaleSize = 1.00 \\
// set p.gravity = 0.00 // Assign no gravity to this proj. \\
// set p.projCollision = 64.00 \\
// set p.unitCollision = 128.00 \\
// set p.collideable = true \\
// \\
// call p.project(targx,targy,GetUnitFlyHeight(target),1000.00) \\
// \\
// set caster = null \\
// set target = null \\
// endfunction \\
// \\
//-----------------------------------------------------------------------\\
//-----------------------------------------------------------------------\\
// Configurables: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - PROJ_DUMMY_ID --> The raw code of the dummy unit used for \\
// the projectiles in this system. \\
// - MAX_UNIT_COLLISION --> The maximum unit collision size allowed \\
// in your map. \\
// - DEFAULT_GRAVITY --> The default gravity used for all the \\
// projectiles in the map. \\
// - DEFAULT_UNIT_COLK --> The default unit collision radius for \\
// projectiles. \\
// - DEFAULT_PROJ_COLL --> The default projectile collision radius \\
// for projectiles. \\
// \\
//-----------------------------------------------------------------------\\
globals
private constant integer PROJ_DUMMY_ID = 039;proj039;
private constant real MAX_UNIT_COLLISION = 256.00
private constant real DEFAULT_GRAVITY = -1000.00
private constant real DEFAULT_UNIT_COLL = 128.00
private constant real DEFAULT_PROJ_COLL = 64.00
endglobals
//-----------------------------------------------------------------------\\
// \\
// DO NOT TOUCH PAST THIS POINT UNLESS YOU KNOW WHAT YOUR ARE DOING! \\
// \\
//-----------------------------------------------------------------------\\
globals
private Event CollisionEvent = 0
private Projectile FirstProjData = 0
private Projectile SecondProjData = 0
endglobals
native UnitAlive takes unit id returns boolean
//-----------------------------------------------------------------------\\
// ProjInterface: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Methods available to users if they wish to extend \\
// a struct to obtain extra functionality. \\
// \\
// Methods available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - .onStart() --> Fires when the .start method is used. \\
// Can be used as an onCreate method as well. \\
// - .onLoop() --> Fires each interval of the periodic timer. \\
// More movement functionality can be added here. \\
// - .onFinish() --> Fires when the proj instance is destroyed. \\
// Will fire even if the proj did not hit land. \\
// \\
// - .onLandImpact() --> Fires when the projectile hit the ground. \\
// Only fires if the proj reaches the ground. \\
// - .onProjImpact() --> Fires when two projectiles collide. \\
// Will only fire if a proj is collideable. \\
// - .onUnitImpact() --> Fires when the projectile hits a unit. \\
// Can be used for collision missiles. \\
// \\
//-----------------------------------------------------------------------\\
private interface ProjInterface
method onStart takes nothing returns nothing defaults nothing
method onLoop takes nothing returns nothing defaults nothing
method onFinish takes nothing returns nothing defaults nothing
method onLandImpact takes nothing returns nothing defaults nothing
method onProjImpact takes nothing returns nothing defaults nothing
method onUnitImpact takes unit whichUnit returns nothing defaults nothing
endinterface
//-----------------------------------------------------------------------\\
// ProjData: \\
// ¯¯¯¯¯¯¯¯¯¯ \\
// Struct that handles and keeps track of Projectile structs for each \\
// dummy unit, making if far easier to associate units from \\
// GroupEnumProjectilesInRange() with their allocated structs. \\
// \\
// Makes use of AIDS structs, and will only work for one dummy unit \\
// type. It is only for internal use within the system. \\
// \\
// Methods available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - None. \\
// \\
//-----------------------------------------------------------------------\\
private struct ProjData extends array
//! runtextmacro AIDS()
Projectile data
private static method AIDS_filter takes unit whichUnit returns boolean
return GetUnitTypeId(whichUnit) == PROJ_DUMMY_ID
endmethod
private method AIDS_onCreate takes nothing returns nothing
set this.data = 0
endmethod
private method AIDS_onDestroy takes nothing returns nothing
set this.data = 0
endmethod
endstruct
//-----------------------------------------------------------------------\\
// ProjSpeedMod: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Projectile speed module. Takes care of modify a projectiles speed \\
// and keeps track of original and previous speeds of each proj. \\
// \\
// This is only here to make it easier for me to read through the \\
// script and add extra functionality when needed. \\
// \\
// Method operators available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - .currentSpeed --> Returns the current speed of a projectile. \\
// Returned speed is in units per second. \\
// - .previousSpeed --> Returns the previous speed of a projectile, \\
// before it's speed was modified. \\
// - .originalSpeed --> Returns the original speed of a projectile, \\
// even if it's speed has been modified lots. \\
// \\
// Methods available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - .modifySpeed() --> Modifies a projectiles speed depending on \\
// the value given by the user. \\
// \\
//-----------------------------------------------------------------------\\
private module ProjSpeedMod
method operator currentSpeed takes nothing returns real
return this.speed * T32_FPS
endmethod
method operator previousSpeed takes nothing returns real
return this.oldSpeed * T32_FPS
endmethod
method operator originalSpeed takes nothing returns real
return this.oriSpeed * T32_FPS
endmethod
method modifySpeed takes real value returns nothing
local vector v = vector.sum(this.pos,this.vel)
set this.oldSpeed = this.speed
set this.theta = Atan2((v.y - this.pos.y),(v.x - this.pos.x))
set this.phi = Atan2(SquareRoot((v.x - this.pos.x) * (v.x - this.pos.x) + (v.y - this.pos.y) * (v.y - this.pos.y)),(v.z - this.pos.z))
set this.speed = value * T32_PERIOD
set this.vel.x = Sin(this.phi) * Cos(this.theta) * this.speed
set this.vel.y = Sin(this.phi) * Sin(this.theta) * this.speed
set this.vel.z = Cos(this.phi) * this.speed
call v.destroy()
endmethod
endmodule
//-----------------------------------------------------------------------\\
// ProjOperatorMod: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Projectile operator module. This module holds all the method \\
// operators available for use with this system. Again, I only have \\
// them in a module so they are easy for me to find and update. They \\
// will be properly added to the Projectile struct when I think that \\
// they are 100% complete. \\
// \\
// Method operators available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - Projectile[] --> Returns the Projectile instance associated \\
// with the unit specified by the user. \\
// - .owner= --> Sets the owner of the projectile. The owner \\
// is commonly associated with the caster. \\
// - .owner --> Retrieves the owner of the projectile. \\
// Useful for filters and damage functions. \\
// - .scaleSize= --> Sets the scale size of the projectile. Due \\
// to warcraft limits this cannot be retrieved. \\
// - .gravity= --> Users can set the gravity for individual \\
// projectiles if needed. \\
// - .gravity --> Retrieves the gravity a projectile is using. \\
// Only useful if custom gravity is set. \\
// - .effectPath= --> Sets the effect path of the projectile. This \\
// is what the projectile will look like. \\
// - .effectPath --> Returns the string path of what the proj \\
// looks like.
// - .isTerminated --> Returns true if the projectile has been \\
// terminated. Rarely useful. \\
// \\
//-----------------------------------------------------------------------\\
private module ProjOperatorMod
static method operator[] takes unit whichUnit returns thistype
return ProjData[whichUnit].data
endmethod
method operator owner= takes player whichPlayer returns nothing
set this.own = whichPlayer
call SetUnitOwner(this.proj,whichPlayer,true)
endmethod
method operator owner takes nothing returns player
return this.own
endmethod
method operator scaleSize= takes real value returns nothing
call SetUnitScale(this.proj,value,0.00,0.00)
endmethod
method operator gravity= takes real value returns nothing
set this.grav.z = ((value * T32_PERIOD * T32_PERIOD) / 2.00)
endmethod
method operator gravity takes nothing returns real
return ((this.grav.z * 2.00) / T32_PERIOD) / T32_PERIOD
endmethod
method operator effectPath= takes string whichPath returns nothing
if this.sfx != null then
call DestroyEffect(this.sfx)
set this.path = ""
endif
if whichPath == "" then
set this.sfx = null
set this.path = ""
else
set this.sfx = AddSpecialEffectTarget(whichPath,this.proj,"origin")
set this.path = whichPath
endif
endmethod
method operator effectPath takes nothing returns string
return this.path
endmethod
method operator isTerminated takes nothing returns boolean
return this.stop
endmethod
endmodule
//-----------------------------------------------------------------------\\
// Projectile: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Projectile is the main struct of the system that handles the \\
// creation, movement and destruction of projectiles, as well as unit \\
// and projectile collision. It makes use of T32 and vectors to make \\
// sure that minimal strain is put on computers while projectiles are \\
// running. \\
// \\
// Users can either use Projectile directly for most common needs, or \\
// extend a struct with Projectile for more advanced uses. \\
// \\
// Public members available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// These are members that users can freely change and modify at any \\
// time throughout the life time of the Projectile. However, it is \\
// best that all changes be made when the projectile is created. \\
// \\
// - .caster --> The casting unit of a projectile spell. Can \\
// use null if the proj is just for effect. \\
// - .timedLife --> Gives the projectile a life span. By \\
// default projectiles an unlimited life span. \\
// - .unitCollision --> The radius in which a unit has to be to be \\
// considered a target. \\
// - .projCollision --> The radius in which a projectile has to be \\
// to be considered a target. \\
// - .pauseProj --> Users can pause all movement of a proj. \\
// This still allows .onLoop() and collision. \\
// - .collideable --> Users can specify if a projectile is able \\
// to collide with other projectiles. \\
// \\
// Readonly members available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// These are members that users are not meant to change or modify. \\
// However, they are still useful for certain aspects of spells, \\
// therefore users are allowed to access them but not change them. \\
// \\
// - .proj --> Refers to the projectile unit itself. Can be \\
// useful for attaching effects amongst other things. \\
// - .theta --> Refers to the current XY facing angle of the proj. \\
// Does not include Z coordinates. \\
// - .phi --> Refers to the current XYZ facing angle of the \\
// projectile. Includes Z coordinates as well. \\
// - .pos --> Refers to the current position of the projectile. \\
// Users can access: .pos.x/.pos.y/.pos.z \\
// - .vel --> Refers to the current velocity of the projectile. \\
// Users can access: .vel.x/.vel.y/.vel.z \\
// - .start --> Refers to the starting coordinates of the proj. \\
// Users can access: .start.x/.start.y/.start.z \\
// - .target --> Refers to the target coordinates of the proj. \\
// Users can access: .target.x/.target.y/.target.z \\
// \\
// Methods available: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - .create() --> Creates the projectile at an XYZ \\
// location, facing a certain angle. \\
// - .project() --> Starts the projectile moving to a \\
// XYZ location at a certain speed. \\
// - .modifyTargeting() --> Adjust the target coordinates of a \\
// projectile. Changing its direction. \\
// - .isUnitInHeightRange() --> Checks to see if a unit is within \\
// vertical distance of a projectile. \\
// - .terminate() --> Terminates a projectile. Will fire \\
// .onFinish() method. \\
// \\
//-----------------------------------------------------------------------\\
struct Projectile extends ProjInterface
unit caster = null
real timedLife = -1.00
real unitCollision = DEFAULT_UNIT_COLL
real projCollision = DEFAULT_PROJ_COLL
boolean pauseProj = false
boolean collideable = false
readonly unit proj = null
readonly real theta = 0.00
readonly real phi = 0.00
readonly vector pos = 0
readonly vector vel = 0
readonly vector start = 0
readonly vector target = 0
private player own = null
private effect sfx = null
private string path = ""
private real speed = 0.00
private real oriSpeed = 0.00
private real oldSpeed = 0.00
private vector grav = 0
private boolean stop = false
private group dmged = null
private static boolexpr tempFilt = null
private static group tempGroup = null
private static thistype tempData = 0
private static vector tempVect = 0
private method destroy takes nothing returns nothing
call this.onFinish()
set ProjData[this.proj].data = 0
call this.pos.destroy()
call this.vel.destroy()
call this.grav.destroy()
call this.start.destroy()
call this.target.destroy()
if this.sfx != null then
call DestroyEffect(this.sfx)
endif
call RemoveUnit(this.proj)
// call GroupClear(this.dmged)
call Group.release(this.dmged)
set this.caster = null
set this.proj = null
set this.sfx = null
set this.path = ""
call this.deallocate()
endmethod
private static method unitFilter takes nothing returns boolean
local thistype this = thistype.tempData
local unit filt = GetFilterUnit()
// if IsUnitInRangeXY(filt,this.pos.x,this.pos.y,this.unitCollision) then
if /*UnitAlive(filt) == true and*/ IsUnitInGroup(filt,this.dmged) == false then
call BJDebugMsg("WTF!")
call GroupAddUnit(this.dmged,filt)
call this.onUnitImpact(filt)
endif
// endif
set filt = null
return false
endmethod
private method projCollisions takes nothing returns nothing
local thistype that = thistype(0).next
loop
exitwhen that == 0
if this != that and that.collideable then
if that.pos.isInSphere(this.pos,this.projCollision) and this.pos.isInSphere(that.pos,that.projCollision) then
call this.onProjImpact()
call that.onProjImpact()
set FirstProjData = this
set SecondProjData = that
call CollisionEvent.fire()
endif
endif
set that = that.next
endloop
endmethod
private method periodic takes nothing returns nothing
if this.stop then
call this.stopPeriodic()
call this.destroy()
endif
if this.collideable then
call this.projCollisions()
endif
call this.onLoop()
if not this.pauseProj then
if this.timedLife > -1.0 then
set this.timedLife = this.timedLife - T32_PERIOD
if this.timedLife <= 0.00 then
set this.stop = true
endif
endif
call this.vel.add(this.grav)
call this.pos.add(this.vel)
call this.vel.add(this.grav)
call thistype.tempVect.getTerrainPoint(this.pos.x,this.pos.y)
call SetUnitX(this.proj,this.pos.x)
call SetUnitY(this.proj,this.pos.y)
call SetUnitFlyHeight(this.proj,this.pos.z - thistype.tempVect.z,0.00)
set thistype.tempData = this
call GroupEnumUnitsInRange(thistype.tempGroup,this.pos.x,this.pos.y,this.unitCollision/* + MAX_UNIT_COLLISION*/,thistype.tempFilt)
if thistype.tempVect.z >= this.pos.z then
call this.onLandImpact()
set this.stop = true
endif
endif
endmethod
implement T32x
implement ProjSpeedMod
implement ProjOperatorMod
method terminate takes nothing returns nothing
set this.effectPath = ""
set this.stop = true
endmethod
method isUnitInHeightRange takes unit whichUnit returns boolean
return SquareRoot((this.pos.z - GetUnitFlyHeight(whichUnit)) * (this.pos.z - GetUnitFlyHeight(whichUnit))) <= this.unitCollision
endmethod
method modifyTargeting takes real xPos, real yPos, real zPos returns nothing
set this.theta = Atan2((yPos - this.pos.y),(xPos - this.pos.x))
set this.phi = Atan2(SquareRoot((xPos - this.pos.x) * (xPos - this.pos.x) + (yPos - this.pos.y) * (yPos - this.pos.y)),(zPos - this.pos.z))
set this.vel.x = Sin(this.phi) * Cos(this.theta) * this.speed
set this.vel.y = Sin(this.phi) * Sin(this.theta) * this.speed
set this.vel.z = Cos(this.phi) * this.speed
set this.target.x = xPos
set this.target.y = yPos
set this.target.z = zPos
endmethod
method project takes real xPos, real yPos, real zPos, real speed returns nothing
set this.theta = Atan2((yPos - this.pos.y),(xPos - this.pos.x))
set this.phi = Atan2(SquareRoot((xPos - this.pos.x) * (xPos - this.pos.x) + (yPos - this.pos.y) * (yPos - this.pos.y)),(zPos - this.pos.z))
set this.speed = speed * T32_PERIOD
set this.oriSpeed = this.speed
set this.vel = vector.create(Sin(this.phi) * Cos(this.theta) * this.speed,Sin(this.phi) * Sin(this.theta) * this.speed,Cos(this.phi) * this.speed)
set this.target = vector.create(xPos,yPos,zPos)
call this.onStart()
call this.startPeriodic()
endmethod
static method create takes real xPos, real yPos, real zPos, real facing returns thistype
local thistype this = thistype.allocate()
set this.proj = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),PROJ_DUMMY_ID,xPos,yPos,facing * bj_RADTODEG)
set this.grav = vector.create(0.00,0.00,(DEFAULT_GRAVITY * T32_PERIOD * T32_PERIOD) / 2.00)
set this.pos = vector.create(xPos,yPos,zPos)
set this.start = vector.create(xPos,yPos,zPos)
call SetUnitX(this.proj,xPos)
call SetUnitY(this.proj,yPos)
call thistype.tempVect.getTerrainPoint(xPos,yPos)
call SetUnitFlyHeight(this.proj,zPos - thistype.tempVect.z,0.00)
// if this.dmged == null then
set this.dmged = Group.get()
// endif
set ProjData[this.proj].data = this
return this
endmethod
private static method onInit takes nothing returns nothing
set thistype.tempVect = vector.create(0.00,0.00,0.00)
set thistype.tempFilt = Filter(function thistype.unitFilter)
set thistype.tempGroup = CreateGroup()
endmethod
endstruct
function GroupEnumProjectilesInRange takes group whichGroup, real x, real y, real z, real radius returns nothing
local Projectile p = Projectile(0).next
loop
exitwhen p == 0
if p.pos.isInSphereEx(x,y,z,radius) then
call GroupAddUnit(whichGroup,p.proj)
endif
set p = p.next
endloop
endfunction
function RegisterProjectileCollisionEvent takes trigger whichTrigger returns EventReg
return CollisionEvent.register(whichTrigger)
endfunction
function GetFirstCollisionProj takes nothing returns Projectile
return FirstProjData
endfunction
function GetSecondCollisionProj takes nothing returns Projectile
return SecondProjData
endfunction
private function Init takes nothing returns nothing
set CollisionEvent = Event.create()
endfunction
endlibrary
The trouble area:
JASS:
...
private static method unitFilter takes nothing returns boolean
local thistype this = thistype.tempData
local unit filt = GetFilterUnit()
// if IsUnitInRangeXY(filt,this.pos.x,this.pos.y,this.unitCollision) then
if /*UnitAlive(filt) == true and*/ IsUnitInGroup(filt,this.dmged) == false then
call BJDebugMsg("WTF!")
call GroupAddUnit(this.dmged,filt)
call this.onUnitImpact(filt)
endif
// endif
set filt = null
return false
endmethod
...
The [ljass]"WTF!"[/ljass] debug message runs twice for the same unit.
Can anyone spot the problem?
In the test map, just pick one of the blood mages, and target another unit with Hurl Boulder. You should see three debug messages. One for the caster and two for the target (WHICH SHOULDN'T HAPPEN!).
EDIT:
Sometimes the debug message will show up multiple (two) times for a unit and sometimes it wont show up at all, and rarely it will just work as it should. This is really weird, I am beginning to think I missed something very obvious.
Meh, figured it out, don't worry. It was something stupid after all.