- Joined
- Jan 11, 2009
- Messages
- 3,414
Hello guys! I posted a thread earlier about how to approach making your own custom missile system, and got some nice responses. Now i am almost done making it, but have run into some awkward bugs.
First of all, i am trying to create missiles for ships which launch from an offset point. To calculate this offset point, i use relative x, y, and z coordinates, and rotate them using a simple 2d rotation matrix to align to the units rotation. The problem is, that GetLocationZ() returns the water height when used on water. This means my values become totally faulty when launching ship missiles. Is there any way to surpass this, or do i just add an if-statement which replaces the "GetTerrainZ()" for the constant value of the water height, if the point is water-pathable?
Second, my way of detecting when a unit fires was to set the attack damage multipiler to 0 for the attack type (in map constants), make the attack type instant, and detect when a unit takes damage. I was going to use the condition "if GetEventDamage() == 0" to detect a custom attack, but when i print the damage taken, the game says it is 1.00 (even though the target takes no damage). I figured this might come from the roll calculation, but if i set the roll to 0, the unit loses the attack altogether. How do i detect if the attack is custom?
Apart from this, i somehow feel like my attack system is inconsistent. Some times, the missiles don't show up until they have reached a certain height. Also, when some units are in range of two enemies at once, they fire one missile at each of them, at the same time! I checked to see if "number of targets" was set to something else than 1 in the object editor - it was not. I am posting the libraries below, if you want to have a look at them. Any feedback is very welcome.
First of all, i am trying to create missiles for ships which launch from an offset point. To calculate this offset point, i use relative x, y, and z coordinates, and rotate them using a simple 2d rotation matrix to align to the units rotation. The problem is, that GetLocationZ() returns the water height when used on water. This means my values become totally faulty when launching ship missiles. Is there any way to surpass this, or do i just add an if-statement which replaces the "GetTerrainZ()" for the constant value of the water height, if the point is water-pathable?
Second, my way of detecting when a unit fires was to set the attack damage multipiler to 0 for the attack type (in map constants), make the attack type instant, and detect when a unit takes damage. I was going to use the condition "if GetEventDamage() == 0" to detect a custom attack, but when i print the damage taken, the game says it is 1.00 (even though the target takes no damage). I figured this might come from the roll calculation, but if i set the roll to 0, the unit loses the attack altogether. How do i detect if the attack is custom?
Apart from this, i somehow feel like my attack system is inconsistent. Some times, the missiles don't show up until they have reached a certain height. Also, when some units are in range of two enemies at once, they fire one missile at each of them, at the same time! I checked to see if "number of targets" was set to something else than 1 in the object editor - it was not. I am posting the libraries below, if you want to have a look at them. Any feedback is very welcome.
JASS:
library mMissile initializer Init requires mEnvironment, mHashtable
globals
private constant real INTERVAL = 0.034
private constant real AIR_FRICTION = 0.98
private constant real GRAVITY = 4*INTERVAL
private constant integer SYSTEM_ID = StringHash("mMissile")
missile_class Missile_Nuke
missile_class Missile_Aegis
missile_class Missile_Trident
missile_class Missile_MLRS
missile_class Missile_ICBM
endglobals
struct missile_class
string path
integer unitid = 0
real muzzlevel = 10
real accel = 0.6
real tspeed = 0.2
real blastrad = 300
real damage = 50
real trackdelay = 0
real lifespan = 10
boolean homing = false
static method create takes string path, integer unitid, real muzzlevel, real accel, real tspeed, real blastrad, real damage, real trackdelay, real lifespan, boolean homing returns thistype
local thistype this = thistype.allocate()
set .path = path
set .unitid = unitid
set .muzzlevel = muzzlevel
set .accel = accel
set .tspeed = tspeed
set .blastrad = blastrad
set .damage = damage
set .trackdelay = trackdelay
set .lifespan = lifespan
set .homing = homing
return this
endmethod
endstruct
struct missile
implement List
missile_class c
unit u
unit target
effect e
quaternion dir
real age = 0
real x = 0
real y = 0
real z = 0
real vx = 0
real vy = 0
real vz = 0
real tx = 0
real ty = 0
static timer t
method operator owner takes nothing returns player
return GetOwningPlayer(.u)
endmethod
method kill takes nothing returns nothing
if .count == 1 then
call PauseTimer(.t)
endif
call DestroyEffect(.e)
call KillUnit(.u)
call .listRemove()
call .dir.destroy()
call .destroy()
call BJDebugMsg("Missile destroyed!")
endmethod
method explode takes nothing returns nothing
if .c == Missile_Nuke then
call explosion.create("NuclearExplosion.mdl", .x, .y, .z+90, 100, .c.blastrad*Pow(1.1, GetPlayerTechCount(.owner, 'Rabm', true)), .c.damage*Pow(1.25, GetPlayerTechCount(.owner, 'Rabm', true)), 0.31, 100, 0.65, true)
elseif .c == Missile_ICBM then
call explosion.create("CruiseMissile.mdl", .x, .y, .z+90, 100, .c.blastrad, .c.damage, 0.25, 100, 0.7, true)
else
call explosion.createSmall("NewGroundEX.mdl", .x, .y, .z+45, .c.blastrad, .c.damage, 0.65, GetOwningPlayer(.u))
endif
call .kill()
endmethod
method intercept takes nothing returns nothing
local vector heading = vector.create(1,0,0).rotateByQuaternion(.dir)
local vector course = vector.create(.tx-.x, .ty-.y, getTerrainZ(.tx, .ty)-.z).normalize()
local vector axis = vector.cross(heading, course)
local real angle = vector.angleBetween(heading, course)
local quaternion rot
if angle > .c.tspeed then
set angle = .c.tspeed
endif
set rot = quaternion.createAxisAngle(axis, angle).multiply(.dir)
call .dir.setTo(rot).normalize()
call rot.destroy()
call heading.destroy()
call course.destroy()
call axis.destroy()
endmethod
static method update takes nothing returns nothing
local thistype this = .first
local thistype temp
local real tz
local vector v
loop
exitwhen this == 0
set temp = .next
set .age = .age+INTERVAL
if .age > .c.lifespan then
call BJDebugMsg("Timed out")
call .kill()
else
if .c.homing and .age > .c.trackdelay then
call .intercept()
else
//call BJDebugMsg("No track..")
endif
if .target != null and .c.homing then
set .tx = GetUnitX(.target)
set .ty = GetUnitY(.target)
endif
if .c.accel > 0 then
set v = vector.create(.c.accel, 0, 0).rotateByQuaternion(.dir)
set .vx = (.vx + v.xv)*AIR_FRICTION
set .vy = (.vy + v.yv)*AIR_FRICTION
set .vz = (.vz + v.zv)*AIR_FRICTION
else
set v = vector.create(1, 0, 0).rotateByQuaternion(.dir)
endif
set .x = .x + .vx
set .y = .y + .vy
set .z = .z + .vz
//call BJDebugMsg("Velocity: "+R2S(SquareRoot(.vx*.vx+.vy*.vy+.vz*.vz)))
//call BJDebugMsg("Angle: "+R2S(vector.angleBetween(v, v2)*bj_RADTODEG))
set tz = getTerrainZ(.x, .y)
if .z < tz then
call .explode()
endif
call SetUnitLookAt(.u, "Bone_Head", .u, v.xv*100000, v.yv*100000, v.zv*100000)
call SetUnitPosition(.u, .x, .y)
call SetUnitFlyHeight(.u, .z-tz, 0)
call v.destroy()
endif
set this = temp
endloop
endmethod
static method create takes real x, real y, real z, real tx, real ty, unit target, real pitch, player owner, missile_class c returns thistype
local thistype this = thistype.allocate()
local real yaw = Atan2(ty-y, tx-x)
local vector v
call .listAdd()
set .c = c
set .x = x
set .y = y
set .z = z
set .tx = tx
set .ty = ty
set .target = target
set .dir = quaternion.createEuler(0, pitch*bj_DEGTORAD, yaw).normalize()
if .c.muzzlevel > 0 then
set v = vector.create(.c.muzzlevel, 0, 0).rotateByQuaternion(.dir)
set .vx = v.xv
set .vy = v.yv
set .vz = v.zv
call v.destroy()
endif
set .u = CreateUnit(owner, .c.unitid, .x, .y, 0)
call SetUnitAnimationByIndex(.u, 0)
call UnitAddAbility(.u, 'Amrf')
call UnitRemoveAbility(.u, 'Amrf')
call SetUnitFlyHeight(.u, .z-getTerrainZ(.x, .y), 0)
set .e = AddSpecialEffectTarget(.c.path, .u, "origin")
if .count == 1 then
call TimerStart(.t, INTERVAL, true, function thistype.update)
endif
return this
endmethod
static method onInit takes nothing returns nothing
set .t = CreateTimer()
endmethod
endstruct
struct weapon
missile_class ammo
real rel_x
real rel_y
real rel_z
real theta
real phi
static method create takes integer unitid, missile_class c, real rx, real ry, real rz, real theta, real phi returns thistype
local thistype this = thistype.allocate()
set .ammo = c
set .rel_x = rx
set .rel_y = ry
set .rel_z = rz
set .theta = theta
set .phi = phi
call SaveInteger(Hash, SYSTEM_ID, unitid, this)
return this
endmethod
static method get takes unit u returns thistype
if HaveSavedInteger(Hash, SYSTEM_ID, GetUnitTypeId(u)) then
return LoadInteger(Hash, SYSTEM_ID, GetUnitTypeId(u))
endif
return 0
endmethod
method fire takes unit u, real tx, real ty, unit target returns nothing
local real f = GetUnitFacing(u)
local real cos = Cos(f*bj_DEGTORAD)
local real sin = Sin(f*bj_DEGTORAD)
local real x = (.rel_x*cos - .rel_y*sin) + GetUnitX(u)
local real y = (.rel_x*sin + .rel_y*cos) + GetUnitY(u)
local real z = getTerrainZ(x, y)+.rel_z
call BJDebugMsg("X: "+R2S(x)+"Y: "+R2S(y)+"Z: "+R2S(z))
call BJDebugMsg("Firing!")
call missile.create(x, y, z, tx, ty, target, .phi, GetOwningPlayer(u), .ammo)
endmethod
endstruct
private function Init takes nothing returns nothing
// takes integer unitid, real muzzlevel, real accel, real tspeed, real blastrad, real damage, real trackdelay, real lifespan, boolean homing
set Missile_Nuke = missile_class.create("Abilities\\Weapons\\Mortar\\MortarMissile.mdl", 'dumm', 0, 8*INTERVAL, 0.06, 650, 2000, 3., 9999, true)
set Missile_Aegis = missile_class.create("Abilities\\Weapons\\RocketMissile\\RocketMissile.mdl", 'dumm', 0, 8*INTERVAL, 0.06, 290, 55, 3., 12, true)
set Missile_Trident = missile_class.create("Abilities\\Weapons\\RocketMissile\\RocketMissile.mdl", 'dumm', 0, 8*INTERVAL, 0.06, 330, 65, 3., 12, true)
set Missile_ICBM = missile_class.create("Abilities\\Weapons\\Mortar\\MortarMissile.mdl", 'dumm', 0, 8*INTERVAL, 0.06, 400, 250, 3., 9999, true)
call weapon.create('acru', Missile_Aegis, 30, 0, 50, 0, 270)
call weapon.create('tsub', Missile_Trident, -25, 0, 50, 0, 270)
endfunction
endlibrary
JASS:
library mDamage initializer Init requires UnitDex
globals
private constant integer ABIL_PREVENT_DEATH = 'hlth'
private constant integer SYSTEM_ID = StringHash("DamageEngine")
private constant real TRIGGER_REFRESH_PERIOD = 300.0
private group damageGroup = CreateGroup()
private group damageUnits = CreateGroup()
private timer damageTimer = CreateTimer()
private trigger damageTrigger = CreateTrigger()
private trigger damageTriggerOld
private real array unitNewLife
endglobals
struct damageDetection extends array
static method filter takes unit u returns boolean
return GetUnitTypeId(u) != 'dumm' and GetUnitAbilityLevel(u, 'Avul') == 0
endmethod
static method add takes unit u returns nothing
call GroupAddUnit(damageUnits, u)
call TriggerRegisterUnitEvent(damageTrigger, u, EVENT_UNIT_DAMAGED)
endmethod
static method addOnIndex takes nothing returns boolean
local unit u = GetIndexedUnit()
if thistype.filter(u) then
//Register damage to all units which pass the filter.
call TriggerRegisterUnitEvent(damageTrigger, u, EVENT_UNIT_DAMAGED)
call GroupAddUnit(damageUnits, u)
endif
set u = null
return false
endmethod
static method addEnum takes nothing returns boolean
local unit u = GetFilterUnit()
if thistype.filter(u) then
//Register damage to all units which pass the filter.
call TriggerRegisterUnitEvent(damageTrigger, u, EVENT_UNIT_DAMAGED)
call GroupAddUnit(damageUnits, u)
endif
set u = null
return false
endmethod
static method stopDamageCallback takes nothing returns nothing
local unit u
loop
set u = FirstOfGroup(damageGroup)
exitwhen u == null
if GetUnitAbilityLevel(u, ABIL_PREVENT_DEATH) > 0 then
call UnitRemoveAbility(u, ABIL_PREVENT_DEATH)
endif
call SetUnitState(u, UNIT_STATE_LIFE, unitNewLife[GetUnitId(u)])
call GroupRemoveUnit(damageGroup, u)
endloop
set u = null
endmethod
static method stopDamage takes unit u, real newlife, boolean overkill returns nothing
local thistype this = thistype[GetUnitId(u)]
if u == null then
return
endif
if overkill then
call UnitAddAbility(u, ABIL_PREVENT_DEATH)
endif
call SetUnitState(u, UNIT_STATE_LIFE, 10000)
call GroupAddUnit(damageGroup, u)
set unitNewLife[GetUnitId(u)] = newlife
call TimerStart(damageTimer, 0, false, function thistype.stopDamageCallback)
endmethod
static method onDamage takes nothing returns boolean
local unit target = GetTriggerUnit()
local unit source = GetEventDamageSource()
local real damage = GetEventDamage()
local integer id = GetUnitTypeId(source)
local weapon wpn
local producer p
if id == 'acru' or id == 'tsub' then
set wpn = weapon.get(source)
if wpn != 0 then
call wpn.fire(source, GetUnitX(target), GetUnitY(target), target)
endif
endif
set p = producer.get(target)
if p != 0 then
if p.city and GetWidgetLife(target) - damage < GetUnitState(target, UNIT_STATE_MAX_LIFE)*0.2 then
call stopDamage(target, GetUnitState(target, UNIT_STATE_MAX_LIFE), damage > GetWidgetLife(target))
call p.changeOwner(GetOwningPlayer(source))
endif
endif
set target = null
set source = null
return false
endmethod
static method enumTriggerUnits takes nothing returns nothing
call TriggerRegisterUnitEvent(damageTrigger, GetEnumUnit(), EVENT_UNIT_DAMAGED)
endmethod
static method refresh takes nothing returns nothing
if damageTriggerOld != null then
call DestroyTrigger(damageTriggerOld)
endif
call DisableTrigger(damageTrigger)
set damageTriggerOld = damageTrigger
set damageTrigger = CreateTrigger()
call TriggerAddAction(damageTrigger, function thistype.onDamage)
call ForGroup(damageUnits, function thistype.enumTriggerUnits)
endmethod
endstruct
private function Init takes nothing returns nothing
call RegisterUnitIndexEvent(Filter(function damageDetection.addOnIndex), EVENT_UNIT_INDEX)
call TriggerAddAction(damageTrigger, function damageDetection.onDamage)
call TimerStart(CreateTimer(), TRIGGER_REFRESH_PERIOD, true, function damageDetection.refresh)
endfunction
endlibrary