• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[vJASS] Fire and Ice System

Status
Not open for further replies.
Level 14
Joined
Jul 1, 2008
Messages
1,314
Hey guys,

I am trying to write an autonomous fire and ice system, where users are able to create fires, that move around and also ice patches, that may interact.

So far, everything seems to work, but sometimes, terrain is not restored in very little patches, and sometimes terrain is not even set -> so clearly there is optimization and debugging to be done.

Therefore I would like to ask you, if you could point me towards something, that I should defenitely change or fix, either by checking the code or checking out that little test map, I attached.


there is 3 structs.

fire struct
ice struct
burnt ground struct

when a fire is created, it creates a unit, that moves around and checks, which terrain may be burnt. Also it favours certain terrains and is able to double itself to some extend. It tries to create burnt ground instances, where it moves. If it encounters water or ice, it will be extinguished. fire damages destructs and units around (units by a dummy abil)

when an ice is created, it will stay in place and clone itself around. If placed on water, there will be additional ice doodads. ice damages and slows units.


JASS:
library ELEMENTS initializer ElementInit requires TimerUtils, HM

    // Element Library enables to create fires or frost patches that are changing their
    // environment. Fires may not burn on ice or melt it, either way that ice may not
    // be formed on top of a fire or extinguish it.
    // fire and ice:
    // if a fire encounters ice, they will extinguish each other. The check-up for that will be inside
    // the fire handling. However, it is optional for each frost type, if it is able to freeze burnt ground.
    //
    // Sections:
    // A) USER DEFINITION
    // B) UTILITIES
    // C) ICE BLOCK
    // D) FIRE BLOCK
    // E) INIT
 
 
    // PRESETS
    globals
        private          hashtable      elementtypes    = InitHashtable()
        private          group          tempEnemies     = CreateGroup()
        private          boolexpr       FROST_FILTER
        private          integer        FROST_INDEX                 = 0 // register # frost terrain spots
        private          Freeze array   FROST_INSTANCE 
        private          integer        BURNT_INDEX                 = 0 // register # burnt terrain spots
        private          BurntGround array  BURNT_INSTANCE     
        private          integer        BURNT_CURRENT_GROUND_INDEX  = 0 // important helper. Is only set, when a ground is evaluated as already burning
        private          real           tempX = 0.      // helper var
        private          real           tempY = 0.      // helper var
        private          location       loc   = Location( 0., 0.) // helper
    endglobals
 
