1 Star 1 Fork 1

游戏程序龙之介/黑帝斯源码拆解学习

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
RoomEvents.lua 102.40 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775
function ActivateRandomGroups( eventSource, args )
if args.Groups == nil then
return
end
local groups = ShallowCopyTable( args.Groups )
local count = args.Count or 1
for i = 1, count do
local groupToActivate = RemoveRandomValue( groups )
Activate({ Name = groupToActivate })
local ids = GetIds({ Name = groupToActivate })
SetupEnemyObjects( ids )
end
end
function ActivatePrePlacedByShrineLevel( eventSource, args )
local shrineLevel = GetNumMetaUpgrades( eventSource.ShrineMetaUpgradeName )
ActivatePrePlaced( eventSource, args[shrineLevel] )
end
function ActivatePrePlaced( eventSource, args )
local doPresentation = true
if args.SkipPresentation then
doPresentation = false
end
if args.Types ~= nil then
for k, type in pairs( args.Types ) do
local typeIds = GetInactiveIdsByType({ Name = type })
Activate({ Ids = typeIds, DoPresentation = doPresentation })
SetupEnemyObjects( typeIds, args )
end
return
end
if args.FractionMin == nil then
args.FractionMin = 1.0
end
if args.FractionMax == nil then
args.FractionMax = 1.0
end
local currentRun = CurrentRun
local toActivate = {}
if args.Groups ~= nil then
for k, groupName in pairs( args.Groups ) do
local ids = GetInactiveIds({ Name = groupName })
for j, id in pairs( ids ) do
local name = GetName({ Id = id, CheckInactive = true })
local enemyData = EnemyData[name]
if IsActivationEligible( id, enemyData ) then
toActivate[id] = enemyData
end
end
end
else
for enemyName, enemyData in pairs( EnemyData ) do
local enemyTypeIds = GetInactiveIdsByType({ Name = enemyName })
if args.LegalTypes == nil or Contains( args.LegalTypes, enemyName ) then
for k, id in pairs( enemyTypeIds ) do
if IsActivationEligible( id, enemyData ) then
toActivate[id] = enemyData
end
end
end
end
end
local prePlacedFraction = RandomFloat( args.FractionMin, args.FractionMax )
local prePlacedCount = TableLength( toActivate )
local activateCount = round( prePlacedCount * prePlacedFraction )
for i = 1, activateCount, 1 do
local enemyId = GetRandomKey( toActivate )
local enemyData = toActivate[enemyId]
local newEnemy = DeepCopyTable( enemyData )
newEnemy.ObjectId = enemyId
Activate({ Id = newEnemy.ObjectId, DoPresentation = doPresentation })
SetupEnemyObject( newEnemy, currentRun, args )
toActivate[enemyId] = nil
if args.SpawnOverrides ~= nil then
for key, value in pairs( args.SpawnOverrides ) do
newEnemy[key] = value
end
end
end
end
function ActivatePrePlacedObstacles( eventSource, args )
Activate({ Ids = args.Ids, Names = args.Groups })
end
function FadeOutIds( eventSource, args )
SetAlpha({ Ids = args.Ids, Fraction = 0.0, Duration = args.Duration or 0.3 })
end
function ActivatePrePlacedUnits( eventSource, args )
for k, id in pairs( args.Ids ) do
local name = GetName({ Id = id, CheckInactive = true })
if name ~= nil then
local enemyData = EnemyData[name]
local newEnemy = DeepCopyTable( enemyData )
newEnemy.ObjectId = id
Activate({ Id = newEnemy.ObjectId, DoPresentation = args.DoPresentation })
SetupEnemyObject( newEnemy, CurrentRun, args )
if args.DistanceTrigger ~= nil then
thread( CheckDistanceTrigger, args.DistanceTrigger, newEnemy )
end
if args.FadeInDuration ~= nil then
SetAlpha({ Id = id, Fraction = 0 })
SetAlpha({ Id = id, Fraction = 1, Duration = args.FadeInDuration })
end
if args.CheckConversations then
CheckAvailableTextLines( newEnemy )
end
if args.AddEncounterEvent ~= nil then
table.insert( CurrentRun.CurrentRoom.Encounter.UnthreadedEvents, args.AddEncounterEvent )
end
end
end
end
function ActivateAnyPrePlaced( eventSource, args )
Activate({ Ids = args.Ids, Names = args.Groups, DoPresentation = args.DoPresentation })
for k, id in pairs( args.Ids ) do
local name = GetName({ Id = id, CheckInactive = true })
local enemyData = EnemyData[name]
if enemyData ~= nil then
local newEnemy = DeepCopyTable( enemyData )
newEnemy.ObjectId = id
args.PreLoadBinks = true
SetupEnemyObject( newEnemy, CurrentRun, args )
end
end
end
function ActivateOneOfAPrePlacedGroupSet( eventSource, args )
if args.ActivationChance then
if not RandomChance( args.ActivationChance ) then
return
end
end
local randomGroup = RemoveRandomValue( args.Groups )
Activate({ Name = randomGroup })
local ids = GetIds({ Name = randomGroup })
for k, id in pairs( ids ) do
local unitName = GetName({ Id = id })
local unitData = EnemyData[unitName]
if unitData ~= nil then
local newUnit = DeepCopyTable( unitData )
newUnit.ObjectId = id
SetupEnemyObject( newUnit, CurrentRun, args )
end
end
end
function ActivateRotatingNPCs( eventSource, args )
local doPresentation = true
if args.SkipPresentation then
doPresentation = false
end
local numActivations = 0
local activationCap = nil
if args.ActivationCapMin ~= nil and args.ActivationCapMax ~= nil then
activationCap = RandomInt( args.ActivationCapMin, args.ActivationCapMax )
end
local ids = {}
ConcatTableValues( ids, args.Ids )
if args.Types ~= nil then
for k, type in ipairs( args.Types ) do
local typeIds = GetInactiveIdsByType({ Name = type })
ConcatTableValues( ids, typeIds )
end
end
while not IsEmpty( ids ) do
local id = RemoveRandomValue( ids )
local name = GetName({ Id = id, CheckInactive = true })
local unitData = EnemyData[name]
if IsActivationEligible( id, unitData ) then
if ShouldRotatorActivate( id, unitData, numActivations, activationCap ) then
if unitData.Binks ~= nil then
PreLoadBinks({ Names = unitData.Binks })
end
Activate({ Ids = id, DoPresentation = doPresentation })
local newUnit = DeepCopyTable( unitData )
newUnit.ObjectId = id
SetupEnemyObject( newUnit, CurrentRun, args )
if CurrentRun.AnimationState[newUnit.ObjectId] ~= nil then
SetAnimation({ DestinationId = newUnit.ObjectId, Name = CurrentRun.AnimationState[newUnit.ObjectId] })
end
if CurrentRun.EventState ~= nil then
local eventState = CurrentRun.EventState[newUnit.ObjectId]
if eventState ~= nil then
local eventFunction = _G[eventState.FunctionName]
if eventFunction ~= nil then
thread( eventFunction, newUnit, eventState.Args )
end
end
end
CurrentRun.ActivationRecord[id] = true
numActivations = numActivations + 1
else
if unitData.MissingDistanceTrigger ~= nil then
local missingUnit = {}
missingUnit.Name = name
missingUnit.ObjectId = SpawnObstacle({ Name = "BlankObstacle" })
local location = GetLocation({ Id = id, CheckInactive = true })
Teleport({ Id = missingUnit.ObjectId, OffsetX = location.X, OffsetY = location.Y })
thread( CheckDistanceTrigger, unitData.MissingDistanceTrigger, missingUnit )
end
end
else
if unitData.ActivationFailedDistanceTrigger ~= nil then
local missingUnit = {}
missingUnit.Name = name
missingUnit.ObjectId = SpawnObstacle({ Name = "BlankObstacle" })
local location = GetLocation({ Id = id, CheckInactive = true })
Teleport({ Id = missingUnit.ObjectId, OffsetX = location.X, OffsetY = location.Y })
thread( CheckDistanceTrigger, unitData.ActivationFailedDistanceTrigger, missingUnit )
end
end
end
end
function SetPersephoneAwayAtRunStart()
if GameState.Flags.PersephoneAwayAtRunStart then
GameState.Flags.PersephoneAway = true
GameState.Flags.PersephoneAwayAtRunStart = false
end
end
function SetPersephoneAway( eventSource, args )
local completedRuns = GetCompletedRuns()
GameState.LastPersephoneAwayRunCount = completedRuns
GameState.Flags.PersephoneGoingAway = false
GameState.Flags.PersephoneAwayAtRunStart = true
end
function CheckIsPersephoneAway( eventSource, args )
if PreviousDeathAreaRoom ~= nil then
return
end
local completedRuns = GetCompletedRuns()
GameState.LastPersephoneReturnRunCount = GameState.LastPersephoneReturnRunCount or completedRuns
if GameState.Flags.PersephoneAway then
if GameState.LastPersephoneAwayRunCount ~= nil then
if completedRuns - GameState.LastPersephoneAwayRunCount < 7 then
return
else
GameState.Flags.PersephoneAway = false
GameState.Flags.PersephoneJustReturned = true
GameState.LastPersephoneReturnRunCount = completedRuns
end
end
else
if completedRuns - GameState.LastPersephoneReturnRunCount >= 7 then
GameState.Flags.PersephoneGoingAway = true
end
end
if completedRuns - GameState.LastPersephoneReturnRunCount > 0 then
GameState.Flags.PersephoneJustReturned = false
end
end
function ShouldRotatorActivate( id, unitData, numActivations, activationCap )
if activationCap == nil then
return true
end
if numActivations < activationCap then
return true
end
if not GameState.NPCInteractions[unitData.Name] then
return true
end
if not IsEmpty( GameState.RunHistory ) then
local prevRun = GameState.RunHistory[#GameState.RunHistory]
if prevRun ~= nil and not prevRun.ActivationRecord[id] then
return true
end
end
return false
end
function RandomizeTrapTypes( eventSource, args )
local room = eventSource
local trapOptions = args.TrapOptions or room.TrapOptions
local trapType = GetRandomValue(trapOptions)
if room ~= nil then
room.TrapType = trapType
end
if args.FractionMin == nil then
args.FractionMin = 1.0
end
if args.FractionMax == nil then
args.FractionMax = 1.0
end
local activateFraction = RandomFloat(args.FractionMin, args.FractionMax)
local deactivatedTraps = GetMapThingsByType({ Names = {"TrapPadDisabled", "TrapFissureDisabled"} })
local deactivatedCount = TableLength( deactivatedTraps )
local activateCount = round( deactivatedCount * activateFraction )
for i = 0, activateCount, 1 do
local disabledId = RemoveRandomKey( deactivatedTraps )
if disabledId ~= nil then
ActivateTrap( disabledId, trapType )
end
end
end
function ActivateTrap( disabledId, trapType )
SwapName({ DestinationId = disabledId, Name = trapType })
end
function IsActivationEligible( id, sourceData )
if sourceData == nil then
return false
end
if sourceData.ActivateRequirements ~= nil and not IsGameStateEligible( CurrentRun, sourceData.ActivateRequirements ) then
return false
end
if sourceData.IdActivateRequirements ~= nil and sourceData.IdActivateRequirements[id] ~= nil and not IsGameStateEligible( CurrentRun, sourceData.IdActivateRequirements[id] ) then
return false
end
return true
end
function SetupEnemyObjects( ids, args )
for k, id in pairs( ids ) do
local name = GetName({ Id = id })
local enemyData = EnemyData[name]
if enemyData ~= nil then
local newEnemy = DeepCopyTable( enemyData )
newEnemy.ObjectId = id
SetupEnemyObject( newEnemy, CurrentRun )
if args.CheckConversations then
CheckConversations( newEnemy )
end
end
end
end
function DisableObjects( eventSource, args )
local thingIds = GetIdsByType({ Name = args.Type })
UseableOff({ Ids = thingIds })
for k, thingId in pairs( thingIds ) do
ActivatedObjects[thingId] = nil
end
end
function HandleThanatosSpawn( eventSource )
local currentRun = CurrentRun
local currentRoom = CurrentRun.CurrentRoom
local currentEncounter = eventSource
local newUnit = DeepCopyTable( EnemyData.NPC_Thanatos_Field_01 )
local spawnPointId = SelectSpawnPoint( currentRoom, newUnit, { SpawnNearId = currentRun.Hero.ObjectId, SpawnRadius = 500 })
if spawnPointId == nil then
DebugPrint({ Text = "Thanatos no spawn point; spawning on player." })
spawnPointId = currentRun.Hero.ObjectId
end
newUnit.ObjectId = SpawnUnit({ Name = "NPC_Thanatos_Field_01", Group = "Standing", DestinationId = spawnPointId })
currentEncounter.ThanatosId = newUnit.ObjectId
SetupEnemyObject( newUnit, CurrentRun, { IgnoreAI = true, PreLoadBinks = true, } )
UseableOff({ Id = newUnit.ObjectId })
ActivatedObjects[newUnit.ObjectId] = newUnit
ThanatosSpawnPresentation( newUnit )
SetupAI( CurrentRun, newUnit )
currentRun.ThanatosSpawns = currentRun.ThanatosSpawns + 1
end
function HandleEnemySpawns( eventSource )
if GetConfigOptionValue({ Name = "EditingMode" }) then
waitUntil( "EditingModeOff" )
end
local currentRun = CurrentRun
local currentRoom = CurrentRun.CurrentRoom
local currentEncounter = eventSource
if currentEncounter.SpawnWaves == nil then
if currentEncounter.Spawns ~= nil then
currentEncounter.SpawnWaves = { {Spawns = currentEncounter.Spawns} }
else
DebugPrint({ Text = "Encounter has no spawns!" })
return
end
end
if currentEncounter.SpawnHazards then
thread(HandleHazardSpawns, currentRoom, currentEncounter)
end
if currentEncounter.SpawnShadeWeapons then
thread(HandleShadeWeaponSpawns, currentRoom)
end
CheckObjectiveSet( currentEncounter.EncounterType )
CheckObjectiveSet( currentEncounter.ObjectiveSets )
currentEncounter.ActiveEnemyCap = CalculateActiveEnemyCap( currentRun, currentRoom, currentEncounter )
if currentEncounter.SpawnPassiveRoomWeapons ~= nil then
currentEncounter.PassiveRoomWeapons = {}
for k, name in pairs(currentEncounter.SpawnPassiveRoomWeapons) do
local newEnemy = DeepCopyTable( EnemyData[name] )
newEnemy.ObjectId = SpawnUnit({ Name = name, Group = "Standing", DestinationId = CurrentRun.Hero.ObjectId, DoActivatePresentation = false })
SetupEnemyObject( newEnemy, CurrentRun )
table.insert(currentEncounter.PassiveRoomWeapons, newEnemy.ObjectId)
end
end
local waves = currentEncounter.SpawnWaves
local waveCount = TableLength( waves )
for waveNum, wave in ipairs( waves ) do
DebugPrint({ Text = "Starting Wave "..waveNum })
currentEncounter.CurrentWaveNum = waveNum
if wave.MetaUpgradeDataOverrides and GetNumMetaUpgrades(currentEncounter.ShrineMetaUpgradeName) > 0 then
OverwriteTableKeys(wave, wave.MetaUpgradeDataOverrides)
end
-- Start a new wave
if currentEncounter.WaveStartPresentationFunction ~= nil then
local waveStartFunction = _G[currentEncounter.WaveStartPresentationFunction]
waveStartFunction(currentEncounter)
end
if GetConfigOptionValue({ Name = "EditingMode" }) then
waitUntil( "EditingModeOff" )
end
wait( wave.StartDelay, currentEncounter.SpawnThreadName or RoomThreadName )
if wave.OverrideValues ~= nil then
OverwriteTableKeys(currentEncounter, wave.OverrideValues)
currentEncounter.ActiveEnemyCap = CalculateActiveEnemyCap( currentRun, currentRoom, currentEncounter )
end
WaveStartChanges(wave, currentEncounter)
currentEncounter.Spawns = {}
AddEncounterLayer( currentRun, currentRoom, currentEncounter, wave )
local ignoreSpawnPreferences = currentEncounter.IgnoreSpawnPreferences or false
if currentEncounter.PreSpawnEnemies and waveNum == 1 then
currentEncounter.PreSpawning = true
currentEncounter.RequireMinPlayerDistance = currentEncounter.PreSpawnMinPlayerDistance
end
while GetRemainingSpawns( currentRun, currentRoom, currentEncounter ) > 0 and currentEncounter.InProgress do
local waitForEnemeyKilled = false
if currentEncounter.GroupReinforcements and currentEncounter.WaitingForAllDead then
if GetActiveEnemyCount() == 0 then
currentEncounter.WaitingForAllDead = false
ignoreSpawnPreferences = true
end
end
local nextSpawnInterval = RandomFloat( currentEncounter.SpawnIntervalMin, currentEncounter.SpawnIntervalMax )
if currentEncounter.PreSpawnEnemies and currentEncounter.PreSpawning and waveNum == 1 then
nextSpawnInterval = 0
end
local spawnedId = nil
if GetActiveEnemyCount() < currentEncounter.ActiveEnemyCap and not currentEncounter.WaitingForAllDead then
spawnedId = HandleNextSpawn( currentEncounter, ignoreSpawnPreferences )
if spawnedId == nil or GetActiveEnemyCount() >= currentEncounter.ActiveEnemyCap then
-- Hit cap
waitForEnemeyKilled = true
currentEncounter.RequireMinPlayerDistance = 0
currentEncounter.PreSpawning = false
if currentEncounter.ReinforcementsDataOverride ~= nil then
OverwriteTableKeys(currentEncounter, currentEncounter.ReinforcementsDataOverride)
end
local minEnemiesLeft = 5
if currentEncounter.GroupReinforcements and GetRemainingSpawns( currentRun, currentRoom, currentEncounter ) > minEnemiesLeft then
currentEncounter.WaitingForAllDead = true
end
if currentEncounter.GroupReinforcements and currentEncounter.RequireNearPlayerDistance ~= nil then
currentRoom.SpawnPointsUsed = {}
end
end
end
-- Prevent soft lock if no enemy spawned
if spawnedId == nil and (nextSpawnInterval == nil or nextSpawnInterval <= 0) then
nextSpawnInterval = 0.1
end
if waitForEnemeyKilled then
waitUntil( "RequiredEnemyKilled" )
if CheckCancelSpawns(currentRun, currentRoom, currentEncounter) then
break
end
nextSpawnInterval = currentEncounter.SpawnCapHitReleaseDuration or nextSpawnInterval
nextSpawnInterval = math.max( nextSpawnInterval, 0.03 ) -- Don't handle the next spawn on the same frame as the last kill
end
--DebugAssert({ Condition = nextSpawnInterval > 0, Text = "0 spawn interval may cause freeze." })
if GetConfigOptionValue({ Name = "EditingMode" }) then
waitUntil( "EditingModeOff" )
end
wait( nextSpawnInterval, currentEncounter.SpawnThreadName or RoomThreadName )
end
if waveNum < waveCount then
CheckForEncounterEnemiesDead( eventSource )
end
end
end
function WaveStartChanges(wave, encounter)
if wave.EnableRoomTraps then
EnableRoomTraps( )
end
if wave.DisableRoomTraps then
DisableRoomTraps( )
end
if wave.PauseHazards then
encounter.PauseHazards = true
end
if wave.UnpauseHazards then
encounter.PauseHazards = false
end
end
function HandleNextSpawn( encounter, ignoreSpawnPreferences )
local spawnInfo = GetNextSpawn( encounter )
if spawnInfo == nil then
DebugPrint({ Text = "Nothing spawned!" })
return nil
end
if EnemyData[spawnInfo.Name] == nil then
DebugPrint({ Text = "Invalid name: "..spawnInfo.Name })
-- Back Compat: If the enemy we're trying to spawn is invalid, attempt
-- to choose another one from the encounter's EnemySet
local validEnemyName = spawnInfo.Name
local tries = 0
local maxTries = TableLength( encounter.EnemySet )
while EnemyData[validEnemyName] == nil and tries < maxTries do
validEnemyName = GetRandomValue(encounter.EnemySet)
tries = tries + 1
end
DebugAssert({ Condition = tries < maxTries, Text = "Failed to find another enemy to spawn. The game may hang because there is nothing to spawn." })
if tries >= maxTries then
return
end
spawnInfo.Name = validEnemyName
end
local newEnemy = DeepCopyTable( EnemyData[spawnInfo.Name] )
if encounter.SpawnAggroed then
newEnemy.StartAggroed = true
end
if ignoreSpawnPreferences then
newEnemy.PreferredSpawnPoint = nil
end
local spawnPointId = spawnInfo.SpawnOnId or RemoveRandomValue(CurrentRun.CurrentRoom.SpawnOnIds) or RemoveRandomValue(spawnInfo.SpawnOnIds) or SelectSpawnPoint(CurrentRun.CurrentRoom, newEnemy, encounter)
if spawnPointId == nil then
return nil
end
local spawnPointOffset = { X = RandomInt( -5, 5 ), Y = RandomInt( -5, 5 ) }
local doActivatePresentation = true
if encounter.PreSpawnEnemies and encounter.PreSpawning and encounter.CurrentWaveNum == 1 then
doActivatePresentation = false
newEnemy.AggroReactionTimeMin = newEnemy.PreSpawnAggroReactionTimeMin
newEnemy.AggroReactionTimeMax = newEnemy.PreSpawnAggroReactionTimeMax
newEnemy.AIWakeDelay = encounter.PreSpawnAIWakeDelay
end
newEnemy.ObjectId = SpawnUnit({ Name = newEnemy.Name, Group = "Standing", DestinationId = spawnPointId, OffsetX = spawnPointOffset.X, OffsetY = spawnPointOffset.Y, ForceToValidLocation = true, DoActivatePresentation = doActivatePresentation })
SetupEnemyObject( newEnemy, CurrentRun )
AddToGroup({ Id = newEnemy.ObjectId, Name = encounter.Name.."Spawns" })
CurrentRun.CurrentRoom.SpawnPointsUsed[spawnPointId] = newEnemy.ObjectId
newEnemy.OccupyingSpawnPointId = spawnPointId
thread( UnoccupySpawnPointOnDistance, newEnemy, spawnPointId, 400 )
if encounter.SpawnedUnitAnimation ~= nil then
CreateAnimation({ Name = encounter.SpawnedUnitAnimation, DestinationId = newEnemy.ObjectId, Scale = 0.15 })
newEnemy.SpawnedUnitAnimation = encounter.SpawnedUnitAnimation
end
if not spawnInfo.InfiniteSpawns then
spawnInfo.RemainingSpawns = spawnInfo.RemainingSpawns - 1
end
if spawnInfo.SpawnOverrides ~= nil then
for key, value in pairs( spawnInfo.SpawnOverrides ) do
newEnemy[key] = value
end
end
return newEnemy.ObjectId
end
function SelectSpawnPoint( currentRoom, enemy, encounter )
local shuffledSpawnPointIds = {}
if enemy.RequiredSpawnPoint ~= nil then
if currentRoom.SpawnPoints[enemy.RequiredSpawnPoint] == nil then
currentRoom.SpawnPoints[enemy.RequiredSpawnPoint] = ShallowCopyTable( GetIdsByType({ Name = enemy.RequiredSpawnPoint }) )
end
shuffledSpawnPointIds = FYShuffle( currentRoom.SpawnPoints[enemy.RequiredSpawnPoint] or MapState.SpawnPoints )
elseif enemy.PreferredSpawnPoint ~= nil then
if currentRoom.SpawnPoints[enemy.PreferredSpawnPoint] == nil then
currentRoom.SpawnPoints[enemy.PreferredSpawnPoint] = ShallowCopyTable( GetIdsByType({ Name = enemy.PreferredSpawnPoint }) )
end
shuffledSpawnPointIds = FYShuffle( currentRoom.SpawnPoints[enemy.PreferredSpawnPoint] or MapState.SpawnPoints )
else
shuffledSpawnPointIds = FYShuffle( MapState.SpawnPoints )
end
for k, id in ipairs( shuffledSpawnPointIds ) do
if IsSpawnPointEligible( id, encounter, currentRoom ) then
return id
end
end
-- Nothing eligible
if enemy.PreferredSpawnPoint ~= nil then
enemy.PreferredSpawnPoint = nil
return SelectSpawnPoint( currentRoom, enemy, encounter )
end
if enemy.RequiredSpawnPoint ~= nil then
enemy.RequiredSpawnPoint = nil
--DebugPrint({ Text = "No eligible spawn point nearby, removing RequiredSpawnPoint on"..enemy.Name })
return SelectSpawnPoint( currentRoom, enemy, encounter )
end
if encounter.RequireNearPlayerDistance ~= nil and encounter.RequireNearPlayerDistance < 5000 then
--DebugPrint({ Text = "No eligible spawn point nearby, increasing eligible distance." })
encounter.RequireNearPlayerDistance = encounter.RequireNearPlayerDistance * 1.5
return SelectSpawnPoint(currentRoom, enemy, encounter)
end
--DebugPrint({ Text = "Resetting spawn points, no eligible ids found within 5000 units" })
currentRoom.SpawnPointsUsed = {}
end
function UnoccupySpawnPoint(spawnPointId)
CurrentRun.CurrentRoom.SpawnPointsUsed[spawnPointId] = nil
end
function UnoccupySpawnPointOnDistance(enemy, spawnPointId, distance)
enemy.SpawnPointThread = "OutsideSpawnDistance"..enemy.ObjectId
NotifyOutsideDistance({ Id = enemy.ObjectId, DestinationId = spawnPointId, Distance = distance, Notify = enemy.SpawnPointThread })
waitUntil( enemy.SpawnPointThread )
UnoccupySpawnPoint(spawnPointId)
end
function IsSpawnPointEligible( spawnPointId, encounter, currentRoom )
if currentRoom.SpawnPointsUsed[spawnPointId] ~= nil then
return false
end
if encounter.SpawnNearId ~= nil and encounter.SpawnRadius ~= nil then
local distance = GetDistance({ Id = spawnPointId, DestinationId = encounter.SpawnNearId })
if distance > encounter.SpawnRadius then
return false
end
if encounter.SpawnRadiusMin ~= nil and distance < encounter.SpawnRadiusMin then
return false
end
end
if encounter.RequireMinPlayerDistance ~= nil and currentRoom.HeroEndPoint ~= nil then
local distance = GetDistance({ Id = currentRoom.HeroEndPoint, DestinationId = spawnPointId })
if distance < encounter.RequireMinPlayerDistance then
return false
end
end
if encounter.RequireNearPlayerDistance ~= nil then
local distance = GetDistance({ Id = CurrentRun.Hero.ObjectId, DestinationId = spawnPointId })
if distance > encounter.RequireNearPlayerDistance then
return false
end
end
return true
end
ConstantsData.MaxActiveEnemyCount = 10
function CalculateActiveEnemyCap( currentRun, currentRoom, currentEncounter )
local enemyCap = currentEncounter.ActiveEnemyCapBase or ConstantsData.MaxActiveEnemyCount
local maxEnemyCap = currentEncounter.ActiveEnemyCapMax or ConstantsData.MaxActiveEnemyCount
if currentEncounter.ActiveEnemyCapDepthRamp then
enemyCap = enemyCap + (currentRun.BiomeDepthCache * currentEncounter.ActiveEnemyCapDepthRamp)
end
if currentEncounter.ActiveEnemyCapMin ~= nil and currentEncounter.ActiveEnemyCapMax ~= nil then
enemyCap = RandomInt(currentEncounter.ActiveEnemyCapMin, currentEncounter.ActiveEnemyCapMax)
end
if currentEncounter.EnemyCountShrineModifierName then
local modifierName = currentEncounter.EnemyCountShrineModifierName
enemyCap = math.floor( enemyCap + (GetNumMetaUpgrades(modifierName) * currentEncounter.EnemyCountShineModifierAmount ) )
else
enemyCap = math.floor( enemyCap + (GetNumMetaUpgrades("EnemyCountShrineUpgrade") * 0.4) )
end
for enemyName, attributes in pairs(currentRoom.EliteAttributes) do
if Contains(attributes, "Disguise") then
enemyCap = maxEnemyCap
DebugPrint({ Text = "DISGUISE Active Enemy Cap: "..enemyCap })
break
end
end
if enemyCap > maxEnemyCap then
enemyCap = maxEnemyCap
end
DebugPrint({ Text = "Active Enemy Cap: "..enemyCap })
return enemyCap
end
function HandleTimedSpawns( eventSource, args )
local currentRun = CurrentRun
local currentRoom = CurrentRun.CurrentRoom
local currentEncounter = eventSource
local newSpawns = currentEncounter.SpawnWaves
local nextLayerIndex = 1
local timeLimit = currentEncounter.TimeLimit
local startingTime = _worldTime
currentEncounter.RemainingTime = timeLimit
currentEncounter.TimeModifier = currentEncounter.TimeModifier or 0
CheckObjectiveSet( currentEncounter.EncounterType )
CheckObjectiveSet( currentEncounter.ObjectiveSets )
UpdateObjective( currentEncounter.EncounterType, "RemainingSeconds", math.ceil(currentEncounter.RemainingTime))
thread( SurvivalObjectivePresentation, currentEncounter )
currentEncounter.ActiveEnemyCap = CalculateActiveEnemyCap(currentRun, currentRoom, currentEncounter)
if currentEncounter.SpawnHazards then
thread(HandleHazardSpawns, currentRoom, currentEncounter)
end
if GetConfigOptionValue({ Name = "EditingMode" }) then
waitUntil( "EditingModeOff" )
end
wait( 1.0, RoomThreadName )
local spawnIntervalStart = 0
local nextSpawnInterval = 0
local lastTrapActivateTime = 0
local trapType = GetRandomValue(currentEncounter.TrapTypes)
currentEncounter.DisabledTrapIds = GetIds({ Name = "Traps" })
currentEncounter.EnabledTrapIds = {}
-- While there is still time
currentEncounter.TimeIsUp = false
while currentEncounter.RemainingTime > 0 do
-- Check if there are new spawn layers to add
if newSpawns ~= nil and newSpawns[nextLayerIndex] ~= nil and currentEncounter.RemainingTime <= newSpawns[nextLayerIndex].AddAtTime then
AddEncounterLayer(currentRun, currentRoom, currentEncounter, newSpawns[nextLayerIndex])
nextLayerIndex = nextLayerIndex + 1
end
-- Spawn a new unit
if _worldTime > spawnIntervalStart + nextSpawnInterval then
if currentEncounter.ActiveEnemyCap == nil or GetActiveEnemyCount() < currentEncounter.ActiveEnemyCap then
HandleNextSpawn(currentEncounter)
elseif GetActiveEnemyCount() >= currentEncounter.ActiveEnemyCap then
if currentEncounter.SpawnIntervalMin == 0 and currentEncounter.SpawnIntervalMax == 0 then
nextSpawnInterval = 0.2
end
end
nextSpawnInterval = RandomFloat( currentEncounter.SpawnIntervalMin, currentEncounter.SpawnIntervalMax )
spawnIntervalStart = _worldTime
end
if CurrentRun.CurrentRoom.ElapsedTimeMultiplier then
startingTime = startingTime + ( 1 - CurrentRun.CurrentRoom.ElapsedTimeMultiplier) * 0.25
end
if GetConfigOptionValue({ Name = "EditingMode" }) then
waitUntil( "EditingModeOff" )
end
wait( 0.25, RoomThreadName )
currentEncounter.RemainingTime = timeLimit - (_worldTime - startingTime) + currentEncounter.TimeModifier
end
currentEncounter.TimeIsUp = true
thread( HadesSpeakingPresentation, eventSource, { VoiceLines = GlobalVoiceLines.SurvivalEncounterSurvivedVoiceLines } )
if currentEncounter.EncounterType == "SurvivalChallenge" then
thread(DestroyRequiredKills, ({ BlockLoot = true, DestroyInterval = currentEncounter.DestroyEnemyInterval or 0.05, BlockDeathWeapons = true }) )
end
thread( MarkObjectiveComplete, currentEncounter.EncounterType )
end
function AddEncounterLayer( currentRun, currentRoom, currentEncounter, layerData )
for k, spawnInfo in pairs( layerData.Spawns ) do
spawnInfo.RemainingSpawns = CalcTotalSpawns( currentRun, currentRoom, currentEncounter, spawnInfo )
if spawnInfo.Name == nil then
spawnInfo.Name = k
DebugPrint({ Text = "WARNING "..k.."'s spawnInfo had no name, adding now" })
end
currentEncounter.Spawns[spawnInfo.Name] = spawnInfo
--DebugPrint({ Text = spawnInfo.Name.." added to encounter ("..spawnInfo.RemainingSpawns..")" })
end
end
function GetActiveEnemyCount()
local count = 0
for id, enemy in pairs( RequiredKillEnemies ) do
if not enemy.SkipActiveCount then
local activeCapWeight = enemy.ActiveCapWeight or 1
count = count + activeCapWeight
end
end
count = count + (MapState.PlaceholderEnemyCount or 0)
return count
end
function CheckForAllEnemiesDead( eventSource, args )
local currentRoom = CurrentRun.CurrentRoom
local currentEncounter = eventSource or CurrentRun.CurrentRoom.Encounter
local notifyName = "AllRequiredKillEnemiesDead"
NotifyOnAllDead({ Names = { "RequiredKillEnemies" }, Notify = notifyName })
waitUntil( notifyName, RoomThreadName )
currentEncounter.InProgress = false
if currentEncounter.TimerBlock ~= nil then
RemoveTimerBlock( CurrentRun, currentEncounter.TimerBlock )
end
if ShouldDoLastKillPresentation(currentEncounter) then
local lastKillFunctionName = currentEncounter.LastKillPresentationFunction or "LastKillPresentation"
local lastKillFunction = _G[lastKillFunctionName]
lastKillFunction( LastEnemyKilled )
end
ExpireProjectiles({ Names = { "PoisonPuddle", "PoisonPuddleSmall", "RatPoisonShake", "RatDeathPuddle", "LavaPuddleLarge", "LavaSplash", "PoisonTrapWeapon", "ArcherTrapWeapon" } })
ClearEffect({ Id = CurrentRun.Hero.ObjectId, Name = "StyxPoison" })
ClearEffect({ Id = CurrentRun.Hero.ObjectId, Name = "DamageOverTime" })
if currentEncounter.PassiveRoomWeapons ~= nil then
Destroy({ Ids = currentEncounter.PassiveRoomWeapons })
end
local killHazardEnemies = { "LavaSplash" }
for k, enemyType in pairs(killHazardEnemies) do
for k, enemyId in pairs( GetIdsByType({ Name = enemyType }) ) do
if ActiveEnemies[enemyId] ~= nil then
Kill( ActiveEnemies[enemyId] )
end
end
end
thread(MarkObjectiveComplete, "KillRequiredEnemies")
if currentEncounter.EncounterType == "PerfectClear" then
thread( HadesSpeakingPresentation, eventSource, { VoiceLines = GlobalVoiceLines.PerfectClearEncounterClearedVoiceLines, ColorGrade = "PerfectClear" } )
if not currentEncounter.PlayerTookDamage then
thread(MarkObjectiveComplete, "PerfectClear")
end
thread(MarkObjectiveComplete, "PerfectClearCleanup")
end
end
function CheckForEncounterEnemiesDead( eventSource, args )
local currentRoom = CurrentRun.CurrentRoom
local currentEncounter = eventSource or CurrentRun.CurrentRoom.Encounter
local notifyName = "AllEncounterEnemiesDead"
NotifyOnAllDead({ Names = { currentEncounter.Name.."Spawns" }, Notify = notifyName })
waitUntil( notifyName )
end
function ShouldDoLastKillPresentation( currentEncounter )
if currentEncounter.SkipLastKillPresentation then
return false
end
if CurrentRun.CurrentRoom.SkipLastKillPresentation then
return false
end
if LastEnemyKilled == nil then
return false
end
if CurrentRun.Hero.HandlingDeath then
return false
end
local currentWaveNum = currentEncounter.CurrentWaveNum or 1
if currentEncounter.SpawnWaves ~= nil and TableLength(currentEncounter.SpawnWaves) > currentWaveNum then
return false
end
return true
end
function EncounterAudio( eventSource )
local currentRun = CurrentRun
local currentRoom = CurrentRun.CurrentRoom
local currentEncounter = CurrentRun.CurrentRoom.Encounter
if currentEncounter.StartVoiceLines ~= nil then
thread( PlayVoiceLines, currentEncounter.StartVoiceLines )
end
if currentEncounter.StartGlobalVoiceLines ~= nil then
thread( PlayVoiceLines, GlobalVoiceLines[currentEncounter.StartGlobalVoiceLines] )
elseif not currentEncounter.SkipCombatBeginsVoiceLines then
thread( PlayVoiceLines, GlobalVoiceLines.CombatBeginsVoiceLines, true )
end
local music = currentEncounter.Music
if music ~= nil then
if currentEncounter.MusicTarget then
local musicTargetId = GetClosest({ Id = CurrentRun.Hero.ObjectId, DestinationNames = currentEncounter.MusicTarget, Distance = 99999 })
MusicPlayer( music, nil, musicTargetId )
SetSoundCueValue({ Id = MusicId, Names = { "LowPass" }, Value = 0.0, Duration = 0.5 })
else
MusicPlayer( music )
end
end
if currentEncounter.MusicianMusic ~= nil and currentEncounter.MusicTarget ~= nil then
MusicianMusic( currentEncounter, { TrackName = currentEncounter.MusicianMusic, TargetName = currentEncounter.MusicTarget } )
SetSoundCueValue({ Id = AmbientMusicId, Names = { "LowPass" }, Value = 0.0, Duration = 0.5 })
end
thread( MusicMixer, currentEncounter )
end
function PostCombatAudio( eventSource )
local currentRun = CurrentRun
local currentRoom = CurrentRun.CurrentRoom
local currentEncounter = CurrentRun.CurrentRoom.Encounter
local endMusicDuration = currentRoom.EndMusicOnCombatOver or currentEncounter.EndMusicOnCombatOver
if endMusicDuration ~= nil then
EndMusic( MusicId, MusicName, endMusicDuration )
end
if currentEncounter.Spawns ~= nil then
-- VO
if currentEncounter.ThanatosId == nil then
local currentHealth = currentRun.Hero.Health
local currentHealthFraction = currentRun.Hero.Health / currentRun.Hero.MaxHealth
local prevRoom = GetPreviousRoom( currentRun )
if currentRoom.CombatResolvedVoiceLines ~= nil then
thread( PlayVoiceLines, currentRoom.CombatResolvedVoiceLines, true )
else
-- if you suffered a lot of damage and were reduced to low health
if prevRoom ~= nil and prevRoom.EndingHealth ~= nil and prevRoom.EndingHealth - currentHealth >= 25 and currentHealthFraction < 0.3 then
thread( PlayVoiceLines, GlobalVoiceLines.CombatResolvedLowHealthVoiceLines, true )
end
thread( PlayVoiceLines, GlobalVoiceLines.CombatResolvedVoiceLines, true )
end
else
for k, unit in pairs( ActiveEnemies ) do
if unit.EncounterEndVoiceLines ~= nil then
thread( PlayVoiceLines, unit.EncounterEndVoiceLines, nil, unit )
end
end
end
if MusicSection ~= 2 then
SetSoundCueValue({ Names = { "Guitar" }, Id = MusicId, Value = 0, Duration = 0.25 })
end
if currentEncounter.MuteSecretMusicDrumsOnCombatOver and SecretMusicId ~= nil then
SetSoundCueValue({ Names = { "Drums" }, Id = SecretMusicId, Value = 0 })
end
CheckMusicEvents( currentRun, CombatOverMusicEvents )
end
if currentEncounter.EncounterResolvedGlobalVoiceLines ~= nil then
thread( PlayVoiceLines, GlobalVoiceLines[currentEncounter.EncounterResolvedGlobalVoiceLines] )
end
end
function GrantLoot(eventSource, args)
CreateLoot( LootData[args.ConsumableName] )
end
function ActivateObjects( eventSource, args )
if args.ObjectType == nil then
return nil
end
local ids = GetInactiveIdsByType({ Name = args.ObjectType })
Activate({ Ids = ids })
end
function ActivateConditionalItems( eventSource, args )
args = args or {}
for name, itemData in pairs( ConditionalItemData ) do
if itemData.Slot == "CosmeticBedroom" then
-- Not for this room
elseif GameState.Cosmetics[name] and not itemData.Disabled and not GameState.Flags.InFlashback then
ActivateConditionalItem( itemData )
elseif not GetConfigOptionValue({ Name = "EditingMode" }) then
DeactivateConditionalItem( itemData )
end
end
end
function ActivateBedroomConditionalItems()
local ids = GetIds({ Names = { "Conditional" } })
local hiddenItems = {}
for k, id in pairs( ids ) do
local name = GetName({ Id = id })
local itemData = ConditionalItemData[name]
if itemData ~= nil then
if ( not GameState.Cosmetics[name]
and not IsGameStateEligible(CurrentRun, ConditionalItemData[name].AlwaysOnIf ))
or GameState.Cosmetics[name] == UIData.Constants.PENDING_REVEAL then
if ConditionalItemData[name].ConnectedIds ~= nil then
SetAlpha({ Ids = ConditionalItemData[name].ConnectedIds, Fraction = 0, Duration = 0 })
SetThingProperty({ Property = "StopsUnits", Value = false, DestinationIds = ConditionalItemData[name].ConnectedIds})
SetThingProperty({ Property = "StopsLight", Value = false, DestinationIds = ConditionalItemData[name].ConnectedIds})
BlockVfx({ DestinationIds = ConditionalItemData[name].ConnectedIds })
UseableOff({ Ids = ConditionalItemData[name].ConnectedIds })
end
SetAlpha({ Id = id, Fraction = 0, Duration = 0 })
BlockVfx({ DestinationIds = ConditionalItemData[name].ConnectedIds })
SetThingProperty({ Property = "StopsUnits", Value = false, DestinationId = id})
SetThingProperty({ Property = "StopsLight", Value = false, DestinationId = id})
UseableOff({ Id = id })
else
if itemData.DeactivateIds ~= nil then
SetAlpha({ Ids = itemData.DeactivateIds, Fraction = 0, Duration = 0 })
end
end
end
end
end
function ActivateConditionalItem( itemData )
-- On
if itemData.DestroyIds ~= nil then
Destroy({ Ids = itemData.DestroyIds })
end
if itemData.DestroyNames ~= nil then
Destroy({ Ids = GetIdsByType({ Name = itemData.DestroyNames }) })
end
if itemData.ActivateIds ~= nil then
Activate({ Ids = itemData.ActivateIds })
SetAlpha({ Ids = itemData.ActivateIds, Fraction = 1 })
if itemData.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = true, DestinationIds = itemData.ActivateIds })
SetThingProperty({ Property = "StopsLight", Value = true, DestinationIds = itemData.ActivateIds })
end
if itemData.ToggleVfx then
UnblockVfx({ DestinationIds = itemData.ActivateIds, ResetCap = true })
end
end
if itemData.ToggleUseability then
UseableOn({ Ids = itemData.ToggleUseabilityIds or itemData.ActivateIds })
end
if itemData.DeactivateIds ~= nil and not GetConfigOptionValue({ Name = "EditingMode" }) then
SetAlpha({ Ids = itemData.DeactivateIds, Fraction = 0 })
if itemData.ActivateStopAnimations then
StopAnimation({ Names = itemData.ActivateStopAnimations, DestinationIds = itemData.DeactivateIds, PreventChain = true })
end
if itemData.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = false, DestinationIds = itemData.DeactivateIds })
SetThingProperty({ Property = "StopsLight", Value = false, DestinationIds = itemData.DeactivateIds })
end
end
if itemData.ToggleUseabilityOffIds then
UseableOff({ Ids = itemData.ToggleUseabilityOffIds })
end
if itemData.DeactivateCosmetics ~= nil then
for k, name in pairs( itemData.DeactivateCosmetics ) do
DeactivateConditionalItem( ConditionalItemData[name] )
end
end
if itemData.ActivateNames ~= nil then
local activateNameIds = GetIdsByType({ Name = itemData.ActivateNames })
Activate({ Ids = activateNameIds })
SetAlpha({ Ids = activateNameIds, Fraction = 1 })
if itemData.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = true, DestinationIds = activateNameIds })
SetThingProperty({ Property = "StopsLight", Value = true, DestinationIds = activateNameIds })
end
end
if itemData.DeactivateNames ~= nil and not GetConfigOptionValue({ Name = "EditingMode" }) then
local deactivateNameIds = GetIdsByType({ Name = itemData.DeactivateNames })
SetAlpha({ Ids = deactivateNameIds, Fraction = 0 })
end
if itemData.ActivateGroups ~= nil then
Activate({ Names = itemData.ActivateGroups })
SetAlpha({ Ids = GetIds({ Names = itemData.ActivateGroups }), Fraction = 1 })
if itemData.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = true, DestinationNames = itemData.ActivateGroups })
SetThingProperty({ Property = "StopsLight", Value = true, DestinationNames = itemData.ActivateGroups })
end
end
if itemData.DeactivateGroups ~= nil and not GetConfigOptionValue({ Name = "EditingMode" }) then
SetAlpha({ Ids = GetIds({ Names = itemData.DeactivateGroups }), Fraction = 0 })
end
if itemData.ActivateObstacles ~= nil then
for id, obstacleData in pairs( itemData.ActivateObstacles ) do
local obstacle = DeepCopyTable( obstacleData )
obstacle.ObjectId = id
Activate({ Id = obstacle.ObjectId })
SetAlpha({ Id = obstacle.ObjectId, Fraction = 1 })
SetupObstacle( obstacle )
end
end
if itemData.InspectPoint and CurrentDeathAreaRoom.InspectPoints[itemData.InspectPoint] and not HasUsed( itemData.InspectPoint ) then
Activate({ Id = itemData.InspectPoint })
SetAlpha({ Id = itemData.InspectPoint, Fraction = 1 })
UseableOn({ Id = itemData.InspectPoint })
local inspectPointData = CurrentDeathAreaRoom.InspectPoints[itemData.InspectPoint]
inspectPointData.RecordUse = true
AttachLua({ Id = itemData.InspectPoint, Table = inspectPointData })
end
if itemData.SetAnimations ~= nil then
for name, animation in pairs( itemData.SetAnimations ) do
SetAnimation({ DestinationIds = GetIdsByType({ Name = name }), Name = animation })
end
end
if itemData.SetAnimationIds ~= nil then
for id, animation in pairs( itemData.SetAnimationIds ) do
SetAnimation({ DestinationId = id, Name = animation })
end
end
if itemData.SetHSVs ~= nil then
for name, hsv in pairs( itemData.SetHSVs ) do
SetHSV({ Ids = GetIdsByType({ Name = name }), HSV = hsv, ValueChangeType = "Absolute" })
end
end
if itemData.SetColorIds ~= nil then
SetColor({ Ids = itemData.SetColorIds, Color = itemData.SetColorValue, SetBase = true, })
end
if itemData.DistanceTriggers ~= nil then
StartTriggers( itemData, itemData.DistanceTriggers )
end
end
function DeactivateConditionalItem( itemData )
if itemData == nil then
return
end
-- Off
if itemData.RemoveCosmetics == nil or not ContainsAnyKey( GameState.Cosmetics, itemData.RemoveCosmetics ) or itemData.IndependentToggle then
if itemData.ActivateIds ~= nil then
SetAlpha({ Ids = itemData.ActivateIds, Fraction = 0 })
if itemData.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = false, DestinationIds = itemData.ActivateIds })
SetThingProperty({ Property = "StopsLight", Value = false, DestinationIds = itemData.ActivateIds })
end
if itemData.ToggleVfx then
BlockVfx({ DestinationIds = itemData.ActivateIds })
end
if itemData.DeactivateStopAnimations then
StopAnimation({ Names = itemData.DeactivateStopAnimations, DestinationIds = itemData.ActivateIds, PreventChain = true })
end
end
if itemData.ToggleUseability then
UseableOff({ Ids = itemData.ToggleUseabilityIds or itemData.ActivateIds })
end
if itemData.ActivateNames ~= nil then
local activateNameIds = GetIdsByType({ Name = itemData.ActivateNames })
SetAlpha({ Ids = activateNameIds, Fraction = 0 })
if itemData.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = false, DestinationIds = activateNameIds })
SetThingProperty({ Property = "StopsLight", Value = false, DestinationIds = activateNameIds })
end
end
if itemData.ActivateGroups ~= nil then
SetAlpha({ Ids = GetIds({ Names = itemData.ActivateGroups }), Fraction = 0 })
if itemData.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = false, DestinationNames = itemData.ActivateGroups })
SetThingProperty({ Property = "StopsLight", Value = false, DestinationNames = itemData.ActivateGroups })
end
end
if itemData.RemovalDeactivateIds ~= nil or itemData.DeactivateIds ~= nil then
SetAlpha({ Ids = itemData.RemovalDeactivateIds or itemData.DeactivateIds, Fraction = 1 })
if itemData.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = true, DestinationIds = itemData.RemovalDeactivateIds or itemData.DeactivateIds })
SetThingProperty({ Property = "StopsLight", Value = true, DestinationIds = itemData.RemovalDeactivateIds or itemData.DeactivateIds })
end
end
if itemData.ConnectedIds then
SetAlpha({ Ids = itemData.ConnectedIds, Fraction = 0 })
end
if itemData.InspectPoint and CurrentDeathAreaRoom.InspectPoints[itemData.InspectPoint] and not HasUsed( itemData.InspectPoint ) then
UseableOff({ Id = itemData.InspectPoint })
SetAlpha({ Id = itemData.InspectPoint, Fraction = 0 })
end
if itemData.DeactivateAnimations ~= nil then
for name, animation in pairs( itemData.DeactivateAnimations ) do
SetAnimation({ DestinationIds = GetIdsByType({ Name = name }), Name = animation })
end
end
if itemData.DeactivateAnimationIds ~= nil then
for id, animation in pairs( itemData.DeactivateAnimationIds ) do
SetAnimation({ DestinationId = id, Name = animation })
end
end
end
end
-- ending
function ActivateHadesBedroomDoor( source, args )
SetAlpha({ Id = 555678, Fraction = 1.0 })
SetThingProperty({ Property = "StopsUnits", Value = true, Id = 555678 })
SetThingProperty({ Property = "StopsLight", Value = true, Id = 555678 })
UseableOn({ Id = 555678 })
Destroy({ Id = 555681 })
end
function RestoreActivateHadesBedroomDoor()
wait( 2.0, RoomThreadName )
ActivateHadesBedroomDoor()
end
-- New Run Door / Pact of Punishment
function SetupExitDoor( source )
if GetConfigOptionValue({ Name = "EditingMode" }) then
return
end
if GetNumRunsCleared() > 0 or GameState.Flags.HardMode then
SetAlpha({ Ids = GetIds({ Name = "ExitDoorBasic" }), Fraction = 0.0, Duration = 0 })
local entranceId = 487596
local contractId = 487586
SetAnimation({ Name = "HouseContractClosed", DestinationId = contractId })
thread( SetupPactDoor, source, entranceId, contractId )
else
SetAlpha({ Ids = GetIds({ Name = "ExitDoorPact" }), Fraction = 0.0, Duration = 0 })
end
end
function SetupPactDoor( source, entranceId, contractId )
local activationDistance = 600
local repeatBufferDistance = 30
while CurrentDeathAreaRoom and CurrentDeathAreaRoom.Name == source.Name do
NotifyWithinDistance({ Id = entranceId, DestinationId = CurrentRun.Hero.ObjectId, Distance = activationDistance - repeatBufferDistance, Notify = "ContractOpen" })
waitUntil( "ContractOpen" )
SetAnimation({ Name = "HouseContractOpen", DestinationId = contractId })
NotifyOutsideDistance({ Id = entranceId, DestinationId = CurrentRun.Hero.ObjectId, Distance = activationDistance + repeatBufferDistance, Notify = "ContractClose" })
waitUntil( "ContractClose" )
SetAnimation({ Name = "HouseContractClose", DestinationId = contractId })
wait(0.01)
end
end
OnUsed{ "CosmeticRevealPoint",
function(triggerArgs)
CreateAnimation({ Name = "ProjectileTempFlare", DestinationId = CurrentRun.Hero.ObjectId, Scale = 2.0 })
PlaySound({ Name = "/SFX/Menu Sounds/ObjectiveCompletedSparkles", Id = CurrentRun.Hero.ObjectId })
UseableOff({ Id = triggerArgs.triggeredById })
BlockVfx({ DestinationId = triggerArgs.triggeredById })
SetAlpha({ Id = triggerArgs.triggeredById, Fraction = 0.0, Duration = 0.25 })
end
}
function RevealPendingItems()
AddInputBlock({ Name = "RevealPendingItems" })
local ids = GetIds({ Names = { "Conditional" }})
local revealedItems = {}
for k, id in pairs (ids) do
local name = GetName({ Id = id })
if GameState.Cosmetics[name] == UIData.Constants.PENDING_REVEAL then
table.insert( revealedItems, { Name = name, Id = id, DoReveal = true, InspectPoint = ConditionalItemData[name].InspectPoint, ToggleCollision = ConditionalItemData[name].ToggleCollision })
if ConditionalItemData[name].ConnectedIds ~= nil then
for i, connectedId in pairs(ConditionalItemData[name].ConnectedIds) do
table.insert( revealedItems, { Name = name, Id = connectedId, DoReveal = false })
end
end
GameState.Cosmetics[name] = UIData.Constants.VISIBLE
end
end
thread( PlayVoiceLines, GlobalVoiceLines.CosmeticUnlockedVoiceLines, true )
for i, data in pairs( revealedItems ) do
local itemData = ConditionalItemData[data.Name]
if data.DoReveal then
local sparkleId = SpawnObstacle({ Name = "GenericSparkles", Group = "FX_Standing", DestinationId = data.Id })
PlaySound({ Name = "/Leftovers/Menu Sounds/EmoteExcitement", Id = data.Id })
PanCamera({ Id = data.Id, Duration = 1.5, EaseIn = 0.05, EaseOut = 0.3 })
AngleTowardTarget({ Id = CurrentRun.Hero.ObjectId, DestinationId = data.Id })
SetAnimation({ Name = "ZagreusInteractionThoughtful", DestinationId = CurrentRun.Hero.ObjectId })
wait(1.5)
SetAlpha({ Id = data.Id, Fraction = 1, Duration = 1.75 })
PlaySound({ Name = "/Leftovers/World Sounds/MapZoomSlow", Id = data.Id })
if itemData.DeactivateIds ~= nil then
SetAlpha({ Ids = itemData.DeactivateIds, Fraction = 0.0, Duration = 1.75 })
end
if ConditionalItemData[data.Name].BedroomRevealGlobalVoiceLines then
local voiceData = ConditionalItemData[data.Name].BedroomRevealGlobalVoiceLines
thread( PlayVoiceLines, GlobalVoiceLines[voiceData], true )
wait(0.25)
elseif ConditionalItemData[data.Name].BedroomRevealVoiceLines then
thread( PlayVoiceLines, ConditionalItemData[data.Name].BedroomRevealVoiceLines, true )
wait(0.25)
else
wait(2.5)
end
Destroy({ Id = sparkleId })
end
if data.InspectPoint then
Activate({ Id = data.InspectPoint })
local inspectPointData = CurrentDeathAreaRoom.InspectPoints[data.InspectPoint]
inspectPointData.RecordUse = true
AttachLua({ Id = data.InspectPoint, Table = inspectPointData })
end
UnblockVfx({ DestinationId = data.Id })
SetAlpha({ Id = data.Id, Fraction = 1, Duration = 3 })
if itemData.DeactivateIds ~= nil then
SetAlpha({ Ids = itemData.DeactivateIds, Fraction = 0.0, Duration = 3.0 })
end
UseableOn({ Id = data.Id })
if data.ToggleCollision then
SetThingProperty({ Property = "StopsUnits", Value = true, DestinationId = data.Id })
end
end
PanCamera({ Id = CurrentRun.Hero.ObjectId, Duration = 1.75 })
RemoveInputBlock({ Name = "RevealPendingItems" })
end
function SpawnRoomReward( eventSource, args )
local currentRun = CurrentRun
local currentRoom = CurrentRun.CurrentRoom
local currentEncounter = CurrentRun.CurrentRoom.Encounter
local rewardType = currentRoom.ChangeReward or currentRoom.ChosenRewardType
if rewardType == nil or rewardType == "Story" or currentRoom.DeferReward then
if args ~= nil and args.VoiceLines ~= nil then
thread( PlayVoiceLines, args.VoiceLines, true )
end
return
end
if currentRoom.SpawnRewardGlobalVoiceLines ~= nil then
thread( PlayVoiceLines, GlobalVoiceLines[currentRoom.SpawnRewardGlobalVoiceLines], true )
end
if rewardType == "Shop" then
if args ~= nil and args.VoiceLines ~= nil then
thread( PlayVoiceLines, args.VoiceLines, true )
end
-- @refactor Move to data
SpawnStoreItemsInWorld()
ActivatePrePlaced( eventSource, { FractionMin = 1.0, FractionMax = 1.0, LegalTypes = { "NPC_Charon_01" } } )
local shopIds = GetInactiveIds({ Name = "Shop" })
for k, id in pairs( shopIds ) do
Activate({ Id = id })
end
local boatIds = GetInactiveIds({ Name = "Boat" })
for k, id in pairs( boatIds ) do
Activate({ Id = id })
end
return
end
local offsetTowardId = SelectRoomRewardSpawnPoint( CurrentRun.CurrentRoom )
local lootPointId = currentRoom.SpawnRewardOnId or currentRun.Hero.ObjectId
local angle = 0
local lootOffset = {X=0, Y=0}
local reward = nil
if currentRoom.SpawnRewardOnId == nil then
angle = GetAngleBetween({ Id = currentRun.Hero.ObjectId, DestinationId = offsetTowardId })
lootOffset = CalcOffset( math.rad(angle), 110 )
end
wait( currentRoom.SpawnRewardDelay, RoomThreadName )
if rewardType == "StackUpgrade" then
reward = CreateStackLoot({ SpawnPoint = lootPointId, SuppressSpawnSounds = currentRoom.SuppressRewardSpawnSounds, OffsetX = lootOffset.X, OffsetY = lootOffset.Y, SuppressSpawnSounds = currentRoom.SuppressRewardSpawnSounds })
elseif rewardType == "WeaponUpgrade" then
reward = CreateWeaponLoot({ SpawnPoint = lootPointId, SuppressSpawnSounds = currentRoom.SuppressRewardSpawnSounds, OffsetX = lootOffset.X, OffsetY = lootOffset.Y, SuppressSpawnSounds = currentRoom.SuppressRewardSpawnSounds })
elseif rewardType == "HermesUpgrade" then
reward = GiveLoot({ ForceLootName = "HermesUpgrade", SpawnPoint = lootPointId, OffsetX = lootOffset.X, OffsetY = lootOffset.Y, SuppressSpawnSounds = currentRoom.SuppressRewardSpawnSounds })
elseif rewardType == "Boon" then
reward = GiveLoot({ ForceLootName = currentRoom.ForceLootName, SpawnPoint = lootPointId, OffsetX = lootOffset.X, OffsetY = lootOffset.Y, SuppressSpawnSounds = currentRoom.SuppressRewardSpawnSounds })
if currentRoom.ForcedReward ~= nil then
if currentRoom.ForcedReward.ForcedTextLines ~= nil then
ProcessTextLines( currentRoom.ForcedReward.ForcedTextLines )
reward.ForcedTextLines = currentRoom.ForcedReward.ForcedTextLines
end
if currentRoom.ForcedReward.ForcedUpgradeOptions ~= nil then
reward.UpgradeOptions = currentRoom.ForcedReward.ForcedUpgradeOptions
reward.BlockReroll = true
end
if currentRoom.ForcedReward.PostPickupFunction ~= nil then
reward.PostPickupFunction = currentRoom.ForcedReward.PostPickupFunction
end
end
local lootSpacing = 100
local numBonusLoot = GetTotalHeroTraitValue("BonusLootAmount")
UseHeroTraitsWithValue("BonusLootAmount")
while numBonusLoot > 0 do
numBonusLoot = numBonusLoot - 1
GiveLoot({ OffsetX = lootOffset.X, OffsetY = lootOffset.Y })
lootOffset.X = lootOffset.X + lootSpacing
end
elseif rewardType == "TrialUpgrade" then
reward = GiveLoot({ ForceLootName = "TrialUpgrade", SpawnPoint = lootPointId, OffsetX = lootOffset.X, OffsetY = lootOffset.Y })
elseif rewardType == "Devotion" then
reward = GiveLoot({ ForceLootName = currentEncounter.SpurnedGodName, ExchangeOnlyFromLootName = currentEncounter.ChosenGodName, SpawnPoint = lootPointId, OffsetX = lootOffset.X, OffsetY = lootOffset.Y })
reward.CanReceiveGift = false
-- Reconcile presentation
else
local consumableId = SpawnObstacle({ Name = rewardType, DestinationId = lootPointId, Group = "Standing", OffsetX = lootOffset.X, OffsetY = lootOffset.Y })
reward = CreateConsumableItem( consumableId, rewardType, 0 )
if reward ~= nil then
ApplyConsumableItemResourceMultiplier( currentRoom, reward )
ExtractValues( CurrentRun.Hero, reward, reward )
ActivatedObjects[consumableId] = reward
if reward.SpawnSound ~= nil then
PlaySound({ Name = reward.SpawnSound, Id = reward.ObjectId })
end
thread( PlayRandomEligibleVoiceLines, { reward.OnSpawnVoiceLines, GlobalVoiceLines.MiscRewardGrantedVoiceLines } )
end
end
local rewardOverrides = currentRoom.BoonRaritiesOverride or currentRoom.RewardConsumableOverrides or currentRoom.RewardOverrides or {}
local rewardBoostedAnimation = rewardOverrides.RewardBoostedAnimation or currentEncounter.RewardBoostedAnimation or currentRoom.RewardBoostedAnimation
if currentRoom.ChangeReward == nil and rewardBoostedAnimation ~= nil then
CreateAnimation({ DestinationId = reward.ObjectId, Name = rewardBoostedAnimation })
end
if currentRoom.DisableRewardMagnetisim then
SetObstacleProperty({ Property = "MagnetismWhileBlocked", Value = 0, DestinationId = reward.ObjectId })
end
if args ~= nil and args.VoiceLines ~= nil then
thread( PlayVoiceLines, args.VoiceLines, true )
end
if args ~= nil and args.WaitUntilPickup then
if reward.MenuNotify ~= nil then
waitUntil( UIData.BoonMenuId )
else
reward.NotifyName = "OnUsed"..reward.ObjectId
waitUntil( reward.NotifyName )
end
end
end
-- 生成当完全清理所有怪物的事件奖励
function SpawnPerfectClearRoomReward( eventSource, args )
local encounter = CurrentRun.CurrentRoom.Encounter
if encounter.PlayerTookDamage then
CurrentRun.CurrentRoom.ChangeReward = "RoomRewardConsolationPrize"
else
CheckAchievement( { Name = "AchClearHeatGate" })
end
SpawnRoomReward(eventSource, args)
end
function CalcMetaProgressRatio( run )
local metaProgressCount = 0
local totalCount = 0
if run.CurrentRoom.ChosenRewardType ~= nil then
totalCount = totalCount + 1
if run.CurrentRoom.RewardStoreName == "MetaProgress" then
metaProgressCount = metaProgressCount + 1
end
end
for k, room in ipairs( run.RoomHistory ) do
if room.ChosenRewardType ~= nil then
totalCount = totalCount + 1
if room.RewardStoreName == "MetaProgress" then
metaProgressCount = metaProgressCount + 1
end
end
end
if totalCount == 0 then
--DebugPrint({ Text = "CalcMetaProgressRatio = 0.0 (0/0)" })
return nil
end
local ratio = metaProgressCount / totalCount
--DebugPrint({ Text = "CalcMetaProgressRatio = "..ratio.." ("..metaProgressCount.."/"..totalCount..")" })
return ratio
end
function HandleChallengeLoot( challengeSwitch, challengeEncounter )
if challengeEncounter ~= nil then
Destroy({ Id = challengeSwitch.ValueTextAnchor })
SetAnimation({ DestinationId = challengeSwitch.ObjectId, Name = "ChallengeSwitchOpen" })
-- presentation
PlaySound({ Name = "/Leftovers/World Sounds/Caravan Interior/ChestOpen", Id = challengeSwitch.ObjectId })
PlaySound({ Name = "/Leftovers/Menu Sounds/EmoteAffection" })
local healingMultiplier = CalculateHealingMultiplier()
if ( challengeSwitch.RewardType == "Health" and healingMultiplier == 0 ) or (challengeSwitch.RewardType == "Money" and HasHeroTraitValue("BlockMoney")) then
thread( PlayVoiceLines, GlobalVoiceLines.ChallengeSwitchEmptyVoiceLines, true )
else
thread( PlayVoiceLines, GlobalVoiceLines.ChallengeSwitchOpenedVoiceLines, true )
end
UseableOff({ Id = challengeSwitch.ObjectId })
local lootPointId = CurrentRun.Hero.ObjectId
--local angle = GetAngleBetween({ Id = challengeSwitch.ObjectId, DestinationId = lootPointId })
local angle = GetAngleBetween({ Id = challengeSwitch.ObjectId, DestinationId = CurrentRun.Hero.ObjectId })
local distance = GetDistance({ Id = challengeSwitch.ObjectId, DestinationId = CurrentRun.Hero.ObjectId })
local dropOffset = CalcOffset(math.rad(angle), distance/2)
for lootName, lootData in pairs(challengeEncounter.LootDrops) do
if lootData.DropChance == nil or RandomChance(lootData.DropChance) then
local minDrop = lootData.MinDrop or 1
local maxDrop = lootData.MaxDrop or 1
local dropCount = lootData.DropCount or RandomInt(minDrop, maxDrop)
for index = 1, dropCount, 1 do
local consumableId = SpawnObstacle({ Name = lootName, DestinationId = CurrentRun.Hero.ObjectId, Group = "Standing", ForceToValidLocation = true, })
local cost = 0 -- All challenge loot is free
CreateConsumableItem( consumableId, lootName, cost )
ApplyUpwardForce({ Id = consumableId, Speed = RandomFloat( 500, 700 ) })
ApplyForce({ Id = consumableId, Speed = RandomFloat( 50, 100 ), Angle = angle, SelfApplied = true })
end
end
end
if challengeSwitch.RewardType == "Money" then
local moneyMultiplier = GetTotalHeroTraitValue( "MoneyMultiplier", { IsMultiplier = true } )
local amount = round( challengeSwitch.CurrentValue * moneyMultiplier )
thread( GushMoney, { Amount = amount, LocationId = challengeSwitch.ObjectId, Radius = 50, Source = challengeSwitch.Name, Offset = dropOffset } )
elseif challengeSwitch.RewardType == "Health" then
Heal( CurrentRun.Hero, { HealAmount = challengeSwitch.CurrentValue, Name = "HealthChallengeSwitch" } )
elseif challengeSwitch.RewardType == "MetaPoints" then
local consumableId = SpawnObstacle({ Name = "RoomRewardMetaPointDrop", DestinationId = CurrentRun.Hero.ObjectId, Group = "Standing", ForceToValidLocation = true, })
local cost = 0
local consumable = CreateConsumableItem( consumableId, "RoomRewardMetaPointDrop", cost )
consumable.AddResources = consumable.AddResources or {}
consumable.AddResources.MetaPoints = round( challengeSwitch.CurrentValue * CalculateMetaPointMultiplier() )
ApplyUpwardForce({ Id = consumableId, Speed = RandomFloat( 500, 700 ) })
ApplyForce({ Id = consumableId, Speed = RandomFloat( 50, 100 ), Angle = angle, SelfApplied = true })
elseif challengeSwitch.RewardType == "Gems" then
local gemMultiplier = GetTotalHeroTraitValue( "GemMultiplier", { IsMultiplier = true } )
local consumableId = SpawnObstacle({ Name = "GemDrop", DestinationId = CurrentRun.Hero.ObjectId, Group = "Standing", ForceToValidLocation = true, })
local cost = 0
local consumable = CreateConsumableItem( consumableId, "GemDrop", cost )
consumable.AddResources = consumable.AddResources or {}
consumable.AddResources.Gems = round(challengeSwitch.CurrentValue * gemMultiplier )
ApplyUpwardForce({ Id = consumableId, Speed = RandomFloat( 500, 700 ) })
ApplyForce({ Id = consumableId, Speed = RandomFloat( 50, 100 ), Angle = angle, SelfApplied = true })
end
end
end
function HandleChallengeLootDecay( challengeSwitch, challengeEncounter )
local startingValue = challengeSwitch.StartingValue
local rewardMultiplier = challengeSwitch.RewardMultiplier or 1
local minValue = round( challengeEncounter.MinValue * rewardMultiplier )
if challengeSwitch.RewardType == "Health" then
minValue = round( minValue * CalculateHealingMultiplier())
end
if challengeSwitch.RewardType == "Money" and HasHeroTraitValue("BlockMoney") then
minValue = 0
end
challengeSwitch.CurrentValue = startingValue
local decayMultiplier = challengeSwitch.DecayMultiplier or 1
local decayAmount = challengeEncounter.ValueDecayAmount * decayMultiplier * GetTotalHeroTraitValue("ChallengeDecayIncrease", {IsMultiplier = true})
local challengeSwitchId = challengeSwitch.ObjectId
wait( challengeEncounter.DecayStartDelay, RoomThreadName )
ModifyTextBox({ Id = challengeSwitch.ValueTextAnchor, ColorTarget = {1, 0.8, 0, 1}, ColorDuration = 0.2 })
ModifyTextBox({ Id = challengeSwitch.ValueTextAnchor, ColorTarget = {1, 1, 1, 1}, ColorDuration = 2, Delay = 0.2 })
thread(PulseText, {Id = challengeEncounter.ValueTextAnchor, ScaleTarget = 1.8, ScaleDuration = 0.15, HoldDuration = 0.0})
ModifyTextBox({ Id = challengeSwitch.ValueTextAnchor, Text = challengeSwitch.ChallengeText, LuaKey = "Amount", LuaValue = round(challengeSwitch.CurrentValue), Format = "LootFormat" })
while( challengeSwitch.CurrentValue > minValue and challengeEncounter.InProgress and not CurrentRun.Hero.IsDead ) do
challengeSwitch.CurrentValue = challengeSwitch.CurrentValue - decayAmount
if challengeSwitch.CurrentValue < minValue then
challengeSwitch.CurrentValue = minValue
end
ModifyTextBox({ Id = challengeSwitch.ValueTextAnchor, Text = challengeSwitch.ChallengeText, LuaKey = "Amount", LuaValue = round(challengeSwitch.CurrentValue), Format = "LootFormat" })
if challengeSwitch.CurrentValue % 10 == 0 then
thread(PulseText, {Id = challengeEncounter.ValueTextAnchor, ScaleTarget = 1.5, ScaleDuration = 0.15, HoldDuration = 0.0})
PlaySound({ Name = "/Leftovers/Menu Sounds/RosterPickup" })
end
local intervalMultiplier = challengeSwitch.IntervalMultiplier or 1
local ticks = 10
for i = 1, ticks do
local tickTime = (challengeEncounter.LootDecayInterval * intervalMultiplier) / ticks
if CurrentRun.CurrentRoom.ElapsedTimeMultiplier then
tickTime = tickTime / CurrentRun.CurrentRoom.ElapsedTimeMultiplier
end
wait( tickTime, RoomThreadName )
end
end
end
function StartDevotionTest( currentRun, currentRoom, currentEncounter )
currentRun = currentRun or CurrentRun
currentRoom = currentRoom or CurrentRun.CurrentRoom
currentEncounter = currentEncounter or CurrentRun.CurrentRoom.Encounter
thread( PlayVoiceLines, GlobalVoiceLines.DevotionLootGrantedVoiceLines )
local lootA = GiveLoot({ OffsetX = -85, OffsetY = 35, ForceLootName = currentEncounter.LootAName, SuppressSpawnSounds = true })
lootA.CanReceiveGift = false
SetThingProperty({ Property = "SortBoundsScale", Value = 1, DestinationId = lootA.ObjectId })
SetObstacleProperty({ Property = "MagnetismWhileBlocked", Value = 0, DestinationId = lootA.ObjectId })
local lootB = GiveLoot({ OffsetX = 85, OffsetY = -35, ForceLootName = currentEncounter.LootBName, SuppressSpawnSounds = true })
lootB.CanReceiveGift = false
SetThingProperty({ Property = "SortBoundsScale", Value = 1, DestinationId = lootB.ObjectId })
SetObstacleProperty({ Property = "MagnetismWhileBlocked", Value = 0, DestinationId = lootB.ObjectId })
waitUntil( UIData.BoonMenuId )
local lootAId = lootA.ObjectId
local lootBId = lootB.ObjectId
local newLoots = { lootAId, lootBId }
-- upgrade enemies with alternate upgrade
local chosenLootName = NotifyResultsTable[ UIData.BoonMenuId ]
local alternateLootId = nil
local alternateLootData = nil
if lootA.Name == chosenLootName then
alternateLootId = lootBId
alternateLootData = lootB
else
alternateLootId = lootAId
alternateLootData = lootA
end
currentEncounter.ChosenGodName = chosenLootName
currentEncounter.SpurnedGodName = alternateLootData.Name
-- wait until slot upgrade is done
DebugPrint({ Text = "Apply "..alternateLootData.Name.." to Enemies" })
AddEnemyUpgrade( alternateLootData.Name, CurrentRun )
currentEncounter.RemoveUpgradeOnEnd = alternateLootData.Name
if EnemyData[alternateLootData.Name.."RoomWeapon"] ~= nil then
currentEncounter.SpawnPassiveRoomWeapons = currentEncounter.SpawnPassiveRoomWeapons or {}
table.insert(currentEncounter.SpawnPassiveRoomWeapons, alternateLootData.Name.."RoomWeapon")
end
CurrentRun.CurrentRoom.RejectedLootData = alternateLootData
UseableOff({ Ids = newLoots })
StartDevotionTestPresentation( CurrentRun.CurrentRoom, alternateLootData, alternateLootId )
Destroy({ Ids = newLoots })
EnableRoomTraps()
ActivatedObjects[alternateLootId] = nil
RemoveInputBlock({ Name = "DevotionTest" })
StartEncounterEffects( CurrentRun )
end
function BossIntroHydra( eventSource, args )
args.ProcessTextLinesIds = { eventSource.BossId }
args.SetupBossIds = { eventSource.BossId }
BossIntro( eventSource, args )
end
function BossIntroElysium( eventSource, args )
local shrineLevel = GetNumMetaUpgrades( eventSource.ShrineMetaUpgradeName )
BossIntro( eventSource, args[shrineLevel] )
end
function BossIntroCharon( eventSource, args )
AddInputBlock({ Name = "BossIntro" })
AddTimerBlock(CurrentRun, "CharonIntro")
if not TextLinesRecord.BossCharonEncounter01 then
wait( 4 )
else
wait( 1.5 )
end
RemoveInputBlock({ Name = "BossIntro" })
RemoveTimerBlock(CurrentRun, "CharonIntro")
BossIntro( eventSource, args )
end
function BossIntroHades(eventSource, args)
if eventSource.Encounter.Name == "BossHadesPeaceful" then
args.ProcessTextLinesIds = { 552710 }
args.UnlockDelay = 0.0
args.SetupBossIds = nil
args.ResetRoomZoom = nil
eventSource.SpawnRewardDelay = 1.32
end
if TextLinesRecord["HadesAllowsLegendaryKeepsakes01"] ~= nil then
eventSource.BlockHadesAssistTraits = false
else
eventSource.BlockHadesAssistTraits = true
end
BossIntro( eventSource, args )
if eventSource.Encounter.Name == "BossHadesPeaceful" then
CurrentRun.ActiveBiomeTimer = false
end
end
function BossIntro( eventSource, args )
HideCombatUI("BossIntro")
AddInputBlock({ Name = "BossIntro" })
AddTimerBlock( CurrentRun, "BossIntro" )
ToggleControl({ Names = { "AdvancedTooltip", }, Enabled = false })
PlayVoiceLines( args.VoiceLines )
wait( 0.4, RoomThreadName )
if args.ProcessTextLinesIds ~= nil then
for k, id in ipairs(args.ProcessTextLinesIds) do
if ActiveEnemies[id] ~= nil then
local enemy = ActiveEnemies[id]
if not args.SkipAngleTowardTarget then
AngleTowardTarget({ Id = id, DestinationId = CurrentRun.Hero.ObjectId })
end
PanCamera({ Ids = id, Duration = args.DurationIn or 1.5, EaseIn = 0.05, EaseOut = 0.3 })
if args.UsePanSound then
PlaySound({ Name = "/Leftovers/World Sounds/MapZoomSlow" })
end
ProcessTextLines( enemy.BossPresentationSuperPriorityIntroTextLineSets )
ProcessTextLines( enemy.BossPresentationPriorityIntroTextLineSets )
ProcessTextLines( enemy.BossPresentationIntroTextLineSets )
ProcessTextLines( enemy.BossPresentationTextLineSets )
ProcessTextLines( enemy.BossPresentationRepeatableTextLineSets )
if not PlayRandomRemainingTextLines( enemy, enemy.BossPresentationSuperPriorityIntroTextLineSets ) then
if not PlayRandomRemainingTextLines( enemy, enemy.BossPresentationPriorityIntroTextLineSets ) then
if not PlayRandomRemainingTextLines( enemy, enemy.BossPresentationIntroTextLineSets ) then
if not PlayRandomRemainingTextLines( enemy, enemy.BossPresentationTextLineSets ) then
PlayRandomRemainingTextLines( enemy, enemy.BossPresentationRepeatableTextLineSets )
end
end
end
end
if not args.SkipBossMusic then
StartBossRoomMusic()
end
end
end
end
if args.ResetRoomZoom then
FocusCamera({ Fraction = CurrentRun.CurrentRoom.ZoomFraction or 1.0, Duration = 6.0, ZoomType = "Ease" })
end
if args.SetupBossIds ~= nil then
for k, id in ipairs(args.SetupBossIds) do
if ActiveEnemies[id] ~= nil then
thread(SetupBoss, ActiveEnemies[id])
-- used for Final Boss EM4
CurrentRun.CurrentRoom.BossId = id
end
end
end
if args.UnlockDelay ~= nil then
wait(args.UnlockDelay)
end
LockCamera({ Id = CurrentRun.Hero.ObjectId, Duration = args.DurationOut or 1.25, EaseIn = 0.04, EaseOut = 0.275 })
RemoveInputBlock({ Name = "BossIntro" })
RemoveTimerBlock( CurrentRun, "BossIntro" )
ToggleControl({ Names = { "AdvancedTooltip", }, Enabled = true })
ShowCombatUI("BossIntro")
if args.DelayedStart then
StartEncounterEffects( CurrentRun )
end
end
function SetupBoss(enemy)
if enemy.FightStartGlobalVoiceLines ~= nil then
thread( PlayVoiceLines, GlobalVoiceLines[enemy.FightStartGlobalVoiceLines], nil, boss )
end
CreateBossHealthBar(enemy)
wait(enemy.AISetupDelay)
SetupAI( CurrentRun, enemy )
end
function BossStageTransition(boss, currentRun, aiStage)
local bossId = boss.ObjectId
SetUnitInvulnerable( boss )
if aiStage.MoveToId ~= nil then
MoveWithinRange(boss, aiStage.MoveToId, boss)
end
if aiStage.TransitionAnimation then
SetAnimation({ DestinationId = bossId, Name = aiStage.TransitionAnimation })
end
if aiStage.CombatText ~= nil then
thread(InCombatText, bossId, aiStage.CombatText, 3)
end
if aiStage.StageTransitionVoiceLines ~= nil and IsAlive({ Id = bossId }) then
thread( PlayVoiceLines, aiStage.StageTransitionVoiceLines, nil, boss )
end
if aiStage.StageTransitionGlobalVoiceLines ~= nil and IsAlive({ Id = bossId }) then
thread( PlayVoiceLines, GlobalVoiceLines[aiStage.StageTransitionGlobalVoiceLines], nil, boss )
end
if aiStage.FireWeapon ~= nil then
boss.WeaponName = aiStage.FireWeapon
local weaponAIData = GetWeaponAIData(boss)
boss.ForcedWeaponInterrupt = nil
DoAttackerAILoop( boss, currentRun, nil, weaponAIData )
end
if aiStage.TransitionUnthreadedFunctionNames ~= nil then
for i, functionName in ipairs(aiStage.TransitionUnthreadedFunctionNames) do
_G[functionName](boss, aiStage.TransitionUnthreadedFunctionArgs[i])
end
end
-- Fire passive weapons
if aiStage.DumbFireWeapons ~= nil then
for k, weaponName in pairs(aiStage.DumbFireWeapons) do
local weaponData = WeaponData[weaponName].AIData or WeaponData[weaponName]
weaponData.Name = weaponName
thread( DumbFireAttack, boss, currentRun, weaponData )
end
end
wait( aiStage.WaitDuration or 2.0)
SetUnitVulnerable( boss )
StopAnimation({ Name = "Invincibubble", DestinationId = bossId })
end
function MinotaurFinalStageTransition( boss, currentRun, aiStage )
if boss.CannotDieFromDamage then
currentRun.CurrentRoom.Encounter.CancelSpawns = true
boss.SkipOnDeathSpawnEncounter = true
DestroyRequiredKills( { BlockLoot = true, SkipIds = { boss.ObjectId } } )
MinotaurEarlyExitPresentation( boss, currentRun )
Kill( boss, { SkipOnDeathFunction = true, Silent = true, SkipDestroyDelay = true, } )
else
BossStageTransition( boss, currentRun, aiStage )
end
end
function MinotaurArmorBreak( boss, currentRun, aiStage )
SetHSV({ Id = boss.ObjectId, HSV = { 0, 0, 0 }, ValueChangeType = "Absolute" })
BossStageTransition( boss, currentRun, aiStage )
end
function CharonFightEnd( boss, currentRun, aiStage )
currentRun.CurrentRoom.Encounter.CancelSpawns = true
boss.SkipOnDeathSpawnEncounter = true
DestroyRequiredKills( { BlockLoot = true, SkipIds = { boss.ObjectId } } )
CharonFightEndPresentation( boss, currentRun )
Kill( boss, { SkipOnDeathFunction = true, Silent = true, SkipDestroyDelay = true, } )
end
function StartChallengeEncounter( challengeSwitch )
local challengeSwitchId = challengeSwitch.ObjectId
SetAnimation({ DestinationId = challengeSwitch.ObjectId, Name = "ChallengeSwitchInProgress" })
challengeSwitch.IsOpen = false
challengeSwitch.UseText = "UseChallengeSwitchInProgress"
RefreshUseButton( challengeSwitch.ObjectId, challengeSwitch )
ModifyTextBox({ Id = challengeSwitch.ValueTextAnchor, Text = challengeSwitch.ChallengeText, LuaKey = "Amount", LuaValue = round(challengeSwitch.StartingValue), Format = "LootFormat", FadeTarget = 1.0, FadeDuration = 0.5 })
SetAnimation({ Name = "InCombatTextShadow", DestinationId = challengeSwitch.ValueTextAnchor, OffsetY = -227, OffsetX = -40 })
SetScaleX({ Id = challengeSwitch.ValueTextAnchor, Fraction = 0.66 })
if CurrentRun.CurrentRoom.WellShop then
SetAnimation({ DestinationId = CurrentRun.CurrentRoom.WellShop.ObjectId, Name = "WellShopRelock" })
UseableOff({ Id = CurrentRun.CurrentRoom.WellShop.ObjectId })
end
if CurrentRun.CurrentRoom.SellTraitShop ~= nil then
SetAnimation({ DestinationId = CurrentRun.CurrentRoom.SellTraitShop.ObjectId, Name = "SellTraitShopRelock" })
UseableOff({ Id = CurrentRun.CurrentRoom.SellTraitShop.ObjectId })
end
local encounter = CurrentRun.CurrentRoom.ChallengeEncounter
CurrentRun.CurrentRoom.StartedChallengeEncounter = true
encounter.InProgress = true
local difficultyModifer = challengeSwitch.DifficultyModifier or 1
encounter.DifficultyRating = encounter.DifficultyRating * difficultyModifer
RecordEncounter( CurrentRun, CurrentRun.CurrentRoom.ChallengeEncounter )
EnableRoomTraps()
HandleTrapChains( encounter )
thread( HandleChallengeLootDecay, challengeSwitch, CurrentRun.CurrentRoom.ChallengeEncounter )
StartEncounter( CurrentRun, CurrentRun.CurrentRoom, CurrentRun.CurrentRoom.ChallengeEncounter )
end
function EndChallengeEncounter( eventSource )
local challengeEncounter = eventSource
local challengeSwitch = challengeEncounter.Switch
if challengeEncounter.EndedEarly then
return
end
thread(MarkObjectiveComplete, "TimeChallenge")
ChallengeEncounterEndPresentation( eventSource )
SetAnimation({ DestinationId = challengeSwitch.ObjectId, Name = challengeSwitch.ReadyToOpenAnimationName })
UseableOn({ Id = challengeSwitch.ObjectId })
challengeSwitch.UseText = challengeSwitch.ChallengeResolvedUseText
RefreshUseButton( challengeSwitch.ObjectId, challengeSwitch )
challengeSwitch.ReadyToUse = true
if CurrentRun.CurrentRoom.WellShop then
SetAnimation({ DestinationId = CurrentRun.CurrentRoom.WellShop.ObjectId, Name = "WellShopUnlocked" })
UseableOn({ Id = CurrentRun.CurrentRoom.WellShop.ObjectId })
end
if CurrentRun.CurrentRoom.SellTraitShop then
SetAnimation({ DestinationId = CurrentRun.CurrentRoom.SellTraitShop.ObjectId, Name = "SellTraitShopUnlocked" })
UseableOn({ Id = CurrentRun.CurrentRoom.SellTraitShop.ObjectId })
end
end
function EnableCodex()
CodexStatus.Enabled = true
wait(0.25)
DisplayUnlockText({
TitleText = "Codex_Unlocked",
SubtitleText = "Codex_Unlocked_Subtitle",
SubtitleData = { LuaKey = "TempTextData", LuaValue = { Name = weaponName }},
-- Duration = 4.5,
})
wait(0.25)
thread( ShowCodexUpdate )
end
function EnableMirrorSwap()
GameState.Flags.SwapMetaupgradesEnabled = true
wait(0.25)
DisplayUnlockText({
TitleText = "MetaUpgradeRespec_Unlocked",
SubtitleText = "MetaUpgradeRespec_Unlocked_Subtitle",
SubtitleData = { LuaKey = "TempTextData", LuaValue = { Name = weaponName }},
AnimationName = "LocationTextBGGeneric_Mirror",
AnimationOutName = "LocationTextBGGenericOut_Mirror",
-- Duration = 4.5,
})
end
function SquelchHermes( source, args )
CurrentRun.SquelchedHermes = true
if args ~= nil and args.Permanent then
CurrentRun.SquelchedHermesPermanently = true
end
end
function AssignWeaponKits( eventSource, args )
local kitIds = GetIdsByType({ Name = "WeaponKit01" })
table.sort( kitIds )
MapState.WeaponKits = {}
--[[
if SessionState.NeedWeaponPickupBinkLoad then
PreLoadBinks({ Reset = true })
SessionState.NeedWeaponPickupBinkLoad = false
end
]]
local packages = {}
for k, weaponName in ipairs( WeaponSets.HeroMeleeWeapons ) do
table.insert( packages, weaponName )
local linkdWeaponNames = WeaponSets.HeroWeaponSets[weaponName]
if linkdWeaponNames ~= nil then
ConcatTableValues( packages, linkedWeaponNames )
end
end
LoadPackages({ Names = packages })
local kitBinks = {}
local weaponPrefixes = { "WeaponBow", "WeaponFists", "WeaponGun", "WeaponShield", "WeaponSpear", "WeaponSword" }
for k, weaponPrefix in ipairs( weaponPrefixes ) do
local weaponPostFixes = { "", "01", "02", "03" }
for j, weaponPostFix in ipairs( weaponPostFixes ) do
local kitBink = weaponPrefix..weaponPostFix
table.insert( kitBinks, kitBink.."FloatingIdle" )
table.insert( kitBinks, kitBink.."FloatingIdleOff" )
end
end
PreLoadBinks({ Names = kitBinks })
PreLoadBinks({ Names = TraitData.SkellyAssistTrait.LoadBinks })
for k, weaponName in ipairs( WeaponSets.HeroMeleeWeapons ) do
local weaponData = WeaponData[weaponName]
local kitId = RemoveFirstIndexValue( kitIds )
if args.IgnoreRequirements or IsWeaponEligible( CurrentRun, weaponData ) then
AttachLua({ Id = kitId, Table = weaponData })
weaponData.ObjectId = kitId
MapState.WeaponKits[kitId] = weaponData
weaponData.TextAnchorId = SpawnObstacle({ Name = "BlankObstacle", Group = "Standing", DestinationId = kitId, OffsetZ = 81 })
if IsWeaponUnlocked( weaponName ) then
if IsWeaponUnused( weaponData.Name ) then
if GameState.Cosmetics.UnusedWeaponBonusAddGems then
weaponData.MetaPointPercentBonus = (TraitData.UnusedWeaponBonusTraitAddGems.MetaPointMultiplier - 1) * 100
weaponData.GemPercentBonus = (TraitData.UnusedWeaponBonusTraitAddGems.GemMultiplier - 1) * 100
weaponData.UseText = "UseWeaponKit_MetaPointBonus2"
else
weaponData.MetaPointPercentBonus = (TraitData.UnusedWeaponBonusTrait.MetaPointMultiplier - 1) * 100
weaponData.UseText = "UseWeaponKit_MetaPointBonus"
end
end
else
if HasResource( weaponData.ResourceName, weaponData.UnlockCost ) then
if weaponData.ResourceName == "LockKeys" then
weaponData.UseText = "UseWeaponKit_LockedHasKey"
elseif weaponData.ResourceName == "SuperLockKeys" then
weaponData.UseText = "UseWeaponKit_LockedHasSuperKey"
end
SetAnimation({ Name="LockedIconHasKey", DestinationId = weaponData.TextAnchorId })
else
weaponData.UseText = "UseWeaponKit_LockedNoKey"
SetAnimation({ Name="LockedIconNoKey", DestinationId = weaponData.TextAnchorId })
end
end
if args.PreLoadBinks and not SessionState.NeedWeaponPickupBinkLoad then
local overrideWeaponData = WeaponData[weaponName] --GetWeaponData( CurrentRun.Hero, weaponName )
if GameState.LastWeaponUpgradeData[weaponName] ~= nil then
local weaponUpgradeIndex = GameState.LastWeaponUpgradeData[weaponName].Index or 1
local weaponUpgradeData = WeaponUpgradeData[weaponName][weaponUpgradeIndex]
if weaponUpgradeData ~= nil and weaponUpgradeData.TraitName ~= nil then
--DebugPrint({ Text = "weaponUpgradeData.TraitName = "..weaponUpgradeData.TraitName })
overrideWeaponData = TraitData[weaponUpgradeData.TraitName].WeaponDataOverride[weaponName] or overrideWeaponData
end
end
if overrideWeaponData.Binks then
PreLoadBinks({ Names = overrideWeaponData.Binks, Cache = args.BinkCacheOverrides[weaponName] })
elseif overrideWeaponData.WeaponBinks then
local weaponBinks = CombineTables( overrideWeaponData.WeaponBinks, CurrentRun.Hero.WeaponBinks )
PreLoadBinks({ Names = weaponBinks, Cache = args.BinkCacheOverrides[weaponName], Reset = (args.BinkCacheOverrides[weaponName] ~= nil) })
end
end
else
Destroy({ Id = kitId })
end
end
-- Remove any unused kits
Destroy({ Ids = CollapseTable( kitIds ) })
UpdateWeaponKits()
end
function SpawnAllLoot( eventSource, args )
local lootPointIds = GetIdsByType({ Name = "LootPoint" })
table.sort( lootPointIds )
local lootNames = GetAllKeys( LootData )
table.sort( lootNames )
for k, lootName in ipairs( lootNames ) do
if not LootData[lootName].DebugOnly then
local lootPointId = RemoveFirstIndexValue( lootPointIds )
local loot = GiveLoot({ ForceLootName = lootName, SpawnPoint = lootPointId })
SetObstacleProperty({ Property = "MagnetismWhileBlocked", Value = 0, DestinationId = loot.ObjectId })
loot.RespawnAfterUse = args.RespawnAfterUse
loot.StripRequirements = args.StripInteractTextRequirements
loot.WipeRecordsAfterUse = args.WipeRecordsAfterUse
BlockVfx({ DestinationId = loot.ObjectId })
if args.StopAnimations ~= nil then
StopAnimation({ Names = args.StopAnimations, DestinationId = loot.ObjectId })
end
if args.StripInteractTextRequirements and loot.PickupTextLineSets ~= nil then
for lineSetName, lineSet in pairs( loot.PickupTextLineSets ) do
StripRequirements( lineSet )
end
end
end
end
local weaponLoot = CreateWeaponLoot({ SpawnPoint = RemoveFirstIndexValue( lootPointIds )})
weaponLoot.RespawnAfterUse = args.RespawnAfterUse
weaponLoot.WipeRecordsAfterUse = args.WipeRecordsAfterUse
weaponLoot.UpgradeOptions = nil
SetObstacleProperty({ Property = "MagnetismWhileBlocked", Value = 0, DestinationId = weaponLoot.ObjectId })
local stackLoot = CreateStackLoot({ SpawnPoint = RemoveFirstIndexValue( lootPointIds )})
stackLoot.RespawnAfterUse = args.RespawnAfterUse
stackLoot.WipeRecordsAfterUse = args.WipeRecordsAfterUse
stackLoot.StripRequirements = args.StripInteractTextRequirements
stackLoot.UpgradeOptions = nil
SetObstacleProperty({ Property = "MagnetismWhileBlocked", Value = 0, DestinationId = stackLoot.ObjectId })
end
function SpawnAllConsumables( eventSource, args )
local spawnPointIds = GetIds({ Name = "ConsumablePoints" })
table.sort( spawnPointIds )
local names = GetAllKeys( ConsumableData )
for k, excludeName in pairs( args.ExcludeNames ) do
RemoveValueAndCollapse( names, excludeName )
end
table.sort( names )
for k, name in ipairs( names ) do
local consumableId = SpawnObstacle({ Name = name, DestinationId = spawnPointIds[k], Group = "Standing" })
local rampedData = GetRampedConsumableData( ConsumableData[name] )
local consumable = CreateConsumableItemFromData( consumableId, rampedData, 0, args )
SetObstacleProperty({ Property = "MagnetismWhileBlocked", Value = 0, DestinationId = consumable.ObjectId })
if args.StopAnimations ~= nil then
StopAnimation({ Names = args.StopAnimations, DestinationId = consumable.ObjectId })
end
end
end
function CreateEnemySpawnButtons( eventSource, args )
local spawnButtonIds = GetIds({ Name = args.SpawnButtonGroup })
for k, name in ipairs( args.Names ) do
local spawnPointId = GetClosest({ Id = CurrentRun.Hero.ObjectId, DestinationIds = spawnButtonIds })
RemoveValueAndCollapse( spawnButtonIds, spawnPointId )
local spawnButton = { OnUsedSpawnUnit = name, OnUsedSpawnOnId = args.SpawnOnId, }
AttachLua({ Id = spawnPointId, Table = spawnButton })
local textOffsetY = 50
local enemyData = EnemyData[name]
local codexEntry = Codex.Enemies.Entries[enemyData.GenusName or enemyData.Name]
if codexEntry ~= nil and codexEntry.Image ~= nil then
SetAnimation({ DestinationId = spawnPointId, Name = codexEntry.Image })
SetScale({ Id = spawnPointId, Fraction = 0.5 })
textOffsetY = 80
if enemyData.HealthBuffer ~= nil and enemyData.HealthBuffer > 0 then
CreateTextBox({ Id = spawnPointId, Text = "(Elite)", FontSize = 16, OffsetY = textOffsetY, Color = Color.White, OutlineColor = Color.Black, OutlineThickness = 1, Justification = "CENTER" })
end
else
CreateTextBox({ Id = spawnPointId, Text = name, FontSize = 16, OffsetY = textOffsetY, Color = Color.White, OutlineColor = Color.Black, OutlineThickness = 1, Justification = "CENTER" })
end
end
end
function CreateNPCSpawnButtons( eventSource, args )
local names = GetAllKeys( UnitSetData.NPCs )
for k, excludeName in pairs( args.ExcludeNames ) do
RemoveValueAndCollapse( names, excludeName )
end
table.sort( names )
local spawnButtonIds = ShallowCopyTable( args.SpawnButtonIds )
for k, name in ipairs( names ) do
local spawnPointId = GetClosest({ Id = CurrentRun.Hero.ObjectId, DestinationIds = spawnButtonIds })
RemoveValueAndCollapse( spawnButtonIds, spawnPointId )
local spawnButton = { OnUsedSpawnUnit = name, OnUsedSpawnOnId = args.SpawnOnId, DisableOnUse = true, OnUsedFunctionName = "CheckConversations", }
AttachLua({ Id = spawnPointId, Table = spawnButton })
CreateTextBox({ Id = spawnPointId, Text = name, FontSize = 16, OffsetY = 50, Color = Color.White, OutlineColor = Color.Black, OutlineThickness = 1, Justification = "CENTER" })
end
end
function EnemyNamePlates( eventSource, args )
for enemyId, enemy in pairs( ActiveEnemies ) do
CreateTextBox({ Id = enemyId, Text = enemy.Name, FontSize = 18, Font = "MonospaceTypewriterBold", ShadowOffset = {0, 2}, ShadowBlur = 0, ShadowColor = {0,0,0,1}, ShadowOffset = {0, 4}, OutlineThickness = 2, OutlineColor = {0.0, 0.0, 0.0,1} })
end
end
function ShowMaterialText( eventSource, args )
local ids = GetIds({ Name = args.Group })
for k, id in pairs( ids ) do
local obstacle = MapState.ActiveObstacles[id]
local name = nil
if obstacle ~= nil then
name = obstacle.Name
else
name = GetName({ Id = id })
end
local material = GetMaterial( name, obstacle )
local text = "Material = "..tostring(material)
CreateTextBox({ Id = id, Text = text, FontSize = 18, Font = "MonospaceTypewriterBold", ShadowOffset = {0, 2}, ShadowBlur = 0, ShadowColor = {0,0,0,1}, ShadowOffset = {0, 4}, OutlineThickness = 2, OutlineColor = {0.0, 0.0, 0.0,1} })
end
end
function StripInteractTextRequirements( eventSource, args )
for enemyId, enemy in pairs( ActiveEnemies ) do
if enemy.InteractTextLineSets ~= nil then
for lineSetName, lineSet in pairs( enemy.InteractTextLineSets ) do
lineSet.UseableOffSource = false
StripRequirements( lineSet )
end
end
end
end
function StripRequirements( source )
if not GetConfigOptionValue({ Name = "AllowStripRequirements" }) then
return
end
--@refactor Make more universal
source.RequiredTextLinesThisRun = nil
source.RequiredFalseTextLinesThisRun = nil
source.RequiredMinCompletedRuns = nil
source.RequiredMinDepth = nil
source.RequiredRoom = nil
source.RequiredRooms = nil
source.RequiredTextLines = nil
end
function AddResources( eventSource, args )
for name, amount in pairs( args ) do
AddResource( name, amount, "AddResources" )
end
end
function HandleCoverSpawns( eventSource )
local encounter = eventSource.Encounter or eventSource
local coverData = encounter.CoverData
if coverData == nil then
return
end
coverData.SpawnIds = GetIds({ Name = coverData.SpawnPointGroupName })
if coverData.StartDelay ~= nil then
wait(coverData.StartDelay, RoomThreadName)
end
for i=1, coverData.StartingCount do
local destinationId = RemoveRandomValue(coverData.SpawnIds)
if IsEmpty(coverData.SpawnIds) then
coverData.SpawnIds = GetIds({ Name = coverData.SpawnPointGroupName })
end
SpawnMovingObstacle(coverData.ObstacleName, destinationId, coverData)
end
thread(HandleObstacleSpawns, encounter, encounter.CoverData)
end
function HandleObstacleSpawns( encounter, spawnData )
if spawnData == nil then
return
end
local spawnRate = spawnData.SpawnRate or 0.3
while not encounter.Completed do
wait(spawnRate, RoomThreadName)
if encounter.Completed then
break
end
local spawnsPerBurst = spawnData.SpawnsPerBurst
if spawnData.SpawnsPerBurstMin ~= nil and spawnData.SpawnsPerBurstMax ~= nil then
spawnsPerBurst = RandomInt(spawnData.SpawnsPerBurstMin, spawnData.SpawnsPerBurstMax)
end
for i=1, spawnsPerBurst do
local destinationId = RemoveRandomValue(spawnData.SpawnIds)
if IsEmpty(spawnData.SpawnIds) then
spawnData.SpawnIds = GetIds({ Name = spawnData.SpawnPointGroupName or "SpawnPoints" })
end
SpawnMovingObstacle(spawnData.ObstacleName, destinationId, spawnData)
end
end
end
function HandleShadeWeaponSpawns( currentRoom, currentEncounter )
local spawnData = currentRoom.ShadeWeaponSpawnData
if spawnData == nil then
DebugPrint({ Text = "No Weapon Spawn Data in"..currentRoom.Name.."'s RoomData" })
return
end
currentEncounter = currentEncounter or currentRoom.Encounter
currentRoom.WeaponSpawnPointIds = GetIds({ Name = spawnData.SpawnPointGroupName or "WeaponSpawnPoints" })
currentRoom.WeaponSpawnPointsUsed = currentRoom.WeaponSpawnPointsUsed or {}
if IsEmpty(currentRoom.WeaponSpawnPointIds) or currentEncounter == nil then
return
end
if spawnData.StartDelay ~= nil then
wait( spawnData.StartDelay, RoomThreadName )
end
local spawnRate = spawnData.SpawnRate or 0.3
if spawnData.SpawnRateMin ~= nil and spawnData.SpawnRateMax ~= nil then
spawnRate = RandomFloat(spawnData.SpawnRateMin, spawnData.SpawnRateMax)
end
if currentEncounter == nil then
return
end
local startingSpawnCount = spawnData.StartingSpawnCount or 0
if spawnData.StartingSpawnCountMin ~= nil and spawnData.StartingSpawnCountMax ~= nil then
startingSpawnCount = RandomInt(spawnData.StartingSpawnCountMin, spawnData.StartingSpawnCountMax)
end
for i=1, startingSpawnCount do
local weaponId = SpawnEnemyWeapon(currentRoom, spawnData, true)
if weaponId == nil then
break
end
end
while not currentEncounter.Completed do
if currentEncounter.PauseWeaponSpawns or TableLength(GetIds({ Name = spawnData.SpawnGroup })) > spawnData.MaxSpawns then
wait(0.5)
else
wait( spawnRate, RoomThreadName )
if currentRoom.Encounter == nil or currentRoom.Encounter.Completed then
break
elseif not currentEncounter.PauseWeaponSpawns then
local spawnsPerBurst = spawnData.SpawnsPerBurst or 1
if spawnData.SpawnsPerBurstMin ~= nil and spawnData.SpawnsPerBurstMax ~= nil then
spawnsPerBurst = RandomInt(spawnData.SpawnsPerBurstMin, spawnData.SpawnsPerBurstMax)
end
for i=1, spawnsPerBurst do
SpawnEnemyWeapon(currentRoom, spawnData)
end
end
end
end
end
function SpawnEnemyWeapon(currentRoom, spawnData, useRandomSpawn)
local spawnPointOptions = {}
local spawnPointIds = currentRoom.WeaponSpawnPointIds or GetIds({ Name = "WeaponSpawnPoints" })
for k, id in pairs(spawnPointIds) do
if currentRoom.WeaponSpawnPointsUsed[id] == nil then
table.insert(spawnPointOptions, id)
end
end
local destinationId = nil
if useRandomSpawn then
destinationId = GetRandomValue( spawnPointOptions )
else
destinationId = GetClosest({ Id = CurrentRun.Hero.ObjectId, DestinationIds = spawnPointOptions })
end
if destinationId == nil then
DebugPrint({ Text = "No weapon spawn points remaining" })
return nil
end
local spawnName = GetRandomValue(spawnData.SpawnOptions)
local newWeaponId = SpawnMovingObstacle( spawnName, destinationId, spawnData)
--local newWeaponId = SpawnObstacle({ Name = spawnName, Group = "Standing", DestinationId = destinationId })
AddToGroup({ Id = newWeaponId, Name = spawnData.SpawnGroup })
currentRoom.WeaponSpawnPointsUsed[destinationId] = true
return newWeaponId
end
function HandleHazardSpawns( currentRoom, currentEncounter )
local spawnData = currentRoom.HazardData
if spawnData == nil then
return
end
spawnData.SpawnIds = GetIds({ Name = spawnData.SpawnPointGroupName or "Hazards" })
if IsEmpty(spawnData.SpawnIds) then
return
end
if spawnData.StartDelay ~= nil then
wait( spawnData.StartDelay, RoomThreadName )
end
spawnData.TimesSpawned = 0
local spawnRate = spawnData.SpawnRate or 0.3
local spawnStagger = spawnData.SpawnStagger or 0.1
currentEncounter = currentEncounter or currentRoom.Encounter
while currentEncounter ~= nil and not currentEncounter.Completed do
if currentEncounter.PauseHazards then
wait(0.5, RoomThreadName)
else
spawnRate = spawnData.SpawnRate or 0.3
if spawnData.SkipInitialDelay and spawnData.TimesSpawned == 0 then
spawnRate = 0.1
end
wait( spawnRate, RoomThreadName )
if currentRoom.Encounter == nil or currentRoom.Encounter.Completed then
break
elseif not currentEncounter.PauseHazards then
local spawnsPerBurst = spawnData.SpawnsPerBurst
if spawnData.SpawnsPerBurstMin ~= nil and spawnData.SpawnsPerBurstMax ~= nil then
spawnsPerBurst = RandomInt(spawnData.SpawnsPerBurstMin, spawnData.SpawnsPerBurstMax)
end
spawnData.SpawnIds = GetIds({ Name = spawnData.SpawnPointGroupName or "Hazards" })
for i=1, spawnsPerBurst do
if not currentEncounter.InProgress then
return
end
local destinationId = RemoveRandomValue(spawnData.SpawnIds)
if IsEmpty(spawnData.SpawnIds) then
spawnData.SpawnIds = GetIds({ Name = spawnData.SpawnPointGroupName or "Hazards" })
end
local newHazard = DeepCopyTable( EnemyData[spawnData.ObstacleName] )
newHazard.ObjectId = SpawnUnit({ Name = spawnData.ObstacleName, Group = "Standing", DestinationId = destinationId })
SetupEnemyObject( newHazard, CurrentRun )
wait(spawnStagger, RoomThreadName)
end
spawnData.TimesSpawned = spawnData.TimesSpawned + 1
end
end
end
end
function SpawnMovingObstacle( obstacleName, destinationId, spawnData )
local spawnGroup = spawnData.SpawnGroupName or "Standing"
local obstacleId = SpawnObstacle({ Name = obstacleName, DestinationId = destinationId, Group = spawnGroup })
local spawnScale = spawnData.SpawnScale
if spawnData.SpawnScaleMin ~= nil and spawnData.SpawnScaleMax ~= nil then
spawnScale = RandomFloat(spawnData.SpawnScaleMin, spawnData.SpawnScaleMax)
end
if spawnScale ~= nil then
SetScale({ Id = obstacleId, Fraction = spawnScale })
end
if spawnData.SpawnOffsetZ ~= nil then
AdjustZLocation({ Id = obstacleId, Distance = spawnData.SpawnOffsetZ })
end
if spawnData.FallForce ~= nil then
ApplyUpwardForce({ Id = obstacleId, Speed = -spawnData.FallForce })
end
if spawnData.LerpMovement then
local offset = CalcOffset(math.rad(-145), 3000)
SetObstacleProperty({ DestinationId = obstacleId, Property = "CollidesWithObstacles", Value = false })
Move({ Id = obstacleId, DestinationId = obstacleId, OffsetX = offset.X, OffsetY = offset.Y, Duration = 10.0 })
elseif spawnData.MoveSpeed ~= nil then
ApplyForce({ Id = obstacleId, Speed = spawnData.MoveSpeed, Angle = spawnData.MoveAngle })
end
if spawnData.AllowFlip and CoinFlip() then
FlipHorizontal({ Id = obstacleId })
end
return obstacleId
end
function SetAnimationState( source, args )
SetAnimation({ DestinationId = source.ObjectId, Name = args.Animation })
CurrentRun.AnimationState[source.ObjectId] = args.Animation
end
function RelockAllDoors( source, args )
for id, door in pairs( OfferedExitDoors ) do
if id ~= args.DoorId then
DestroyDoorRewardPresenation( door )
SetAnimation({ DestinationId = id, Name = GetThingDataValue({ Id = id, Property = "Graphic" }) })
door.UseText = door.LockedUseText
door.ReadyToUse = false
end
end
OfferedExitDoors = {}
end
function UnlockDoor( source, args )
if args.RelockAllDoors then
RelockAllDoors( source, args )
end
local door = MapState.ActiveObstacles[args.DoorId]
local roomForDoorData = nil
if door.ForceRoomName ~= nil then
roomForDoorData = RoomData[door.ForceRoomName]
else
roomForDoorData = ChooseNextRoomData( CurrentRun )
end
local roomForDoor = CreateRoom( roomForDoorData )
AssignRoomToExitDoor( door, roomForDoor )
CreateDoorRewardPreview( door )
thread( ExitDoorUnlockedPresentation, door )
door.ReadyToUse = true
end
function SetupShrinePointClearObject( usee, args )
if GameState.ShrinePointClearsComplete[usee.ObjectId] then
-- Already completed
SetAnimation({ DestinationId = usee.ObjectId, Name = usee.CompleteAnimation })
SetHSV({ Id = usee.ObjectId, HSV = { 0, 0, 0 }, ValueChangeType = "Multiply" })
if usee.FlipHorizontalOnComplete then
FlipHorizontal({ Id = usee.ObjectId })
end
if AreAllShrinePointClearsComplete() then
usee.UseText = usee.AllCompleteUseText
return
end
UseableOff({ Id = usee.ObjectId })
return
end
if GameState.ShrinePointClearObjectId == usee.ObjectId then
UpdateShrinePointClearText( usee, args )
CreateAnimation({ Name = usee.AttractAnimation, DestinationId = usee.ObjectId, OffsetY = -100 })
local recordShrinePoints = GetHighestShrinePointRunClear( CurrentRun )
if recordShrinePoints >= usee.GoalShrinePointClear then
usee.UseText = usee.CompleteUseText
return
end
usee.UseText = usee.InProgressUseText
elseif usee.PrevGoalId ~= nil and not GameState.ShrinePointClearsComplete[usee.PrevGoalId] then
-- Not available until prev goal has been completed
UseableOff({ Id = usee.ObjectId })
else
CreateAnimation({ Name = usee.AttractAnimation, DestinationId = usee.ObjectId, OffsetY = -100 })
end
end
function UseShrinePointClearObject( usee, args )
local source = ActiveEnemies[ GetClosestUnitOfType({ Id = usee.ObjectId, DestinationName = usee.SourceName, Distance = 9999 }) ]
if source == nil then
return
end
UseableOff({ Id = usee.ObjectId })
if AreAllShrinePointClearsComplete() then
StatueAdmirePresentation( usee )
UseableOn({ Id = usee.ObjectId })
return
end
if GameState.ShrinePointClearObjectId == nil then
-- Set as active goal
if usee.OnTrophyRevealedTextLineSets ~= nil then
PlayFirstEligibleTextLines( usee, usee.OnTrophyRevealedTextLineSets )
end
ShrinePointClearAvailablePresentation( usee, source )
GameState.ShrinePointClearObjectId = usee.ObjectId
local recordShrinePoints = GetHighestShrinePointRunClear( CurrentRun, { IgnoreSameMode = true } )
if recordShrinePoints >= usee.GoalShrinePointClear then
usee.UseText = usee.CompleteUseText
else
usee.UseText = usee.InProgressUseText
end
RefreshUseButton( usee.ObjectId, usee )
UseableOn({ Id = usee.ObjectId })
return
end
if GameState.ShrinePointClearObjectId == usee.ObjectId then
local recordShrinePoints = GetHighestShrinePointRunClear( CurrentRun, { IgnoreSameMode = true } )
DebugPrint({ Text = "recordShrinePoints = "..recordShrinePoints })
if recordShrinePoints >= usee.GoalShrinePointClear then
-- Cash in
ShrinePointClearCompletePresentation( usee, source )
UseableOff({ Id = usee.ObjectId })
GameState.ShrinePointClearsComplete[usee.ObjectId] = true
GameState.ShrinePointClearObjectId = nil
if usee.NextGoalId ~= nil then
UseableOn({ Id = usee.NextGoalId })
CreateAnimation({ Name = usee.AttractAnimation, DestinationId = usee.NextGoalId, OffsetY = -100 })
else
if AreAllShrinePointClearsComplete() then
for id, v in pairs( GameState.ShrinePointClearsComplete ) do
local shrinePointClearObject = MapState.ActiveObstacles[id]
shrinePointClearObject.UseText = shrinePointClearObject.AllCompleteUseText
UseableOn({ Id = id })
end
end
end
else
-- Condition not satisfied
ShrinePointClearIncompletePresentation( usee, source )
UseableOn({ Id = usee.ObjectId })
end
end
end
function AreAllShrinePointClearsComplete()
if TableLength( GameState.ShrinePointClearsComplete ) >= 3 then
return true
end
return false
end
function RegenerateElysiumPillar(pillar, currentRun)
wait(pillar.RegenerateStartDelay, RoomThreadName)
if pillar.RegenerateStartAnimation ~= nil then
CreateAnimation({ DestinationId = pillar.ObjectId, Name = pillar.RegenerateStartAnimation, OffsetZ = 200 })
end
wait(pillar.RegenerateDuration, RoomThreadName)
if pillar.RegenerateAnimation ~= nil then
SetAnimation({ DestinationId = pillar.ObjectId, Name = pillar.RegenerateAnimation })
local notifyName = "RegenWaitForAnimation"..pillar.ObjectId
NotifyOnAnimationTimeRemaining({ Id = pillar.ObjectId, Animation = pillar.RegenerateAnimation, Remaining = 0, Notify = notifyName })
waitUntil( notifyName )
end
local newData = ObstacleData[pillar.RegenerateAs]
local newObject = DeepCopyTable( newData )
newObject.ObjectId = pillar.ObjectId
AttachLua({ Id = pillar.ObjectId, Table = newObject })
if pillar.RegenPropertyChanges ~= nil then
ApplyUnitPropertyChanges( newObject, pillar.RegenPropertyChanges, true )
end
if newObject.SpawnPropertyChanges ~= nil then
ApplyUnitPropertyChanges( newObject, newObject.SpawnPropertyChanges, true )
end
RecordObjectState( CurrentRun.CurrentRoom, pillar.ObjectId, "SwapData", pillar.RegenerateAs )
--local newPillar = SpawnObstacle({ Name = pillar.RegenerateAs, DestinationId = pillar.ObjectId, Group = "Standing" })
--DebugPrint({ Text = GetThingDataValue({ Id = pillar.ObjectId, Property = "Scale" }) })
--SetScale({ Id = newPillar, Fraction = GetThingDataValue({ Id = pillar.ObjectId, Property = "Scale" }) })
--Destroy({ Id = pillar.ObjectId })
end
function TrackThanatosChallengeProgress( encounter, victim, killer )
if victim.IgnoreThanatosChallengeTracker then
return
end
local maxTimeSincePlayerDamage = encounter.MaxTimeSincePlayerDamage or 5
if killer ~= nil and killer.ObjectId == encounter.ThanatosId then
encounter.ThanatosKills = encounter.ThanatosKills + 1
UpdateObjectiveDescription( "ThanatosKills", "Objective_ThanatosKills", "ThanatosKills", encounter.ThanatosKills )
elseif killer ~= nil and killer == CurrentRun.Hero then
encounter.PlayerKills = encounter.PlayerKills + 1
UpdateObjectiveDescription( "PlayerKills", "Objective_PlayerKills", "PlayerKills", encounter.PlayerKills )
elseif victim ~= nil and victim.TimeOfLastPlayerDamage ~= nil and _worldTime - victim.TimeOfLastPlayerDamage < maxTimeSincePlayerDamage then
encounter.PlayerKills = encounter.PlayerKills + 1
UpdateObjectiveDescription( "PlayerKills", "Objective_PlayerKills", "PlayerKills", encounter.PlayerKills )
end
end
function HandleTrapChains( eventSource )
local roomTrapChainData = CurrentRun.CurrentRoom.PassiveTrapChains
if roomTrapChainData == nil then
return
end
for k, trapChainData in ipairs(roomTrapChainData) do
thread( HandleTrapChain, trapChainData, eventSource )
end
end
function HandleTrapChain( trapChainData, encounter )
wait( trapChainData.StartDelay, RoomThreadName )
while encounter.InProgress do
for k, chain in ipairs(trapChainData.Chains) do
for k, trapId in ipairs(chain) do
local chainedEnemy = ActiveEnemies[trapId]
notifyExistingWaiters("WithinDistance"..trapId)
local chainedWeaponAIData = ShallowCopyTable(chainedEnemy.DefaultAIData) or chainedEnemy
if WeaponData[chainedEnemy.WeaponName] ~= nil and WeaponData[chainedEnemy.WeaponName].AIData ~= nil then
OverwriteTableKeys( chainedWeaponAIData, WeaponData[chainedEnemy.WeaponName].AIData)
end
chainedWeaponAIData.WeaponName = chainedEnemy.WeaponName
if not chainedEnemy.AIDisabled then
thread(AttackOnce, chainedEnemy, CurrentRun, nil, chainedWeaponAIData )
end
end
wait( trapChainData.ChainInterval, RoomThreadName )
end
wait( trapChainData.ChainCooldown, RoomThreadName )
end
end
function SpawnConsumables( eventSource, args )
for k, spawnData in pairs( args.Spawns ) do
DebugPrint({ Text= spawnData.ConsumableName })
local consumableId = SpawnObstacle({ Name = spawnData.ConsumableName, DestinationId = spawnData.DestinationId, Group = "Standing", })
local consumable = CreateConsumableItem( consumableId, spawnData.ConsumableName, 0 )
ActivatedObjects[consumableId] = consumable
end
end
function AllAIStop( usee, args )
for id, enemy in pairs( ActiveEnemies ) do
killTaggedThreads( enemy.AIThreadName )
killWaitUntilThreads( enemy.AINotifyName )
Stop({ Id = enemy.ObjectId })
StopAnimation({ DestinationId = enemy.ObjectId })
end
end
function AllAIFollow( usee, args )
AllAIStop( usee, args )
for id, enemy in pairs( ActiveEnemies ) do
thread( SetAI, FollowAI, enemy, CurrentRun )
end
end
function ActivateHydra(eventSource, args)
args.LegalTypes = { eventSource.HydraVariant }
eventSource.BossId = GetFirstValue(GetInactiveIdsByType({ Name = eventSource.HydraVariant }))
ActivatePrePlaced(eventSource, args)
end
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/NoGameNoLife1234_admin/HadesSource.git
[email protected]:NoGameNoLife1234_admin/HadesSource.git
NoGameNoLife1234_admin
HadesSource
黑帝斯源码拆解学习
master

搜索帮助