• 🏆 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!

Shielding system for Missile war game

Status
Not open for further replies.
Level 8
Joined
Nov 29, 2007
Messages
371
Hello all, thanks for taking the time to read this post.

Myself and a friend are making a map but we have come up with three problems in the base stages of said map:
  1. How do we make a working shield [like in Ship War by Exilus5]
  2. How do we make a decent rocket system [FIXED, PM if you want details]
  3. And third, I'm awful at JASS, so [for the moment at least] am limited to UI

Here is a link to Exilus5's map: link

This is what we have so far for a missile system:

[EDIT] Missile system has been fixed and replaced

We don't have even a semi-functional shield system however.
A lot of the rest of the game is already planned out and *should* be easy to implement. These two things are, however, the two most important parts and without them the game is essentially unplayable.

Thank you in advance,
DrazharLn

P.S. Playing a few games of Exilus5' excellent Ship War will put what we are trying to do into perspective.

EDIT2: I've finally got round to fixing the code aznricepuff sent me so here it is:
JASS:
library lib

//Configuration - do NOT change the names of the variables, only the values
globals
    real missiletimeout = 0.04  //how often the function that moves all missiles executes
    real shieldtimeout = 0.04  //how often the function that updates all shields' life points executes

    real missilearrivaldist = 20.00  //How far away a missile has to be from its target before it detonates
endglobals
//End Configuration

//Distance Functions - made public if for any reason you see fit to use them yourself

public function DistanceBetweenCoords takes real x1, real y1, real x2, real y2 returns real
    return SquareRoot( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) )
endfunction

public function AngleBetweenCoords takes real x1, real y1, real x2, real y2 returns real
    local real angle = ( bj_RADTODEG * Atan2( y2 - y1, x2 - x1 ) )
    if ( angle < 0.00 ) then
        set angle = angle + 360.00
    endif
    return angle
endfunction

public function PolarProjectionX takes real x, real distance, real angle returns real
    return ( x + distance * Cos( angle * bj_DEGTORAD ) )
endfunction

public function PolarProjectionY takes real y, real distance, real angle returns real
    return ( y + distance * Sin( angle * bj_DEGTORAD ) )
endfunction

//End Distance Functions

//Private Data

globals
    private timer missiletimer = CreateTimer()
    private missiledata array missilearray
    private integer missiletotal = 0

    private timer shieldtimer = CreateTimer()
    private shielddata array shieldarray
    private integer shieldtotal = 0

    private group missilegroup = CreateGroup()
    private group shieldgroup = CreateGroup()

    private group array teamshieldgroup
    private integer teamtotal = 0

    private group tempgroup = CreateGroup()
    private real tempdamage
    private integer temptotal
endglobals

struct missiledata
    unit missile
    player owner
    unit target
    real targetx
    real targety
    real x
    real y
    real launchz = 0.00
    real speed = 0.00
    real arc = 0.00
    real collision = 0.00
    real damage = 0.00
    real aoe = 0.00
    real aoedamage = 0.00
    real increment = 0.00
    boolean destroyplease = false
    method onDestroy takes nothing returns nothing
        call GroupRemoveUnit( missilegroup, .missile )
    endmethod
endstruct

struct shielddata
    unit shield
    real regen
    real regendelay
    real regencounter = 0.00
    integer teamslot
    boolean destroyable = false
    boolean noncombat = false
    boolean dead = false
    boolean destroyplease = false
    method onDestroy takes nothing returns nothing
        call GroupRemoveUnit( shieldgroup, .shield )
        call GroupRemoveUnit( teamshieldgroup[.teamslot], .shield )
    endmethod
endstruct

private function Shield_DamageChild takes nothing returns nothing
    local unit shield = GetEnumUnit()
    local shielddata data = GetUnitUserData( shield )
    local real damage = tempdamage
    local real life = GetWidgetLife( shield )
    if ( data.dead ) then
       //shield has collapsed, so do not do damage
       return
    else
        if ( life - damage < 0.405 ) then
            //shield has taken too much damage
            if ( data.destroyable ) then
                //shield is destroyable, so kill the shield
                call KillUnit( shield )
                set data.destroyplease = true
            else
                //shield is not destroyable, so we collapse the shield and set it to dead
                call SetWidgetLife( shield, 0.500 )
                call SetUnitAnimation( shield, "death" )
                set data.dead = true
                set data.regencounter = data.regendelay
            endif
        else
            //shield has not taken too much damage, so we simply subtract life
            call SetWidgetLife( shield, life - damage )
            if ( data.noncombat ) then
                //shield cannot regen HP in combat, so we set up the regen counter
                set data.regencounter = data.regendelay
            endif
        endif
    endif
    set shield = null
endfunction

private function Shield_Damage takes unit hurter, unit shield, real damage returns nothing
    local integer counter
    loop
        exitwhen ( counter >= teamtotal )
        if ( IsUnitInGroup( shield, teamshieldgroup[counter] ) ) then
            set temptotal = CountUnitsInGroup( teamshieldgroup[counter] )
            set tempdamage = damage / temptotal
            call ForGroup( teamshieldgroup[counter], function Shield_DamageChild )
        endif
        set counter = counter + 1
    endloop
endfunction