// ***********************************************************************************************************
// A) USER DEFINITION
// ***********************************************************************************************************
    globals
        private constant integer        DUMMY_ID                    = 'h000'
        // Fire Parameters
        private constant integer        TERRAIN_TYPE_GRAS_0         = 'Agrs'
        private constant integer        TERRAIN_TYPE_GRAS_1         = 'Agrd'
        private constant integer        TERRAIN_TYPE_GRAS_2         = 'Vgrt'
        private constant integer        TERRAIN_TYPE_GRAS_3         = 'Vgrs'
        private constant integer        TERRAIN_TYPE_GRAS_4         = 'Lgrs'
        private constant integer        TERRAIN_TYPE_DIRT_0         = 'Vdrt'
        private constant integer        TERRAIN_TYPE_DIRT_1         = 'Vdrr'
        private constant integer        TERRAIN_TYPE_DIRT_2         = 'Vcrp'
        private constant integer        TERRAIN_TYPE_STONE_0        = 'Vstp'
        private constant integer        TERRAIN_TYPE_STONE_1        = 'Vrck'
        private constant integer        TERRAIN_TYPE_LEAF_0         = 'Alvd'
        private constant integer        TERRAIN_TYPE_BURNT          = 'Oaby'
        private constant integer        TERRAIN_TYPE_UNDEFINED      = 30 // if a terrain type is not recognized, use this value to grade it (1 to 100)
        private constant real           FIRE_MAINTAINANCE_TIMING    = 1.
        private constant real           FIRE_SCALING_FACTOR         = 1.05 // determines, how much the flames grow
        private constant integer        FIRE_DMG_ABIL               = 'A000'
        private constant real           FIRE_DESTRUCT_DMG           = 100.
        private constant real           FIRE_DESTRUCT_RANGE         = 100.
        private constant real           BURNT_TIME                  = 20. // how long burnt ground will be burnt
        private constant string         SFX_FIRE_SMOKE              = "Doodads\\LordaeronSummer\\Props\\SmokeSmudge\\SmokeSmudge0.mdl"
        private constant string         SFX_FIRE_START              = "Abilities\\Spells\\Orc\\LiquidFire\\Liquidfire.mdl"
        private constant string         SFX_FIRE_DESTRUCT           = "Environment\\NightElfBuildingFire\\ElfLargeBuildingFire1.mdl"
        private constant string         SFX_FIRE_EXTINGUISH         = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
        private constant string         SFX_FIRE_WATERDEATH         = "Doodads\\Icecrown\\Water\\BubbleGeyserSteam\\BubbleGeyserSteam.mdl"
        // Freeze Parameters
        private constant real           FROST_MAINTAINANCE_TIMING   = 1.
        private constant integer        FROST_ABIL                  = 'A001'
        private constant real           FROST_DURATION              = 3. // increase this to increase the duration, a unit stays frozen
        private constant integer        FROST_ICE_DESTRUCT          = 'B000'
        private constant string         SFX_FROST_START             = "Abilities\\Weapons\\ZigguratFrostMissile\\ZigguratFrostMissile.mdl"
    endglobals
 
    private function DEFINE_ELEMENT_TYPES takes nothing returns nothing
        // define frost and fire types here.
     
        // FIRE type "normal": '0'
        local integer j = 0
        //      burns:      on every ground except stones (restricts movement and start)
        call SaveInteger( elementtypes, j, 0, TERRAIN_TYPE_STONE_0)
        call SaveInteger( elementtypes, j, 1, TERRAIN_TYPE_STONE_1)
        //      prefers:    leaves > gras > dirt
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_LEAF_0, 100)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_0, 95)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_1, 90)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_2, 95)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_3, 80)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_4, 90)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_DIRT_0, 50)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_DIRT_1, 40)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_DIRT_2, 60)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_BURNT, 5) // how favourable is burnt ground?
        //      fire modifieres
        call SaveBoolean( elementtypes, j, 100, true)    //  boolean MoveIt: Is the fire able to move?
        call SaveReal( elementtypes, j, 101, 30.)        //  real FireSpeed: aka wc3 movement speed
        call SaveBoolean( elementtypes, j, 102, false)   //  boolean BurnBurntGround: is the fire able to dublicate on burnt ground?
        call SaveBoolean( elementtypes, j, 103, true)    //  boolean DoRestoreGround: Restore Ground after burning is over
        call SaveReal( elementtypes, j, 104, 100.)       //  defines, how far a fire may reach
        call SaveBoolean( elementtypes, j, 105, true)    //  kill trees?
        call SaveBoolean( elementtypes, j, 106, false)   //  burn on water?
        // fire appaerance
        call SaveStr( elementtypes, j, 110, "Doodads\\Cinematic\\FireRockSmall\\FireRockSmall.mdl")                   // model stage 0
        call SaveStr( elementtypes, j, 111, "Doodads\\Cinematic\\TownBurningFireEmitter\\TownBurningFireEmitter.mdl") // model stage 1
        call SaveStr( elementtypes, j, 112, "Doodads\\Cinematic\\FirePillarMedium\\FirePillarMedium.mdl")             // model stage 2
     
        // FIRE type "inferno": '1'
        set j = 1
        //      burns:      on every ground (restricts movement and start)
        //      prefers:    leaves = gras > dirt > stones > burnt
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_LEAF_0, 100)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_0, 100)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_1, 100)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_2, 100)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_3, 100)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_GRAS_4, 100)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_DIRT_0, 90)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_DIRT_1, 90)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_DIRT_2, 95)
        call SaveInteger( elementtypes, j, TERRAIN_TYPE_BURNT, 20) // how favourable is burnt ground?
        //      fire modifieres
        call SaveBoolean( elementtypes, j, 100, true)    //  boolean MoveIt: Is the fire able to move?
        call SaveReal( elementtypes, j, 101, 20.)        //  real FireSpeed: aka wc3 movement speed
        call SaveBoolean( elementtypes, j, 102, true)    //  boolean BurnBurntGround: is the fire able to dublicate on burnt ground?
        call SaveBoolean( elementtypes, j, 103, true)    //  boolean DoRestoreGround: Restore Ground after burning is over
        call SaveReal( elementtypes, j, 104, 180.)       //  defines, how far a fire may reach
        call SaveBoolean( elementtypes, j, 105, true)    //  kill trees?
        call SaveBoolean( elementtypes, j, 106, false)   //  burn on water?
        // fire appaerance
        call SaveStr( elementtypes, j, 110, "Doodads\\Cinematic\\TownBurningFireEmitterBlue\\TownBurningFireEmitterBlue.mdl") // model stage 0
        call SaveStr( elementtypes, j, 111, "Doodads\\Cinematic\\TownBurningFireEmitterBlue\\TownBurningFireEmitterBlue.mdl") // model stage 1
        call SaveStr( elementtypes, j, 112, "Doodads\\Cinematic\\FirePillarMedium\\FirePillarMedium.mdl")                     // model stage 2
 
        // FROST type "normal": '5'
        set j = 5
       // call SaveInteger( elementtypes, j, 0, 5) // how many times may the frost dublicate
        call SaveBoolean( elementtypes, j, 1, true) // is frost able to exist on water
        call SaveBoolean( elementtypes, j, 2, true) // is frost able to freeze burnt ground?
       // combine frost and fire libs to be able to depend on each other
        call SaveStr( elementtypes, j, 3, "Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl") // frost appearance
        call SaveInteger( elementtypes, j, 4, 'Iice') // frost terrain type
        call SaveReal( elementtypes, j, 5, 10.) // frost dmg amount on units
    endfunction
// ***********************************************************************************************************
// / END USER DEFINITION
// ***********************************************************************************************************
 
// ***********************************************************************************************************
// B) UTILITIES
// ***********************************************************************************************************

    private function IsGroundFrozen takes integer tt returns boolean
        // list all terrain types, that indicate frost
        return (tt == 'Iice') // or
    endfunction
 
    private function GetFreezeInstance takes real x, real y returns integer
        // this will return the frozen instancem that is closest to a certain location
        local integer index = 1
        local boolean b     = false
        local Freeze frost
        loop
            exitwhen index > FROST_INDEX or b
            set frost = FROST_INSTANCE[index]
            // return true, if the position is close to a frost instance
            set b = (HM_DistanceBetweenLoc( frost.x, frost.y, x, y) < 32.)
            set index = index + 1
        endloop
        if not b then
            set frost = 0
        endif
        return frost // returns 0, if no instance founc
    endfunction
 
    private function IsGroundBurnt takes real x, real y returns boolean
        // this functions checks, if ground is burnt or not
        // it searches through all curent active burnt ground cells and decides, whether
        // the specified spot is already part of one of these.
        local integer index = 1
        local boolean b     = false
        local BurntGround burnt
        loop
            exitwhen index > BURNT_INDEX or b
            set burnt = BURNT_INSTANCE[index]
            // only return true, if that position is already burning on the same cell
            set b = (HM_DistanceBetweenLoc( burnt.x, burnt.y, x, y) < 32.)
            if b then
                set BURNT_CURRENT_GROUND_INDEX = index // report the current burning instance
            endif
            set index = index + 1
        endloop
        return b // returns by default "not burnt"
    endfunction
 
    private function IsTerrainBurnable takes real x, real y, integer FireType returns boolean
        // this function checks, if a certain terrain spot is burnable by a specified fire
        local boolean forbidden = false
        local integer i         = 0
        local integer tt        = GetTerrainType( x, y)
         
        // check, if the fire is allowed to burn here?
        loop
            exitwhen (not HaveSavedInteger( elementtypes, FireType, i) or forbidden)
            if tt == LoadInteger( elementtypes, FireType, i) then
                set forbidden = true
            endif
            set i = i + 1
        endloop
        return (not forbidden) // returns true, if the terrain is burnable
    endfunction
 
    private function IsLineBurnable takes real x, real y, real xEnd, real yEnd, integer FireType returns boolean
        // this function checks, if a straight line contains any forbidden terrain types,
        // when trying to move a fire of a certain type
        local real dist        = HM_DistanceBetweenLoc( x, y, xEnd, yEnd)
        local boolean burnable = true
        local real dir         = Atan2(yEnd - y, xEnd - x) // this is in radians
     
        loop
            exitwhen (not burnable) or dist <= 0.
            set x = x + 32. * Cos( dir)
            set y = y + 32. * Sin( dir)
            set burnable = IsTerrainBurnable( x, y, FireType)
            set dist = dist - 32.
        endloop
        return burnable // returns false, if a forbidden terrain type was found
    endfunction
 
