Summary
For the past 3 days we have been observing engine hard crashes every 1 to 3 times we compiled our verse code. We initially thought it was related to the v38.10 patch, but it seems to be something related to a very specific Verse issue. The strange thing is it has nothing to do with the logic or syntax of the Verse code, but rather the location of class definitions in the file.
Please select what you are reporting on:
Unreal Editor for Fortnite
What Type of Bug are you experiencing?
Verse
Steps to Reproduce
We’re uncertain how to reproduce this in a new project, but we know how to reproduce it in our project. Here is the exact code that is causing our project to crash after 2-3 compiles. Note that it’s specifically due to the existence of the two classes buff_regeneration_health and buff_regeneration_shield. If we delete either one, the project does not crash.
Ironically, the fix we identified was by moving those two class definitions to the top of the file (above buff_damage_multiplier), and all of a sudden our project no longer crashes. The only difference is the location of where in this file our classes are defined, no other code is changed.
using { /Fortnite.com/Characters }
using { /Fortnite.com/Game }
using { /Verse.org/Assets }
using { /Verse.org/Simulation }
using { Kini.Algo }
using { Kini.Extensions }
buff_damage_multiplier := class(buff, buffable_dealt_damage_listener):
DisplayName<override><localizes>:message = "Damage Multiplier"
Texture<override>:texture = Textures.T_UI_Damage
var Multiplier:float = 0.0
OnBuffableDealtDamage<override>(DamagedBuffable:buffable, DamageResult:damage_result):void =
AdditionalDamage := DamageResult.Amount * Multiplier
if (AdditionalDamage > 0.0, Character := DamagedBuffable.GetAgent().GetFortCharacter[]):
Character.Damage of damage_args:
Amount := AdditionalDamage
Instigator := DamageResult.Instigator
Source := option{Buffable}
buff_damage_over_time := class(buff):
DisplayName<override><localizes>:message = "DOT"
Texture<override>:texture = Textures.T_UI_Damage
DamagePerSecond:float = 0.0
Duration:float = 0.0
Instigator:?game_action_instigator = false
Source:?game_action_causer = false
var<private> StartTime:float = 0.0
GetWidgetTitle<override>():message =
TitleMessage()
OnAddedToBuffable<override>():void =
(super:)OnAddedToBuffable()
spawn{ResolveDamageOverTime()}
HasExpired()<transacts><decides>:void =
Duration > 0.0
CurrentTime := GetSimulationElapsedTime()
CurrentTime - StartTime > Duration
ResolveDamageOverTime()<suspends>:void =
Character := Buffable.GetAgent().WaitForFortCharacter()
set StartTime = GetSimulationElapsedTime()
loop:
Sleep(1.0)
if (IsCancelled?). break
Character.Damage of damage_args:
Amount := DamagePerSecond
Instigator := Instigator
Source := Source
if (HasExpired[]). break
TitleMessage<localizes>():message = "{DamagePerSecond}/s\n{Duration}s"
buff_regenerate_health := class(buff):
var AmountPerSecond:float = 0.0
GetWidgetTitle<override>():message =
BuffMessage.ValuePerSecond(AmountPerSecond.Format(1))
OnAddedToBuffable<override>():void =
(super:)OnAddedToBuffable()
spawn{ResolveRegenerate()}
ResolveRegenerate()<suspends>:void =
Character := Buffable.GetAgent().WaitForFortCharacter()
loop:
if (IsCancelled?). break
if (not Character.IsDownButNotOut[]):
MaxHealth := Character.GetMaxHealth()
CurrentHealth := Character.GetHealth()
if (CurrentHealth < MaxHealth):
Character.SetHealth(CurrentHealth + AmountPerSecond)
Sleep(1.0)
buff_regenerate_shield := class(buff):
var AmountPerSecond:float = 0.0
GetWidgetTitle<override>():message =
BuffMessage.ValuePerSecond(AmountPerSecond.Format(1))
OnAddedToBuffable<override>():void =
(super:)OnAddedToBuffable()
spawn{ResolveRegenerate()}
ResolveRegenerate()<suspends>:void =
Character := Buffable.GetAgent().WaitForFortCharacter()
loop:
if (IsCancelled?). break
if (not Character.IsDownButNotOut[]):
MaxShield := Character.GetMaxShield()
CurrentShield := Character.GetShield()
if (CurrentShield < MaxShield):
Character.SetShield(CurrentShield + AmountPerSecond)
Sleep(1.0)
buff_siphon := class(buff, buffable_dealt_damage_listener):
DisplayName<override><localizes>:message = "Siphon"
Texture<override>:texture = Textures.T_UI_Siphon
var Percentage:float = 0.0
GetWidgetTitle<override>():message =
TitleMessage(Percentage)
OnBuffableDealtDamage<override>(DamagedBuffable:buffable, DamageResult:damage_result):void =
Agent := Buffable.GetAgent()
if (Percentage > 0.0, DamageResult.Amount > 0.0, Character := Agent.GetFortCharacter[]):
MaxHealth := Character.GetMaxHealth()
CurrentHealth := Character.GetHealth()
var Remainder:float = DamageResult.Amount * Percentage
if (CurrentHealth < MaxHealth):
NewHealth := CurrentHealth + Remainder
Character.SetHealth(NewHealth)
set Remainder = NewHealth - MaxHealth
if (Remainder > 0.0):
MaxShield := Character.GetMaxShield()
CurrentShield := Character.GetShield()
if (CurrentShield < MaxShield):
Character.SetShield(CurrentShield + Remainder)
TitleMessage<localizes>(Value:float):message = "+{Value}%"
buff_incendiary_ammo := class(buff, buffable_dealt_damage_listener):
DisplayName<override><localizes>:message = "Incendiary Ammo"
Texture<override>:texture = Textures.T_UI_Incendiary_Ammo
var DamagePerSecond:float = 0.0
var Duration:float = 0.0
var Probability:float = 0.0
OnBuffableDealtDamage<override>(DamagedBuffable:buffable, DamageResult:damage_result):void =
if (Kini.Math.RollProbability[Probability]):
Burn := buff_burn:
Buffable := DamagedBuffable
DamagePerSecond := DamagePerSecond
Duration := Duration
Instigator := DamageResult.Instigator
Source := option{Buffable}
Burn.Add()
TitleMessage<localizes>():message = "{Probability}%\n{DamagePerSecond}/s\n{Duration}s"
buff_burn := class(buff_damage_over_time):
DisplayName<override><localizes>:message = "Burn"
Texture<override>:texture = Textures.T_UI_Incendiary_Ammo
OnAddedToBuffable<override>():void =
(super:)OnAddedToBuffable()
Agent := Buffable.GetAgent()
if (BuffManager := GetBuffManager[]):
BuffManager.PowerupBurn.Pickup(Agent)
OnRemovedFromBuffable<override>():void =
(super:)OnRemovedFromBuffable()
Agent := Buffable.GetAgent()
if (BuffManager := GetBuffManager[]):
BuffManager.PowerupBurnRemove.Pickup(Agent)
BuffMessage<public> := module:
ValuePerSecond<public><localizes>(Value:string):message = "{Rate}/s"
Expected Result
We expect the engine not to crash due to the existence of these classes
Observed Result
It crashes 100% of the time after a maximum of 3 compiles
Platform(s)
PC UEFN
Island Code
Unpublished
Video
Note the crash around 0:24, this is when the 2 class definitions are lower in the file.
At 0:58, I cut/paste the 2 class definitions higher up in the same verse file, no other changes are made
At 1:55 I finish restarting the project after the crash, reopen VS Code to confirm the two class definitions are now at the top of the file, and then hit compile 5+ times without issue.
At 2:36, I cut/paste the 2 class definitions back down lower in the file to where it originally was, hit compile, and the editor immediately crashes.
Additional Notes
We spent nearly 8 hours troubleshooting this today to finally identify what was causing the crash, but the solution does not seem reasonable and makes us nervous that it will happen again. Any help would be appreciated, please do reach out and we can point you to our project and version number to reproduce it yourselves.