private function Shield_Update takes nothing returns nothing
    local shielddata data
    local real life
    local real maxlife
    local real regen
    local real regencounter
    local integer counter = 0
    local unit shield
    loop
        exitwhen ( counter >= shieldtotal )
        set data = shieldarray[counter]
        if ( data.destroyplease ) then
            //struct is slated for destruction, so we take care of that
            set shieldarray[counter] = shieldarray[shieldtotal - 1]
            set shieldtotal = shieldtotal - 1
            call data.destroy()
            set counter = counter - 1
        else
            set life = GetWidgetLife( data.shield )
            set maxlife = GetUnitState( data.shield, UNIT_STATE_MAX_LIFE )
            if ( regencounter <= 0.00 ) then
                if ( data.dead ) then
                    //shield was previously collapsed, so we "uncollapse" it
                    set data.dead = false
                    call SetUnitAnimation( shield, "birth" )
                endif
                //update shield's HP
                if ( ( life + regen * shieldtimeout ) >= maxlife ) then
                    call SetWidgetLife( data.shield, maxlife )
                else
                    call SetWidgetLife( data.shield, life + regen * shieldtimeout )
                endif
            else
                //shield is waiting to regenerate, countdown the counter
                set regencounter = regencounter - shieldtimeout
            endif
        endif
        set counter = counter + 1
    endloop
    if ( shieldtotal == 0 ) then
        call PauseTimer( shieldtimer )
    endif
endfunction