// ***********************************************************************************************************
// C) FREEZE BLOCK
// ***********************************************************************************************************
    struct Freeze
        timer tmr
        effect model // model of the frost
        destructable ice        = null // this is only in case the freezing occurs on water and is allowed to do so
        unit frostOrigin        = null // store a unit that benefits from killed units by frost dmg
        real x                  = 0.
        real y                  = 0.
        integer previousTerrain = 0 // store and recover after freezing is over
        integer doubleFactor    = 0 // can frost advance further?
        integer frostType       = 0
        real lifetime           = 0. // how long does the frozen patch stay
        real maxTime            = 0.
        real spreadTime         = 1. // after which delay is frost able to spread again
        real initialSpreadTime  = 1.
        real frostDmg           = 0. // dmg dealt to units on the frost patch
        integer instance        = 0  // store struct instance # in global array
     
        method destroy takes nothing returns nothing
            // restore terrain
            call SetTerrainType( .x, .y, .previousTerrain, -1, 1, 0)
            call ReleaseTimer( .tmr)
            call DestroyEffect( .model)
            if .ice != null then
                call RemoveDestructable( .ice)
                set .ice = null
            endif
            // release data
            set FROST_INSTANCE[.instance] = FROST_INSTANCE[FROST_INDEX]
            if FROST_INDEX > 0 then
                set FROST_INDEX = FROST_INDEX - 1
            endif
            set .model = null
            set .tmr   = null
            // nulling
            call this.deallocate()
        endmethod
     
        static method MaintainFrost takes nothing returns nothing
            local Freeze this  = GetTimerData( GetExpiredTimer())
            local real x       = 0.
            local real y       = 0.
            local real i       = 0.
            local Freeze frost = 0 // helper instance
            local unit u
         
            // check life time
            set this.lifetime = this.lifetime - FROST_MAINTAINANCE_TIMING
            if this.lifetime > 0. then
                if this.doubleFactor > 0 then
                    set this.spreadTime = this.spreadTime - FROST_MAINTAINANCE_TIMING - GetRandomReal( 0., FROST_MAINTAINANCE_TIMING/2)
                    if this.spreadTime <= 0. then
                        set this.spreadTime = this.initialSpreadTime
                        // if the frost is able to spread, try to do this now
                        set i = GetRandomReal( 0, 7)
                        set x = this.x + 64. * Cos( i * 45. * bj_DEGTORAD)
                        set y = this.y + 64. * Sin( i * 45. * bj_DEGTORAD)
                        // try to create frost and limit multiplication!
                        set this.doubleFactor = this.doubleFactor - 1
                        set frost = Freeze.start( x, y, this.maxTime, this.frostType, this.doubleFactor, this.initialSpreadTime, this.frostOrigin)
                        call BJDebugMsg("Frost factor set to " + I2S( this.doubleFactor))
                        if frost == 0 then
                             set this.doubleFactor = this.doubleFactor + 1
                        endif
                    endif
                endif
                // freeze enemies around
                call GroupClear( tempEnemies)
                call GroupEnumUnitsInRange( tempEnemies, this.x, this.y, 128., FROST_FILTER)
                loop
                    set u = FirstOfGroup( tempEnemies)
                    exitwhen u == null
                    call UnitAddAbility( u, FROST_ABIL)
                    call DELAYED_REMOVE_ABIL_FROM_UNIT( u, FROST_ABIL, GetRandomReal( FROST_DURATION, FROST_DURATION*2.))
                    if this.frostOrigin == null then
                        call UnitDamageTarget( u, u, this.frostDmg, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
                    else
                        call UnitDamageTarget( this.frostOrigin, u, this.frostDmg, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
                    endif
                    call GroupRemoveUnit( tempEnemies, u)
                endloop
                set u = null
            else
                // time over, restore the terrain and destroy forst
                call this.destroy()
            endif
        endmethod
 
        static method start takes real X, real Y, real Time, integer FrostType, integer DoubleFactor, real SpreadTime, unit FrostOrigin returns thistype
            local thistype this       = 0
            local integer PrevTerrain = GetTerrainType( X, Y)
            local boolean FreezeBurnt = false
            local boolean IsLocBurnt  = false
            local boolean FreezeWater = true
            local boolean IsOnWater   = false
            local BurntGround burnt
         
            // create frost, if this is not forbidden
            if not IsGroundFrozen( PrevTerrain) then
                 // trying to freeze water? Is this allowed?
                 set FreezeWater = LoadBoolean( elementtypes, FrostType, 1)
                 set IsOnWater   = (HM_CheckLocationWaterType( X, Y) > 0)
                 if (IsOnWater and FreezeWater) or (not IsOnWater) then
                    // check, if that ground is burnt and whether it is allowed to freeze that?
                    set BURNT_CURRENT_GROUND_INDEX = 0
                    set IsLocBurnt  = IsGroundBurnt( X, Y)
                    set burnt       = BURNT_INSTANCE[BURNT_CURRENT_GROUND_INDEX]
                    set FreezeBurnt = LoadBoolean( elementtypes, FrostType, 2)
                    if (IsLocBurnt and FreezeBurnt) or (not IsLocBurnt) then
                        // if that ground was really actually burnt before, then reset this ground before creating
                        // a new terrain type first, so that we do not get any inconsistencies
                        if IsLocBurnt then
                            call burnt.destroy()
                        endif
                        set this                   = thistype.allocate()
                        set this.frostType         = FrostType
                        set this.x                 = X
                        set this.y                 = Y
                        set this.lifetime          = Time * FROST_MAINTAINANCE_TIMING
                        set this.maxTime           = this.lifetime
                        set this.initialSpreadTime = SpreadTime * FROST_MAINTAINANCE_TIMING
                        set this.spreadTime        = this.initialSpreadTime
                        set this.doubleFactor      = DoubleFactor // how often will this frost spread?
                        set this.previousTerrain   = PrevTerrain
                        set this.frostDmg          = LoadReal( elementtypes, FrostType, 5)
                        call SetTerrainType( X, Y, LoadInteger( elementtypes, FrostType, 4), -1, 1, 0)

                        // if a originator unit is specified, store it, in case we want it to gain xp upon frost kills
                        if FrostOrigin != null then
                            set this.frostOrigin = FrostOrigin
                        endif
                        // if on water, create some ice destructables
                        if IsOnWater then
                            call MoveLocation( loc, X, Y)
                            set this.ice = CreateDestructableZ( FROST_ICE_DESTRUCT, X + GetRandomReal( -32., 32.), Y + GetRandomReal( -32., 32.), GetLocationZ( loc) + GetRandomReal( 10., 30.), GetRandomReal( 0., 360.), GetRandomReal( 0.3, 0.8), 1)
                        endif
                        // register the new frost in the list
                        set FROST_INDEX                 = FROST_INDEX + 1
                        set FROST_INSTANCE[FROST_INDEX] = this
                        set this.instance               = FROST_INDEX
                        // create the core frost effect and start surveying it
                        call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FROST_START, .x + GetRandomReal( -32., 32.), .y + GetRandomReal( -32., 32.)), 2.)
                        set .model = AddSpecialEffect( LoadStr( elementtypes, FrostType, 3), X, Y)
                        set .tmr   = NewTimerEx( this)
                        call TimerStart( .tmr, FROST_MAINTAINANCE_TIMING, true, function thistype.MaintainFrost)
                    endif
                endif
            endif
            return this // will return 0, if no frost created
        endmethod
    endstruct
 
