Files
2HUCardTDGame/aghanim_singleplayer/scripts/vscripts/map_encounter.lua
2021-10-24 15:36:18 -04:00

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