private function Missile_Increment takes nothing returns nothing
    local missiledata data
    local missiledata data2
    local unit u
    local real targetx
    local real targety
    local real angle
    local real disttotarget
    local real timeleft
    local real z1
    local real z2
    local real arc
    local integer counter = 0
    local unit target
    loop
        exitwhen ( counter >= missiletotal )
        set data = missilearray[counter]
        if ( data.destroyplease ) then
            //struct is slated for destruction, so we take care of that
            set missilearray[counter] = missilearray[missiletotal - 1]
            set missiletotal = missiletotal - 1
            call data.destroy()
            set counter = counter - 1
        else
            if ( GetWidgetLife( data.target ) < 0.405 ) then
                //target is dead, set variable to null; now target coordinates will be locked onto the last coordinates of the target when it was still alive
                set data.target = null
            else
                //target is alive, update target coordinates
                set data.targetx = GetUnitX( target )
                set data.targety = GetUnitY( target )
            endif
            set targetx = data.targetx
            set targety = data.targety
            set angle = AngleBetweenCoords( data.x, data.y, targetx, targety )
            set disttotarget = DistanceBetweenCoords( data.x, data.y, targetx, targety )
            set timeleft = disttotarget / data.speed
            set z1 = GetUnitFlyHeight( data.missile )
            set z2 = GetUnitFlyHeight( data.target )
            if ( disttotarget <= missilearrivaldist ) then
                //missile has reached target, detonate missile and deal damage
                call KillUnit( data.missile )
                set data.destroyplease = true
                if ( IsUnitInGroup( data.target, shieldgroup ) ) then
                    //target is a shield, so we damage the shield
                    call Shield_Damage( data.missile, data.target, data.damage )
                    if ( data.aoe != 0.00 ) then
                       call UnitDamagePoint( data.missile, 0.00, data.aoe, targetx, targety, data.aoedamage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
                    endif
                else
                    //target is a normal unit, we deal damage
                    if ( data.aoe == 0.00 ) then
                        call UnitDamageTarget( data.missile, data.target, data.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
                    else
                        call UnitDamageTarget( data.missile, data.target, data.damage - data.aoedamage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
                        call UnitDamagePoint( data.missile, 0.00, data.aoe, targetx, targety, data.aoedamage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
                    endif
                endif
            else
                if ( data.collision != 0.00 ) then
                    //missile has collision, check to see if missile has "collided"
                    call GroupEnumUnitsInRange( tempgroup, data.x, data.y, data.collision, null )
                    loop
                        set u = FirstOfGroup( tempgroup )
                        exitwhen ( u == null )
                        if ( IsUnitEnemy( u, data.owner ) ) then
                            if ( IsUnitInGroup( u, missilegroup ) and RAbsBJ( GetUnitFlyHeight( u ) - z1 ) <= data.collision ) then
                                //missile has collided, destroy two missiles
                                call KillUnit( data.missile )
                                set data.destroyplease = true
                                call KillUnit( u )
                                set data2 = GetUnitUserData( u )
                                set data2.destroyplease = true
                            endif
                        endif
                    endloop
                    call GroupClear( tempgroup )
                else
                    if ( GetWidgetLife( data.missile ) >= 0.405 ) then
                        //missile is still alive, move missile toward target
                        call SetUnitFacing( data.missile, angle )
                        call SetUnitPosition( data.missile, PolarProjectionX( data.x, data.speed * missiletimeout, angle ), PolarProjectionY( data.y, data.speed * missiletimeout, angle ) )
                        //adjust missile flying height
                        call SetUnitFlyHeight( data.missile, z1 + ( ( z2 - z1 + 0.5 * arc * timeleft * timeleft ) / timeleft ) * missiletimeout, 0 )
                    endif
                endif
            endif
        endif
        set counter = counter + 1
    endloop
    if ( missiletotal == 0 ) then
        call PauseTimer( missiletimer )
    endif
endfunction

//End Private Data

//Control Functions

function Missile_LaunchTarget takes player owner, integer unitid, real launchx, real launchy, real launchz, real speed, real arc, real collision, unit target, real damage, real aoe, real aoedamage returns unit
    local missiledata data = missiledata.create()
    local real angle = AngleBetweenCoords( launchx, launchy, GetUnitX( target ), GetUnitY( target ) )
    if ( IsUnitEnemy( target, owner ) ) then
        set data = missiledata.create()
        set data.missile = CreateUnit( owner, unitid, launchx, launchy, angle )
        if ( launchz != 0.00 ) then
            call SetUnitFlyHeight( data.missile, launchz, 0 )
        endif
        set data.owner = owner
        set data.target = target
        set data.x = launchx
        set data.y = launchy
        set data.launchz = launchz
        set data.increment = speed
        set data.arc = arc * 8000.0
        set data.collision = collision
        set data.damage = damage
        set data.aoe = aoe
        set data.aoedamage = aoedamage
        call SetUnitUserData( data.missile, data )
        if ( missiletotal == 0 ) then
            call TimerStart( missiletimer, missiletimeout, true, function Missile_Increment )
        endif
        set missilearray[missiletotal] = data
        set missiletotal = missiletotal + 1
        return data.missile
    endif
    return null
endfunction

function Missile_Modify takes unit missile, real collision, real damage, real aoe, real aoedamage returns boolean
    local missiledata data = GetUnitUserData( missile )
    if ( IsUnitInGroup( missile, missilegroup ) == false ) then
        return false
    endif
    set data.collision = collision
    set data.damage = damage
    set data.aoe = aoe
    set data.aoedamage = aoedamage
    return true
endfunction

function Missile_Destroy takes unit missile returns boolean
    local missiledata data = GetUnitUserData( missile )
    if ( IsUnitInGroup( missile, missilegroup ) == false ) then
        return false
    endif
    call KillUnit( missile )
    set data.destroyplease = true
    return ( GetWidgetLife( missile ) < 0.405 )
endfunction

function Shield_Create takes player owner, integer unitid, integer teamslot, real x, real y, real facing, real regen, real regendelay, boolean noncombat, boolean destroyable returns unit
    local shielddata data
    if ( teamshieldgroup[teamslot] == null ) then
        return null
    endif
    set data = shielddata.create()
    set data.shield = CreateUnit( owner, unitid, x, y, facing )
    set data.regen = regen
    set data.regendelay = regendelay
    set data.teamslot = teamslot
    set data.noncombat = noncombat
    set data.destroyable = destroyable
    call SetUnitUserData( data.shield, data )
    call GroupAddUnit( shieldgroup, data.shield )
    call GroupAddUnit( teamshieldgroup[teamslot], data.shield )
    if ( shieldtotal == 0 ) then
        call TimerStart( shieldtimer, shieldtimeout, true, function Shield_Update )
    endif
    set shieldarray[shieldtotal] = data
    set shieldtotal = shieldtotal + 1
    return data.shield
endfunction

function Shield_Modify takes unit shield, real regen, real regendelay, boolean noncombat, boolean destroyable returns boolean
    local shielddata data = GetUnitUserData( shield )
    if ( IsUnitInGroup( shield, shieldgroup ) == false ) then
        return false
    endif
    set data.regen = regen
    set data.regendelay = regendelay
    set data.noncombat = noncombat
    set data.destroyable = destroyable
    return true
endfunction

function Shield_ModifyState takes unit shield, boolean dead returns boolean
    local shielddata data = GetUnitUserData( shield )
    if ( IsUnitInGroup( shield, shieldgroup ) == false ) then
        return false
    endif
    set data.dead = dead
    return ( data.dead == dead )
endfunction

function Shield_TeamCreate takes nothing returns integer
    set teamshieldgroup[teamtotal] = CreateGroup()
    set teamtotal = teamtotal + 1
    return ( teamtotal - 1 )
endfunction

function Shield_TeamDestroy takes integer teamslot returns boolean
    if ( teamshieldgroup[teamslot] == null ) then
        return false
    endif
    call DestroyGroup( teamshieldgroup[teamslot] )
    set teamshieldgroup[teamslot] = teamshieldgroup[teamtotal - 1]
    set teamshieldgroup[teamtotal - 1] = null
    set teamtotal = teamtotal - 1
    return true
endfunction

//End Control Functions

endlibrary


EDIT3: You also need vJASS to run this code.
 
Last edited:
Level 11
Joined
Feb 22, 2006
Messages
752
Hmmm...shields have become conspicuously popular in the past few days...

Basically, the idea is to create a trigger detecting damage (not attack) for a certain unit. If the unit is damaged, modify the damage by adding the damage back to its life so that it looks like it never took the damage. Then you subtract that damage from the shield's life using variables and such. When the shield "collapses" destroy the trigger. When the unit dies also destroy the trigger to avoid leaking triggers.

JASS:
struct shielddata
    trigger t = CreateTrigger()
    triggeraction taction
    real shieldmaxlife
    real shieldlife
    method onDestroy takes nothing returns nothing
        call TriggerRemoveAction( .t, .taction )
        call DestroyTrigger( .t )
    endmethod
endstruct
globals
    group shieldgroup = CreateGroup()
endglobals
function Shield_Damage takes nothing returns nothing
    local unit target = GetTriggerUnit()
    local shielddata data = GetUnitUserData( target )
    local real damage = GetEventDamage()
    local real life = GetWidgetLife( target )
    local real maxlife = GetUnitState( target, UNIT_STATE_MAX_LIFE )
    local real lifeback
    if ( life < 0.405 ) then
        //unit is dead, we destroy shield and trigger and don't worry about it anymore
        call data.destroy()
        call GroupRemoveUnit( shieldgroup, target )
        return
    endif
    if ( data.shieldlife - damage <= 0.00 ) then
        //shield is dead because it took too much damage, we manipulate life given back to match shield's remaining life before it "died"
        set lifeback = data.shieldlife
        call data.destroy()
        call GroupRemoveUnit( shieldgroup, target )
    else
        //shield is still alive, so all damage is blocked
        set lifeback = damage
    endif
    if ( life + lifeback >= maxlife ) then
        call SetWidgetLife( target, maxlife )
    else
        call SetWidgetLife( target, life + lifeback )
    endif
    set target = null
endfunction

//call the function Shield_Create to create a shield on a unit. Target is the target unit, shieldmaxlife is the maximum life of the shield,
//shieldlife is the starting life of the shield, and addbonus is to check whether you want the shieldmaxlife and shieldlife to stack
//if there was a previous shield on the unit
//for example, to create a shield on unit udg_Test with maximum life of 1000 and starting life of 1000 that does not stack stats,
//you would include the script: call Shield_Create( udg_Test, 1000.00, 1000.00, false )

function Shield_Create takes unit target, real shieldmaxlife, real shieldlife, boolean addbonus returns nothing
    local shielddata data
    if ( IsUnitInGroup( target, shieldgroup ) ) then
        //unit already has shield
        set data = GetUnitUserData( target )
        if ( addbonus ) then
            //we add to the shield's life and maximum life
            set data.shieldmaxlife = data.shieldmaxlife + shieldmaxlife
            set data.shieldlife = data.shieldlife + shieldlife
        else
            //we replace the shield's old life and maximum life with new stats
            set data.shieldmaxlife = shieldmaxlife
            set data.shieldlife = shieldlife
        endif
    else
        //unit doesn't have shield, so we create one
        set data = shielddata.create()
        set data.shieldmaxlife = shieldmaxlife
        set data.shieldlife = shieldlife
        call TriggerRegisterUnitEvent( data.t, target, EVENT_UNIT_DAMAGED )
        call TriggerRegisterUnitEvent( data.t, target, EVENT_UNIT_DEATH )
        set data.taction = TriggerAddAction( data.t, function Shield_Damage )
        call SetUnitUserData( target, data )
        call GroupAddUnit( shieldgroup, target )
    endif
endfunction

//Use Shield_Modify to modify an existing shield's maximum life and life. The arguments are the same as the ones for Shield_Create.
//For example, if you wanted to increase a shield's life by 100 but not its maxlife, then you would include the script:
//call Shield_Modify( udg_Test, 0.00, 100.00, true )
//
//Shield_Modify will return a boolean: false if the unit does not in fact have a shield, true if it was able to modify the stats

function Shield_Modify takes unit target, real shieldmaxlife, real shield life, boolean addbonus returns boolean
    local shielddata data = GetUnitUserData( target )
    if ( IsUnitInGroup( target, shieldgroup ) == false ) then
        //unit does not have shield...return false
        return false
    endif
    if ( addbonus ) then
        //we add to the shield's life and maximum life
        set data.shieldmaxlife = data.shieldmaxlife + shieldmaxlife
        set data.shieldlife = data.shieldlife + shieldlife
    else
        //we replace the shield's old life and maximum life with new stats
        set data.shieldmaxlife = shieldmaxlife
        set data.shieldlife = shieldlife
    endif
    return true
endfunction


You will need vJass, and easiest way to get it is from JNGP:

http://www.wc3campaigns.net/showthread.php?t=90999

Make sure you DISABLE WE syntax checker in the Grimoire menu in WE when you start up newgen WE.

This is just a skeleton script. It doesn't have any special effects and it assumes shields are permanent until destroyed. If you need something else (like special effects or timed shields) or want me to change how something works, let me know.

Caveats:

You cannot use units' custom data if you are going to use this script. This is because I really want to exclude the use of gamecache, but if you absolutely need to use custom data I can change the script so that it doesn't require custom data.

Let's say a unit has 20 HP left, it has a shield set up to block 100 dmg and something attacks it, dealing 50 dmg. The unit WILL die instead of the shield taking the damage and the unit living because the only way to get the damage done and update the shield's HP is for the unit to be damaged first, and if that damage results in the unit's death, then...there's nothing I know that can fix it.
 
Last edited:
Level 8
Joined
Nov 29, 2007
Messages
371
Firstoff, thanks for the replies, especially aznricepuff for putting a clear amount of time and effort in.

However, it's not quite what I was after, but it will probably help somewhere...
Downloading Ship War [by Exilus] will make understanding this problem much easier.
If you don't want to do that here's a description: There are two ships in the game, they don't move, they're made of terrain. You start on one of the aforementioned ships and build missile firing towers, shield generators etc. Each ship has two shields: an inner with less hp but a faster regen time and an outer with more hp but a slower regen.
Exilus made his shields out of lines of units with the wisp graphics that shared damage between them, when a rocket hit one the damage is instantly transferred to all other wisp-shield units of the same type. Making shield generators increased the max health of your shield. When the shield died it would regenerate after an amount of time (base time/number of generators]*10) or something similar.
If that doesn't make sense please play the game by exilus [linked in first post]

Thanks for all the replies,
DrazharLn
 
Level 2
Joined
Jan 23, 2008
Messages
15
To (hopefully) clarify what my colleague is saying a little: We are hoping for a system whereby a 'wall', which blocks a certian amount of damage from incoming projectiles (which is displayed on a scoreboard, for ease of access), and regenerates this ability to absorb damage at a rate determined by the number of buildings of a certian type on your 'ship' (area of the map). The projectiles, at least in Exilius's map, were actual units, which detonated upon contact with either a shield or a building.
We have another system, for a secondary defensive measure (armour) already in place, but are looking for one which spreads damage evenly between every unit in the wall, rather than having individual units or small groups of units take the explosion damage.
 
Level 11
Joined
Feb 22, 2006
Messages
752
I don't really have time to go play the map you guys were talking about, but from what you've described, I'll give it another shot.

So, I think it will be easier to link the shield system and the missile/projectile system since it seems that only the custom missiles will be able to damage the shield anyway.



JASS:
library MissileShield_System

//Configuration - do NOT change the names of the variables, only the values
globals
    real missiletimeout = 0.04  //how often the function that moves all missiles executes
    real shieldtimeout = 0.04  //how often the function that updates all shields' life points executes

    real missilearrivaldist = 20.00  //How far away a missile has to be from its target before it detonates
endglobals
//End Configuration

//Distance Functions - made public if for any reason you see fit to use them yourself

public function DistanceBetweenCoords takes real x1, real y1, real x2, real y2 returns real
    return SquareRoot( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) )
endfunction

public function AngleBetweenCoords takes real x1, real y1, real x2, real y2 returns real
    local real angle = ( bj_RADTODEG * Atan2( y2 - y1, x2 - x1 ) )
    if ( angle < 0.00 ) then
        set angle = angle + 360.00
    endif
    return angle
endfunction

public function PolarProjectionX takes real x, real distance, real angle returns real
    return ( x + distance * Cos( angle * bj_DEGTORAD ) )
endfunction

public function PolarProjectionY takes real y, real distance, real angle returns real
    return ( y + distance * Sin( angle * bj_DEGTORAD ) )
endfunction

//End Distance Functions

//Private Data

globals
    private timer missiletimer = CreateTimer()
    private missiledata array missilearray
    private integer missiletotal = 0

    private timer shieldtimer = CreateTimer()
    private shielddata array shieldarray
    private integer shieldtotal = 0

    private group missilegroup = CreateGroup()
    private group shieldgroup = CreateGroup()

    private group array teamshieldgroup
    private integer teamtotal = 0

    private group tempgroup = CreateGroup()
    private real tempdamage
    private integer temptotal
endglobals

struct missiledata
    unit missile
    player owner
    unit target
    real targetx
    real targety
    real x
    real y
    real launchz = 0.00
    real speed = 0.00
    real arc = 0.00
    real collision = 0.00
    real damage = 0.00
    real aoe = 0.00
    real aoedamage = 0.00
    boolean destroyplease = false
    method onDestroy takes nothing returns nothing
        call GroupRemoveUnit( missilegroup, .missile )
    endmethod
endstruct

struct shielddata
    unit shield
    real regen
    real regendelay
    real regencounter = 0.00
    integer teamslot
    boolean destroyable = false
    boolean noncombat = false
    boolean dead = false
    boolean destroyplease = false
    method onDestroy takes nothing returns nothing
        call GroupRemoveUnit( shieldgroup, .shield )
        call GroupRemoveUnit( teamshieldgroup[.teamslot], .shield )
    endmethod
endstruct

private function Shield_DamageChild takes nothing returns nothing
    local unit shield = GetEnumUnit()
    local shielddata data = GetUnitUserData( shield )
    local real damage = tempdamage
    local real life = GetWidgetLife( shield )
    if ( data.dead ) then
       //shield has collapsed, so do not do damage
       return
    else
        if ( life - damage < 0.405 ) then
            //shield has taken too much damage
            if ( data.destroyable ) then
                //shield is destroyable, so kill the shield
                call KillUnit( shield )
                set data.destroyplease = true
            else
                //shield is not destroyable, so we collapse the shield and set it to dead
                call SetWidgetLife( shield, 0.500 )
                call SetUnitAnimation( shield, "death" )
                set data.dead = true
                set data.regencounter = data.regendelay
            endif
        else
            //shield has not taken too much damage, so we simply subtract life
            call SetWidgetLife( shield, life - damage )
            if ( data.noncombat ) then
                //shield cannot regen HP in combat, so we set up the regen counter
                set data.regencounter = data.regendelay
            endif
        endif
    endif
    set shield = null
endfunction

private function Shield_Damage takes unit hurter, unit shield, real damage returns nothing
    local integer counter
    loop
        exitwhen ( counter >= teamtotal )
        if ( IsUnitInGroup( shield, teamshieldgroup[counter] ) ) then
            set temptotal = CountUnitsInGroup( teamshieldgroup[counter] )
            set tempdamage = damage / total
            call ForGroup( teamshieldgroup[counter], function Shield_DamageChild )
        endif
        set counter = counter + 1
    endloop
endfunction

private function Shield_Update takes nothing returns nothing
    local shielddata data
    local real life
    local real maxlife
    local integer counter = 0
    loop
        exitwhen ( counter >= shieldtotal )
        set data = shieldarray[counter]
        if ( data.destroyplease ) then
            //struct is slated for destruction, so we take care of that
            set shieldarray[counter] = shieldarray[shieldtotal - 1]
            set shieldtotal = shieldtotal - 1
            call data.destroy()
            set counter = counter - 1
        else
            set life = GetWidgetLife( data.shield )
            set maxlife = GetUnitState( data.shield, UNIT_STATE_MAX_LIFE )
            if ( regencounter <= 0.00 ) then
                if ( data.dead ) then
                    //shield was previously collapsed, so we "uncollapse" it
                    set data.dead = false
                    call SetUnitAnimation( shield, "birth" )
                endif
                //update shield's HP
                if ( ( life + regen * shieldtimeout ) >= maxlife ) then
                    call SetWidgetLife( data.shield, maxlife )
                else
                    call SetWidgetLife( data.shield, life + regen * shieldtimeout )
                endif
            else
                //shield is waiting to regenerate, countdown the counter
                set regencounter = regencounter - shieldtimeout
            endif
        endif
        set counter = counter + 1
    endloop
    if ( shieldtotal == 0 ) then
        call PauseTimer( shieldtimer )
    endif
endfunction

private function Missile_Increment takes nothing returns nothing
    local missiledata data
    local missiledata data2
    local unit u
    local real targetx
    local real targety
    local real angle
    local real disttotarget
    local real timeleft
    local real z1
    local real z2
    local integer counter = 0
    loop
        exitwhen ( counter >= missiletotal )
        set data = missilearray[counter]
        if ( data.destroyplease ) then
            //struct is slated for destruction, so we take care of that
            set missilearray[counter] = missilearray[missiletotal - 1]
            set missiletotal = missiletotal - 1
            call data.destroy()
            set counter = counter - 1
        else
            if ( GetWidgetLife( data.target ) < 0.405 ) then
                //target is dead, set variable to null; now target coordinates will be locked onto the last coordinates of the target when it was still alive
                set data.target = null
            else
                //target is alive, update target coordinates
                set data.targetx = GetUnitX( target )
                set data.targety = GetUnitY( target )
            endif
            set targetx = data.targetx
            set targety = data.targety
            set angle = AngleBetweenCoords( data.x, data.y, targetx, targety )
            set disttotarget = DistanceBetweenCoords( data.x, data.y, targetx, targety )
            set timeleft = disttotarget / data.speed
            set z1 = GetUnitFlyHeight( data.missile )
            set z2 = GetUnitFlyHeight( data.target )
            if ( disttotarget <= missilearrivaldist ) then
                //missile has reached target, detonate missile and deal damage
                call KillUnit( data.missile )
                set data.destroyplease = true
                if ( IsUnitInGroup( data.target, shieldgroup ) ) then
                    //target is a shield, so we damage the shield
                    call Shield_Damage( data.missile, data.target, data.damage )
                    if ( data.aoe != 0.00 ) then
                       call UnitDamagePoint( data.missile, 0.00, data.aoe, targetx, targety, data.aoedamage, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
                    endif
                else
                    //target is a normal unit, we deal damage
                    if ( data.aoe == 0.00 ) then
                        call UnitDamageTarget( data.missile, data.target, data.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
                    else
                        call UnitDamageTarget( data.missile, data.target, data.damage - data.aoedamage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
                        call UnitDamagePoint( data.missile, 0.00, data.aoe, targetx, targety, data.aoedamage, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
                    endif
                endif
            else
                if ( data.collision != 0.00 ) then
                    //missile has collision, check to see if missile has "collided"
                    call GroupEnumUnitsInRange( tempgroup, data.x, data.y, data.collision, null )
                    loop
                        set u = FirstOfGroup( tempgroup )
                        exitwhen ( u == null )
                        if ( IsUnitEnemy( u, data.owner ) ) then
                            if ( IsUnitInGroup( u, missilegroup ) and Abs( GetUnitFlyHeight( u ) - z1 ) <= data.collision ) then
                                //missile has collided, destroy two missiles
                                call KillUnit( data.missile )
                                set data.destroyplease = true
                                call KillUnit( u )
                                set data2 = GetUnitUserData( u )
                                set data2.destroyplease = true
                            endif
                        endif
                    endloop
                    call GroupClear( tempgroup )
                else
                    if ( GetWidgetLife( data.missile ) >= 0.405 ) then
                        //missile is still alive, move missile toward target
                        call SetUnitFacing( data.missile, angle )
                        call SetUnitPosition( data.missile, PolarProjectionX( data.x, data.speed * missiletimeout, angle ), PolarProjectionY( data.y, data.speed * missiletimeout, angle ) )
                        //adjust missile flying height
                        call SetUnitFlyHeight( data.missile, z1 + ( ( z2 - z1 + 0.5 * arc * timeleft * timeleft ) / timeleft ) * missiletimeout, 0 )
                    endif
                endif
            endif
        endif
        set counter = counter + 1
    endloop
    if ( missiletotal == 0 ) then
        call PauseTimer( missiletimer )
    endif
endfunction

//End Private Data

//Control Functions

function Missile_LaunchTarget takes player owner, integer unitid, real launchx, real launchy, real launchz, real speed, real arc, real collision, unit target, real damage, real aoe, real aoedamage returns unit
    local missiledata data = missiledata.create()
    local real angle = AngleBetweenCoords( launchx, launchy, GetUnitX( target ), GetUnitY( target ) )
    if ( IsUnitEnemy( target, owner ) ) then
        set data = missiledata.create()
        set data.missile = CreateUnit( owner, unitid, launchx, launchy, angle )
        if ( launchz != 0.00 then )
            call SetUnitFlyHeight( data.missile, launchz, 0 )
        endif
        set data.owner = owner
        set data.target = target
        set data.x = launchx
        set data.y = launchy
        set data.launchz = launchz
        set data.increment = speed
        set data.arc = arc * 8000.0
        set data.collision = collision
        set data.damage = damage
        set data.aoe = aoe
        set data.aoedamage = aoedamage
        call SetUnitUserData( data.missile, data )
        if ( missiletotal == 0 ) then
            call TimerStart( missiletimer, missiletimeout, true, function Missile_Increment )
        endif
        set missilearray[missiletotal] = data
        set missiletotal = missiletotal + 1
        return data.missile
    endif
    return null
endfunction

function Missile_Modify takes unit missile, real collision, real damage, real aoe, real aoedamage returns boolean
    local missiledata data = GetUnitUserData( missile )
    if ( IsUnitInGroup( missile, missilegroup ) == false ) then
        return false
    endif
    set data.collision = collision
    set data.damage = damage
    set data.aoe = aoe
    set data.aoedamage = aoedamage
    return true
endfunction

function Missile_Destroy takes unit missile returns boolean
    local missiledata data = GetUnitUserData( missile )
    if ( IsUnitInGroup( missile, missilegroup ) == false ) then
        return false
    endif
    call KillUnit( missile )
    set data.destroyplease = true
    return ( GetWidgetLife( missile ) < 0.405 )
endfunction

function Shield_Create takes player owner, integer unitid, integer teamslot, real x, real y, real facing, real regen, real regendelay, boolean noncombat, boolean destroyable returns unit
    local shielddata data
    if ( teamshieldgroup[teamslot] == null ) then
        return null
    endif
    set data = shielddata.create()
    set data.shield = CreateUnit( owner, unitid, x, y, facing )
    set data.regen = regen
    set data.regendelay = regendelay
    set data.teamslot = teamslot
    set data.noncombat = noncombat
    set data.destroyable = destroyable
    call SetUnitUserData( data.shield, data )
    call GroupAddUnit( shieldgroup, data.shield )
    call GroupAddUnit( teamshieldgroup[teamslot], data.shield )
    if ( shieldtotal == 0 ) then
        call TimerStart( shieldtimer, shieldtimeout, true, function Shield_Update )
    endif
    set shieldarray[shieldtotal] = data
    set shieldtotal = shieldtotal + 1
    return data.shield
endfunction

function Shield_Modify takes unit shield, real regen, real regendelay, boolean noncombat, boolean destroyable returns boolean
    local shielddata data = GetUnitUserData( shield )
    if ( IsUnitInGroup( shield, shieldgroup ) == false ) then
        return false
    endif
    set data.regen = regen
    set data.regendelay = regendelay
    set data.noncombat = noncombat
    set data.destroyable = destroyable
    return true
endfunction

function Shield_ModifyState takes unit shield, boolean dead returns boolean
    local shielddata data = GetUnitUserData( shield )
    if ( IsUnitInGroup( shield, shieldgroup ) == false ) then
        return false
    endif
    set data.dead = dead
    return ( data.dead == dead )
endfunction

function Shield_TeamCreate takes nothing returns integer
    set teamshieldgroup[teamtotal] = CreateGroup
    set teamtotal = teamtotal + 1
    return ( teamtotal - 1 )
endfunction

function Shield_TeamDestroy takes integer teamslot returns boolean
    if ( teamshieldgroup[teamslot] == null ) then
        return false
    endif
    call DestroyGroup( teamshieldgroup[teamslot] )
    set teamshieldgroup[teamslot] = teamshieldgroup[teamtotal - 1]
    set teamshieldgroup[teamtotal - 1] = null
    set teamtotal = teamtotal - 1
    return true
endfunction

//End Control Functions

endlibrary




Wow, that's some looooonnnnnng code...

Anyway, the following is a description of the functions, you can put it in a disabled trigger in your map for reference.

Code:
function Missile_LaunchTarget takes player owner, integer unitid, real launchx, real launchy, real launchz, real speed, real arc, real collision, unit target, real damage, real aoe, real aoedamage returns unit

player owner is the player that the missile belongs to
integer unitid is the raw code of the unit
real launchx is the x coordinate of the launch site
real launchy is the y coordinate of the launch site
real launchz is the z coordinate of the launch site (how high it is)
real speed is the speed of the missile in wc3 distance units per second
real arc is the arc of the missile: 0.00 is no arc, with arc increasing as this value increases
real collision is the collision size of the missile in all THREE dimensions: if this is anything other than 0, when the missile collides with another enemy missile, the missile will detonate in the air, destroying the enemy missile as well, but dealing no damage to anything else
unit target is the target: this can be any unit that is an enemy of player owner; if it is not an enemy of player owner, no unit will be created and the function will return a null unit
real damage is the damage done to the target when the missile hits the target
real aoe is the aoe radius of the missile's detonation
real aoedamage is the aoe damage of the missile (if aoe is 0, then the missile will not do any aoe damage so don't worry about this)

This function creates a missile and "launches" it at a target. The missile will 
track the target (if the target moves around) and will explode once it gets 
close enough to the target. If the missile was created the function will return 
the unit created.

If the target dies before the missile reaches it, the missile will continue 
toward the last known coordinates of the target before it died and will 
detonate upon arrival, dealing aoe damage (if any was specified).


function Missile_Modify takes unit missile, real collision, real damage, real aoe, real aoedamage returns boolean

unit missile is the missile you want to modify
real collision is the new collision value
real damage is the new damage value
real aoe is the new aoe value
real aoedamage is the new aoe damage value

This function can be used to modify some of the stats of the missile. If you 
input a unit that is not a missile, then the function will return false, otherwise 
it will return true.


function Missile_Destroy takes unit missile returns boolean

unit missile is the missile being destroyed

This function will kill a missile and clean up all variables that were related to 
it. If you try to destroy a missile that wasn't a missile, the function will return 
false, otherwise it will return true.


function Shield_Create takes player owner, integer unitid, integer teamslot, real x, real y, real facing, real regen, real regendelay, boolean noncombat, boolean destroyable returns unit

player owner is the player that owns the shield
integer unitid is the raw code of the shield
integer teamslot is the team number that the shield will belong to
real x is the x coordinate of the shield
real y is the y coordinate of the shield
real facing is the facing of the shield
real regen is the HP regeneration rate (in HP/sec) of the shield
real regendelay is how long the shield will wait after "collapsing" before regenerating its HP again
boolean noncombat when set to true will make it so that the shield will stop 
    regeneration HP when it is damaged, and will wait a period of time equal to 
    regendelay before resuming HP regeneration
boolean destroyable controls whether the shield can be killed (not the same 
    as collapsing) when its HP drops to 0. If set to false, the shield will simply 
    collpase when it takes too much damage and regenerate its HP later. If 
    set to true, the shield unit will actually die if it takes too much damage.

This function will create a shield unit at the specified coordinates for the 
specified player. Shield's must belong to a team. If you input an invalid 
teamslot number, then the shield will not be created and the function will 
return null. Otherwise, the function will return the created shield unit.


function Shield_Modify takes unit shield, real regen, real regendelay, boolean noncombat, boolean destroyable returns boolean

This function allows you to modify some of an existing shield's stats. If you 
specify a unit that is not a shield, then the function will return false. 
Otherwise it will return true.


function Shield_ModifyState takes unit shield, boolean dead returns boolean

This function allows you to change whether the system thinks a shield is 
dead or not. If a shield is considered dead, it will not take any damage from 
missiles. However, it will still regenerate HP. If you specify a unit that is not a 
shield, then the function will return false. Otherwise it will return true.


function Shield_TeamCreate takes nothing returns integer

This function creates a shield team. Every shield created MUST belong to a 
team. The function returns an integer that is the created team's teamslot 
number, used when creating shields and when destroying the shield team, so 
it is good to save the integer to a variable you can access later.


function Shield_TeamDestroy takes integer teamslot returns boolean

This function destroys a shield team. If you try to specify an invalid integer 
(i.e. the teamslot number points to a null group) the function will return false. 
Otherwise it will return true.

EDIT: Forgot to mention the following, which is actually quite important:

When you create custom units that will act as shields, make sure they have an armor type that has 100% damage reduction against ALL attack types. The reason is that if normal units start damaging shields the system gets screwed up. Also, make sure any custom units that act as missiles have the locust ability (so they are unselectable and untargetable) and that they are either flying units or they have the storm crow form ability. I shouldn't have to say this, but they should also have appropriate model files so they actually look like and you don't have footmen flying across your map.
 
Last edited:
Level 2
Joined
Jan 23, 2008
Messages
15
Ah, thankee muchly. My colleague is implementing this new system now. Your help is greatly appreciated, aznricepuff.
 
Level 8
Joined
Nov 29, 2007
Messages
371
Thanks an awful lot aznricepuff, I haven't worked out how all that code works yet, much less how to implement it effectively but your help is appreciated immensely by both of us. The time you have put in is likewise appreciated, a lot!

I think I need to learn JASS properly first though.

Many thanks for all your work,
DrazharLn
 
Status
Not open for further replies.
Top