// ***********************************************************************************************************
// D) FIRE BLOCK
// ***********************************************************************************************************
    private function GetFirePriority takes integer terrainType, integer FireType returns integer
        // use this function to compare terrain spots, which one will be favoured by a given fire
        if not HaveSavedInteger( elementtypes, FireType, terrainType) then
            return TERRAIN_TYPE_UNDEFINED
        endif
        return LoadInteger( elementtypes, FireType, terrainType)
    endfunction
 
    struct BurntGround
        real x                = 0.
        real y                = 0.
        integer previousType  = 0
        real lifetime         = 0.
        real renewingTresh    = 0. // if a burnt ground is close to death, and a flame comes by, reset its lifetime, if below this tresh.
        real maxLifeTime      = 0.
        integer instance     
        timer tmr
     
        method destroy takes nothing returns nothing
            // restore ground, if burning is over
            call SetTerrainType( .x, .y, .previousType, -1, 1, 0)
            call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_SMOKE, .x, .y), GetRandomReal( 2., 4.))
            // release data
            set BURNT_INSTANCE[.instance] = BURNT_INSTANCE[BURNT_INDEX]
            if BURNT_INDEX > 0 then
                set BURNT_INDEX = BURNT_INDEX - 1
            endif
            call ReleaseTimer( .tmr)
            set .tmr = null
            // nulling
            call this.deallocate()
        endmethod
     
        private static method SurveyBurntGround takes nothing returns nothing
            // this function will called by the struct timer and maintain the actual timing
            local BurntGround this = GetTimerData( GetExpiredTimer())
         
            set this.lifetime = this.lifetime - FIRE_MAINTAINANCE_TIMING
            if this.lifetime <= 0. then
                // override the burnt index and release data
                call this.destroy()
            endif
        endmethod
     
        static method createBurntTerrain takes real X, real Y, integer PreviousTerrain, real Time, real RenewingTresh, boolean restore returns thistype
            // this creates burnt ground after some fire wasted it
            // if restore == false, then do not really register this ground, instead just create it forever
            local thistype this    = thistype.allocate()
            set this.x             = X
            set this.y             = Y
            set this.lifetime      = Time * FIRE_MAINTAINANCE_TIMING
            set this.maxLifeTime   = this.lifetime
            set this.renewingTresh = RenewingTresh
            set this.previousType  = PreviousTerrain
         
            call SetTerrainType( X, Y, TERRAIN_TYPE_BURNT, -1, 1, 0)
            call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_SMOKE, .x + GetRandomReal( -32., 32.), .y + GetRandomReal( -32., 32.)), GetRandomReal( Time / 2., Time))
                     
            // register the new burnt ground in the list
            if restore then
                set BURNT_INDEX                 = BURNT_INDEX + 1
                set BURNT_INSTANCE[BURNT_INDEX] = this
                set this.instance               = BURNT_INDEX
                // start surveying the burnt ground
                set this.tmr = NewTimerEx( this) // attach the data
                call TimerStart( this.tmr, FIRE_MAINTAINANCE_TIMING, true, function thistype.SurveyBurntGround)
            endif
            return this
        endmethod
    endstruct
 
    private function MayDestructableBurn takes integer dId returns boolean
        // this module is used, if destructables are enumerated by a filter func
        return dId == 'B001'
    endfunction
 
    private function DestructDamageModule takes nothing returns nothing
        // enables fires to damage registered trees
        local destructable d = GetEnumDestructable()
        local real hp        = 0.
     
        if MayDestructableBurn( GetDestructableTypeId( d)) then
            if (GetWidgetLife( d) > .405) then
                set hp = GetDestructableLife( d) - FIRE_DESTRUCT_DMG
                if hp <= 0. then
                    call KillDestructable( d)
                    call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_SMOKE, GetDestructableX(d), GetDestructableY(d)), GetRandomReal( 8., 14.))
                else
                    call SetDestructableLife( d, hp)
                    call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_DESTRUCT, GetDestructableX(d), GetDestructableY(d)), GetRandomReal( 3., 5.))
                endif
            else
                call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_SMOKE, GetDestructableX(d), GetDestructableY(d)), GetRandomReal( 5., 7.))
            endif
        endif
        set d = null
    endfunction
 
    struct Fire
        unit core
        timer tmr
        effect model
        rect enumDestructables    = null
        real x                    = 0. // center pos x
        real y                    = 0. // center pos y
        real coreX                = 0. // actual position of fire x
        real coreY                = 0. // actual position of fire y
        real lifetime             = 0.
        real maxLifeTime          = 0.
        real scale                = 0.
        real stage                = 0.
        real firstStage           = 0.
        real secondStage          = 0.
        real speed                = 0. // move speed of the core unit
        integer fireType          = 0  // reference to the fire type list for parameter look-up
        integer fireDmgLvl        = 0
       // boolean IsCombinedStorm   = false
       // boolean combine           = true  // switch, whether a fire can be combined with another one to grow bigger
        boolean move              = true
        boolean burnBurntGround   = false // if this is set to false, fire extunguishes faster on burnt ground
        boolean restore           = true  // restore original ground after burning
        integer doubleFactor      = 0       // probability, that that the fire will dublicate each timer call
        real busyFire             = 0.  // this will be set to prevent too many orders to fires
        real range                = 0.  // defines, how far the fire may travel
        boolean killDestructables = true // kill trees?
        boolean burnWater         = false
        boolean extinguishFlag    = false // set to true, if extinguish effect ought to be played up on destruction
     
        private method UpdateModelForStage takes Fire fire returns string
            // this function updates the actual fire model by converting fire stages to an actual model path
            local integer modelIndex = 110
            if fire.stage == fire.maxLifeTime then
                // this is the appearance, when the fire starts
                set modelIndex = 110
            elseif fire.stage == fire.firstStage then
                set modelIndex = 111
            elseif fire.stage == fire.secondStage then
                set modelIndex = 112
            endif
            return LoadStr( elementtypes, fire.fireType, modelIndex)
        endmethod
     
        private static method MaintainFires takes nothing returns nothing
            // this function will called by the struct timer and maintain the actual timing
            local Fire this    = GetTimerData( GetExpiredTimer())
            local real xx      = 0.
            local real yy      = 0.
            local integer i    = 0
            local integer terrainType = 0
            local integer bestTerrain = 0
            local real IsOnWater
            local BurntGround burnt
            local Freeze frost = 0
            set this.coreX     = GetUnitX( this.core)
            set this.coreY     = GetUnitY( this.core)
            set IsOnWater      = I2R( HM_CheckLocationWaterType( this.coreX, this.coreY))
         
            // check life time
            set this.lifetime = this.lifetime - FIRE_MAINTAINANCE_TIMING - IsOnWater
            if this.lifetime <= 0. or ((not this.burnWater) and (IsOnWater > 0.)) then
                if IsOnWater > 0. then
                    set this.extinguishFlag = true
                    call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_WATERDEATH, this.coreX, this.coreY), GetRandomReal( 8., 14.))
                endif
                call this.destroy()     
            else
                // if there is frost at the current location, extinguish fire and destroy frost instance as well
                set frost = GetFreezeInstance( this.coreX, this.coreY)
                if frost != 0 then
                    // there is indead a frost spot at the curren x,y. Destroy the instances
                    call frost.destroy()
                    set this.extinguishFlag = true
                    call this.destroy()
                    call BJDebugMsg("frost found in periodic. extinguish fire")
                else
                    // maintain the fire. check stage first
                    set this.stage = this.stage - 1
                    if this.stage == this.firstStage or this.stage == this.secondStage then
                        // fire reached next stage
                        set this.scale      = this.scale * FIRE_SCALING_FACTOR
                        call SetUnitScale( this.core, this.scale, 0., 0.)
                        set this.fireDmgLvl = this.fireDmgLvl + 1
                        call IssueImmediateOrder( this.core, "unimmolation")
                        call SetUnitAbilityLevel( this.core, FIRE_DMG_ABIL, this.fireDmgLvl)
                        call IssueImmediateOrder( this.core, "immolation")
                        call DestroyEffect( this.model)
                        set this.model = AddSpecialEffectTarget( UpdateModelForStage( this), this.core, "origin")
                    endif
                    // destroy destructables near by?
                    if this.killDestructables then
                        call MoveRectTo( this.enumDestructables, this.coreX, this.coreY)
                        call EnumDestructablesInRect( this.enumDestructables, null, function DestructDamageModule)
                    endif
                    // if the fire is meant to move, make it move around its cooridantes
                    if this.move then
                        if this.busyFire > 0. then
                            set this.busyFire = this.busyFire - FIRE_MAINTAINANCE_TIMING
                        else
                            // flame may be moved to a new random point. Grade the terrains, where the flame may move and
                            // choose the most attractive spot to move the flame to. Exclude non-burnable spots
                            set xx = 100000. // some high unreachable value
                            loop
                                exitwhen i >= 10
                                set tempX = this.x + GetRandomReal( -this.range, this.range) * this.stage / 5.
                                set tempY = this.y + GetRandomReal( -this.range, this.range) * this.stage / 5.
                                if IsLineBurnable( this.coreX, this.coreY, tempX, tempY, this.fireType) then
                                    // the walk way is burnable by principle, now register and grade specific terrain
                                    set terrainType = GetFirePriority( GetTerrainType( tempX, tempY), this.fireType)
                                    if terrainType > bestTerrain then
                                        // this is the most valid spot
                                        set bestTerrain = terrainType
                                        set xx = tempX
                                        set yy = tempY
                                    endif
                                endif
                                set i = i + 1
                            endloop
                            // if no valid position was found, move the fire to its original position
                            if xx == 100000. then
                            call BJDebugMsg(">>> FIRE MOVED TO ORIG POSITION")
                                set xx = this.x + GetRandomReal( -this.range/5., this.range/5.)
                                set yy = this.y + GetRandomReal( -this.range/5., this.range/5.)
                            endif
                            call IssuePointOrder( this.core, "move", xx, yy)
                            set this.busyFire = HM_DistanceBetweenLoc( this.coreX, this.coreY, xx, yy) / (this.speed * 1.5)
                        endif
                    endif
                    // dublicate the fire by a certain chance, but only if ground is not burning yet
                    set BURNT_CURRENT_GROUND_INDEX = 0 // internal helper for IsGroundBurning
                    if IsGroundBurnt( this.coreX, this.coreY) then
                        set burnt = BURNT_INSTANCE[BURNT_CURRENT_GROUND_INDEX]
                        if burnt.lifetime <= burnt.renewingTresh then
                            // if the lifetime of the burnt ground is close to its end, renew it
                            set burnt.lifetime = burnt.maxLifeTime
                        endif
                        if this.burnBurntGround then
                            // if burnt ground may be burned, do it anyway
                            if this.doubleFactor >= GetRandomInt( 0, 100) then
                                set this.doubleFactor = this.doubleFactor - 1 // self limiting fires
                                call Fire.start( this.coreX, this.coreY, this.scale, this.maxLifeTime, this.firstStage, this.secondStage, this.fireType, this.doubleFactor)
                            endif
                        else
                            // if flame is not meant to be buring on burnt ground, it will extinguish faster
                            set this.lifetime = this.lifetime - FIRE_MAINTAINANCE_TIMING
                        endif
                    else
                        // not burning, burn ground and dublicate fire, if possible
                        if this.doubleFactor >= GetRandomInt( 0, 100) then
                            set this.doubleFactor = this.doubleFactor - 1 // self limiting fires
                            call Fire.start( this.coreX, this.coreY, this.scale, this.maxLifeTime, this.firstStage, this.secondStage, this.fireType, this.doubleFactor)
                        endif
                        // try to create burnt ground
                        if IsOnWater < 1 then
                            set terrainType = GetTerrainType( this.coreX, this.coreY)
                            if terrainType != TERRAIN_TYPE_BURNT then
                                call BurntGround.createBurntTerrain( this.coreX, this.coreY, terrainType, GetRandomReal( (BURNT_TIME/2.)-2., BURNT_TIME), GetRandomReal( BURNT_TIME/5., BURNT_TIME/3.), this.restore)
                            endif
                        else
                            // if a fire is allowed to burn water, create a little effect
                            call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_WATERDEATH, this.coreX, this.coreY), GetRandomReal( 5., 8.))
                        endif
                    endif
                endif
            endif
        endmethod
 
        static method start takes real X, real Y, real Scale, real Time, real first, real second, integer FireType, integer DoubleFactor returns thistype //, boolean Combine returns thistype
            local thistype this 
            //local boolean IsFrozen = IsGroundFrozen( GetTerrainType( X, Y))
            local Freeze frost     = 0
         
            // only allow the creation of a fire, if there is no frozen ground there
            set frost = GetFreezeInstance( X, Y)
            if frost != 0 then
                call BJDebugMsg("frost found in start. extinguish fire")
                // there is indead a frost spot at the curren x,y. Destroy the instances
                call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_EXTINGUISH, X, Y), GetRandomReal( 1., 3.))
                call frost.destroy()
            else
                // create a fire, if the terrain is able to be burnt
                if IsTerrainBurnable( X, Y, FireType) then
                    set this                 = thistype.allocate()
                    set this.fireType        = FireType
                    set this.x               = X
                    set this.y               = Y
                    set this.stage           = Time * FIRE_MAINTAINANCE_TIMING  // unique stage index that is needed for fire storms and will be referenced by GetModelForStage
                    set this.lifetime        = this.stage                       // life time of the fire
                    set this.maxLifeTime     = this.stage
                    set this.scale           = Scale
                    set this.firstStage      = first
                    set this.secondStage     = second
                    set this.fireDmgLvl      = 1
                    set this.move            = LoadBoolean( elementtypes, this.fireType, 100) // do we want the storms to wander around?
                    set this.speed           = LoadReal( elementtypes, this.fireType, 101)
                    set this.burnBurntGround = LoadBoolean( elementtypes, this.fireType, 102) // is this fire able to spread on burnt ground?
                    set this.restore         = LoadBoolean( elementtypes, this.fireType, 103) // do restore Ground after burning is over?
                    set this.range           = LoadReal( elementtypes, this.fireType, 104)       //  defines, how far a fire may travel
                    set this.killDestructables = LoadBoolean( elementtypes, this.fireType, 105)
                    set this.burnWater       = LoadBoolean( elementtypes, this.fireType, 106)
                    set this.doubleFactor    = DoubleFactor // how often will this fire spread?
                 
                    // create the core firestorm unit with the requested model
                    set .core  = CreateUnit( Player(5), DUMMY_ID, X, Y, GetRandomReal( 0., 360.))
                    call SetUnitPathing( .core, false)
                    call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_START, .x + GetRandomReal( -32., 32.), .y + GetRandomReal( -32., 32.)), 2.)
                    if .killDestructables then
                        set .enumDestructables = Rect( X - FIRE_DESTRUCT_RANGE * Scale, Y - FIRE_DESTRUCT_RANGE * Scale, X + FIRE_DESTRUCT_RANGE * Scale, Y + FIRE_DESTRUCT_RANGE * Scale)
                    endif
                    call UnitAddAbility( .core, FIRE_DMG_ABIL)
                    call IssueImmediateOrder( .core, "immolation")
                    set .model = AddSpecialEffectTarget( UpdateModelForStage( this), .core, "origin")
                    call SetUnitScale( .core, .scale, 0., 0.)
                    call SetUnitMoveSpeed( .core, this.speed)
                    set .tmr   = NewTimerEx( this)
                    call TimerStart( .tmr, FIRE_MAINTAINANCE_TIMING, true, function thistype.MaintainFires)
                endif
            endif
            return this
        endmethod
     
        private method destroy takes nothing returns nothing
            // release data
            call ReleaseTimer( .tmr)
            call DestroyEffect( .model)
            call RemoveUnit( .core)
            if .enumDestructables != null then
                call RemoveRect( .enumDestructables)
                set .enumDestructables = null
            endif
            //play an extinguishing effect, if wanted
            if .extinguishFlag then
                call DELAYED_SFX_DESTRUCTION( AddSpecialEffect( SFX_FIRE_EXTINGUISH, .coreX, .coreY), GetRandomReal( 1., 3.))
            endif
            set .core  = null
            set .model = null
            set .tmr   = null
            // nulling
            call this.deallocate()
        endmethod
    endstruct

