2690 lines
91 KiB
Lua
Executable File
2690 lines
91 KiB
Lua
Executable File
require( "constants" )
|
|
require( "ascension_levels" )
|
|
require( "map_room" )
|
|
require( "utility_functions" )
|
|
require( "spawner" )
|
|
require( "portalspawner" )
|
|
require( "portalspawnerv2" )
|
|
|
|
|
|
LinkLuaModifier( "modifier_monster_leash", "modifiers/modifier_monster_leash", LUA_MODIFIER_MOTION_NONE )
|
|
LinkLuaModifier( "modifier_passive_autocast", "modifiers/modifier_passive_autocast", LUA_MODIFIER_MOTION_NONE )
|
|
LinkLuaModifier( "modifier_ability_cast_warning", "modifiers/modifier_ability_cast_warning", LUA_MODIFIER_MOTION_NONE )
|
|
LinkLuaModifier( "modifier_ascension_plasma_field_display", "modifiers/modifier_ascension_plasma_field_display", LUA_MODIFIER_MOTION_NONE )
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
if CMapEncounter == nil then
|
|
CMapEncounter = class({})
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:constructor( hRoom, szEncounterName )
|
|
self.szEncounterName = szEncounterName
|
|
self.hRoom = hRoom
|
|
|
|
if self.hRoom == nil then
|
|
print( "ERROR - Nil room for " .. szEncounterName )
|
|
return
|
|
end
|
|
|
|
self.Spawners = {}
|
|
self.PortalSpawners = {}
|
|
self.RetreatPoints = {}
|
|
self.PortalSpawnersV2 = {}
|
|
|
|
self.flStartTime = -1
|
|
self.EventListeners = {}
|
|
self.bCompleted = false
|
|
self.flCompletionTime = nil
|
|
self.bDevForceCompleted = false
|
|
self.bHasGeneratedRewards = false
|
|
self.bHasSpawnedEndLevelEntities = false
|
|
self.vRoomRewardCratePos = self.hRoom:GetOrigin()
|
|
self.szPortalTriggerSpawner = nil
|
|
self.flPortalTriggerDelay = 0
|
|
|
|
self.nMaxSpawnedUnitCount = 0
|
|
self.nUnitsRemainingForRewardDrops = 0
|
|
|
|
self.SpawnedEnemies = {}
|
|
self.SpawnedSecondaryEnemies = {}
|
|
self.SpawnedPortalTriggerUnits = {}
|
|
self.nKilledEnemies = 0
|
|
self.nKilledSecondaryEnemies = 0
|
|
|
|
self.SpawnedBreakables = {}
|
|
self.SpawnedExplosiveBarrels = {}
|
|
|
|
self.bCalculateRewardsFromUnitCount = false
|
|
|
|
local nTotalGoldReward = self:GetTotalGoldRewardPerPlayer()
|
|
self.nGoldReward = nTotalGoldReward
|
|
self.nTotalGoldFromEnemies = nTotalGoldReward - self.nGoldReward
|
|
self.nRemainingGoldFromEnemies = self.nTotalGoldFromEnemies
|
|
|
|
local nTotalXPReward = self:GetTotalXPRewardPerPlayer()
|
|
self.nXPReward = nTotalXPReward / 2
|
|
self.nTotalXPFromEnemies = nTotalXPReward - self.nXPReward
|
|
self.nRemainingXPFromEnemies = self.nTotalXPFromEnemies
|
|
|
|
self.nNumItemsToDrop = 0
|
|
self.nNumBPToDrop = 0
|
|
self.nNumFragmentsToDrop = 0
|
|
|
|
self.nChestsToSpawn = 0
|
|
if self.hRoom:GetType() == ROOM_TYPE_TRAPS then
|
|
self.nChestsToSpawn = self:RoomRandomInt( DEFAULT_MIN_CHESTS, DEFAULT_MAX_CHESTS )
|
|
--elseif self.hRoom:GetType() == ROOM_TYPE_STARTING then
|
|
--self.nChestsToSpawn = 8 -- dev test
|
|
end
|
|
|
|
self.nCratesToSpawn = 0
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY then
|
|
self.nCratesToSpawn = self:RoomRandomInt( DEFAULT_MIN_CRATES_ENEMY_ENC, DEFAULT_MAX_CRATES_ENEMY_ENC )
|
|
elseif self.hRoom:GetType() == ROOM_TYPE_BOSS then
|
|
self.nCratesToSpawn = self:RoomRandomInt( DEFAULT_MIN_CRATES_BOSS_ENC, DEFAULT_MAX_CRATES_BOSS_ENC )
|
|
end
|
|
|
|
self.nExplosiveBarrelsToSpawn = 0
|
|
local bSpawnExplosiveBarrels = self:RoomRollPercentage( ENCOUNTER_SPAWN_BARRELS_CHANCE )
|
|
if bSpawnExplosiveBarrels then
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY then
|
|
self.nExplosiveBarrelsToSpawn = self:RoomRandomInt( DEFAULT_MIN_BARRELS_ENEMY_ENC, DEFAULT_MAX_BARRELS_ENEMY_ENC )
|
|
end
|
|
end
|
|
|
|
self.nObjectiveNumber = 1
|
|
|
|
self.ClientData = {}
|
|
self.ClientData[ "encounter_name" ] = szEncounterName
|
|
self.ClientData[ "encounter_depth" ] = self.hRoom:GetDepth()
|
|
self.ClientData[ "room_type" ] = GetStringForRoomType( self.hRoom:GetType() )
|
|
self.ClientData[ "objectives" ] = {}
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:RoomRandomInt( nMinInt, nMaxInt )
|
|
return self:GetRoom():RoomRandomInt( nMinInt, nMaxInt )
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:RoomRandomFloat( flMin, flMin )
|
|
return self:GetRoom():RoomRandomFloat( flMin, flMin )
|
|
end
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:RoomRollPercentage( nChance )
|
|
local bOutcome = self:RoomRandomInt( 1, 100 ) <= nChance
|
|
return bOutcome
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:OnEliteRankChanged( nEliteDepthBonus )
|
|
|
|
if nEliteDepthBonus > 0 then
|
|
self.ClientData[ "hard_room" ] = 1
|
|
else
|
|
self.ClientData[ "hard_room" ] = 0
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SelectAscensionAbilities( )
|
|
self.AscensionAbilities = {}
|
|
self.hDummyAscensionCaster = nil
|
|
self.ClientData[ "ascension_abilities" ] = {}
|
|
self.ClientData[ "total_difficulty" ] = 0
|
|
|
|
if self:GetRoom():GetType() ~= ROOM_TYPE_ENEMY then
|
|
return
|
|
end
|
|
|
|
local nEliteLevel = self:GetRoom():GetEliteRank()
|
|
local nAscensionLevel = nEliteLevel + GameRules.Aghanim:GetAscensionLevel()
|
|
self.ClientData[ "total_difficulty" ] = nAscensionLevel
|
|
|
|
local nDesiredAbilityCount = EXTRA_ABILITIES_PER_ASCENSION_LEVEL[ nAscensionLevel + 1 ]
|
|
if nDesiredAbilityCount == 0 then
|
|
return
|
|
end
|
|
|
|
local vecAbilityOptions = {}
|
|
local vecEliteAbilityOptions = {}
|
|
for abilityName,hPossibleAbility in pairs( ASCENSION_ABILITIES ) do
|
|
if hPossibleAbility.vecBlacklistedEncounters ~= nil then
|
|
for i=1,#hPossibleAbility.vecBlacklistedEncounters do
|
|
local szBlacklisted = hPossibleAbility.vecBlacklistedEncounters[i]
|
|
if szBlacklisted == self.szEncounterName then
|
|
--print( "Encounter " .. self.szEncounterName .. " blacklisted " .. abilityName )
|
|
goto continue
|
|
end
|
|
end
|
|
end
|
|
|
|
if hPossibleAbility.nRestrictToAct ~= nil then
|
|
if hPossibleAbility.nRestrictToAct ~= self:GetRoom():GetAct() then
|
|
goto continue
|
|
end
|
|
|
|
if hPossibleAbility.szRequiredBoss ~= nil and hPossibleAbility.szRequiredBoss ~= GameRules.Aghanim:GetBossUnitForAct( hPossibleAbility.nRestrictToAct ) then
|
|
goto continue
|
|
end
|
|
end
|
|
|
|
if hPossibleAbility.nMinAscensionLevel ~= nil and hPossibleAbility.nMinAscensionLevel > nAscensionLevel then
|
|
goto continue
|
|
end
|
|
|
|
if hPossibleAbility.nMaxAscensionLevel ~= nil and hPossibleAbility.nMaxAscensionLevel < nAscensionLevel then
|
|
goto continue
|
|
end
|
|
|
|
if hPossibleAbility.bEliteOnly == true then
|
|
table.insert( vecEliteAbilityOptions, abilityName )
|
|
else
|
|
table.insert( vecAbilityOptions, abilityName )
|
|
end
|
|
::continue::
|
|
end
|
|
|
|
-- Force specific abilities
|
|
local nStart = 1
|
|
if ASCENSION_ABILITIES_FORCE_LIST ~= nil then
|
|
for i=1,#ASCENSION_ABILITIES_FORCE_LIST do
|
|
--print( "Encounter " .. self.szEncounterName .. " added ascension ability " .. ASCENSION_ABILITIES_FORCE_LIST[i] )
|
|
table.insert( self.AscensionAbilities, ASCENSION_ABILITIES_FORCE_LIST[i] )
|
|
self.ClientData[ "ascension_abilities" ][ tostring(i) ] = ASCENSION_ABILITIES_FORCE_LIST[i]
|
|
for j=1,#vecAbilityOptions do
|
|
if vecAbilityOptions[j] == ASCENSION_ABILITIES_FORCE_LIST[i] then
|
|
table.remove( vecAbilityOptions, j )
|
|
break
|
|
end
|
|
end
|
|
for j=1,#vecEliteAbilityOptions do
|
|
if vecEliteAbilityOptions[j] == ASCENSION_ABILITIES_FORCE_LIST[i] then
|
|
table.remove( vecEliteAbilityOptions, j )
|
|
break
|
|
end
|
|
end
|
|
end
|
|
nStart = #ASCENSION_ABILITIES_FORCE_LIST + 1
|
|
end
|
|
|
|
-- Pick elite-only abilities
|
|
if nEliteLevel > 0 then
|
|
|
|
local nDesiredEliteAbilityCount = ELITE_ABILITIES_PER_ASCENSION_LEVEL[ nAscensionLevel + 1 ]
|
|
for i=nStart,nDesiredEliteAbilityCount do
|
|
if #vecEliteAbilityOptions == 0 then
|
|
break
|
|
end
|
|
|
|
local nPick = self:RoomRandomInt( 1, #vecEliteAbilityOptions )
|
|
print( "Encounter " .. self.szEncounterName .. " added ELITE ascension ability " .. vecEliteAbilityOptions[nPick] )
|
|
table.insert( self.AscensionAbilities, vecEliteAbilityOptions[nPick] )
|
|
self.ClientData[ "ascension_abilities" ][ tostring(i) ] = vecEliteAbilityOptions[nPick]
|
|
table.remove( vecEliteAbilityOptions, nPick )
|
|
|
|
nStart = nStart + 1
|
|
end
|
|
|
|
end
|
|
|
|
-- Pick ascension abilities
|
|
for i=nStart,nDesiredAbilityCount do
|
|
if #vecAbilityOptions == 0 then
|
|
break
|
|
end
|
|
|
|
local nPick = self:RoomRandomInt( 1, #vecAbilityOptions )
|
|
print( "Encounter " .. self.szEncounterName .. " added ascension ability " .. vecAbilityOptions[nPick] )
|
|
table.insert( self.AscensionAbilities, vecAbilityOptions[nPick] )
|
|
self.ClientData[ "ascension_abilities" ][ tostring(i) ] = vecAbilityOptions[nPick]
|
|
table.remove( vecAbilityOptions, nPick )
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:Precache( context )
|
|
|
|
-- By default, precache all units referenced by spawners
|
|
for _, hSpawner in pairs( self:GetSpawners() ) do
|
|
hSpawner:Precache( context )
|
|
end
|
|
|
|
for _, hPortalSpawner in pairs( self:GetPortalSpawners() ) do
|
|
hPortalSpawner:Precache( context )
|
|
end
|
|
|
|
for _, hPortalSpawner in pairs( self.PortalSpawnersV2 ) do
|
|
hPortalSpawner:Precache( context )
|
|
end
|
|
|
|
-- Precache preview units for exit directions
|
|
for nExitDirection=ROOM_EXIT_LEFT,ROOM_EXIT_RIGHT do
|
|
local szExitRoomName = self:GetRoom():GetExit( nExitDirection )
|
|
if szExitRoomName ~= nil then
|
|
local hExitRoom = GameRules.Aghanim:GetRoom( szExitRoomName )
|
|
if hExitRoom ~= nil and hExitRoom:GetEncounter():GetPreviewUnit() ~= nil then
|
|
PrecacheUnitByNameSync( hExitRoom:GetEncounter():GetPreviewUnit(), context, -1 )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:OnEncounterLoaded()
|
|
--print( "CMapEncounter:OnEncounterLoaded " .. self.szEncounterName )
|
|
|
|
-- Some NPCs require retreat points
|
|
self.RetreatPoints = self:GetRoom():FindAllEntitiesInRoomByName( "retreat_point" )
|
|
|
|
-- All spawners can now look for their spawn info targets
|
|
for _, hSpawner in pairs( self:GetSpawners() ) do
|
|
hSpawner:OnEncounterLoaded( self )
|
|
end
|
|
|
|
-- find all potential portal locations and then assign them out to the portal spawners
|
|
local PortalLocations = {}
|
|
for _, hPortalSpawner in pairs ( self:GetPortalSpawners() ) do
|
|
local name = hPortalSpawner:GetLocatorName()
|
|
if PortalLocations[name] == nil then
|
|
PortalLocations[name] = {}
|
|
PortalLocations[name] = self:GetRoom():FindAllEntitiesInRoomByName( name )
|
|
ShuffleListInPlace( PortalLocations[name] )
|
|
--print( "Collected " .. #PortalLocations[name] .. " portal locations for name " .. name )
|
|
end
|
|
end
|
|
|
|
-- assign each portal spawner a unique spawn location
|
|
for _, hPortalSpawner in pairs ( self:GetPortalSpawners() ) do
|
|
local name = hPortalSpawner:GetLocatorName()
|
|
if #PortalLocations[name] <= 0 then
|
|
print( "ERROR: Can't find a unique portal location for portal spawner named " .. name )
|
|
break
|
|
end
|
|
|
|
local portalLocation = PortalLocations[name][1]
|
|
--print( "Grabbing portal named " .. name .. ". Location is " .. portalLocation:GetOrigin().x .. ", " .. portalLocation:GetOrigin().y .. ", " .. portalLocation:GetOrigin().z )
|
|
table.remove( PortalLocations[name], 1 )
|
|
|
|
hPortalSpawner:SetLocation( portalLocation:GetOrigin() )
|
|
hPortalSpawner:OnEncounterLoaded( self )
|
|
end
|
|
|
|
for _, hPortalSpawner in pairs ( self.PortalSpawnersV2 ) do
|
|
hPortalSpawner:OnEncounterLoaded( self )
|
|
end
|
|
|
|
-- We can also look for a boss preview entity
|
|
self:SpawnBossPreviewEntity()
|
|
|
|
self:InitializeObjectives()
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:InitializeObjectives()
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY then
|
|
self:AddEncounterObjective( "defeat_all_enemies", 0, 0 )
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:Introduce()
|
|
GameRules.Aghanim:GetAnnouncer():OnEncounterSelected( self )
|
|
CustomGameEventManager:Send_ServerToAllClients( "introduce_encounter", self.ClientData )
|
|
self:UpdateClient()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:AddEncounterObjective( szKey, nValue, nGoal )
|
|
local Objective = {}
|
|
Objective[ "name" ] = szKey
|
|
Objective[ "value" ] = nValue
|
|
Objective[ "goal" ] = nGoal
|
|
Objective[ "order" ] = self.nObjectiveNumber
|
|
self.nObjectiveNumber = self.nObjectiveNumber + 1
|
|
|
|
self.ClientData[ "objectives" ][ szKey ] = Objective
|
|
self:UpdateClient()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- nGoal can be purposely nil to skip changing the goal
|
|
--------------------------------------------------------------------------------
|
|
function CMapEncounter:UpdateEncounterObjective( szKey, nValue, nGoal )
|
|
if self.ClientData[ "objectives" ][ szKey ] == nil then
|
|
return
|
|
end
|
|
|
|
self.ClientData[ "objectives" ][ szKey ][ "value" ] = nValue
|
|
if nGoal ~= nil then
|
|
self.ClientData[ "objectives" ][ szKey ][ "goal" ] = nGoal
|
|
end
|
|
self:UpdateClient()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetEncounterObjectiveProgress( szKey )
|
|
if self.ClientData[ "objectives" ][ szKey ] == nil then
|
|
return -1
|
|
end
|
|
|
|
return self.ClientData[ "objectives" ][ szKey ][ "value" ]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetEncounterObjectiveGoal( szKey )
|
|
if self.ClientData[ "objectives" ][ szKey ] == nil then
|
|
return -1
|
|
end
|
|
|
|
return self.ClientData[ "objectives" ][ szKey ][ "goal" ]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:UpdateClient()
|
|
CustomNetTables:SetTableValue( "encounter_state", "depth", { tostring( self.hRoom:GetDepth() ) } )
|
|
CustomNetTables:SetTableValue( "encounter_state", tostring( self.hRoom:GetDepth() ), self.ClientData )
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SpawnBossPreviewEntity()
|
|
|
|
local hBossPreviewEntity = self:GetRoom():FindAllEntitiesInRoomByName( "encounter_boss_preview_locator", false )
|
|
if #hBossPreviewEntity == 0 then
|
|
return
|
|
end
|
|
|
|
local nAct = self:GetRoom():GetAct()
|
|
--print( "CMapEncounter:SpawnBossPreviewEntity() " .. nAct )
|
|
|
|
-- Find the boss room for the act this encounter is in
|
|
local hBossRoom = nil
|
|
for _,room in pairs( GameRules.Aghanim:GetRoomList() ) do
|
|
|
|
if room:GetType() == ROOM_TYPE_BOSS and room:GetAct() == nAct then
|
|
hBossRoom = room
|
|
break;
|
|
end
|
|
end
|
|
|
|
if hBossRoom == nil then
|
|
return
|
|
end
|
|
|
|
local strPreviewUnit = hBossRoom:GetEncounter():GetPreviewUnit()
|
|
local bossPreviewTable =
|
|
{
|
|
BossUnit = strPreviewUnit,
|
|
BossModelScale = ENCOUNTER_PREVIEW_SCALES[ strPreviewUnit ],
|
|
ExtraModelScale = 2,
|
|
EncounterType = ROOM_TYPE_BOSS,
|
|
}
|
|
|
|
if bossPreviewTable.BossModelScale == nil then
|
|
bossPreviewTable.BossModelScale = 1.0
|
|
end
|
|
|
|
for i=1,#hBossPreviewEntity do
|
|
local vOrigin = hBossPreviewEntity[i]:GetAbsOrigin()
|
|
local vAngles = hBossPreviewEntity[i]:GetAnglesAsVector()
|
|
bossPreviewTable.origin = tostring( vOrigin.x ) .. " " .. tostring( vOrigin.y ) .. " " .. tostring( vOrigin.z )
|
|
bossPreviewTable.angles = tostring( vAngles.x ) .. " " .. tostring( vAngles.y ) .. " " .. tostring( vAngles.z )
|
|
|
|
SpawnEntityFromTableAsynchronous( "dota_aghsfort_boss_preview", bossPreviewTable, nil, nil )
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:IsComplete()
|
|
if self.flStartTime == -1 then
|
|
return false
|
|
end
|
|
|
|
return self.bCompleted
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:CheckForCompletion()
|
|
if not self:HasRemainingEnemies() and self:AreScheduledSpawnsComplete() and not self:HasAnyPortals() then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:ShouldAutoStartGlobalAscensionAbilities()
|
|
return true
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetAscensionAbilities()
|
|
return self.AscensionAbilities
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:StartGlobalAscensionAbilities()
|
|
|
|
-- Protect against double calls
|
|
if self.hDummyAscensionCaster ~= nil then
|
|
return
|
|
end
|
|
|
|
if #self.AscensionAbilities > 0 then
|
|
|
|
local vOrigin = self:GetRoom():GetOrigin()
|
|
local dummyTable =
|
|
{
|
|
targetname = "ascension_global_caster",
|
|
MapUnitName = "npc_dota_dummy_caster",
|
|
teamnumber = DOTA_TEAM_BADGUYS,
|
|
}
|
|
self.hDummyAscensionCaster = CreateUnitFromTable( dummyTable, vOrigin )
|
|
if self.hDummyAscensionCaster ~= nil then
|
|
self:AddAscensionAbilities( self.hDummyAscensionCaster )
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:Start()
|
|
print( string.format( "Encounter %s starting..\n", self.szEncounterName ) )
|
|
for nPlayerID=0,AGHANIM_PLAYERS-1 do
|
|
PlayerResource:SetCustomIntParam( nPlayerID, self.hRoom:GetDepth() - 1 )
|
|
end
|
|
|
|
for i=0,GameRules:NumDroppedItems()-1 do
|
|
local hDroppedItem = GameRules:GetDroppedItem( i )
|
|
if hDroppedItem then
|
|
local hContainedItem = hDroppedItem:GetContainedItem()
|
|
if hContainedItem and hContainedItem:IsNeutralDrop() then
|
|
PlayerResource:AddNeutralItemToStash( 0, DOTA_TEAM_GOODGUYS, hContainedItem )
|
|
|
|
local nFXIndex = ParticleManager:CreateParticle( "particles/items2_fx/neutralitem_teleport.vpcf", PATTACH_CUSTOMORIGIN, nil )
|
|
ParticleManager:SetParticleControl( nFXIndex, 0, hDroppedItem:GetAbsOrigin() )
|
|
ParticleManager:ReleaseParticleIndex( nFXIndex )
|
|
|
|
EmitSoundOn( "NeutralItem.TeleportToStash", hDroppedItem )
|
|
|
|
UTIL_Remove( hDroppedItem )
|
|
end
|
|
end
|
|
end
|
|
|
|
local nEntityKilledGameEvent = ListenToGameEvent( "entity_killed", Dynamic_Wrap( getclass( self ), "OnEntityKilled" ), self )
|
|
table.insert( self.EventListeners, nEntityKilledGameEvent )
|
|
|
|
local nTriggerStartTouchEvent = ListenToGameEvent( "trigger_start_touch", Dynamic_Wrap( getclass( self ), "OnTriggerStartTouch" ), self )
|
|
table.insert( self.EventListeners, nTriggerStartTouchEvent )
|
|
|
|
local nTriggerEndTouchEvent = ListenToGameEvent( "trigger_end_touch", Dynamic_Wrap( getclass( self ), "OnTriggerEndTouch" ), self )
|
|
table.insert( self.EventListeners, nTriggerEndTouchEvent )
|
|
|
|
GameRules.Aghanim:GetAnnouncer():OnEncounterStarted( self )
|
|
|
|
self.nMaxSpawnedUnitCount = 0
|
|
self.nUnitsRemainingForRewardDrops = 0
|
|
if self.bCalculateRewardsFromUnitCount then
|
|
self.nMaxSpawnedUnitCount = self:GetMaxSpawnedUnitCount()
|
|
self.nUnitsRemainingForRewardDrops = self.nMaxSpawnedUnitCount
|
|
if self.nMaxSpawnedUnitCount == 0 then
|
|
print( "*** WARNING : Encounter " .. self.szEncounterName .. " indicates 0 units to be spawned.. Check your GetMaxSpawnedUnitCount()" )
|
|
end
|
|
end
|
|
|
|
self.flStartTime = GameRules:GetGameTime()
|
|
|
|
if self:IsWaitingToSpawnPortals() == false then
|
|
for _,PortalSpawner in pairs ( self.PortalSpawners ) do
|
|
PortalSpawner:Start( self.flStartTime )
|
|
end
|
|
end
|
|
|
|
if self.flEnrageTimer then
|
|
CustomNetTables:SetTableValue( "room_data", "enrage_timer", { active=true, startTime=self.flStartTime, enrageTimer=self.flEnrageTimer } )
|
|
else
|
|
CustomNetTables:SetTableValue( "room_data", "enrage_timer", { active=false } )
|
|
end
|
|
|
|
self:SpawnChests()
|
|
self:SpawnBreakableContainers()
|
|
self:SpawnExplosiveBarrels()
|
|
|
|
CustomNetTables:SetTableValue( "room_data", "status", { complete=false } )
|
|
|
|
if self:ShouldAutoStartGlobalAscensionAbilities() == true then
|
|
self:StartGlobalAscensionAbilities()
|
|
end
|
|
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY then
|
|
self.nNumItemsToDrop = GameRules.Aghanim:RollRandomNeutralItemDrops( self )
|
|
self.nNumBPToDrop = self:RoomRandomInt( 0, 2 )
|
|
end
|
|
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY or self.hRoom:GetType() == ROOM_TYPE_TRAPS then
|
|
self.nNumFragmentsToDrop = GameRules.Aghanim:RollRandomFragmentDrops()
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SpawnChests()
|
|
printf( "-----------------------------------" )
|
|
--printf( "CMapEncounter:SpawnChests()" )
|
|
|
|
--PrintTable( TreasureChestData, " " )
|
|
|
|
for i, chestData in ipairs( TreasureChestData ) do
|
|
--print( "" )
|
|
--print( "Looking at chestData #" .. i )
|
|
if ( chestData.szSpawnerName == nil ) then
|
|
printf( "ERROR -- CMapEncounter:SpawnChests(): No szSpawnerName specified for this chest." )
|
|
end
|
|
|
|
local fSpawnChance = chestData.fSpawnChance
|
|
if fSpawnChance == nil or fSpawnChance <= 0 then
|
|
printf( "ERROR -- CMapEncounter:SpawnChests: Treasure chest spawn chance is not valid." )
|
|
end
|
|
|
|
if chestData.nMaxSpawnDistance == nil or chestData.nMaxSpawnDistance < 0 then
|
|
printf( "WARNING -- CMapEncounter:SpawnChests: nMaxSpawnDistance is not valid. Defaulting to 0." )
|
|
chestData.nMaxSpawnDistance = 0
|
|
end
|
|
|
|
local nChestsSpawned = 0
|
|
|
|
-- printf( "CMapEncounter:SpawnChests - chestData.szSpawnerName == %s", chestData.szSpawnerName )
|
|
local hSpawners = self:GetRoom():FindAllEntitiesInRoomByName( chestData.szSpawnerName, false )
|
|
for _, hSpawner in pairs( hSpawners ) do
|
|
printf( "Iterating over hSpawners" )
|
|
if nChestsSpawned < self.nChestsToSpawn then
|
|
local vSpawnLoc = hSpawner:GetOrigin() + RandomVector( RandomFloat( 0, chestData.nMaxSpawnDistance ) )
|
|
|
|
local fThreshold = 1 - fSpawnChance
|
|
local bSpawnChest = self:RoomRandomFloat( 0, 1 ) >= fThreshold
|
|
|
|
-- Force chest spawns if we'd run out of spawners otherwise
|
|
local nSpawnersRemaining = #hSpawners - i
|
|
local nChestsNeeded = self.nChestsToSpawn - nChestsSpawned
|
|
if nSpawnersRemaining <= nChestsNeeded then
|
|
--printf( " %d spawners remaining and still need to spawn %d chests, so force the rest of the spawners to spawn chests", nSpawnersRemaining, nChestsNeeded )
|
|
bSpawnChest = true
|
|
end
|
|
|
|
if bSpawnChest then
|
|
local hUnit = CreateUnitByName( chestData.szNPCName, vSpawnLoc, true, nil, nil, DOTA_TEAM_GOODGUYS )
|
|
if hUnit ~= nil then
|
|
local vSpawnerForward = hSpawner:GetForwardVector()
|
|
hUnit:SetForwardVector( vSpawnerForward )
|
|
|
|
-- print( "CMapEncounter:SpawnChests - Created chest unit named " .. hUnit:GetUnitName() )
|
|
hUnit.fNeutralItemChance = chestData.fNeutralItemChance
|
|
hUnit.nMinNeutralItems = chestData.nMinNeutralItems
|
|
hUnit.nMaxNeutralItems = chestData.nMaxNeutralItems
|
|
hUnit.fItemChance = chestData.fItemChance
|
|
hUnit.nMinItems = chestData.nMinItems
|
|
hUnit.nMaxItems = chestData.nMaxItems
|
|
hUnit.Items = chestData.Items
|
|
hUnit.fTrapChance = chestData.fTrapChance
|
|
hUnit.nTrapLevel = chestData.nTrapLevel
|
|
hUnit.szTraps = chestData.szTraps
|
|
else
|
|
printf( "ERROR -- CMapEncounter:SpawnChests: Failed to spawn chest named \"%s\"", chestData.szNPCName )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SpawnBreakableContainers()
|
|
--print( "-----------------------------------" )
|
|
--print( "CMapEncounter:SpawnBreakableContainers()" )
|
|
--PrintTable( BreakablesData, " " )
|
|
|
|
for i, breakableData in ipairs( BreakablesData ) do
|
|
--print( "" )
|
|
--print( "Looking at breakableData #" .. i )
|
|
if ( breakableData.szSpawnerName == nil ) then
|
|
printf( "CMapEncounter:SpawnBreakableContainers() - ERROR: No szSpawnerName specified for this breakable container." )
|
|
return
|
|
end
|
|
|
|
local fSpawnChance = breakableData.fSpawnChance
|
|
if fSpawnChance == nil or fSpawnChance <= 0 then
|
|
printf( "CMapEncounter:SpawnBreakableContainers - ERROR: Breakable container spawn chance is not valid" )
|
|
return
|
|
end
|
|
|
|
if breakableData.nMaxSpawnDistance == nil or breakableData.nMaxSpawnDistance < 0 then
|
|
printf( "CMapEncounter:SpawnBreakableContainers - ERROR: nMaxSpawnDistance is not valid." )
|
|
return
|
|
end
|
|
|
|
local nCratesSpawned = 0
|
|
|
|
--printf( "This room wants to spawn %d crates", self.nCratesToSpawn )
|
|
|
|
if ( breakableData.szSpawnerName ~= nil ) then
|
|
--print( "breakableData.szSpawnerName == " .. breakableData.szSpawnerName )
|
|
local hSpawners = self:GetRoom():FindAllEntitiesInRoomByName( breakableData.szSpawnerName, false )
|
|
for i, hSpawner in pairs( hSpawners ) do
|
|
if nCratesSpawned < self.nCratesToSpawn then
|
|
local vSpawnLoc = hSpawner:GetOrigin() + RandomVector( RandomFloat( 0, breakableData.nMaxSpawnDistance ) )
|
|
|
|
local fThreshold = 1 - fSpawnChance
|
|
local bSpawnBreakable = self:RoomRandomFloat( 0, 1 ) >= fThreshold
|
|
|
|
-- Force crate spawns if we'd run out of spawners otherwise
|
|
local nSpawnersRemaining = #hSpawners - i
|
|
local nCratesNeeded = self.nCratesToSpawn - nCratesSpawned
|
|
if nSpawnersRemaining <= nCratesNeeded then
|
|
--printf( " %d spawners remaining and still need to spawn %d crates, so force the rest of the spawners to spawn crates", nSpawnersRemaining, nCratesNeeded )
|
|
bSpawnBreakable = true
|
|
end
|
|
|
|
if bSpawnBreakable then
|
|
|
|
local vAngles = VectorAngles( RandomVector( 1 ) )
|
|
local breakableTable =
|
|
{
|
|
MapUnitName = breakableData.szNPCName,
|
|
origin = tostring( vSpawnLoc.x ) .. " " .. tostring( vSpawnLoc.y ) .. " " .. tostring( vSpawnLoc.z ),
|
|
angles = tostring( vAngles.x ) .. " " .. tostring( vAngles.y ) .. " " .. tostring( vAngles.z ),
|
|
teamnumber = DOTA_TEAM_BADGUYS,
|
|
NeverMoveToClearSpace = false,
|
|
}
|
|
local hUnit = CreateUnitFromTable( breakableTable, vSpawnLoc )
|
|
if hUnit ~= nil then
|
|
--print( "Created breakable container unit named " .. hUnit:GetUnitName() )
|
|
hUnit.CommonItems = breakableData.CommonItems
|
|
hUnit.fCommonItemChance = breakableData.fCommonItemChance
|
|
hUnit.MonsterUnits = breakableData.MonsterUnits
|
|
hUnit.fMonsterChance = breakableData.fMonsterChance
|
|
hUnit.RareItems = breakableData.RareItems
|
|
hUnit.fRareItemChance = breakableData.fRareItemChance
|
|
hUnit.nMinGold = breakableData.nMinGold
|
|
hUnit.nMaxGold = breakableData.nMaxGold
|
|
hUnit.fGoldChance = breakableData.fGoldChance
|
|
hUnit:AddNewModifier( hUnit, nil, "modifier_breakable_container", {} )
|
|
|
|
table.insert( self.SpawnedBreakables, hUnit )
|
|
|
|
nCratesSpawned = nCratesSpawned + 1
|
|
--printf( " Spawned %d crates", nCratesSpawned )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SpawnExplosiveBarrels()
|
|
--printf( "-----------------------------------" )
|
|
--printf( "CMapEncounter:SpawnExplosive()" )
|
|
--PrintTable( ExplosiveBarrelData, " " )
|
|
|
|
for i, barrelData in ipairs( ExplosiveBarrelData ) do
|
|
--printf( "Looking at barrelData #", i )
|
|
if ( barrelData.szSpawnerName == nil ) then
|
|
printf( "CMapEncounter:SpawnExplosiveBarrels() - ERROR: No szSpawnerName specified for this explosive barrel." )
|
|
return
|
|
end
|
|
|
|
local fSpawnChance = barrelData.fSpawnChance
|
|
if fSpawnChance == nil or fSpawnChance <= 0 then
|
|
printf( "CMapEncounter:SpawnExplosiveBarrels() - ERROR: Explosive barrel spawn chance is not valid" )
|
|
return
|
|
end
|
|
|
|
if barrelData.nMaxSpawnDistance == nil or barrelData.nMaxSpawnDistance < 0 then
|
|
printf( "CMapEncounter:SpawnExplosiveBarrels() - ERROR: nMaxSpawnDistance is not valid." )
|
|
return
|
|
end
|
|
|
|
local nExplosiveBarrelsSpawned = 0
|
|
|
|
--printf( "This room wants to spawn %d explosive barrels", self.nExplosiveBarrelsToSpawn )
|
|
|
|
if ( barrelData.szSpawnerName ~= nil ) then
|
|
--print( "barrelData.szSpawnerName == " .. barrelData.szSpawnerName )
|
|
local hSpawners = self:GetRoom():FindAllEntitiesInRoomByName( barrelData.szSpawnerName, false )
|
|
for i, hSpawner in pairs( hSpawners ) do
|
|
if nExplosiveBarrelsSpawned < self.nExplosiveBarrelsToSpawn then
|
|
local vSpawnLoc = hSpawner:GetOrigin() + RandomVector( RandomFloat( 0, barrelData.nMaxSpawnDistance ) )
|
|
|
|
local fThreshold = 1 - fSpawnChance
|
|
local bSpawnBarrel = self:RoomRandomFloat( 0, 1 ) >= fThreshold
|
|
|
|
-- Force crate spawns if we'd run out of spawners otherwise
|
|
local nSpawnersRemaining = #hSpawners - i
|
|
local nBarrelsNeeded = self.nExplosiveBarrelsToSpawn - nExplosiveBarrelsSpawned
|
|
if nSpawnersRemaining <= nBarrelsNeeded then
|
|
--printf( " %d spawners remaining and still need to spawn %d barrels, so force the rest of the spawners to spawn barrels", nSpawnersRemaining, nBarrelsNeeded )
|
|
bSpawnBarrel = true
|
|
end
|
|
|
|
if bSpawnBarrel then
|
|
|
|
local vAngles = VectorAngles( RandomVector( 1 ) )
|
|
local barrelTable =
|
|
{
|
|
MapUnitName = barrelData.szNPCName,
|
|
origin = tostring( vSpawnLoc.x ) .. " " .. tostring( vSpawnLoc.y ) .. " " .. tostring( vSpawnLoc.z ),
|
|
angles = tostring( vAngles.x ) .. " " .. tostring( vAngles.y ) .. " " .. tostring( vAngles.z ),
|
|
teamnumber = DOTA_TEAM_BADGUYS,
|
|
NeverMoveToClearSpace = false,
|
|
}
|
|
local hUnit = CreateUnitFromTable( barrelTable, vSpawnLoc )
|
|
if hUnit ~= nil then
|
|
-- Set the barrel's level based on the depth we're in. It'll use its level to set its
|
|
-- explosion ability level
|
|
local hAbility = hUnit:FindAbilityByName( "aghsfort_explosive_barrel" )
|
|
local nDepth = self.hRoom:GetDepth()
|
|
--printf( "nDepth == %d", nDepth )
|
|
|
|
for i = 1, nDepth do
|
|
hAbility:UpgradeAbility( true )
|
|
end
|
|
|
|
--printf( "nDepth: %d; explosive barrel ability level: %d", nDepth, hAbility:GetLevel() )
|
|
|
|
--local vSpawnerForward = hSpawner:GetForwardVector()
|
|
--hUnit:SetForwardVector( vSpawnerForward )
|
|
|
|
--printf( "Created explosive barrel unit named \"%s\"", hUnit:GetUnitName() )
|
|
--hUnit:AddNewModifier( hUnit, nil, "modifier_explosive_barrel", {} )
|
|
|
|
table.insert( self.SpawnedExplosiveBarrels, hUnit )
|
|
|
|
nExplosiveBarrelsSpawned = nExplosiveBarrelsSpawned + 1
|
|
--printf( " Spawned %d crates", nExplosiveBarrelsSpawned )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:OnThink()
|
|
|
|
--print( 'CMapEncounter:OnThink()' )
|
|
if self.flStartTime ~= -1 and not self.bCompleted then
|
|
self:TrySpawningUnits()
|
|
self:TrySpawningPortalUnits()
|
|
self:TrySpawningPortalUnitsV2()
|
|
self:TryCompletingMapEncounter()
|
|
end
|
|
|
|
if self.flCompletionTime ~= nil and ( GameRules:GetGameTime() - self.flCompletionTime ) > 0.6 then
|
|
self:GenerateRewards()
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:NeedsToThink()
|
|
return self.bHasGeneratedRewards == false or self.bHasSpawnedEndLevelEntities == false
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:TrySpawningPortalUnits()
|
|
--print( "CMapEncounter:TrySpawningPortalUnits" )
|
|
for _,PortalSpawner in pairs ( self.PortalSpawners ) do
|
|
PortalSpawner:TrySpawningUnits()
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:RunSpawnSchedule( hSpawner )
|
|
|
|
if hSpawner.schedule == nil or hSpawner.schedule.flStartTime < 0 then
|
|
return
|
|
end
|
|
|
|
-- Debug visualization of focus path
|
|
-- if hSpawner.schedule.spawnFocusPath ~= nil then
|
|
-- local flRelativeTime = GameRules:GetGameTime() - hSpawner.schedule.flStartTime
|
|
-- local vSpawnFocus = self:ComputeSpawnFocusPosition( hSpawner.schedule.spawnFocusPath, flRelativeTime )
|
|
-- DebugDrawCircle( vSpawnFocus, Vector( 0, 255, 0 ), 0, hSpawner.schedule.spawnFocusPath.flRadius, false, 1.0 )
|
|
-- end
|
|
|
|
-- Try to spawn this wave
|
|
for nWave = hSpawner.schedule.nCurrentWaveIndex, #hSpawner.schedule.waveSchedule do
|
|
local flRelativeTime = GameRules:GetGameTime() - hSpawner.schedule.flStartTime
|
|
if hSpawner.schedule.waveSchedule[nWave].Time > flRelativeTime then
|
|
break
|
|
end
|
|
local nCount = hSpawner.schedule.waveSchedule[nWave].Count
|
|
if nCount == nil or nCount <= 0 then
|
|
nCount = hSpawner:GetSpawnPositionCount()
|
|
end
|
|
|
|
-- Used by spawners to force movement around the map
|
|
if hSpawner.schedule.spawnFocusPath ~= nil then
|
|
local vSpawnFocus = self:ComputeSpawnFocusPosition( hSpawner.schedule.spawnFocusPath, flRelativeTime )
|
|
hSpawner:SetSpawnFocus( vSpawnFocus, hSpawner.schedule.spawnFocusPath.flRadius )
|
|
end
|
|
|
|
hSpawner:SpawnUnitsFromRandomSpawners( nCount )
|
|
hSpawner.schedule.nCurrentWaveIndex = hSpawner.schedule.nCurrentWaveIndex + 1
|
|
end
|
|
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:TrySpawningUnits()
|
|
for _,Spawner in pairs ( self.Spawners ) do
|
|
self:RunSpawnSchedule( Spawner )
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:TrySpawningPortalUnitsV2()
|
|
for _,PortalSpawner in pairs ( self.PortalSpawnersV2 ) do
|
|
|
|
self:RunSpawnSchedule( PortalSpawner )
|
|
|
|
-- This ticks the logic to see if any previously spawned portals need to spawn their units
|
|
PortalSpawner:TrySpawningPortalUnits()
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:IsScheduledSpawnComplete( hSpawner )
|
|
if hSpawner.schedule == nil then
|
|
return true
|
|
end
|
|
|
|
if hSpawner.schedule.nCurrentWaveIndex <= #hSpawner.schedule.waveSchedule then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:AreScheduledSpawnsComplete()
|
|
for _,Spawner in pairs ( self.Spawners ) do
|
|
if self:IsScheduledSpawnComplete( Spawner ) == false then
|
|
return false
|
|
end
|
|
end
|
|
for _,PortalSpawner in pairs ( self.PortalSpawnersV2 ) do
|
|
if self:IsScheduledSpawnComplete( PortalSpawner ) == false then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:TryCompletingMapEncounter()
|
|
--print( 'CMapEncounter:TryCompletingMapEncounter()' )
|
|
local bCompleted = ( self.bDevForceCompleted == true or self:CheckForCompletion() == true )
|
|
if bCompleted and self:GetRoom():AreAllExitRoomsReady() then
|
|
self.bCompleted = bCompleted
|
|
self.flCompletionTime = GameRules:GetGameTime()
|
|
|
|
for i=1,#self.EventListeners do
|
|
StopListeningToGameEvent( self.EventListeners[i] )
|
|
self.EventListeners[i] = nil
|
|
end
|
|
|
|
self:DestroyRemainingSpawnedUnits()
|
|
self:ResetHeroState()
|
|
self:SpawnEndLevelEntities()
|
|
self:OnComplete()
|
|
|
|
self.hRoom:OnEncounterCompleted()
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:RegisterSummonForAghanim()
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY then
|
|
GameRules.Aghanim:RegisterSummonForAghanim( self.hRoom:GetDepth(), self:GetAghanimSummon() )
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:OnComplete()
|
|
GameRules.Aghanim:GetAnnouncer():OnEncounterComplete( self )
|
|
|
|
self:RegisterSummonForAghanim()
|
|
|
|
--CustomNetTables:SetTableValue( "room_data", "status", { complete=true } )
|
|
CustomGameEventManager:Send_ServerToAllClients( "complete_encounter", self.ClientData )
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:AddSpawner( hSpawner )
|
|
if hSpawner == nil then
|
|
print( "ERROR: AddSpawner called with a nil spawner." )
|
|
return
|
|
end
|
|
|
|
if self.Spawners[hSpawner:GetSpawnerName()] ~= nil then
|
|
print ( "WARNING: Multiple identical named spawners ( " .. hSpawner:GetSpawnerName() .. ") added to encounter! ")
|
|
return
|
|
end
|
|
|
|
self.Spawners[hSpawner:GetSpawnerName()] = hSpawner
|
|
return hSpawner
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:AddPortalSpawner( hSpawner )
|
|
if hSpawner == nil then
|
|
print( "ERROR: AddPortalSpawner called with a nil spawner." )
|
|
return
|
|
end
|
|
|
|
--print( 'Adding Portal Spawner named ' .. hSpawner:GetSpawnerName() .. '. Looking for locator named ' .. hSpawner:GetLocatorName() )
|
|
|
|
if self.PortalSpawners[hSpawner:GetSpawnerName()] ~= nil then
|
|
print ( "ERROR: Multiple identical named spawners ( " .. hSpawner:GetSpawnerName() .. ") added to encounter! ")
|
|
return
|
|
end
|
|
|
|
self.PortalSpawners[hSpawner:GetSpawnerName()] = hSpawner
|
|
return hSpawner
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:AddPortalSpawnerV2( hSpawner )
|
|
if hSpawner == nil then
|
|
print( "ERROR: AddPortalSpawnerV2 called with a nil spawner." )
|
|
return
|
|
end
|
|
|
|
if self.PortalSpawnersV2[hSpawner:GetSpawnerName()] ~= nil then
|
|
print( "ERROR: Multiple identical named spawners ( " .. hSpawner:GetSpawnerName() .. ") added to encounter! ")
|
|
return
|
|
end
|
|
|
|
self.PortalSpawnersV2[ hSpawner:GetSpawnerName() ] = hSpawner
|
|
return hSpawner
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SetPortalTriggerSpawner( szSpawnerName, flDelay )
|
|
|
|
if szSpawnerName == nil then
|
|
print( "WARNING: CMapEncounter:SetPortalTriggerSpawner .. illegal to specify nil" )
|
|
return
|
|
end
|
|
|
|
if self.Spawners[szSpawnerName] == nil then
|
|
print( "WARNING: CMapEncounter:SetPortalTriggerSpawner specified unknown spawner ( " .. szSpawnerName .. ") in encounter " .. self.szEncounterName .. "! ")
|
|
return
|
|
end
|
|
|
|
self.szPortalTriggerSpawner = szSpawnerName
|
|
self.flPortalTriggerDelay = flDelay
|
|
|
|
for _, hPortalSpawner in pairs( self.PortalSpawnersV2 ) do
|
|
if hPortalSpawner.schedule ~= nil then
|
|
hPortalSpawner.schedule.bWaitingForTrigger = true
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:IsWaitingToSpawnPortals()
|
|
return self.szPortalTriggerSpawner ~= nil
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:StartSpawningPortals()
|
|
|
|
if self.szPortalTriggerSpawner == nil then
|
|
return
|
|
end
|
|
|
|
self.szPortalTriggerSpawner = nil
|
|
|
|
for _, hPortalSpawner in pairs( self.PortalSpawnersV2 ) do
|
|
if hPortalSpawner.schedule ~= nil then
|
|
if hPortalSpawner.schedule.bWaitingForTrigger == true then
|
|
hPortalSpawner.schedule.bWaitingForTrigger = false
|
|
hPortalSpawner.schedule.flStartTime = GameRules:GetGameTime() + hPortalSpawner.schedule.flDelay + self.flPortalTriggerDelay
|
|
end
|
|
end
|
|
end
|
|
|
|
for _,PortalSpawner in pairs ( self.PortalSpawners ) do
|
|
PortalSpawner:Start( GameRules:GetGameTime() + self.flPortalTriggerDelay )
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SetSpawnerSchedule( szSpawnerName, waveScheduleInput )
|
|
|
|
if waveScheduleInput == nil then
|
|
waveScheduleInput = { { Time = 0 } } -- Not specifying count means spawn at all points
|
|
end
|
|
|
|
-- Validate the schedule is valid
|
|
local nLastTime = 0
|
|
for i=1,#waveScheduleInput do
|
|
if nLastTime > waveScheduleInput[i].Time then
|
|
print( "ERROR: BeginSpawnerSchedule: Wave schedule ( " .. szSpawnerName .. ") not specified in increasing order of time in encounter " .. self.szEncounterName )
|
|
return
|
|
end
|
|
nLastTime = waveScheduleInput[i].Time
|
|
end
|
|
|
|
-- Try to find a spawner in all of the lists to spawn
|
|
local bIsPortal = false
|
|
local hSpawner = self:GetSpawner( szSpawnerName )
|
|
if hSpawner == nil then
|
|
hSpawner = self:GetPortalSpawnerV2( szSpawnerName )
|
|
bIsPortal = true
|
|
end
|
|
|
|
if hSpawner == nil then
|
|
print( "ERROR: BeginSpawnerSchedule: Spawner ( " .. szSpawnerName .. ") isn't found in this encounter " .. self.szEncounterName )
|
|
return
|
|
end
|
|
|
|
hSpawner.schedule =
|
|
{
|
|
nCurrentWaveIndex = 1,
|
|
waveSchedule = waveScheduleInput,
|
|
flStartTime = -1,
|
|
bWaitingForTrigger = ( bIsPortal == true ) and self:IsWaitingToSpawnPortals()
|
|
}
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:StartAllSpawnerSchedules( flDelay )
|
|
|
|
-- Start everything up
|
|
for _, hSpawner in pairs( self:GetSpawners() ) do
|
|
if hSpawner.schedule ~= nil then
|
|
hSpawner.schedule.flStartTime = GameRules:GetGameTime() + flDelay
|
|
end
|
|
end
|
|
|
|
for _, hPortalSpawner in pairs( self.PortalSpawnersV2 ) do
|
|
if hPortalSpawner.schedule ~= nil then
|
|
if hPortalSpawner.schedule.bWaitingForTrigger == true then
|
|
hPortalSpawner.schedule.flDelay = flDelay
|
|
else
|
|
hPortalSpawner.schedule.flStartTime = GameRules:GetGameTime() + flDelay
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:StartSpawnerSchedule( szSpawnerName, flDelay )
|
|
|
|
-- Try to find a spawner in all of the lists to spawn
|
|
local bIsPortal = false
|
|
local hSpawner = self:GetSpawner( szSpawnerName )
|
|
if hSpawner == nil then
|
|
bIsPortal = true
|
|
hSpawner = self:GetPortalSpawnerV2( szSpawnerName )
|
|
end
|
|
|
|
if hSpawner == nil then
|
|
print( "ERROR: StartSpawnerSchedule: Spawner ( " .. szSpawnerName .. ") isn't found in this encounter!" )
|
|
return
|
|
end
|
|
|
|
if hSpawner.schedule == nil then
|
|
print( "ERROR: StartSpawnerSchedule: Spawner ( " .. szSpawnerName .. ") doesn't have a specified schedule!" )
|
|
return
|
|
end
|
|
|
|
if hSpawner.schedule.bWaitingForTrigger == true then
|
|
hSpawner.schedule.flDelay = flDelay
|
|
return
|
|
end
|
|
|
|
hSpawner.schedule.flStartTime = GameRules:GetGameTime() + flDelay
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GenerateSpawnFocusPath( szSpawnerName, flSpeed, flDesiredRadiusInput, flMaxTime )
|
|
|
|
-- For portal spawner V2s, we want to force the player to move through the space.
|
|
-- We do this by moving the region where portals are more likely to appear
|
|
local hSpawner = self:GetPortalSpawnerV2( szSpawnerName )
|
|
if hSpawner == nil then
|
|
print( "ERROR: GenerateSpawnFocusPath: Spawner ( " .. szSpawnerName .. ") isn't found in encounter " .. self.szEncounterName )
|
|
return nil
|
|
end
|
|
|
|
-- If they didn't specify a max time, but we have a schedule, figure it out automatically
|
|
if flMaxTime == nil or flMaxTime <= 0 then
|
|
if hSpawner.schedule == nil then
|
|
print( "ERROR: GenerateSpawnFocusPath: Didn't specify duration for " .. szSpawnerName .. " in encounter " .. self.szEncounterName )
|
|
return nil
|
|
end
|
|
flMaxTime = hSpawner.schedule.waveSchedule[ #hSpawner.schedule.waveSchedule ].Time
|
|
end
|
|
|
|
local spawnFocusPath =
|
|
{
|
|
flRadius = flDesiredRadiusInput,
|
|
vecPathNodes = {}
|
|
}
|
|
|
|
local flMaxDist = flMaxTime * flSpeed
|
|
local flCurrDist = 0
|
|
local flTime = 0
|
|
local vLastPos = nil
|
|
while flCurrDist < flMaxDist do
|
|
|
|
local vecSpawnPositions = deepcopy( hSpawner:GetSpawnPositions() )
|
|
local nIndex = math.random( 1, #vecSpawnPositions )
|
|
while #vecSpawnPositions > 1 and vLastPos ~= nil and
|
|
( ( vecSpawnPositions[nIndex]:GetAbsOrigin() - vLastPos ):Length2D() < flDesiredRadiusInput ) do
|
|
table.remove( vecSpawnPositions, nIndex )
|
|
nIndex = math.random( 1, #vecSpawnPositions )
|
|
end
|
|
|
|
local bIdenticalPoint = false
|
|
local vNewPos = vecSpawnPositions[nIndex]:GetAbsOrigin()
|
|
if vLastPos ~= nil then
|
|
local flDist = ( vNewPos - vLastPos ):Length2D()
|
|
flCurrDist = flCurrDist + flDist
|
|
flTime = flCurrDist / flSpeed
|
|
if flDist == 0 then
|
|
bIdenticalPoint = true
|
|
end
|
|
end
|
|
vLastPos = vNewPos
|
|
if bIdenticalPoint == false then
|
|
table.insert( spawnFocusPath.vecPathNodes, { Time = flTime, Position = vNewPos } )
|
|
end
|
|
|
|
end
|
|
|
|
return spawnFocusPath
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:AssignSpawnFocusPath( szSpawnerName, spawnerFocusPathInput )
|
|
|
|
-- Try to find a spawner in all of the lists to spawn
|
|
local hSpawner = self:GetPortalSpawnerV2( szSpawnerName )
|
|
|
|
if hSpawner == nil then
|
|
print( "ERROR: AssignSpawnFocusPath: Spawner ( " .. szSpawnerName .. ") isn't found in this encounter!" )
|
|
return
|
|
end
|
|
|
|
if hSpawner.schedule == nil then
|
|
print( "ERROR: StartSpawnerSchedule: Spawner ( " .. szSpawnerName .. ") doesn't have a specified schedule!" )
|
|
return
|
|
end
|
|
|
|
hSpawner.schedule.spawnFocusPath = spawnerFocusPathInput
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:ComputeSpawnFocusPosition( spawnFocusPath, flTime )
|
|
|
|
for i=1,#spawnFocusPath.vecPathNodes do
|
|
if flTime < spawnFocusPath.vecPathNodes[i].Time then
|
|
if i == 1 then
|
|
return spawnFocusPath.vecPathNodes[i].Position
|
|
end
|
|
|
|
-- Lerp
|
|
local prev = spawnFocusPath.vecPathNodes[i-1]
|
|
local next = spawnFocusPath.vecPathNodes[i]
|
|
local t = ( flTime - prev.Time ) / ( next.Time - prev.Time )
|
|
local vDelta = next.Position - prev.Position
|
|
local v = Vector( prev.Position.x + vDelta.x * t, prev.Position.y + vDelta.y * t, prev.Position.z + vDelta.z * t )
|
|
return v
|
|
end
|
|
end
|
|
|
|
return spawnFocusPath.vecPathNodes[#spawnFocusPath.vecPathNodes].Position
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:ComputeUnitsSpawnedBySchedule( hSpawner )
|
|
|
|
if hSpawner.schedule == nil then
|
|
return 0
|
|
end
|
|
|
|
local nCount = 0
|
|
|
|
local nUnitsPerPortal = hSpawner:GetSpawnCountPerSpawnPosition()
|
|
local nSpawnPositionCount = hSpawner:GetSpawnPositionCount()
|
|
|
|
for j=1,#hSpawner.schedule.waveSchedule do
|
|
local wave = hSpawner.schedule.waveSchedule[j]
|
|
local nWaveCount = wave.Count
|
|
if nWaveCount == nil or nWaveCount <= 0 then
|
|
nWaveCount = nSpawnPositionCount
|
|
end
|
|
nCount = nCount + nWaveCount * nUnitsPerPortal
|
|
end
|
|
|
|
return nCount
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetMaxSpawnedUnitCount()
|
|
|
|
local nCount = 0
|
|
|
|
for _,Spawner in pairs ( self.Spawners ) do
|
|
nCount = nCount + self:ComputeUnitsSpawnedBySchedule( Spawner )
|
|
end
|
|
|
|
for _,PortalSpawner in pairs ( self.PortalSpawnersV2 ) do
|
|
nCount = nCount + self:ComputeUnitsSpawnedBySchedule( PortalSpawner )
|
|
end
|
|
|
|
for _,PortalSpawner in pairs ( self.PortalSpawners ) do
|
|
for _,rgUnitInfo in pairs ( PortalSpawner.rgUnitsInfo ) do
|
|
nCount = nCount + ( rgUnitInfo.Count * PortalSpawner:GetNumSpawnsRemaining() )
|
|
end
|
|
end
|
|
|
|
return nCount
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:MustKillForEncounterCompletion( hEnemyCreature )
|
|
return true
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:SuppressRewardsOnDeath( hEnemyCreature )
|
|
hEnemyCreature.bSuppressRewardsOnDeath = true
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:AddAscensionAbilities( hEnemyCreature )
|
|
|
|
if hEnemyCreature:IsBuilding() == true then
|
|
return
|
|
end
|
|
|
|
if hEnemyCreature:GetUnitName() == "npc_dota_explosive_barrel" then
|
|
return
|
|
end
|
|
|
|
local nAbilityLevel = self:GetRoom():GetEliteRank() + GameRules.Aghanim:GetAscensionLevel()
|
|
|
|
local bIsGlobal = IsGlobalAscensionCaster( hEnemyCreature )
|
|
|
|
-- Ascension abilities make creatures harder.
|
|
-- ability_ascension is always granted and just deals with generic attributes like damage
|
|
-- The other abilities are more flavorful and specific and may be randomly selected
|
|
-- We must level it up once since all other abilities already were levelled on spawn
|
|
-- For the stats
|
|
if bIsGlobal == false then
|
|
local hAbility = hEnemyCreature:AddAbility( "ability_ascension" )
|
|
hAbility:UpgradeAbility( true )
|
|
|
|
-- Bosses do not use ascension modifiers, but do want general ascension scaling for their minions.
|
|
if self.hRoom:GetType() == ROOM_TYPE_BOSS then
|
|
if nAbilityLevel > 0 then
|
|
hEnemyCreature:CreatureLevelUp( nAbilityLevel )
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
for i=1,#self.AscensionAbilities do
|
|
|
|
local abilityInfo = ASCENSION_ABILITIES[ self.AscensionAbilities[i] ]
|
|
local nAbilityType = abilityInfo.nType
|
|
if nAbilityType == nil then
|
|
nAbilityType = ASCENSION_ABILITY_CAPTAINS_ONLY
|
|
end
|
|
|
|
local bRequiresGlobal = ( nAbilityType == ASCENSION_ABILITY_GLOBAL )
|
|
if bIsGlobal ~= bRequiresGlobal then
|
|
goto continue
|
|
end
|
|
|
|
local bIsCaptain = hEnemyCreature:IsConsideredHero() or hEnemyCreature:IsBoss()
|
|
|
|
if nAbilityType == ASCENSION_ABILITY_CAPTAINS_ONLY and bIsCaptain == false then
|
|
goto continue
|
|
elseif nAbilityType == ASCENSION_ABILITY_NON_CAPTAINS_ONLY and bIsCaptain == true then
|
|
goto continue
|
|
end
|
|
|
|
--print( "Ascension adding ability " .. self.AscensionAbilities[i] .. " to unit " .. hEnemyCreature:GetUnitName() )
|
|
hAbility = hEnemyCreature:AddAbility( self.AscensionAbilities[i] )
|
|
hAbility:UpgradeAbility( true )
|
|
|
|
-- This is the glue that causes these abilities to autocast, but passive abilities don't need to do this
|
|
if IsServer() and bitand( hAbility:GetBehavior(), DOTA_ABILITY_BEHAVIOR_PASSIVE ) == 0 then
|
|
|
|
-- This makes the global abilities not just autostart
|
|
if bIsGlobal then
|
|
hAbility:StartCooldown( -1 )
|
|
end
|
|
|
|
local kv =
|
|
{
|
|
cast_behavior = abilityInfo.nCastBehavior,
|
|
target_type = abilityInfo.nTargetType,
|
|
health_percent = abilityInfo.flHealthPercent,
|
|
range = abilityInfo.nRange,
|
|
}
|
|
|
|
if kv.cast_behavior == nil then
|
|
kv.cast_behavior = ASCENSION_CAST_WHEN_COOLDOWN_READY
|
|
end
|
|
if kv.target_type == nil then
|
|
kv.target_type = ASCENSION_TARGET_NO_TARGET
|
|
end
|
|
if kv.health_percent == nil then
|
|
kv.health_percent = 25
|
|
end
|
|
|
|
hEnemyCreature:AddNewModifier( hEnemyCreature, hAbility, "modifier_passive_autocast", kv )
|
|
end
|
|
|
|
::continue::
|
|
end
|
|
|
|
if nAbilityLevel > 0 then
|
|
hEnemyCreature:CreatureLevelUp( nAbilityLevel )
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:OnEnemyCreatureSpawned( hEnemyCreature )
|
|
|
|
if hEnemyCreature.Encounter ~= nil then
|
|
return
|
|
end
|
|
|
|
-- Remove normal last hit gold / xp
|
|
hEnemyCreature:SetMinimumGoldBounty( 0 )
|
|
hEnemyCreature:SetMaximumGoldBounty( 0 )
|
|
hEnemyCreature:SetDeathXP( 0 )
|
|
|
|
-- Filter out creatures that aren't really creatures
|
|
if hEnemyCreature:GetUnitName() == "npc_dota_explosive_barrel" then
|
|
self:SuppressRewardsOnDeath( hEnemyCreature )
|
|
return
|
|
end
|
|
|
|
hEnemyCreature.Encounter = self
|
|
|
|
local bIsDummy = hEnemyCreature:GetUnitName() == "npc_dota_dummy_caster"
|
|
if not bIsDummy then
|
|
if hEnemyCreature:GetUnitName() ~= "npc_dota_creature_bonus_chicken" and hEnemyCreature:GetUnitName() ~= "npc_dota_creature_bonus_greevil" then
|
|
hEnemyCreature:AddNewModifier( hEnemyCreature, nil, "modifier_monster_leash", {} )
|
|
end
|
|
self:AddAscensionAbilities( hEnemyCreature )
|
|
end
|
|
|
|
local bIsGlobal = IsGlobalAscensionCaster( hEnemyCreature )
|
|
if not bIsGlobal and self:MustKillForEncounterCompletion( hEnemyCreature ) == true then
|
|
table.insert( self.SpawnedEnemies, hEnemyCreature )
|
|
else
|
|
table.insert( self.SpawnedSecondaryEnemies, hEnemyCreature )
|
|
self:SuppressRewardsOnDeath( hEnemyCreature )
|
|
end
|
|
|
|
if hEnemyCreature:IsConsideredHero() then
|
|
hEnemyCreature:AddNewModifier( hEnemyCreature, nil, "modifier_ability_cast_warning", kv )
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
-- spawner_finished
|
|
-- * spawner_name
|
|
---------------------------------------------------------
|
|
function CMapEncounter:OnSpawnerFinished( hSpawner, hSpawnedUnits )
|
|
|
|
-- NOTE: This is called *after* OnEnemyCreatureSpawned, and will be called
|
|
-- On the same units. The difference is that OnEnemyCreatureSpawned is called
|
|
-- for secondary summoned units not spawned by the spawner also *and*
|
|
-- OnEnemyCreatureSpawned is called prior to OnSpawnerFinished
|
|
local bIsPortalTriggerUnit = ( self.szPortalTriggerSpawner ~= nil ) and ( self.szPortalTriggerSpawner == hSpawner:GetSpawnerName() )
|
|
|
|
for _,hSpawnedUnit in pairs ( hSpawnedUnits ) do
|
|
if hSpawnedUnit ~= nil then
|
|
hSpawnedUnit:SetRequiresReachingEndPath( true ) -- this ensures that our spawned dudes won't shut down while getting to their goal ent
|
|
|
|
if bIsPortalTriggerUnit == true then
|
|
table.insert( self.SpawnedPortalTriggerUnits, hSpawnedUnit )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:SetInitialGoalEntityToNearestHero( hSpawnedUnit )
|
|
|
|
local hNearestHero = nil
|
|
local flNearestDistance = 1000000
|
|
for nPlayerID = 0, ( DOTA_MAX_TEAM_PLAYERS - 1 ) do
|
|
|
|
if PlayerResource:GetTeam( nPlayerID ) == DOTA_TEAM_GOODGUYS then
|
|
local hPlayerHero = PlayerResource:GetSelectedHeroEntity( nPlayerID )
|
|
if hPlayerHero ~= nil and hPlayerHero:IsAlive() then
|
|
local flDist = ( hPlayerHero:GetAbsOrigin() - hSpawnedUnit:GetAbsOrigin() ):Length2D()
|
|
if flDist < flNearestDistance then
|
|
flDist = flNearestDistance
|
|
hNearestHero = hPlayerHero
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
if hNearestHero ~= nil then
|
|
hSpawnedUnit:SetInitialGoalEntity( hNearestHero )
|
|
end
|
|
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
-- Grants rewards for killing N units
|
|
---------------------------------------------------------
|
|
function CMapEncounter:GrantRewardsForKill( hVictim, hAttacker, nUnitCount )
|
|
|
|
-- Distribute using fixed rewards
|
|
if hVictim.bSuppressRewardsOnDeath == nil or hVictim.bSuppressRewardsOnDeath == false then
|
|
|
|
if self.bCalculateRewardsFromUnitCount and self.nMaxSpawnedUnitCount > 0 then
|
|
|
|
if self.nRemainingXPFromEnemies > 0 then
|
|
|
|
local nDeathXP = math.floor( nUnitCount * self.nTotalXPFromEnemies / self.nMaxSpawnedUnitCount )
|
|
local nXPPerHero = math.min( self.nRemainingXPFromEnemies, nDeathXP )
|
|
|
|
local Heroes = HeroList:GetAllHeroes()
|
|
for _,Hero in pairs ( Heroes ) do
|
|
if Hero ~= nil and Hero:IsRealHero() and Hero:GetTeamNumber() == DOTA_TEAM_GOODGUYS then
|
|
Hero:AddExperience( nXPPerHero, DOTA_ModifyXP_CreepKill, false, true )
|
|
end
|
|
end
|
|
self.nRemainingXPFromEnemies = math.max( 0, self.nRemainingXPFromEnemies - nXPPerHero )
|
|
end
|
|
|
|
if self.nRemainingGoldFromEnemies > 0 then
|
|
|
|
local nMinGoldBounty = math.floor( 0.8 * self.nTotalGoldFromEnemies / self.nMaxSpawnedUnitCount )
|
|
local nMaxGoldBounty = math.ceil( 1.2 * self.nTotalGoldFromEnemies / self.nMaxSpawnedUnitCount )
|
|
local nGoldToDrop = 0
|
|
for i = 1,nUnitCount do
|
|
nGoldToDrop = nGoldToDrop + math.random( nMinGoldBounty, nMaxGoldBounty )
|
|
end
|
|
nGoldToDrop = math.min( self.nRemainingGoldFromEnemies, nGoldToDrop )
|
|
nGoldToDrop = math.floor( nGoldToDrop * 100 / GOLD_BAG_DROP_PCT ) -- Make it so with the randomness, we get roughly what we expect
|
|
if math.random( 1, 100 ) <= GOLD_BAG_DROP_PCT and nGoldToDrop > 0 then
|
|
local newItem = CreateItem( "item_bag_of_gold", nil, nil )
|
|
newItem:SetPurchaseTime( 0 )
|
|
newItem:SetCurrentCharges( nGoldToDrop * AGHANIM_PLAYERS )
|
|
local drop = CreateItemOnPositionSync( hVictim:GetAbsOrigin(), newItem )
|
|
local dropTarget = hVictim:GetAbsOrigin() + RandomVector( RandomFloat( 50, 150 ) )
|
|
newItem:LaunchLoot( true, 150, 0.75, dropTarget )
|
|
self.nRemainingGoldFromEnemies = self.nRemainingGoldFromEnemies - nGoldToDrop
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-- Logic for nUnitsRemainingForRewardDrops makes it more likely
|
|
-- that we drop the lives and items the more units we kill
|
|
if self.nUnitsRemainingForRewardDrops >= 0 then
|
|
self.nUnitsRemainingForRewardDrops = self.nUnitsRemainingForRewardDrops - nUnitCount
|
|
self.nUnitsRemainingForRewardDrops = math.max( 0, self.nUnitsRemainingForRewardDrops )
|
|
end
|
|
|
|
local nEstimatedUnitCount = self.nUnitsRemainingForRewardDrops
|
|
if nEstimatedUnitCount <= 0 then
|
|
nEstimatedUnitCount = max( #self.SpawnedEnemies, 1 )
|
|
end
|
|
|
|
if self.nNumItemsToDrop > 0 then
|
|
local nPct = math.max( 100 / nEstimatedUnitCount, 1 )
|
|
if RollPercentage( nPct ) then
|
|
self:DropNeutralItemFromUnit( hVictim, hAttacker, true )
|
|
self.nNumItemsToDrop = self.nNumItemsToDrop - 1
|
|
end
|
|
end
|
|
|
|
if self.nNumBPToDrop > 0 then
|
|
local nPct = math.max( 100 / nEstimatedUnitCount, 1 )
|
|
if RollPercentage( nPct ) then
|
|
self:DropCurrencyFromUnit( hVictim, hAttacker, RandomInt( BATTLE_POINT_MIN_DROP_VALUE, BATTLE_POINT_MAX_DROP_VALUE ), true, false )
|
|
self.nNumBPToDrop = self.nNumBPToDrop - 1
|
|
end
|
|
end
|
|
|
|
if self.nNumFragmentsToDrop > 0 then
|
|
local nPct = math.max( 100 / nEstimatedUnitCount, 1 )
|
|
if RollPercentage( nPct ) then
|
|
self:DropCurrencyFromUnit( hVictim, hAttacker, self:GetArcaneFragmentDropValue(), false, false )
|
|
self.nNumFragmentsToDrop = self.nNumFragmentsToDrop - 1
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Always drop potions, even if other rewards are suppressed
|
|
local nHealthPct = HEALTH_POTION_DROP_PCT
|
|
local nManaPct = MANA_POTION_DROP_PCT
|
|
if hVictim.bBossMinion ~= nil and hVictim.bBossMinion == true then
|
|
nHealthPct = nHealthPct * 2
|
|
nManaPct = nManaPct * 2
|
|
end
|
|
if RollPercentage( nHealthPct ) then
|
|
local newItem = CreateItem( "item_health_potion", nil, nil )
|
|
newItem:SetPurchaseTime( 0 )
|
|
if newItem:IsPermanent() and newItem:GetShareability() == ITEM_FULLY_SHAREABLE then
|
|
item:SetStacksWithOtherOwners( true )
|
|
end
|
|
local drop = CreateItemOnPositionSync( hVictim:GetAbsOrigin(), newItem )
|
|
local dropTarget = hVictim:GetAbsOrigin() + RandomVector( RandomFloat( 50, 350 ) )
|
|
newItem:LaunchLoot( true, 300, 0.75, dropTarget )
|
|
end
|
|
|
|
if RollPercentage( nManaPct ) then
|
|
local newItem = CreateItem( "item_mana_potion", nil, nil )
|
|
newItem:SetPurchaseTime( 0 )
|
|
if newItem:IsPermanent() and newItem:GetShareability() == ITEM_FULLY_SHAREABLE then
|
|
item:SetStacksWithOtherOwners( true )
|
|
end
|
|
local drop = CreateItemOnPositionSync( hVictim:GetAbsOrigin(), newItem )
|
|
local dropTarget = hVictim:GetAbsOrigin() + RandomVector( RandomFloat( 50, 350 ) )
|
|
newItem:LaunchLoot( true, 300, 0.75, dropTarget )
|
|
end
|
|
|
|
end
|
|
|
|
|
|
---------------------------------------------------------
|
|
-- entity_killed
|
|
-- * entindex_killed
|
|
-- * entindex_attacker
|
|
-- * entindex_inflictor
|
|
-- * damagebits
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:OnEntityKilled( event )
|
|
local hVictim = nil
|
|
local hAttacker = nil
|
|
if event.entindex_killed ~= nil then
|
|
hVictim = EntIndexToHScript( event.entindex_killed )
|
|
end
|
|
if event.entindex_attacker ~= nil then
|
|
hAttacker = EntIndexToHScript( event.entindex_attacker )
|
|
end
|
|
|
|
if hVictim == nil then
|
|
return
|
|
end
|
|
|
|
if hVictim:IsReincarnating() then
|
|
return
|
|
end
|
|
|
|
if hVictim:IsCreature() then
|
|
|
|
if hAttacker and hAttacker:IsOwnedByAnyPlayer() then
|
|
EmitSoundOnClient( "DarkMoonLastHit", hAttacker:GetPlayerOwner() )
|
|
ParticleManager:ReleaseParticleIndex( ParticleManager:CreateParticleForPlayer( "particles/dark_moon/darkmoon_last_hit_effect.vpcf", PATTACH_ABSORIGIN_FOLLOW, hVictim, hAttacker:GetPlayerOwner() ) )
|
|
|
|
GameRules.Aghanim:RegisterPlayerKillStat( hAttacker:GetPlayerOwnerID(), self.hRoom:GetDepth() )
|
|
end
|
|
|
|
if hVictim:IsConsideredHero() or hVictim:IsBoss() then
|
|
GameRules.Aghanim:GetAnnouncer():OnCreatureKilled( self, hVictim )
|
|
end
|
|
|
|
-- Distribute using fixed rewards
|
|
self:GrantRewardsForKill( hVictim, hAttacker, 1 )
|
|
|
|
end
|
|
|
|
if self.szPortalTriggerSpawner ~= nil then
|
|
for k,hSpawnedEnemy in pairs ( self.SpawnedPortalTriggerUnits ) do
|
|
if hSpawnedEnemy == hVictim then
|
|
table.remove( self.SpawnedPortalTriggerUnits, k )
|
|
end
|
|
end
|
|
if #self.SpawnedPortalTriggerUnits == 0 then
|
|
self:StartSpawningPortals()
|
|
end
|
|
end
|
|
|
|
if hVictim.bSuppressRewardsOnDeath == nil or hVictim.bSuppressRewardsOnDeath == false then
|
|
for k,hSpawnedEnemy in pairs ( self.SpawnedEnemies ) do
|
|
if hSpawnedEnemy == hVictim then
|
|
self.nKilledEnemies = self.nKilledEnemies + 1
|
|
table.remove( self.SpawnedEnemies, k )
|
|
self:OnRequiredEnemyKilled( hAttacker, hVictim )
|
|
--print( "Remaining enemies: " .. #self.SpawnedEnemies )
|
|
end
|
|
end
|
|
else
|
|
for k,hSpawnedSecondaryEnemy in pairs ( self.SpawnedSecondaryEnemies ) do
|
|
if hSpawnedSecondaryEnemy == hVictim then
|
|
self.nKilledSecondaryEnemies = self.nKilledSecondaryEnemies + 1
|
|
self:OnSecondaryEnemyKilled( hAttacker, hVictim )
|
|
table.remove( self.SpawnedSecondaryEnemies, k )
|
|
--print( "Remaining enemies: " .. #self.SpawnedEnemies )
|
|
end
|
|
end
|
|
end
|
|
|
|
-- TODO
|
|
--[[ if hVictim:IsBuilding() then
|
|
if hVictim:GetUnitName() == "npc_aghsfort_dark_portal" then
|
|
local nPortalsCountBefore = self:GetRemainingPortalCount() + 1
|
|
for k,PortalSpawner in pairs ( self.PortalSpawners ) do
|
|
if PortalSpawner.Portal.hPortalEnt == hVictim then
|
|
if PortalSpawner.Portal.nWarningFX ~= nil then
|
|
ParticleManager:DestroyParticle( PortalSpawner.Portal.nWarningFX, false )
|
|
PortalSpawner.Portal.nWarningFX = nil
|
|
end
|
|
if PortalSpawner.Portal.nAmbientFX ~= nil then
|
|
ParticleManager:DestroyParticle( PortalSpawner.Portal.nAmbientFX, false )
|
|
PortalSpawner.Portal.nAmbientFX = nil
|
|
end
|
|
|
|
StopSoundOn( "Hero_AbyssalUnderlord.DarkRift.Target", PortalSpawner.Portal.hPortalEnt )
|
|
self.PortalSpawners[ PortalSpawner:GetSpawnerName() ] = nil
|
|
--table.remove( self.PortalSpawners, k )
|
|
print( "Dark Portal killed!" )
|
|
|
|
else
|
|
if PORTAL_ESCALATION_ENABLED == true then
|
|
--local flPreviousInterval = PortalSpawner.Portal.flPortalInterval
|
|
--print( "flPreviousInterval: " .. flPreviousInterval )
|
|
|
|
--local flPct = 1 - ( 1 / nPortalsCountBefore )
|
|
--print( "flPct: " .. flPct )
|
|
|
|
PortalSpawner.Portal.flPortalInterval = PortalSpawner.Portal.flPortalInterval - PORTAL_ESCALATION_RATE
|
|
print( "Setting new portal interval: " .. PortalSpawner.Portal.flPortalInterval )
|
|
|
|
--local flIntervalDiff = flPreviousInterval - PortalSpawner.Portal.flPortalInterval
|
|
PortalSpawner.Portal.flNextSpawnTime = PortalSpawner.Portal.flNextSpawnTime - PORTAL_ESCALATION_RATE
|
|
--print( "Speeding up next spawn time by " .. flIntervalDiff )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--]]
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:OnRequiredEnemyKilled( hAttacker, hVictim )
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:OnSecondaryEnemyKilled( hAttacker, hVictim )
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:OnPortalKilled( hVictim, hAttacker, nUnitCountSuppressed )
|
|
local nCurrentValue = self:GetEncounterObjectiveProgress( "destroy_spawning_portals" )
|
|
if nCurrentValue ~= -1 then
|
|
self:UpdateEncounterObjective( "destroy_spawning_portals", nCurrentValue + 1, nil )
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
-- When portals are killed, determine the XP to drop
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:OnPortalV2Killed( hVictim, hAttacker, nUnitCountSuppressed )
|
|
|
|
if hVictim == nil then
|
|
return
|
|
end
|
|
|
|
if nUnitCountSuppressed > 0 then
|
|
self:GrantRewardsForKill( hVictim, hAttacker, nUnitCountSuppressed )
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- trigger_start_touch
|
|
-- > trigger_name - string
|
|
-- > activator_entindex - short
|
|
-- > caller_entindex- short
|
|
|
|
function CMapEncounter:OnTriggerStartTouch( event )
|
|
|
|
local hUnit = EntIndexToHScript( event.activator_entindex )
|
|
local hTriggerEntity = EntIndexToHScript( event.caller_entindex )
|
|
|
|
-- currently empty
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- trigger_end_touch
|
|
-- > trigger_name - string
|
|
-- > activator_entindex - short
|
|
-- > caller_entindex- short
|
|
|
|
function CMapEncounter:OnTriggerEndTouch( event )
|
|
-- currently empty
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:DropNeutralItemFromUnit( hVictim, hAttacker, bAnnounce )
|
|
local hHero = hAttacker
|
|
if hHero == nil or hHero:IsNull() or hHero:IsRealHero() == false then
|
|
hHero = PlayerResource:GetSelectedHeroEntity( 0 )
|
|
end
|
|
|
|
if hHero == nil or hVictim == nil then
|
|
print( "ERROR, trying to drop neutral item without a valid hero and victim" )
|
|
return
|
|
end
|
|
|
|
local szItemDrop = GameRules.Aghanim:PrepareNeutralItemDrop( self.hRoom, false )
|
|
if szItemDrop == nil then
|
|
return
|
|
end
|
|
|
|
DropNeutralItemAtPositionForHero( szItemDrop, hVictim:GetAbsOrigin(), hHero, -1, true )
|
|
-- local newItem = CreateItem( szItemDrop, nil, nil )
|
|
-- newItem:SetPurchaseTime( 0 )
|
|
|
|
-- local drop = CreateItemOnPositionSync( hVictim:GetAbsOrigin(), newItem )
|
|
-- local dropTarget = hVictim:GetAbsOrigin() + RandomVector( RandomFloat( 50, 150 ) )
|
|
-- newItem:LaunchLoot( false, 150, 0.75, dropTarget )
|
|
|
|
|
|
-- if bAnnounce then
|
|
-- AddFOWViewer( DOTA_TEAM_GOODGUYS, dropTarget, 300.0, 10.0, false )
|
|
-- MinimapEvent( DOTA_TEAM_GOODGUYS, hVictim, dropTarget.x, dropTarget.y, DOTA_MINIMAP_EVENT_HINT_LOCATION, 10.0 )
|
|
-- EmitSoundOn( "NeutralLootDrop.TierComplete", hVictim )
|
|
-- if hAttacker and hAttacker:IsOwnedByAnyPlayer() then
|
|
-- local gameEvent = {}
|
|
-- gameEvent["player_id"] = hAttacker:GetPlayerID()
|
|
-- gameEvent["teamnumber"] = DOTA_TEAM_GOODGUYS
|
|
-- gameEvent["locstring_value"] = "#DOTA_Tooltip_Ability_" .. szItemDrop
|
|
-- gameEvent["message"] = "#Aghanim_FoundItem"
|
|
-- FireGameEvent( "dota_combat_event_message", gameEvent )
|
|
-- else
|
|
-- local gameEvent = {}
|
|
-- gameEvent["player_id"] = 0 --fixme
|
|
-- gameEvent["teamnumber"] = DOTA_TEAM_GOODGUYS
|
|
-- gameEvent["locstring_value"] = "#DOTA_Tooltip_Ability_" .. szItemDrop
|
|
-- gameEvent["message"] = "#Aghanim_FoundItem"
|
|
-- FireGameEvent( "dota_combat_event_message", gameEvent )
|
|
-- end
|
|
-- end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:DropLifeRuneFromUnit( hVictim, hAttacker, bAnnounce )
|
|
local newItem = CreateItem( "item_life_rune", nil, nil )
|
|
newItem:SetPurchaseTime( 0 )
|
|
newItem:SetCurrentCharges( 1 )
|
|
local drop = CreateItemOnPositionSync( hVictim:GetAbsOrigin(), newItem )
|
|
local dropTarget = hVictim:GetAbsOrigin() + RandomVector( RandomFloat( 125, 175 ) )
|
|
newItem:LaunchLoot( false, 150, 0.75, dropTarget )
|
|
|
|
if bAnnounce then
|
|
AddFOWViewer( DOTA_TEAM_GOODGUYS, dropTarget, 300.0, 10.0, false )
|
|
MinimapEvent( DOTA_TEAM_GOODGUYS, hVictim, dropTarget.x, dropTarget.y, DOTA_MINIMAP_EVENT_HINT_LOCATION, 10.0 )
|
|
EmitSoundOn( "Rune.Bounty", hVictim )
|
|
if hAttacker and hAttacker:IsOwnedByAnyPlayer() then
|
|
|
|
local gameEvent = {}
|
|
gameEvent["player_id"] = hAttacker:GetPlayerID()
|
|
gameEvent["teamnumber"] = DOTA_TEAM_GOODGUYS
|
|
gameEvent["locstring_value"] = "#DOTA_Tooltip_Ability_item_life_rune"
|
|
gameEvent["message"] = "#Aghanim_FoundLifeRune"
|
|
FireGameEvent( "dota_combat_event_message", gameEvent )
|
|
|
|
else
|
|
|
|
local gameEvent = {}
|
|
gameEvent["player_id"] = 0
|
|
gameEvent["teamnumber"] = DOTA_TEAM_GOODGUYS
|
|
gameEvent["locstring_value"] = "#DOTA_Tooltip_Ability_item_life_rune"
|
|
gameEvent["message"] = "#Aghanim_FoundLifeRune"
|
|
FireGameEvent( "dota_combat_event_message", gameEvent )
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:DropCurrencyFromUnit( hVictim, hAttacker, nPoints, bBattlePoints, bAnnounce )
|
|
|
|
-- Suppress drop if everyone has maxed out
|
|
if GameRules.Aghanim:CanPlayersAcceptCurrency( bBattlePoints ) == false then
|
|
return
|
|
end
|
|
|
|
local newItem = nil
|
|
if bBattlePoints == true then
|
|
newItem = CreateItem( "item_battle_points", nil, nil )
|
|
else
|
|
newItem = CreateItem( "item_arcane_fragments", nil, nil )
|
|
end
|
|
|
|
newItem:SetPurchaseTime( 0 )
|
|
newItem:SetCurrentCharges( nPoints )
|
|
local drop = CreateItemOnPositionSync( hVictim:GetAbsOrigin(), newItem )
|
|
if bBattlePoints == true then
|
|
drop:SetMaterialGroup( "ti10" )
|
|
else
|
|
drop:SetMaterialGroup( "arcane_fragment" )
|
|
end
|
|
local dropTarget = hVictim:GetAbsOrigin() + RandomVector( RandomFloat( 50, 150 ) )
|
|
newItem:LaunchLoot( true, 150, 0.75, dropTarget )
|
|
|
|
-- if bAnnounce then
|
|
-- AddFOWViewer( DOTA_TEAM_GOODGUYS, dropTarget, 300.0, 10.0, false )
|
|
-- MinimapEvent( DOTA_TEAM_GOODGUYS, hVictim, dropTarget.x, dropTarget.y, DOTA_MINIMAP_EVENT_HINT_LOCATION, 10.0 )
|
|
-- EmitSoundOn( "Rune.Bounty", hVictim )
|
|
-- if hAttacker and hAttacker:IsOwnedByAnyPlayer() then
|
|
|
|
-- local gameEvent = {}
|
|
-- gameEvent["player_id"] = hAttacker:GetPlayerID()
|
|
-- gameEvent["teamnumber"] = DOTA_TEAM_GOODGUYS
|
|
-- gameEvent["locstring_value"] = "#DOTA_Tooltip_Ability_item_life_rune"
|
|
-- gameEvent["message"] = "#Aghanim_FoundLifeRune"
|
|
-- FireGameEvent( "dota_combat_event_message", gameEvent )
|
|
|
|
-- else
|
|
|
|
-- local gameEvent = {}
|
|
-- gameEvent["player_id"] = 0
|
|
-- gameEvent["teamnumber"] = DOTA_TEAM_GOODGUYS
|
|
-- gameEvent["locstring_value"] = "#DOTA_Tooltip_Ability_item_life_rune"
|
|
-- gameEvent["message"] = "#Aghanim_FoundLifeRune"
|
|
-- FireGameEvent( "dota_combat_event_message", gameEvent )
|
|
-- end
|
|
-- end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:DropItemFromRoomRewardContainer( hContainer, szItemName, bAnnounce )
|
|
for szNeutralItem,v in pairs ( PRICED_ITEM_REWARD_LIST ) do
|
|
if szNeutralItem == szItemName then
|
|
DropNeutralItemAtPositionForHero( szItemName, hContainer:GetAbsOrigin() + RandomVector( RandomFloat( 50, 150 ) ), PlayerResource:GetSelectedHeroEntity( 0 ), -1, true )
|
|
return
|
|
end
|
|
end
|
|
|
|
local newItem = CreateItem( szItemName, nil, nil )
|
|
newItem:SetPurchaseTime( 0 )
|
|
|
|
local drop = CreateItemOnPositionSync( hContainer:GetAbsOrigin(), newItem )
|
|
local dropTarget = hContainer:GetAbsOrigin() + RandomVector( RandomFloat( 50, 150 ) )
|
|
newItem:LaunchLoot( false, 150, 0.75, dropTarget )
|
|
|
|
if bAnnounce then
|
|
AddFOWViewer( DOTA_TEAM_GOODGUYS, dropTarget, 300.0, 10.0, false )
|
|
MinimapEvent( DOTA_TEAM_GOODGUYS, hContainer, dropTarget.x, dropTarget.y, DOTA_MINIMAP_EVENT_HINT_LOCATION, 10.0 )
|
|
EmitSoundOn( "NeutralLootDrop.TierComplete", hContainer )
|
|
|
|
local gameEvent = {}
|
|
gameEvent["player_id"] = 0 --fixme
|
|
gameEvent["teamnumber"] = DOTA_TEAM_GOODGUYS
|
|
gameEvent["locstring_value"] = "#DOTA_Tooltip_Ability_" .. szItemDrop
|
|
gameEvent["message"] = "#Aghanim_FoundConsumableItem"
|
|
FireGameEvent( "dota_combat_event_message", gameEvent )
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:DestroyRemainingSpawnedUnits()
|
|
|
|
for k, hSpawner in pairs( self.Spawners ) do
|
|
self.Spawners[k] = nil
|
|
end
|
|
|
|
for k, hPortalSpawner in pairs ( self.PortalSpawners ) do
|
|
hPortalSpawner:DestroyPortal( true )
|
|
self.PortalSpawners[k] = nil
|
|
end
|
|
|
|
for k,hSpawnedEnemy in pairs ( self.SpawnedEnemies ) do
|
|
UTIL_Remove( hSpawnedEnemy )
|
|
self.SpawnedEnemies[k] = nil
|
|
end
|
|
|
|
for k,hSpawnedEnemy in pairs ( self.SpawnedSecondaryEnemies ) do
|
|
UTIL_Remove( hSpawnedEnemy )
|
|
self.SpawnedSecondaryEnemies[k] = nil
|
|
end
|
|
|
|
for k, hBreakable in pairs ( self.SpawnedBreakables ) do
|
|
UTIL_Remove( hBreakable )
|
|
self.SpawnedBreakables[ k ] = nil
|
|
end
|
|
|
|
for k, hExplosiveBarrel in pairs ( self.SpawnedExplosiveBarrels ) do
|
|
UTIL_Remove( hExplosiveBarrel )
|
|
self.SpawnedExplosiveBarrels[ k ] = nil
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:FindEncounterEndLocator()
|
|
|
|
local hExitLocatorList = self:GetRoom():FindAllEntitiesInRoomByName( "encounter_end_locator", false )
|
|
if #hExitLocatorList == 0 then
|
|
return nil
|
|
end
|
|
|
|
return hExitLocatorList[1]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SpawnEndLevelEntities()
|
|
|
|
self.bHasSpawnedEndLevelEntities = true
|
|
|
|
local vExitTemplate = Entities:FindByName( nil, "encounter_end_template" )
|
|
if vExitTemplate == nil then
|
|
print( "Unable to find encounter_end_template\n" )
|
|
return
|
|
end
|
|
|
|
local hExitLocator = self:FindEncounterEndLocator()
|
|
if hExitLocator == nil then
|
|
return
|
|
end
|
|
|
|
local vExitLocation = hExitLocator:GetAbsOrigin()
|
|
local vSpawnLocation = Vector( vExitLocation.x, vExitLocation.y, GetGroundHeight( vExitLocation, nil ) )
|
|
vExitTemplate:SetAbsOrigin( vSpawnLocation )
|
|
vExitTemplate:ForceSpawn()
|
|
|
|
--meh
|
|
local nDepth = self.hRoom:GetDepth()
|
|
|
|
for _,hTemplateEnt in pairs ( vExitTemplate:GetSpawnedEntities() ) do
|
|
if hTemplateEnt:GetName() == "room_reward_spawn" then
|
|
self.vRoomRewardCratePos = hTemplateEnt:GetAbsOrigin()
|
|
end
|
|
if nDepth == 6 or nDepth == 11 or nDepth == 13 or nDepth == 17 then
|
|
if hTemplateEnt:GetName() == "shop" or hTemplateEnt:GetName() == "shop_trigger" or hTemplateEnt:GetName() == "shop_obstruction" or hTemplateEnt:GetName() == "shop_particles" or hTemplateEnt:GetName() == "neutral_stash" then
|
|
UTIL_Remove( hTemplateEnt )
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.hRoom:HasCrystal() then
|
|
local hCrystal = CreateUnitByName( "npc_dota_story_crystal", self.vRoomRewardCratePos + Vector( 0, 350, 0 ), true, nil, nil, DOTA_TEAM_GOODGUYS )
|
|
if hCrystal ~= nil then
|
|
print( "spawned story crystal" )
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:ResetHeroState()
|
|
for nPlayerID = 0,AGHANIM_PLAYERS-1 do
|
|
local hPlayerHero = PlayerResource:GetSelectedHeroEntity( nPlayerID )
|
|
if hPlayerHero then
|
|
if HEAL_ON_ENCOUNTER_COMPLETE then
|
|
if not hPlayerHero:IsAlive() then
|
|
local vLocation = hPlayerHero:GetOrigin()
|
|
if self.hRoom:GetType() == ROOM_TYPE_TRAPS then
|
|
local hExitLocator = self:FindEncounterEndLocator()
|
|
if hExitLocator == nil then
|
|
return
|
|
else
|
|
vLocation = hExitLocator:GetAbsOrigin()
|
|
end
|
|
end
|
|
|
|
hPlayerHero:RespawnHero( false, false )
|
|
FindClearSpaceForUnit( hPlayerHero, vLocation, true )
|
|
CenterCameraOnUnit( nPlayerID, hPlayerHero )
|
|
end
|
|
|
|
-- PositiveBuffs, NegativeBuffs, FrameOnly, RemoveStuns, RemoveExceptions
|
|
hPlayerHero:Purge( false, true, false, true, false )
|
|
--结束奖励
|
|
-- make the players invulnerable for a few seconds after winning - just generally protecting them from stuff that might be lingering in the room
|
|
hPlayerHero:AddNewModifier( hPlayerHero, nil, "modifier_invulnerable", { duration = 5 } )
|
|
|
|
hPlayerHero:SetHealth( hPlayerHero:GetMaxHealth() )
|
|
hPlayerHero:SetMana( hPlayerHero:GetMaxMana() )
|
|
end
|
|
|
|
for i = 0,DOTA_MAX_ABILITIES-1 do
|
|
local hAbility = hPlayerHero:GetAbilityByIndex( i )
|
|
if hAbility and hAbility:IsRefreshable() then
|
|
hAbility:SetFrozenCooldown( false )
|
|
hAbility:EndCooldown()
|
|
hAbility:RefreshCharges()
|
|
end
|
|
end
|
|
|
|
--for j = 0,DOTA_ITEM_INVENTORY_SIZE-1 do
|
|
local j = DOTA_ITEM_TP_SCROLL
|
|
local hItem = hPlayerHero:GetItemInSlot( j )
|
|
if hItem then
|
|
if hItem:GetAbilityName() == "item_bottle" then
|
|
local nMaxCharges = hItem:GetSpecialValueFor( "max_charges" )
|
|
--print( "filling bottle: current charges = " .. hItem:GetCurrentCharges() .. ". Max Charges = " .. nMaxCharges .. ". Restoring up to " .. AGHANIM_ENCOUNTER_BOTTLE_CHARGES )
|
|
hItem:SetCurrentCharges( math.min( hItem:GetCurrentCharges() + AGHANIM_ENCOUNTER_BOTTLE_CHARGES, nMaxCharges ) )
|
|
elseif hItem:IsRefreshable() then
|
|
hItem:SetFrozenCooldown( false )
|
|
hItem:EndCooldown()
|
|
end
|
|
end
|
|
--end
|
|
|
|
local hNeutralItem = hPlayerHero:GetItemInSlot( DOTA_ITEM_NEUTRAL_SLOT )
|
|
if hNeutralItem and hNeutralItem:IsRefreshable() then
|
|
hNeutralItem:SetFrozenCooldown( false )
|
|
hNeutralItem:EndCooldown()
|
|
end
|
|
|
|
local nFXIndex = ParticleManager:CreateParticle( "particles/items2_fx/refresher.vpcf", PATTACH_CUSTOMORIGIN, hPlayerHero )
|
|
ParticleManager:SetParticleControlEnt( nFXIndex, 0, hPlayerHero, PATTACH_POINT_FOLLOW, "attach_hitloc", hPlayerHero:GetAbsOrigin(), true )
|
|
ParticleManager:ReleaseParticleIndex( nFXIndex )
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:AddRewardItemsToCrate( hRewardCrate, bDebug )
|
|
|
|
local bHardRoom = ( self.hRoom:GetEliteRank() > 0 )
|
|
if self.hRoom:GetRoomChoiceReward() == "REWARD_TYPE_EXTRA_LIVES" then
|
|
if bDebug == true then
|
|
return
|
|
end
|
|
|
|
local nNumLives = 2
|
|
if bHardRoom then
|
|
nNumLives = 4
|
|
end
|
|
for i=1,nNumLives do
|
|
table.insert( hRewardCrate.RoomReward, "item_life_rune" )
|
|
end
|
|
end
|
|
|
|
if self.hRoom:GetRoomChoiceReward() == "REWARD_TYPE_GOLD" then
|
|
if bDebug == true then
|
|
-- Can't do this here since it'll drop at the final depth value
|
|
return
|
|
end
|
|
for i=1,AGHANIM_PLAYERS do
|
|
table.insert( hRewardCrate.RoomReward, "item_bag_of_gold" )
|
|
end
|
|
end
|
|
|
|
if self.hRoom:GetRoomChoiceReward() == "REWARD_TYPE_TREASURE" then
|
|
local nTier = 1
|
|
if bHardRoom then
|
|
nTier = 2
|
|
end
|
|
|
|
table.insert( hRewardCrate.RoomReward, "item_tome_of_greater_knowledge" )
|
|
|
|
for nItem=1,nTier do
|
|
local szItemName = GameRules.Aghanim:PrepareNeutralItemDrop( self.hRoom, bHardRoom )
|
|
if szItemName ~= nil then
|
|
table.insert( hRewardCrate.RoomReward, szItemName )
|
|
end
|
|
end
|
|
|
|
local vecItems = TREASURE_REWARDS[ nTier ]
|
|
for i = 1, NUM_CONSUMABLES_FROM_ROOM_REWARD do
|
|
table.insert( hRewardCrate.RoomReward, vecItems[ self:RoomRandomInt( 1, #vecItems ) ] )
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:CreateRewardCrate()
|
|
|
|
if self.hRoom:GetRoomChoiceReward() == nil or self.nGoldReward == 0 then
|
|
return
|
|
end
|
|
|
|
local hDebugRoom = GameRules.Aghanim:GetTestEncounterDebugRoom()
|
|
if hDebugRoom ~= nil then
|
|
self:AddRewardItemsToCrate( GameRules.Aghanim.debugItemsToStuffInCrate, true )
|
|
end
|
|
|
|
local hRewardCrate = CreateUnitByName( "npc_treasure_chest", self.vRoomRewardCratePos, true, nil, nil, DOTA_TEAM_GOODGUYS )
|
|
if hRewardCrate == nil then
|
|
return
|
|
end
|
|
|
|
hRewardCrate:SetAbsAngles( 0, 270, 0 )
|
|
hRewardCrate.CommonItems = {}
|
|
hRewardCrate.fCommonItemChance = 0.0
|
|
hRewardCrate.RareItems = {}
|
|
hRewardCrate.fRareItemChance = {}
|
|
hRewardCrate.nMinGold = 0
|
|
hRewardCrate.nMaxGold = 0
|
|
hRewardCrate.fGoldChance = 0
|
|
if bHardRoom then
|
|
hRewardCrate:SetModelScale( 4.0 )
|
|
else
|
|
hRewardCrate:SetModelScale( 2.0 )
|
|
end
|
|
hRewardCrate.Encounter = self
|
|
hRewardCrate.RoomReward = {}
|
|
hRewardCrate.nDepth = self.hRoom:GetDepth()
|
|
hRewardCrate.nEliteRank = self.hRoom:GetEliteRank()
|
|
self:AddRewardItemsToCrate( hRewardCrate, false )
|
|
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY then
|
|
local nNumItemsToDrop = self.nNumItemsToDrop
|
|
if ( nNumItemsToDrop == 0 ) and GameRules.Aghanim:GetTestEncounterDebugRoom() ~= nil then
|
|
nNumItemsToDrop = GameRules.Aghanim:RollRandomNeutralItemDrops()
|
|
end
|
|
|
|
if nNumItemsToDrop > 0 then
|
|
for i=1,nNumItemsToDrop do
|
|
local szItemName = GameRules.Aghanim:PrepareNeutralItemDrop( self.hRoom, false )
|
|
if szItemName ~= nil then
|
|
print( "adding " .. szItemName .. " to reward crate" )
|
|
table.insert( hRewardCrate.RoomReward, szItemName )
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Spawn any un-dropped BPs
|
|
if self.nNumBPToDrop > 0 then
|
|
for i=1,self.nNumBPToDrop do
|
|
self:DropCurrencyFromUnit( hRewardCrate, hRewardCrate, RandomInt( BATTLE_POINT_MIN_DROP_VALUE, BATTLE_POINT_MAX_DROP_VALUE ), true, false )
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY or self.hRoom:GetType() == ROOM_TYPE_TRAPS then
|
|
-- Spawn any un-dropped Arcance Fragments
|
|
if self.nNumFragmentsToDrop > 0 then
|
|
print( "WARNING! Number of random Arcane Fragment Drops is not 0 at the end of the round! Dropping at exit locator. This is ok for TRAP ROOMS!" )
|
|
for i=1,self.nNumFragmentsToDrop do
|
|
self:DropCurrencyFromUnit( hRewardCrate, hRewardCrate, self:GetArcaneFragmentDropValue(), false, false )
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Stuff items we would have dropped during winning encounters into this crate
|
|
if GameRules.Aghanim:GetTestEncounterDebugRoom() == nil and GameRules.Aghanim.debugItemsToStuffInCrate ~= nil then
|
|
for i = 1,#GameRules.Aghanim.debugItemsToStuffInCrate.RoomReward do
|
|
print( GameRules.Aghanim.debugItemsToStuffInCrate.RoomReward[i] )
|
|
table.insert( hRewardCrate.RoomReward, GameRules.Aghanim.debugItemsToStuffInCrate.RoomReward[i] )
|
|
end
|
|
GameRules.Aghanim.debugItemsToStuffInCrate = nil
|
|
end
|
|
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetArcaneFragmentDropValue()
|
|
local fPoints = ENCOUNTER_DEPTH_ARCANE_FRAGMENTS[ self.hRoom:GetDepth() ]
|
|
--print( 'CMapEncounter:GetArcaneFragmentDropValue() - base value: ' .. fPoints )
|
|
|
|
local fDropEV = GameRules.Aghanim:GetFragmentDropEV()
|
|
fPoints = fPoints / fDropEV
|
|
--print( 'CMapEncounter:GetArcaneFragmentDropValue() - modified by drop EV to ' .. fPoints )
|
|
|
|
local fMultiplier = ARCANE_FRAGMENT_DIFFICULTY_MODIFIERS[ GameRules.Aghanim:GetAscensionLevel() + 1 ]
|
|
fPoints = fPoints * fMultiplier
|
|
--print( 'CMapEncounter:GetArcaneFragmentDropValue() - modified by Ascension multiplier: ' .. fMultiplier .. '. result is: ' .. fPoints )
|
|
|
|
--print( 'CMapEncounter:GetArcaneFragmentDropValue() - adding variance +/-: ' .. ARCANE_FRAGMENT_DROP_VALUE_VARIANCE )
|
|
|
|
local fLow = fPoints - (fPoints * ARCANE_FRAGMENT_DROP_VALUE_VARIANCE)
|
|
local fHigh = fPoints + (fPoints * ARCANE_FRAGMENT_DROP_VALUE_VARIANCE)
|
|
--print( 'CMapEncounter:GetArcaneFragmentDropValue() - adding variance between: ' .. fLow .. ' and: ' .. fHigh )
|
|
|
|
fPoints = RandomFloat( fLow, fHigh )
|
|
--print( 'CMapEncounter:GetArcaneFragmentDropValue() - rolled ' .. fPoints )
|
|
|
|
fPoints = math.ceil( fPoints * ARCANE_FRAGMENT_DROP_VALUE )
|
|
--print( 'CMapEncounter:GetArcaneFragmentDropValue() - trimmed down by drop EV: ' .. ARCANE_FRAGMENT_DROP_VALUE .. ' and rounded to: ' .. fPoints )
|
|
|
|
return fPoints
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GenerateRewards()
|
|
|
|
if self.bHasGeneratedRewards == true then
|
|
return
|
|
end
|
|
|
|
local bHardRoom = ( self.hRoom:GetEliteRank() > 0 ) or ( self.hRoom:GetType() == ROOM_TYPE_TRAPS )
|
|
|
|
local nBPReward = ENCOUNTER_DEPTH_BATTLE_POINTS[ self.hRoom:GetDepth() ]
|
|
nBPReward = nBPReward * BATTLE_POINT_DIFFICULTY_MODIFIERS[ GameRules.Aghanim:GetAscensionLevel() + 1 ]
|
|
|
|
local nArcaneFragmentsReward = ENCOUNTER_DEPTH_ARCANE_FRAGMENTS[ self.hRoom:GetDepth() ]
|
|
|
|
--print( 'CMapEncounter:GenerateRewards() - base Arcane Fragment reward: ' .. nArcaneFragmentsReward )
|
|
|
|
-- only reward a percentage of the points since the rest is given as drops
|
|
if self.hRoom:GetType() == ROOM_TYPE_ENEMY or self.hRoom:GetType() == ROOM_TYPE_TRAPS then
|
|
nArcaneFragmentsReward = nArcaneFragmentsReward * ARCANE_FRAGMENT_ROOM_CLEAR_VALUE
|
|
--print( 'CMapEncounter:GenerateRewards() - reducing room clear reward to: ' .. nArcaneFragmentsReward )
|
|
end
|
|
|
|
-- scale the reward by the difficulty of the run.
|
|
local fMultiplier = ARCANE_FRAGMENT_DIFFICULTY_MODIFIERS[ GameRules.Aghanim:GetAscensionLevel() + 1 ]
|
|
nArcaneFragmentsReward = nArcaneFragmentsReward * fMultiplier
|
|
--print( 'CMapEncounter:GenerateRewards() - Arcane Fragment reward increased by ' .. fMultiplier .. ' for Ascension. new reward: ' .. nArcaneFragmentsReward )
|
|
nArcaneFragmentsReward = math.ceil( nArcaneFragmentsReward )
|
|
--print( 'CMapEncounter:GenerateRewards() - Arcane Fragment reward rounded up to ' .. nArcaneFragmentsReward )
|
|
|
|
local vecBPRewards = {}
|
|
if nBPReward > 0 then
|
|
vecBPRewards = GameRules.Aghanim:GrantAllPlayersPoints( nBPReward, true, "completing " .. self.szEncounterName .. " at depth " .. tostring( self.hRoom:GetDepth() ) )
|
|
end
|
|
|
|
local vecArcaneFragmentRewards = {}
|
|
if nArcaneFragmentsReward > 0 then
|
|
vecArcaneFragmentRewards = GameRules.Aghanim:GrantAllPlayersPoints( nArcaneFragmentsReward, false, "completing " .. self.szEncounterName .. " at depth " .. tostring( self.hRoom:GetDepth() ) )
|
|
end
|
|
|
|
self:CreateRewardCrate()
|
|
|
|
local RewardOptions = {}
|
|
CustomNetTables:SetTableValue( "reward_options", "current_depth", { tostring(self.hRoom:GetDepth()) } )
|
|
|
|
-- certain abilities (like auras) we want to prevent being rolled more than once by the party as a whole
|
|
local vecAbilityNamesToExclude = {}
|
|
|
|
local nXPReward = self.nXPReward + self.nRemainingXPFromEnemies
|
|
local nGoldReward = self.nGoldReward + self.nRemainingGoldFromEnemies
|
|
|
|
for nPlayerID = 0,AGHANIM_PLAYERS-1 do
|
|
local hPlayerHero = PlayerResource:GetSelectedHeroEntity( nPlayerID )
|
|
if hPlayerHero then
|
|
hPlayerHero:AddExperience( nXPReward, DOTA_ModifyXP_Unspecified, false, true )
|
|
PlayerResource:ModifyGold( nPlayerID, nGoldReward, true, DOTA_ModifyGold_Unspecified )
|
|
end
|
|
|
|
for _,szAbilityName in pairs( GetPlayerAbilitiesAndItems( nPlayerID ) ) do
|
|
if string.match( szAbilityName, "aghsfort_aura" ) or string.match( szAbilityName, "aghsfort_tempbuff" ) then
|
|
table.insert( vecAbilityNamesToExclude, szAbilityName )
|
|
end
|
|
end
|
|
end
|
|
|
|
for nPlayerID = 0,AGHANIM_PLAYERS-1 do
|
|
local vecPlayerRewards = GetRoomRewards( self.hRoom:GetDepth(), self.hRoom:GetType(), bHardRoom, nPlayerID, vecAbilityNamesToExclude )
|
|
RewardOptions[ tostring(nPlayerID) ] = vecPlayerRewards;
|
|
--print( "CMapEncounter:GenerateRewards - Sending rewards to player id " .. nPlayerID .. " for encounter " .. self.szEncounterName )
|
|
--DeepPrintTable( vecPlayerRewards )
|
|
end
|
|
|
|
|
|
-- figure out the overall rarity of the rewards for the main block of the reward panel
|
|
local szRarity = "common"
|
|
if bHardRoom then
|
|
szRarity = "elite"
|
|
end
|
|
if self.hRoom:GetType() == ROOM_TYPE_BOSS or self.hRoom:GetDepth() == 1 then
|
|
szRarity = "epic"
|
|
end
|
|
|
|
if TableLength(RewardOptions) > 0 then
|
|
RewardOptions[ "battle_points" ] = vecBPRewards
|
|
RewardOptions[ "arcane_fragments" ] = vecArcaneFragmentRewards
|
|
RewardOptions[ "xp" ] = nXPReward
|
|
RewardOptions[ "gold" ] = nGoldReward
|
|
RewardOptions[ "rarity" ] = szRarity
|
|
|
|
--printf("sending reward options")
|
|
--DeepPrintTable( RewardOptions )
|
|
CustomNetTables:SetTableValue( "reward_options", tostring(self.hRoom:GetDepth()), RewardOptions )
|
|
end
|
|
|
|
self.bHasGeneratedRewards = true
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:HasStarted()
|
|
return self.flStartTime ~= -1
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetStartTime()
|
|
return self.flStartTime
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetRoom()
|
|
return self.hRoom
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetSpawners()
|
|
return self.Spawners
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetPortalSpawners()
|
|
return self.PortalSpawners
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetRemainingPortalCount()
|
|
|
|
local nPortals = 0
|
|
|
|
if self.PortalSpawners ~= nil then
|
|
for _,hPortalSpawner in pairs ( self.PortalSpawners ) do
|
|
if hPortalSpawner and hPortalSpawner:IsDestroyed() == false then
|
|
nPortals = nPortals + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.PortalSpawnersV2 ~= nil then
|
|
for _,hPortalSpawner in pairs ( self.PortalSpawnersV2 ) do
|
|
if hPortalSpawner ~= nil then
|
|
nPortals = nPortals + hPortalSpawner:GetPortalUnitCount()
|
|
end
|
|
end
|
|
end
|
|
|
|
return nPortals
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:HasAnyPortals()
|
|
return self:GetRemainingPortalCount() > 0
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetSpawner( szSpawnerName )
|
|
return self.Spawners[szSpawnerName]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetPortalSpawner( szSpawnerName )
|
|
return self.PortalSpawners[szSpawnerName]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetPortalSpawnerV2( szSpawnerName )
|
|
return self.PortalSpawnersV2[szSpawnerName]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetDepth()
|
|
return self.hRoom:GetDepth()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetTotalGoldRewardPerPlayer()
|
|
return ENCOUNTER_DEPTH_GOLD_REWARD[self:GetDepth()]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetTotalXPRewardPerPlayer()
|
|
return ENCOUNTER_DEPTH_XP_REWARD[self:GetDepth()]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- If this is true, the gold and XP rewards from killing an enemy will be
|
|
-- distrubuted evenly amongst the total enemy count. If false, use the
|
|
-- values from the unit data.
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SetCalculateRewardsFromUnitCount( bCalculate )
|
|
self.bCalculateRewardsFromUnitCount = bCalculate
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetPreviewUnit()
|
|
return nil
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetName()
|
|
return self.szEncounterName
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetRetreatPoints()
|
|
return self.RetreatPoints
|
|
end
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
function CMapEncounter:HasRemainingEnemies()
|
|
return #self.SpawnedEnemies > 0
|
|
end
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetSpawnedUnits()
|
|
return self.SpawnedEnemies
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:GetSpawnedSecondaryUnits()
|
|
return self.SpawnedSecondaryEnemies
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
|
|
function CMapEncounter:GetSpawnedUnitsOfType( szUnitName )
|
|
|
|
local hUnits = {}
|
|
|
|
for i=1,#self.SpawnedEnemies do
|
|
|
|
if self.SpawnedEnemies[i] ~= nil and self.SpawnedEnemies[i]:GetUnitName() == szUnitName then
|
|
table.insert( hUnits, self.SpawnedEnemies[i] )
|
|
end
|
|
|
|
end
|
|
|
|
return hUnits
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:Dev_ForceCompleteEncounter()
|
|
self.bDevForceCompleted = true
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:GetAghanimSummon()
|
|
return self:GetPreviewUnit()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
function CMapEncounter:SetupBristlebackShop( bRepopulateNeutralItems )
|
|
if bRepopulateNeutralItems then
|
|
local vecPricedItems1 = GetPricedNeutralItems( self.hRoom:GetDepth() - 1, false )
|
|
local vecPricedItems2 = GetPricedNeutralItems( self.hRoom:GetDepth() - 2, false )
|
|
|
|
for _,szLessItem in pairs ( vecPricedItems1 ) do
|
|
local bFound = false
|
|
for _,szThisDepthItem in pairs ( vecPricedItems2 ) do
|
|
if szThisDepthItem == szLessItem then
|
|
bFound = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if not bFound then
|
|
table.insert( vecPricedItems2, szLessItem )
|
|
end
|
|
end
|
|
|
|
local vecFilteredItems = GameRules.Aghanim:FilterPreviouslyDroppedItems( vecPricedItems1 )
|
|
|
|
for nItem = #GameRules.Aghanim.BristlebackItems,1,-1 do
|
|
local szPreviousItemName = GameRules.Aghanim.BristlebackItems[ nItem ]
|
|
GameRules:GetGameModeEntity():RemoveItemFromCustomShop( szPreviousItemName, "boss_shop" )
|
|
end
|
|
|
|
for i=1,8 do
|
|
local index = self:RoomRandomInt( 1, #vecFilteredItems )
|
|
local szItemName = vecFilteredItems[ index ]
|
|
GameRules:GetGameModeEntity():AddItemToCustomShop( szItemName, "boss_shop", "2" )
|
|
table.remove( vecFilteredItems, index )
|
|
|
|
table.insert( GameRules.Aghanim.BristlebackItems, szItemName )
|
|
GameRules.Aghanim:MarkNeutralItemAsDropped( szItemName )
|
|
end
|
|
end
|
|
|
|
GameRules:IncreaseItemStock( DOTA_TEAM_GOODGUYS, "item_life_rune", AGHANIM_PLAYERS, -1 )
|
|
GameRules:IncreaseItemStock( DOTA_TEAM_GOODGUYS, "item_book_of_strength", AGHANIM_PLAYERS, -1 )
|
|
GameRules:IncreaseItemStock( DOTA_TEAM_GOODGUYS, "item_book_of_agility", AGHANIM_PLAYERS, -1 )
|
|
GameRules:IncreaseItemStock( DOTA_TEAM_GOODGUYS, "item_book_of_intelligence", AGHANIM_PLAYERS, -1 )
|
|
|
|
local hBristleEnts = self:GetRoom():FindAllEntitiesInRoomByName( "boss_shop" )
|
|
for _,hEnt in pairs ( hBristleEnts ) do
|
|
if hEnt:GetClassname() == "ent_dota_shop" then
|
|
local szWearables =
|
|
{
|
|
"models/heroes/bristleback/bristleback_back.vmdl",
|
|
"models/heroes/bristleback/bristleback_bracer.vmdl",
|
|
"models/heroes/bristleback/bristleback_head.vmdl",
|
|
"models/heroes/bristleback/bristleback_necklace.vmdl",
|
|
}
|
|
|
|
for _,szWearable in pairs ( szWearables ) do
|
|
local hWearable = Entities:CreateByClassname( "wearable_item" )
|
|
if hWearable ~= nil then
|
|
hWearable:SetModel( szWearable )
|
|
hWearable:SetTeam( DOTA_TEAM_GOODGUYS )
|
|
hWearable:SetOwner( hEnt )
|
|
hWearable:FollowEntity( hEnt, true )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return CMapEncounter
|