- Joined
- Jul 28, 2024
- Messages
- 222
Building starts burning at ~70% hp
Can i change this? So building will start tu burn at 50% hp
Can i change this? So building will start tu burn at 50% hp
library BuildingFire requires Table
/**
*
* BuildingFire v1.0
*
* Description
* ===========
*
* This system simulates the fire effect that occurs when buildings are reduced to certain
* health percentages. This allows you to control:
* - The percentages in which fire is emitted
* - The fire effect that is emitted
* - Where the fire effect is attached
*
* Sadly, the default fire effect attachment points aren't exposed to us in code, so once
* you add a unit-type to this system, you'll need to manually configure which effects show
* during the small, medium, and large fire phases (and where they show up).
*
* Most buildings use "sprite first", "sprite second", "sprite third", "sprite fourth", "sprite fifth",
* or "sprite sixth" as attachment points for their fires.
*
* While this system is designed around buildings, you can also use it for units.
*
*
* API
* ===
*
* struct BuildingFireDescription
*
* static method create takes integer unitId returns BuildingFireDescription
* - Registers a unit-type into this system. The system will periodically scan the health
* of these units on the map to add/remove fire effects based on this configuration.
*
* real smallFireThreshold
* - The health percentage when small fire effects will be shown (e.g. 0.75 => 75% health)
* - Default is 0.75
*
* real mediumFireThreshold
* - The health percentage when medium fire effects will be shown (e.g. 0.5 => 50% health)
* - Default is 0.5
* - Assumed to be lower than `smallFireThreshold`
*
* real largeFireThreshold
* - The health percentage when large fire effects will be shown (e.g. 0.25 => 25% health)
* - Default is 0.25
* - Assumed to be lower than `mediumFireThreshold`
*
* method addSmallFireEffect takes string path, string attachmentPoint returns nothing
* - Registers an effect to be attached to the building when the building's health becomes
* lower than `smallFireThreshold`.
* - You can add up to 6 individual effects (controlled by MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING).
*
* method addMediumFireEffect takes string path, string attachmentPoint returns nothing
* - Registers an effect to be attached to the building when the building's health becomes
* lower than `mediumFireThreshold`.
* - You can add up to 6 individual effects (controlled by MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING).
*
* method addLargeFireEffect takes string path, string attachmentPoint returns nothing
* - Registers an effect to be attached to the building when the building's health becomes
* lower than `largeFireThreshold`.
* - You can add up to 6 individual effects (controlled by MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING).
*
*
* Sample
* ======
*
* // Adds the type 'hhou' (Farm) to this system so we can customize its fire effects
* local BuildingFireDescription farmDescription = BuildingFireDescription.create('hhou')
*
* // Small fire effects will show below 90% health
* set farmDescription.smallFireThreshold = 0.9
*
* // Medium fire effects will show below 60% health
* set farmDescription.mediumFireThreshold = 0.6
*
* // Large fire effects will show below 30% health
* set farmDescription.largeFireThreshold = 0.3
*
* // Registers which effects show during small, medium, and large fire phases
* // You can specify up to 6 effects to appear
* call farmDescription.addSmallFireEffect(EFFECT_UNDEAD_BUILDING_FIRE_SMALL, "sprite first")
* call farmDescription.addMediumFireEffect(EFFECT_UNDEAD_BUILDING_FIRE_MEDIUM, "sprite first")
* call farmDescription.addLargeFireEffect(EFFECT_UNDEAD_BUILDING_FIRE_LARGE, "sprite first")
*/
globals
/**
*
* Utility Constants
*
*/
// Orcs and Humans use the same fire effects
constant string EFFECT_HUMAN_BUILDING_FIRE_SMALL = "Environment\\SmallBuildingFire\\SmallBuildingFire2.mdl"
constant string EFFECT_HUMAN_BUILDING_FIRE_MEDIUM = "Environment\\LargeBuildingFire\\LargeBuildingFire2.mdl"
constant string EFFECT_HUMAN_BUILDING_FIRE_LARGE = "Environment\\LargeBuildingFire\\LargeBuildingFire1.mdl"
constant string EFFECT_NIGHT_ELF_BUILDING_FIRE_SMALL = "Environment\\NightElfBuildingFire\\ElfSmallBuildingFire2.mdl"
constant string EFFECT_NIGHT_ELF_BUILDING_FIRE_MEDIUM = "Environment\\NightElfBuildingFire\\ElfLargeBuildingFire2.mdl"
constant string EFFECT_NIGHT_ELF_BUILDING_FIRE_LARGE = "Environment\\NightElfBuildingFire\\ElfLargeBuildingFire1.mdl"
constant string EFFECT_UNDEAD_BUILDING_FIRE_SMALL = "Environment\\UndeadBuildingFire\\UndeadSmallBuildingFire2.mdl"
constant string EFFECT_UNDEAD_BUILDING_FIRE_MEDIUM = "Environment\\UndeadBuildingFire\\UndeadLargeBuildingFire2.mdl"
constant string EFFECT_UNDEAD_BUILDING_FIRE_LARGE = "Environment\\UndeadBuildingFire\\UndeadLargeBuildingFire1.mdl"
/**
*
* Configuration
*
*/
// How often to scan unit health
private constant real SCAN_INTERVAL = 0.1
// Maximum number of units to scan per tick (for performance)
private constant integer MAX_UNITS_TO_SCAN_PER_TICK = 30
// Maximum number of fire effects that can be added per building for each phase (small, medium, large)
private constant integer MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING = 6
/**
*
* Other Globals
*
*/
private constant string SMALL_FIRE_THRESHOLD = "small"
private constant string MEDIUM_FIRE_THRESHOLD = "medium"
private constant string LARGE_FIRE_THRESHOLD = "large"
private Table fireDescriptions = 0
private Table fireInstances = 0
private Table fireEffectGroups = 0
private Table constructionStates = 0
private integer filterUnitId = 0
private rect playableArea = null
private group enumGroup = CreateGroup()
private timer scanTimer = CreateTimer()
endglobals
private function IsUnitUnderConstruction takes unit u returns boolean
return constructionStates.boolean[GetHandleId(u)]
endfunction
private struct FireEffectGroup
integer count
method destroy takes nothing returns nothing
local integer index = 0
local effect fireEffect = null
loop
exitwhen index >= count
set fireEffect = fireEffectGroups[this].effect[index]
if fireEffect != null then
call DestroyEffect(fireEffect)
endif
set index = index + 1
endloop
call this.deallocate()
set fireEffect = null
endmethod
method addFireEffect takes unit building, string path, string attachmentPoint returns nothing
local effect fireEffect = null
if building == null or path == null or path == "" or attachmentPoint == null or attachmentPoint == "" then
return
endif
set fireEffect = AddSpecialEffectTarget(path, building, attachmentPoint)
call fireEffectGroups.link(this).effect.save(count, fireEffect)
set count = count + 1
set fireEffect = null
endmethod
endstruct
private struct BuildingFireInstance
private static thistype lastScannedNode = 0
unit building
string activeThreshold
FireEffectGroup effectGroup
thistype next
thistype prev
private method removeDefaultFireBehavior takes nothing returns nothing
call UnitRemoveAbility(building, 'Afir') // On Fire (Other)
call UnitRemoveAbility(building, 'Afih') // On Fire (Human)
call UnitRemoveAbility(building, 'Afio') // On Fire (Orc)
call UnitRemoveAbility(building, 'Afiu') // On Fire (Undead)
call UnitRemoveAbility(building, 'Afin') // On Fire (Night Elf)
endmethod
private method destroyExistingFireEffects takes nothing returns nothing
if effectGroup != 0 then
call effectGroup.destroy()
set effectGroup = 0
endif
endmethod
method destroy takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
// If the list is now empty, disable the scan
if thistype(0).next == 0 then
call PauseTimer(scanTimer)
endif
call this.destroyExistingFireEffects()
call this.deallocate()
endmethod
private method transitionToNoFire takes nothing returns nothing
if activeThreshold == "" then
return
endif
set activeThreshold = ""
call this.destroyExistingFireEffects()
endmethod
private method transitionToSmallFire takes BuildingFireDescription fireDescription returns nothing
local integer index = 0
if activeThreshold == SMALL_FIRE_THRESHOLD then
return
endif
set activeThreshold = SMALL_FIRE_THRESHOLD
call this.destroyExistingFireEffects()
set effectGroup = FireEffectGroup.create()
loop
exitwhen index >= fireDescription.smallFireEffectCount
call effectGroup.addFireEffect(building, fireDescription.smallFireEffectPath[index], fireDescription.smallFireEffectAttachmentPoint[index])
set index = index + 1
endloop
endmethod
private method transitionToMediumFire takes BuildingFireDescription fireDescription returns nothing
local integer index = 0
if activeThreshold == MEDIUM_FIRE_THRESHOLD then
return
endif
set activeThreshold = MEDIUM_FIRE_THRESHOLD
call this.destroyExistingFireEffects()
set effectGroup = FireEffectGroup.create()
loop
exitwhen index >= fireDescription.mediumFireEffectCount
call effectGroup.addFireEffect(building, fireDescription.mediumFireEffectPath[index], fireDescription.mediumFireEffectAttachmentPoint[index])
set index = index + 1
endloop
endmethod
private method transitionToLargeFire takes BuildingFireDescription fireDescription returns nothing
local integer index = 0
if activeThreshold == LARGE_FIRE_THRESHOLD then
return
endif
set activeThreshold = LARGE_FIRE_THRESHOLD
call this.destroyExistingFireEffects()
set effectGroup = FireEffectGroup.create()
loop
exitwhen index >= fireDescription.largeFireEffectCount
call effectGroup.addFireEffect(building, fireDescription.largeFireEffectPath[index], fireDescription.largeFireEffectAttachmentPoint[index])
set index = index + 1
endloop
endmethod
method checkHealth takes nothing returns nothing
local BuildingFireDescription fireDescription = fireDescriptions[GetUnitTypeId(building)]
local real lifePercentage
if IsUnitUnderConstruction(building) then
return
endif
if GetUnitTypeId(building) == 0 or IsUnitType(building, UNIT_TYPE_DEAD) or fireDescription == 0 then
call this.destroy()
return
endif
set lifePercentage = GetWidgetLife(building) / GetUnitState(building, UNIT_STATE_MAX_LIFE)
if lifePercentage > fireDescription.smallFireThreshold then
call this.transitionToNoFire()
elseif lifePercentage > fireDescription.mediumFireThreshold then
call this.transitionToSmallFire(fireDescription)
elseif lifePercentage > fireDescription.largeFireThreshold then
call this.transitionToMediumFire(fireDescription)
else
call this.transitionToLargeFire(fireDescription)
endif
endmethod
private static method periodicScan takes nothing returns nothing
local thistype current = lastScannedNode.next
local integer scanCount = 0
loop
exitwhen current == 0 or scanCount >= MAX_UNITS_TO_SCAN_PER_TICK
call current.checkHealth()
set lastScannedNode = current
set current = current.next
set scanCount = scanCount + 1
endloop
// If reached the end of the list without scanning the max limit
// then restart at the beginning of the list next tick
if scanCount < MAX_UNITS_TO_SCAN_PER_TICK then
set lastScannedNode = thistype(0)
endif
endmethod
static method create takes unit building returns thistype
local thistype this
local integer handleId = GetHandleId(building)
if fireInstances[handleId] != 0 then
return fireInstances[handleId]
endif
set this = thistype.allocate()
set this.building = building
set this.activeThreshold = ""
set this.effectGroup = 0
set fireInstances[handleId] = this
// If the list is no longer empty, start scanning
if thistype(0).next == 0 then
call TimerStart(scanTimer, SCAN_INTERVAL, true, function thistype.periodicScan)
endif
set thistype(0).next.prev = this
set this.next = thistype(0).next
set thistype(0).next = this
set this.prev = thistype(0)
call this.removeDefaultFireBehavior()
return this
endmethod
private static method enumUnitTypeFilter takes nothing returns boolean
if GetUnitTypeId(GetFilterUnit()) == filterUnitId then
call BuildingFireInstance.create(GetFilterUnit())
endif
return false
endmethod
static method createInstancesForUnitType takes integer unitId returns nothing
set filterUnitId = unitId
call GroupEnumUnitsInRect(enumGroup, playableArea, Condition(function thistype.enumUnitTypeFilter))
endmethod
private static method onEnterMap takes nothing returns nothing
if fireDescriptions[GetUnitTypeId(GetTriggerUnit())] != 0 then
call BuildingFireInstance.create(GetTriggerUnit())
endif
endmethod
private static method onConstructStart takes nothing returns nothing
call constructionStates.boolean.store(GetTriggerUnit(), true)
endmethod
private static method onConstructFinish takes nothing returns nothing
call constructionStates.boolean.forget(GetTriggerUnit())
endmethod
private static method onInit takes nothing returns nothing
local trigger enterMapTrigger = CreateTrigger()
local region playableRegion = CreateRegion()
local trigger startConstructionTrigger = CreateTrigger()
local trigger finishConstructionTrigger = CreateTrigger()
set playableArea = Rect(GetCameraBoundMinX() - GetCameraMargin(CAMERA_MARGIN_LEFT), GetCameraBoundMinY() - GetCameraMargin(CAMERA_MARGIN_BOTTOM), GetCameraBoundMaxX() + GetCameraMargin(CAMERA_MARGIN_RIGHT), GetCameraBoundMaxY() + GetCameraMargin(CAMERA_MARGIN_TOP))
call RegionAddRect(playableRegion, playableArea)
call TriggerRegisterEnterRegion(enterMapTrigger, playableRegion, null)
call TriggerAddAction(enterMapTrigger, function thistype.onEnterMap)
call TriggerRegisterAnyUnitEventBJ(startConstructionTrigger, EVENT_PLAYER_UNIT_CONSTRUCT_START)
call TriggerAddAction(startConstructionTrigger, function thistype.onConstructStart)
call TriggerRegisterAnyUnitEventBJ(finishConstructionTrigger, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH)
call TriggerAddAction(finishConstructionTrigger, function thistype.onConstructFinish)
set enterMapTrigger = null
set playableRegion = null
endmethod
endstruct
struct BuildingFireDescription
real smallFireThreshold
real mediumFireThreshold
real largeFireThreshold
readonly string array smallFireEffectPath[MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING]
readonly string array smallFireEffectAttachmentPoint[MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING]
readonly integer smallFireEffectCount
readonly string array mediumFireEffectPath[MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING]
readonly string array mediumFireEffectAttachmentPoint[MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING]
readonly integer mediumFireEffectCount
readonly string array largeFireEffectPath[MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING]
readonly string array largeFireEffectAttachmentPoint[MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING]
readonly integer largeFireEffectCount
method addSmallFireEffect takes string path, string attachmentPoint returns nothing
if smallFireEffectCount >= MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING then
return
endif
set smallFireEffectPath[smallFireEffectCount] = path
set smallFireEffectAttachmentPoint[smallFireEffectCount] = attachmentPoint
set smallFireEffectCount = smallFireEffectCount + 1
endmethod
method addMediumFireEffect takes string path, string attachmentPoint returns nothing
if mediumFireEffectCount >= MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING then
return
endif
set mediumFireEffectPath[mediumFireEffectCount] = path
set mediumFireEffectAttachmentPoint[mediumFireEffectCount] = attachmentPoint
set mediumFireEffectCount = mediumFireEffectCount + 1
endmethod
method addLargeFireEffect takes string path, string attachmentPoint returns nothing
if largeFireEffectCount >= MAX_ACTIVE_FIRE_EFFECTS_PER_BUILDING then
return
endif
set largeFireEffectPath[largeFireEffectCount] = path
set largeFireEffectAttachmentPoint[largeFireEffectCount] = attachmentPoint
set largeFireEffectCount = largeFireEffectCount + 1
endmethod
static method create takes integer unitId returns thistype
local thistype this
if fireDescriptions[unitId] != 0 then
return fireDescriptions[unitId]
endif
set this = thistype.allocate()
set this.smallFireThreshold = 0.75
set this.mediumFireThreshold = 0.50
set this.largeFireThreshold = 0.25
set this.smallFireEffectCount = 0
set this.mediumFireEffectCount = 0
set this.largeFireEffectCount = 0
set fireDescriptions[unitId] = this
call BuildingFireInstance.createInstancesForUnitType(unitId)
return this
endmethod
private static method onInit takes nothing returns nothing
set fireInstances = Table.create()
set fireDescriptions = Table.create()
set fireEffectGroups = Table.create()
set constructionStates = Table.create()
endmethod
endstruct
endlibrary
scope SetupFires initializer Init
private function Init takes nothing returns nothing
local BuildingFireDescription castleDescription = BuildingFireDescription.create('hcas')
local BuildingFireDescription farmDescription = BuildingFireDescription.create('hhou')
local BuildingFireDescription knightDescription = BuildingFireDescription.create('hkni')
local string knightFireEffect = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeDamageTarget.mdl"
// Set-up Castle Thresholds
set castleDescription.smallFireThreshold = 0.9
set castleDescription.mediumFireThreshold = 0.6
set castleDescription.largeFireThreshold = 0.3
call castleDescription.addSmallFireEffect(EFFECT_HUMAN_BUILDING_FIRE_SMALL, "sprite first")
call castleDescription.addSmallFireEffect(EFFECT_HUMAN_BUILDING_FIRE_SMALL, "sprite second")
call castleDescription.addMediumFireEffect(EFFECT_HUMAN_BUILDING_FIRE_MEDIUM, "sprite first")
call castleDescription.addMediumFireEffect(EFFECT_HUMAN_BUILDING_FIRE_MEDIUM, "sprite second")
call castleDescription.addMediumFireEffect(EFFECT_HUMAN_BUILDING_FIRE_MEDIUM, "sprite third")
call castleDescription.addLargeFireEffect(EFFECT_HUMAN_BUILDING_FIRE_LARGE, "sprite first")
call castleDescription.addLargeFireEffect(EFFECT_HUMAN_BUILDING_FIRE_LARGE, "sprite second")
call castleDescription.addLargeFireEffect(EFFECT_HUMAN_BUILDING_FIRE_LARGE, "sprite third")
call castleDescription.addLargeFireEffect(EFFECT_HUMAN_BUILDING_FIRE_LARGE, "sprite fourth")
// Set-up Farm Thresholds
set farmDescription.smallFireThreshold = 0.75
set farmDescription.mediumFireThreshold = 0.5
set farmDescription.largeFireThreshold = 0.25
call farmDescription.addSmallFireEffect(EFFECT_UNDEAD_BUILDING_FIRE_SMALL, "sprite first")
call farmDescription.addMediumFireEffect(EFFECT_UNDEAD_BUILDING_FIRE_MEDIUM, "sprite first")
call farmDescription.addLargeFireEffect(EFFECT_UNDEAD_BUILDING_FIRE_LARGE, "sprite first")
// Set-up Knight Thresholds
set knightDescription.smallFireThreshold = 0.8
set knightDescription.mediumFireThreshold = 0.5
set knightDescription.largeFireThreshold = 0.2
call knightDescription.addSmallFireEffect(knightFireEffect, "chest")
call knightDescription.addMediumFireEffect(knightFireEffect, "chest")
call knightDescription.addMediumFireEffect(knightFireEffect, "left hand")
call knightDescription.addMediumFireEffect(knightFireEffect, "right hand")
call knightDescription.addLargeFireEffect(knightFireEffect, "chest")
call knightDescription.addLargeFireEffect(knightFireEffect, "left hand")
call knightDescription.addLargeFireEffect(knightFireEffect, "right hand")
call knightDescription.addLargeFireEffect(knightFireEffect, "origin")
call knightDescription.addLargeFireEffect(knightFireEffect, "overhead")
endfunction
endscope