// ***********************************************************************************************************
// E) INIT BLOCK
// ***********************************************************************************************************
 
    private function FrostFilterEx takes nothing returns boolean
        // defines units, that may be damaged by a frost instance
        return (IsUnitType( GetFilterUnit(), UNIT_TYPE_GROUND) and not HM_IsUnitDead( GetFilterUnit()) and (GetUnitAbilityLevel( GetFilterUnit(), 'Avul') < 1) and (GetUnitAbilityLevel( GetFilterUnit(), 'Aloc') < 1))
    endfunction

    function ElementInit takes nothing returns nothing
        call DEFINE_ELEMENT_TYPES()
        set FROST_FILTER = Condition( function FrostFilterEx)
    endfunction
 
endlibrary

I am glad for any help or criticism.

Thanks and best regards
 

Attachments

  • Fire and Ice System Test.w3x
    70 KB · Views: 69
Last edited:
Level 14
Joined
Jul 1, 2008
Messages
1,314
thank you for your comment. I have UMSWE enabled because of tiles, I re uploaded a disabled version (did not know that maps inherit the enabled state) ..

thank you for quoting your system. I am still trying to get some elegant oop style like yours ...

I think, that I should use a linked list to store the burnt and frozen tiles, I think this is the way to go, instead of looping through the whole list.

