- 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.
I am glad for any help or criticism.
Thanks and best regards
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
Last edited: