if CPortalSpawnerV2 == nil then CPortalSpawnerV2 = class({}) end ---------------------------------------------------------------------------- function CPortalSpawnerV2:constructor( szSpawnerNameInput, szLocatorNameInput, nPortalHealthInput, flSummonTimeInput, flScaleInput, rgUnitsInfoInput, bInvulnerableInput ) self.szSpawnerName = szSpawnerNameInput self.szLocatorName = szLocatorNameInput self.rgUnitsInfo = rgUnitsInfoInput self.nPortalHealth = nPortalHealthInput self.flSummonTime = flSummonTimeInput if bInvulnerableInput == nil then self.bInvulnerable = false else self.bInvulnerable = bInvulnerableInput end self.flScale = flScaleInput self.vFocusPosition = nil self.flFocusRadius = nil self.rgSpawners = {} self.rgSpawnedPortals = {} self.Encounter = nil end ---------------------------------------------------------------------------- function CPortalSpawnerV2:GetSpawnerType() return "CPortalSpawnerV2" end ---------------------------------------------------------------------------- function CPortalSpawnerV2:Precache( context ) PrecacheUnitByNameSync( "npc_aghsfort_dark_portal_v2", context, -1 ) PrecacheResource( "particle", "particles/units/heroes/heroes_underlord/abyssal_underlord_darkrift_target.vpcf", context ) PrecacheResource( "particle", "particles/portals/portal_ground_spawn_endpoint.vpcf", context ) for _,rgUnitInfo in pairs ( self.rgUnitsInfo ) do PrecacheUnitByNameSync( rgUnitInfo.EntityName, context, -1 ) end end ---------------------------------------------------------------------------- function CPortalSpawnerV2:OnEncounterLoaded( EncounterInput ) --print( "CPortalSpawnerV2:OnEncounterLoaded called for " .. self.szSpawnerName ) self.Encounter = EncounterInput self.rgSpawners = self.Encounter:GetRoom():FindAllEntitiesInRoomByName( self.szLocatorName, false ) if #self.rgSpawners == 0 then print( "Failed to find entity " .. self.szSpawnerName .. " as spawner position in map " .. self.Encounter:GetRoom():GetMapName() ) end ListenToGameEvent( "entity_killed", Dynamic_Wrap( CPortalSpawnerV2, 'OnEntityKilled' ), self ) end ---------------------------------------------------------------------------- function CPortalSpawnerV2:GetSpawnPositionCount() return #self.rgSpawners end ---------------------------------------------------------------------------- function CPortalSpawnerV2:GetSpawnPositions() return self.rgSpawners end ---------------------------------------------------------------------------- function CPortalSpawnerV2:GetSpawnCountPerSpawnPosition() local nCount = 0 for _,rgUnitInfo in pairs ( self.rgUnitsInfo ) do nCount = nCount + rgUnitInfo.Count end return nCount end ---------------------------------------------------------------------------- function CPortalSpawnerV2:SetSpawnInfo( vMins, vMaxs, vEndPos ) self.vMins = vMins self.vMaxs = vMaxs self.vEndPos = vEndPos end ---------------------------------------------------------------------------- function CPortalSpawnerV2:SetSpawnFocus( vCenter, flRadius ) self.vFocusPosition = vCenter self.flFocusRadius = flRadius end ---------------------------------------------------------------------------- function CPortalSpawnerV2:SpawnSingleUnitType( rgUnitInfo, vLocation ) local hSpawnedUnits = {} for i=1,rgUnitInfo.Count do local vSpawnPos = vLocation if rgUnitInfo.PositionNoise ~= nil then local nAttempts = 0 while nAttempts < 16 do local vTestPos = vSpawnPos + RandomVector( RandomFloat( 0.0, rgUnitInfo.PositionNoise ) ) vTestPos.z = GetGroundHeight( vTestPos, nil ) if GridNav:CanFindPath( vTestPos, vLocation ) then vSpawnPos = vTestPos break end nAttempts = nAttempts + 1 end end local hUnit = CreateUnitByName( rgUnitInfo.EntityName, vSpawnPos, true, nil, nil, rgUnitInfo.Team ) if hUnit == nil then print( "ERROR! Failed to spawn unit named " .. rgUnitInfo.EntityName ) else hUnit.bPortalUnit = true hUnit:FaceTowards( vLocation ) if rgUnitInfo.PostSpawn ~= nil then rgUnitInfo.PostSpawn( hUnit ) end table.insert( hSpawnedUnits, hUnit ) end end return hSpawnedUnits end -------------------------------------------------------------------------------- function CPortalSpawnerV2:TrySpawningPortalUnits() local hSpawnedUnits={} local hActivatedPortals={} for i=#self.rgSpawnedPortals,1,-1 do local hPortal = self.rgSpawnedPortals[i] if hPortal ~= nil then if hPortal.bStartedSound == false and ( hPortal.flSpawnTime - GameRules:GetGameTime() ) <= 6 then hPortal.bStartedSound = true EmitSoundOn( "Aghsfort_DarkPortal.StartSummon", hPortal ) end if hPortal.flSpawnTime <= GameRules:GetGameTime() then local nSpawned = 0 local vLocation = hPortal:GetAbsOrigin() for _,rgUnitInfo in pairs ( self.rgUnitsInfo ) do local hSingleSpawnedUnits = self:SpawnSingleUnitType( rgUnitInfo, vLocation ) for _,hUnit in pairs ( hSingleSpawnedUnits ) do table.insert( hSpawnedUnits, hUnit ) end end table.insert( hActivatedPortals, hPortal ) --printf( "%s spawning %d units", self.szSpawnerName, nSpawned ) end end end if #hSpawnedUnits > 0 then self.Encounter:OnSpawnerFinished( self, hSpawnedUnits ) end -- Once a portal has been spawned, it can die for i=#hActivatedPortals,1,-1 do local hPortal = hActivatedPortals[i] StopSoundOn( "Aghsfort_DarkPortal.StartSummon", hPortal ) EmitSoundOn( "Aghsfort_DarkPortal.Complete", hPortal ) self:RemoveSpawnedPortal( hPortal ) self:DestroyPortal( hPortal ) UTIL_Remove( hPortal ) end return hSpawnedUnits end ---------------------------------------------------------------------------- function CPortalSpawnerV2:SpawnPortal( hPortalEnt ) hPortalEnt:SetTeam( DOTA_TEAM_BADGUYS ) hPortalEnt:SetMaxHealth( self.nPortalHealth ) hPortalEnt:SetBaseMaxHealth( self.nPortalHealth ) hPortalEnt:Heal( self.nPortalHealth, nil ) hPortalEnt:AddNewModifier( hPortalEnt, nil, "modifier_provide_vision", { duration = -1 } ) hPortalEnt:AddNewModifier( hPortalEnt, nil, "modifier_magic_immune", { duration = -1 } ) hPortalEnt:AddNewModifier( hPortalEnt, nil, "modifier_phased", { duration = -1 } ) hPortalEnt:SetAbsScale( self.flScale ) hPortalEnt.flSpawnTime = GameRules:GetGameTime() + self.flSummonTime hPortalEnt.bStartedSound = false -- hPortalEnt.nAmbientFX = ParticleManager:CreateParticle( "particles/units/heroes/heroes_underlord/abyssal_underlord_darkrift_target.vpcf", PATTACH_OVERHEAD_FOLLOW, hPortalEnt ) -- ParticleManager:SetParticleControlEnt( hPortalEnt.nAmbientFX , 1, hPortalEnt, PATTACH_POINT_FOLLOW, "attach_hitloc", hPortalEnt:GetAbsOrigin(), true ) -- ParticleManager:SetParticleControlEnt( hPortalEnt.nAmbientFX , 6, hPortalEnt, PATTACH_ABSORIGIN_FOLLOW, nil, hPortalEnt:GetAbsOrigin(), true ) -- ParticleManager:SetParticleControl( hPortalEnt.nAmbientFX , 10, Vector( 30, 30, 30 ) ); if self.bInvulnerable == true then -- invulnerable portals make the portal invisible and place an effect on the ground hPortalEnt:AddEffects( EF_NODRAW ) hPortalEnt.nWarningFX = ParticleManager:CreateParticle( "particles/portals/portal_ground_spawn_endpoint.vpcf", PATTACH_WORLDORIGIN, nil ) ParticleManager:SetParticleControl( hPortalEnt.nWarningFX, 0, hPortalEnt:GetAbsOrigin() ) else -- vulnerable portals need to remove the invulnerable modifier that's built in to the building hPortalEnt:RemoveModifierByName( "modifier_invulnerable" ) hPortalEnt.nWarningFX = ParticleManager:CreateParticle( "particles/units/heroes/heroes_underlord/abyssal_underlord_darkrift_target.vpcf", PATTACH_OVERHEAD_FOLLOW, hPortalEnt ) ParticleManager:SetParticleControlEnt( hPortalEnt.nWarningFX, 1, hPortalEnt, PATTACH_POINT_FOLLOW, "attach_hitloc", hPortalEnt:GetAbsOrigin(), true ) ParticleManager:SetParticleControlEnt( hPortalEnt.nWarningFX, 6, hPortalEnt, PATTACH_ABSORIGIN_FOLLOW, nil, hPortalEnt:GetAbsOrigin(), true ) ParticleManager:SetParticleControl( hPortalEnt.nWarningFX, 10, Vector( self.flSummonTime*0.9, self.flSummonTime*0.9, self.flSummonTime*0.9 ) ); end if self.flSummonTime <= 6 then hPortalEnt.bStartedSound = true EmitSoundOn( "Aghsfort_DarkPortal.StartSummon", hPortalEnt ) end end ---------------------------------------------------------------------------- function CPortalSpawnerV2:DestroyPortal( hPortal ) if hPortal.nWarningFX ~= nil then ParticleManager:DestroyParticle( hPortal.nWarningFX, false ) hPortal.nWarningFX = nil end -- if hVictim.nAmbientFX ~= nil then -- ParticleManager:DestroyParticle( hPortal.nAmbientFX, false ) -- hPortal.nAmbientFX = nil -- end StopSoundOn( "Aghsfort_DarkPortal.StartSummon", hPortal ) EmitSoundOnLocationWithCaster( hPortal:GetAbsOrigin(), "Aghsfort_DarkPortal.Cancel", hPortal ) end ---------------------------------------------------------------------------- function CPortalSpawnerV2:ComputeSpawnerWeights( ) if self.vFocusPosition == nil then return nil end -- Ok, focus position. Means we're going to preferentially pick -- spawn positions nearer to the spawn focus position local flTotalWeight = 0.0 for i=1,#self.rgSpawners do local flDist = ( self.rgSpawners[i]:GetAbsOrigin() - self.vFocusPosition ):Length2D() local flWeight = math.exp( -0.5 * flDist * flDist / ( self.flFocusRadius * self.flFocusRadius ) ) self.rgSpawners[i].flFocusWeight = flWeight flTotalWeight = flTotalWeight + flWeight end return flTotalWeight end ---------------------------------------------------------------------------- function CPortalSpawnerV2:RandomlySelectSpawner( Spawners, flTotalSpawnerWeight ) if flTotalSpawnerWeight == nil then return math.random( 1, #Spawners ) end -- Ok, focus position. Means we're going to preferentially pick -- spawn positions close to the focus position. -- We've already computed weights in ComputeSpawnerWeights local flValue = RandomFloat( 0, flTotalSpawnerWeight ) local flWeight = 0 for i=1,#Spawners do flWeight = flWeight + Spawners[i].flFocusWeight if flValue < flWeight then return i end end return #Spawners end ---------------------------------------------------------------------------- function CPortalSpawnerV2:SpawnUnitsFromRandomSpawners( nSpawners ) --print( "spawning from " .. nSpawners .. " " .. self.szSpawnerName .. " spawers out of " .. #self.rgSpawners ) local hAllSpawnedUnits = {} local Spawners = nil local flTotalSpawnerWeight = self:ComputeSpawnerWeights( ) for n=1,nSpawners do if Spawners == nil or #Spawners == 0 then Spawners = deepcopy( self.rgSpawners ) end local nIndex = self:RandomlySelectSpawner( Spawners, flTotalSpawnerWeight ) local Spawner = Spawners[ nIndex ] if Spawner == nil then print ( "ERROR! CPortalSpawnerV2:SpawnUnitsFromRandomSpawners went WRONG!!!!!!!!!!!!!" ) else if flTotalSpawnerWeight ~= nil then flTotalSpawnerWeight = flTotalSpawnerWeight - Spawner.flFocusWeight end local vLocation = Spawner:GetAbsOrigin() local portalTable = { MapUnitName = "npc_aghsfort_dark_portal_v2", origin = tostring( vLocation.x ) .. " " .. tostring( vLocation.y ) .. " " .. tostring( vLocation.z ), StatusHealth = self.nPortalHealth, teamnumber = DOTA_TEAM_BADGUYS, modelscale = self.flScale, } local hPortal = CreateUnitFromTable( portalTable, vLocation ) self:SpawnPortal( hPortal ) table.insert( hAllSpawnedUnits, hPortal ) table.insert( self.rgSpawnedPortals, hPortal ) end table.remove( Spawners, nIndex ) end return hAllSpawnedUnits end ---------------------------------------------------------------------------- function CPortalSpawnerV2:RemoveSpawnedPortal( hPortal ) for i=#self.rgSpawnedPortals,1,-1 do if self.rgSpawnedPortals[i] == hPortal then table.remove( self.rgSpawnedPortals, i ) return true end end return false end ---------------------------------------------------------------------------- function CPortalSpawnerV2:OnEntityKilled( event ) local hVictim = nil if event.entindex_killed ~= nil then hVictim = EntIndexToHScript( event.entindex_killed ) end if hVictim == nil or hVictim:IsRealHero() then return end if self:RemoveSpawnedPortal( hVictim ) == true then if self.Encounter ~= nil then local hAttacker = nil if event.entindex_attacker ~= nil then hAttacker = EntIndexToHScript( event.entindex_attacker ) end self.Encounter:OnPortalV2Killed( hVictim, hAttacker, self:GetSpawnCountPerSpawnPosition() ) end self:DestroyPortal( hVictim ) end end ---------------------------------------------------------------------------- function CPortalSpawnerV2:GetPortalUnitCount( event ) return #self.rgSpawnedPortals end ---------------------------------------------------------------------------- function CPortalSpawnerV2:GetSpawnerName() return self.szSpawnerName end ---------------------------------------------------------------------------- function CPortalSpawnerV2:GetLocatorName() return self.szLocatorName end