Do you have any points in mind, where I should definitely change my code? :)
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
Well thanks, but it makes no sense to me to literally use terrain infection.
The thing is, that I would have to modify your system anyway, since frost and fire damage units and destructs in range, so I can also stick to the code, I have.
The fire system literally uses a fire unit anyway so it works a bit differently.
However, looking at your system, I already see some parts where I can improve my code, so this already helped :)
I will try to use this prev.next struct stuff so I can improve the way, I am listing instances at the moment.

short beginner question: I do not have to null handles in the destroy method of jass structs, as far as the manual states, is this really correct?
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
I am aware of that, but I have read in some tutorials, that this.deallocate() will null all handles. So this is not true?

I will think of using infections as burnt ground, thanks. Sometimes the burnt ground struct looses track of the burnt instances somehow. I do not really get how that is possible but it can only be the listing ... (?)

I use a global counter and a struct array to store all instances, currently active:

JASS:
// destroying: release data
            set BURNT_INSTANCE[.instance] = BURNT_INSTANCE[BURNT_INDEX]
            if BURNT_INDEX > 0 then
                set BURNT_INDEX = BURNT_INDEX - 1
            endif

// create: increate count
set BURNT_INDEX                 = BURNT_INDEX + 1
                set BURNT_INSTANCE[BURNT_INDEX] = this
                set this.instance               = BURNT_INDEX
Do you think, this can be bugged?
 
that this.deallocate() will null all handles.
Hm, no, not by default. You could write your own deallocate() function and then null something there, but it's not needed.

I use a global counter and a struct array to store all instances, currently active:
destroy:
If this is the instance that is about being removed, yes.
But you can just always decrease your MaxIndex.

But I wonder if you make it inside a loop or not, because in a loop it would look wrong, as you probably have no list structure to loop through it other than your struct array.

create:
looks okay, too

=========

vjass dynamic indexing can basicly work like this:

JASS:
library MyLibrary

    globals
        private MyStruct array Data
        private integer MaxIndex = -1
    
        private constant timer CLOCK   = CreateTimer()
        private constant real INTERVAL = 1.
    endglobals
 
    struct MyStruct
    
        // null everything here and we remove the instance
        method destroy takes nothing returns nothing
            call .deallocate()
            set MaxIndex = MaxIndex - 1
            if (MaxIndex < 0) then
                call PauseTimer(CLOCK)
            endif
        endmethod
 
        static method callback takes nothing returns nothing
            local integer i = 0
            local thistype this
        
            loop
                exitwhen i > MaxIndex
                set this = Data[i]
            
                // some deindex condition
                if true then
                
                    // destroy instance and set last element to current position in array
                    call .destroy()
                    set Data[i] = Data[MaxIndex]
                
                    // if we dont decrease i, the last element would be skipped, because it is in position of "i" now
                    set i = i - 1
                
                endif
            
                set i = i + 1
            endloop
        endmethod
    
        // you can of course bind the create function to a spell-cast trigger or something
        static method create takes nothing returns thistype
            local thistype this = allocate()
        
            set MaxIndex = MaxIndex + 1
            set Data[MaxIndex] = this
        
            if MaxIndex == 0 then
                call TimerStart(CLOCK, INTERVAL, true, function thistype.callback)
            endif
        
            return this
        endmethod
    
    endstruct
endlibrary
 
Level 13
Joined
Nov 7, 2014
Messages
571
Oh, sorry, because MaxIndex is decreased already in destroy, yes, of course.

Might be a good (and old) idea to bake this into a module:

JASS:
// The updatelist module requires that the implementing struct has defined the 2 static fields:
//    static real ul_timer_frequency = 1.0 / 32.0
//    static code ul_update_handler = function thistype.ul_update
//
module updatelist
    readonly static timer ul_timer = CreateTimer()
    readonly static thistype array ul_list
    readonly static integer array ul_pos
    readonly static integer ul_count = 0

    method ul_add takes nothing returns nothing
        set ul_count = ul_count + 1
        set ul_pos[this] = ul_count
        set ul_list[ul_count] = this
        if ul_count == 1 then
            call TimerStart(ul_timer, ul_timer_frequency, true, ul_update_handler)
        endif
    endmethod

    method ul_remove takes nothing returns nothing
        set ul_pos[ ul_list[ul_count] ] = ul_pos[this]
        set ul_list[ ul_pos[this] ] = ul_list[ul_count]
        set ul_count = ul_count - 1
        if ul_count <= 0 then
            call PauseTimer(ul_timer)
        endif
    endmethod
endmodule

library MyLib

struct MyStruct
    private static real ul_timer_frequency = 1.0 / 32.0
    private static code ul_update_handler
    implement updatelist

    private static method onInit takes nothing returns nothing
        // locals

        call ExecuteFunc("s__" + "thistype" + "_set_handlers")

        // other init stuff
    endmethod

    static method create takes nothing returns thistype
        local thistype this = allocate()

        // initialize this

        call this.ul_add()

        return this
    endmethod

    method destroy takes nothing returns nothing
        call this.ul_remove()
        call this.deallocate()
    endmethod

    static method ul_update takes nothing returns nothing
        local thistype this
        local integer i

        set i = 1
        loop
            exitwhen i > ul_count
            set this = ul_list[i]

            if /*<this-wants-to-get-destroyed>*/ true then
                call this.destroy()
                // lets not forget to update the last element that now
                // has the position of `this` in the `ul_list`
                set i = i - 1
            endif

            set i = i + 1
        endloop
    endmethod

    private static method set_handlers takes nothing returns nothing
        set ul_update_handler = function thistype.ul_update
    endmethod
endstruct

endlibrary

which should prevent future mistakes like these (assuming the implementation is correct) =).

PS: we can also call the destroy method outside of the callback function's loop ;P
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
thanks for your comments! Both :)

The thing is, I do not really need to use the instances in the list. I just want to store all of them and look up later in the list, to find an instance at a current x/y.
Therefore I just need a nice way to store all of them and look up their x/y as fast as possible.
I was not able to use more advanced stuff thats why I stuck to dynamic indexing for now...
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
just a short question to this:

This library uses 2 terrain types. One to symbolize frozen terrain, and one to symbolize burnt terrain.

While in the attached map, both work properly, still, in some maps, when I copy this system over, one of the mentionned terrain changes does not work.

Always the first executed terrain change displays, while the second one does not.

Is there some logic behind this?

If this should be rather in triggers help section, can someone move this please? :)
 
to find an instance at a current x/y
Hm, I'm still not sure the terrain infection library is helpful, but TileDefinition should maybe.
So you maybe can just bind the state frozen/burnt to the index of a tile(x/y).

This library uses 2 terrain types. One to symbolize frozen terrain, and one to symbolize burnt terrain.

While in the attached map, both work properly, still, in some maps, when I copy this system over, one of the mentionned terrain changes does not work.

Always the first executed terrain change displays, while the second one does not.
This sounds pretty strange. Do you mean in other new maps, or in a specific other map?
Because the only issue I might currently think of is if the map already has 16 different tiles in use (maximum terrain type amount),
and none of them represents your ice/fire terrain type. Then the change would fail, because the game can't handle more than 16 terrain types. (@PurgeandFire , is this on the wishlist?^^)

triggers help section
Sure.
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
I bet this was the issue!

thanks for pointing that out, I thought, by using the SetTerrainType() native, we can bypass the limit and it would not be affected. But it makes all sense now, because the maps, where it did not work, the tile limit was reached!

Thank you for being helpfull :)

Also I think, using Tile Definition would help. Also I think you found a very intelligent way to do so :)
I am not sure, if this will be better than the way I identify burnt/frozen ground at the moment, but it will be certainly more precise and bug-resistant.

Do you think, this system may be useful for the spells section? Do not hesitate to call this rubissh please, if it is .
 
Level 11
Joined
Dec 19, 2012
Messages
411
Unfortunately, I'm not sure why I cannot open your map :/ Got trigger function errors pop out said such function doesn't exists (CreateUnitAtLoc, layableMapRect, layersAll, TRIGSTR_003, CreateFogModifierRectBJ) And only your map have this problem though.


EDIT : After enabled UMSWE, I'm able to open your map now...
 
Last edited:
Level 14
Joined
Jul 1, 2008
Messages
1,314
sry I dont get it, why this occurs ...Despite I have enabled UMSWE, I do not use any of its functions. Even if I save the map with UMSWE disabled and try to start it, I get the same issue. Sry, but cool that you can open it now.
 
Status
Not open for further replies.
Top