Exemple #1
0
 public SpawnRequest(
     ObjectSpawnPreset preset,
     int desiredCount,
     int currentCount,
     int spawnCount,
     double density,
     NoiseSelector tileRandomSelector,
     bool useSectorDensity)
 {
     this.Preset             = preset;
     this.DesiredCount       = desiredCount;
     this.CurrentCount       = currentCount;
     this.CountToSpawn       = spawnCount;
     this.Density            = density;
     this.TileRandomSelector = tileRandomSelector;
     this.UseSectorDensity   = useSectorDensity;
 }
Exemple #2
0
        protected override void PrepareProtoTile(Settings settings)
        {
            settings.AmbientSoundProvider = new TileAmbientSoundProvider(
                new AmbientSoundPreset(new SoundResource("Ambient/Pit")));

            settings.AddGroundTexture(
                new ProtoTileGroundTexture(
                    texture: GroundTexture1,
                    blendMaskTexture: BlendMaskTextureGeneric2Smooth,
                    noiseSelector: null));

            // add clay stones decals
            var clayStonesTextures      = ProtoTileDecal.CollectTextures("Terrain/Clay/ClayStones*");
            var clayStonesSize          = new Vector2Ushort(2, 2);
            var clayStonesNoiseSelector = new NoiseSelector(
                from: 0.5,
                to: 0.55,
                noise: new PerlinNoise(seed: 642571234,
                                       scale: 5,
                                       octaves: 4,
                                       persistance: 0.9,
                                       lacunarity: 3.9));

            for (ushort x = 0; x <= 1; x++)
            {
                for (ushort y = 0; y <= 1; y++)
                {
                    settings.AddDecal(
                        new ProtoTileDecal(clayStonesTextures,
                                           size: clayStonesSize,
                                           offset: (x, y),
                                           noiseSelector: clayStonesNoiseSelector));
                }
            }

            // add clay pit decals
            settings.AddDecal(
                new ProtoTileDecal("Terrain/Clay/ClayPit*",
                                   size: (2, 2),
                                   drawOrder: DrawOrder.GroundDecalsUnder,
                                   noiseSelector: new NoiseSelector(
                                       from: 0.95,
                                       to: 1,
                                       noise: new WhiteNoise(seed: 306721460))));
        }
        protected override void PrepareProtoTile(Settings settings)
        {
            settings.AmbientSoundProvider = new TileForestAmbientSoundProvider(
                daySoundPresetPlains: new AmbientSoundPreset(new SoundResource("Ambient/BorealPlains")),
                daySoundPresetForest: new AmbientSoundPreset(new SoundResource("Ambient/BorealForest")),
                nightSoundPresetPlains: new AmbientSoundPreset(new SoundResource("Ambient/BorealPlainsNight")),
                nightSoundPresetForest: new AmbientSoundPreset(new SoundResource("Ambient/BorealForestNight")));

            var groundTextureForest3 = new ProtoTileGroundTexture(
                texture: GroundTextureAtlas3,
                blendMaskTexture: BlendMaskTextureSprayRough,
                noiseSelector: null);

            var groundTextureForest2 = new ProtoTileGroundTexture(
                texture: GroundTexture2,
                blendMaskTexture: BlendMaskTextureSprayRough,
                noiseSelector: new NoiseSelector(from: 0.5,
                                                 to: 1,
                                                 noise: new PerlinNoise(seed: 392721487,
                                                                        scale: 15,
                                                                        octaves: 5,
                                                                        persistance: 0.6,
                                                                        lacunarity: 1.4)));

            var groundTextureForest1 = new ProtoTileGroundTexture(
                texture: GroundTexture1,
                blendMaskTexture: BlendMaskTextureSprayRough,
                noiseSelector: new NoiseSelector(from: 0.4,
                                                 to: 0.65,
                                                 noise: new PerlinNoise(seed: 498651212,
                                                                        scale: 23,
                                                                        octaves: 5,
                                                                        persistance: 0.5,
                                                                        lacunarity: 1.4)));

            settings.AddGroundTexture(groundTextureForest3);
            settings.AddGroundTexture(groundTextureForest2);
            settings.AddGroundTexture(groundTextureForest1);

            // add smallBushes decals
            var smallBushesTextures      = ProtoTileDecal.CollectTextures("Terrain/ForestBoreal/SmallBushes*");
            var smallBushesDrawOrder     = DrawOrder.GroundDecalsUnder;
            var smallBushesSize          = new Vector2Ushort(2, 2);
            var smallBushesNoiseSelector = new CombinedNoiseSelector(
                new NoiseSelector(
                    from: 0.8,
                    to: 0.97,
                    noise: new PerlinNoise(seed: 209123674,
                                           scale: 10,
                                           octaves: 3,
                                           persistance: 0.7,
                                           lacunarity: 1.5)),
                new NoiseSelector(
                    from: 0.85,
                    to: 1,
                    noise: new PerlinNoise(seed: 129308572,
                                           scale: 11,
                                           octaves: 3,
                                           persistance: 0.7,
                                           lacunarity: 3)));

            // add smallBushes decals with a four different offsets (to avoid empty space between neighbor smallBushes sprites)
            for (ushort x = 0; x <= 1; x++)
            {
                for (ushort y = 0; y <= 1; y++)
                {
                    settings.AddDecal(
                        new ProtoTileDecal(smallBushesTextures,
                                           size: smallBushesSize,
                                           offset: (x, y),
                                           drawOrder: smallBushesDrawOrder,
                                           canFlipHorizontally: false,
                                           noiseSelector: smallBushesNoiseSelector));
                }
            }

            // some random small bushes
            settings.AddDecal(
                new ProtoTileDecal(smallBushesTextures,
                                   size: smallBushesSize,
                                   drawOrder: smallBushesDrawOrder,
                                   canFlipHorizontally: false,
                                   noiseSelector: new NoiseSelector(
                                       from: 0.85,
                                       to: 1,
                                       noise: new WhiteNoise(seed: 643613342))));

            // add WhiteFlowers decals
            var whiteFlowersTextures      = ProtoTileDecal.CollectTextures("Terrain/ForestBoreal/WhiteFlowers*");
            var whiteFlowersSize          = new Vector2Ushort(2, 2);
            var whiteFlowersNoiseSelector = new NoiseSelector(
                from: 0.6,
                to: 0.65,
                noise: new PerlinNoise(seed: 25343634,
                                       scale: 8,
                                       octaves: 3,
                                       persistance: 0.6,
                                       lacunarity: 1.7));

            settings.AddDecal(
                new ProtoTileDecal(whiteFlowersTextures,
                                   size: whiteFlowersSize,
                                   noiseSelector: whiteFlowersNoiseSelector,
                                   requiredGroundTextures: new[] { groundTextureForest1, groundTextureForest3 }));

            // add the same whiteFlowers but with a little offset (to make a more dense diagonal placement)
            settings.AddDecal(
                new ProtoTileDecal(whiteFlowersTextures,
                                   size: whiteFlowersSize,
                                   offset: Vector2Ushort.One,
                                   noiseSelector: whiteFlowersNoiseSelector,
                                   requiredGroundTextures: new[] { groundTextureForest1, groundTextureForest3 }));

            // add VioletFlowers decals
            var violetFlowersTextures      = ProtoTileDecal.CollectTextures("Terrain/ForestBoreal/VioletFlowers*");
            var violetFlowersSize          = new Vector2Ushort(2, 2);
            var violetFlowersNoiseSelector = new NoiseSelector(
                from: 0.55,
                to: 0.65,
                noise: new PerlinNoise(seed: 245751427,
                                       scale: 5,
                                       octaves: 3,
                                       persistance: 0.6,
                                       lacunarity: 1.7));

            settings.AddDecal(
                new ProtoTileDecal(violetFlowersTextures,
                                   size: violetFlowersSize,
                                   noiseSelector: violetFlowersNoiseSelector));

            // add the same violetFlowers but with a little offset (to make a more dense diagonal placement)
            settings.AddDecal(
                new ProtoTileDecal(violetFlowersTextures,
                                   size: violetFlowersSize,
                                   offset: Vector2Ushort.One,
                                   noiseSelector: violetFlowersNoiseSelector));

            // add clover bush decal
            settings.AddDecal(
                new ProtoTileDecal("Terrain/ForestBoreal/CloverBush01*",
                                   size: Vector2Ushort.One,
                                   drawOrder: DrawOrder.GroundDecalsOver,
                                   hidingSetting: DecalHidingSetting.AnyObject,
                                   noiseSelector: new NoiseSelector(
                                       from: 0.985,
                                       to: 1,
                                       noise: new WhiteNoise(seed: 982356342))));

            // add fungi decal
            settings.AddDecal(
                new ProtoTileDecal("Terrain/ForestBoreal/Fungi01*",
                                   size: Vector2Ushort.One,
                                   drawOrder: DrawOrder.GroundDecalsOver,
                                   hidingSetting: DecalHidingSetting.AnyObject,
                                   noiseSelector: new NoiseSelector(
                                       from: 0.985,
                                       to: 1,
                                       noise: new WhiteNoise(seed: 474631892))));
        }
Exemple #4
0
        private async Task ServerRunSpawnTaskAsync(SpawnConfig config, IProtoTrigger trigger, IServerZone zone)
        {
            // The spawning algorithm is inspired by random scattering approach described in the article
            // www.voidinspace.com/2013/06/procedural-generation-a-vegetation-scattering-tool-for-unity3d-part-i/
            // but we use a quadtree instead of polygon for the spawn zone definition.
            if (zone.IsEmpty)
            {
                if (trigger != null)
                {
                    Logger.Important($"Cannot spawn at {zone} - the zone is empty");
                }
                else
                {
                    // the spawn script is triggered from the editor system
                    Logger.Dev($"Cannot spawn at {zone} - the zone is empty");
                }

                return;
            }

            var isInitialSpawn   = trigger == null || trigger is TriggerWorldInit;
            var yieldIfOutOfTime = isInitialSpawn
                                       ? () => Task.CompletedTask
                                       : (Func <Task>)Core.YieldIfOutOfTime;

            await yieldIfOutOfTime();

            var stopwatchTotal     = Stopwatch.StartNew();
            var zonePositionsCount = zone.PositionsCount;

            // please note: this operation is super heavy as it collects spawn areas with objects
            // that's why it's made async
            var spawnZoneAreas = await ServerSpawnZoneAreasHelper.ServerGetCachedZoneAreaAsync(zone, yieldIfOutOfTime);

            await this.ServerFillSpawnAreasInZoneAsync(zone,
                                                       spawnZoneAreas,
                                                       yieldIfOutOfTime);

            var maxSpawnFailedAttemptsInRow = isInitialSpawn
                                                  ? InitialSpawnMaxSpawnFailedAttemptsInRow
                                                  : DefaultSpawnMaxSpawnFailedAttemptsInRow;

            maxSpawnFailedAttemptsInRow = (int)Math.Min(int.MaxValue,
                                                        maxSpawnFailedAttemptsInRow * this.MaxSpawnAttempsMultiplier);

            // calculate how many objects are already available
            var spawnedObjectsCount = spawnZoneAreas
                                      .Values
                                      .SelectMany(_ => _.WorldObjectsByPreset)
                                      .GroupBy(_ => _.Key)
                                      .ToDictionary(g => g.Key, g => g.Sum(list => list.Value.Count));

            await yieldIfOutOfTime();

            // calculate how many objects we need to spawn
            using var allSpawnRequests = Api.Shared.WrapInTempList(
                      this.SpawnList
                      .Where(preset => preset.Density > 0)
                      .Select(
                          preset =>
            {
                var density      = preset.Density * config.DensityMultiplier;
                var desiredCount = (int)(density * zonePositionsCount);
                var currentCount = spawnedObjectsCount.Find(preset);
                //if (isInitialSpawn)
                //{
                var countToSpawn =
                    Math.Max(0, desiredCount - currentCount);
                //}

                // TODO: refactor this to be actually useful with local density
                //else // if this is an iteration spawn request
                //{
                //    // limit count to spawn to match the iteration limit
                //    var fractionSpawned = Math.Min(currentCount / (double)desiredCount, 1.0);
                //    var fractionRange = preset.IterationLimitFractionRange;
                //
                //    countToSpawn = (int)Math.Ceiling
                //        (desiredCount * fractionRange.GetByFraction(1 - fractionSpawned));
                //}

                // we're not using this feature
                NoiseSelector tileRandomSelector = null;
                // = this.CreateTileRandomSelector(density, preset, desiredCount);

                var useSectorDensity = preset.PresetUseSectorDensity &&
                                       ((density
                                         * SpawnZoneAreaSize
                                         * SpawnZoneAreaSize)
                                        >= SectorDensityThreshold);

                return(new SpawnRequest(preset,
                                        desiredCount,
                                        currentCount,
                                        countToSpawn,
                                        density,
                                        tileRandomSelector,
                                        useSectorDensity));
            }));

            var mobsTrackingManager = SpawnedMobsTrackingManagersStore.Get(this, zone);

            using var tempPlayersPositions = Api.Shared.WrapInTempList(
                      Server.Characters.EnumerateAllPlayerCharacters(
                          onlyOnline: true,
                          exceptSpectators: true)
                      .Select(p => p.TilePosition));
            var playersPositions = tempPlayersPositions.AsList();

            var physicsSpace = Server.World.GetPhysicsSpace();

            // stage 1: global spawn
            using var activeSpawnRequestsList = Api.Shared.WrapInTempList(
                      allSpawnRequests.Where(request => !request.UseSectorDensity &&
                                             request.CountToSpawn > 0));
            while (activeSpawnRequestsList.Count > 0)
            {
                await yieldIfOutOfTime();

                var spawnPosition = zone.GetRandomPosition(Random);
                TrySpawn(spawnPosition,
                         checkPlayersNearby: !this.CanSpawnIfPlayersNearby,
                         out _,
                         out _);
            }

            stopwatchTotal.Stop();
            var timeSpentSpawnGlobal = stopwatchTotal.Elapsed;

            await yieldIfOutOfTime();

            // stage 2: sector spawn
            stopwatchTotal.Restart();
            if (allSpawnRequests.Any(r => r.UseSectorDensity))
            {
                maxSpawnFailedAttemptsInRow = DefaultAreaSpawnAttempsCountPerPreset;
                if (isInitialSpawn)
                {
                    maxSpawnFailedAttemptsInRow *= 16;
                }

                using var areasList = Api.Shared.WrapInTempList(spawnZoneAreas.Values);
                areasList.Shuffle();

                foreach (var area in areasList)
                {
                    await yieldIfOutOfTime();

                    if (activeSpawnRequestsList.Count > 0)
                    {
                        activeSpawnRequestsList.Clear();
                    }

                    foreach (var spawnRequest in allSpawnRequests)
                    {
                        if (!spawnRequest.UseSectorDensity)
                        {
                            continue;
                        }

                        if (IsAreaLocalDensityExceeded(spawnRequest, spawnRequest.Preset, area, out _))
                        {
                            // already spawned too many objects of the required type in the area
                            continue;
                        }

                        activeSpawnRequestsList.Add(spawnRequest);
                        spawnRequest.FailedAttempts = 0;
                    }

                    if (activeSpawnRequestsList.Count == 0)
                    {
                        continue;
                    }

                    if (!this.CanSpawnIfPlayersNearby &&
                        ServerIsAnyPlayerNearby(area, playersPositions))
                    {
                        continue;
                    }

                    // make a few attempts to spawn in this area
                    var attempts = activeSpawnRequestsList.Count * DefaultAreaSpawnAttempsCountPerPreset;
                    for (var attempt = 0; attempt < attempts; attempt++)
                    {
                        await yieldIfOutOfTime();

                        var spawnPosition = area.GetRandomPositionInside(zone, Random);
                        TrySpawn(spawnPosition,
                                 checkPlayersNearby: false,
                                 out var spawnRequest,
                                 out var isSectorDensityExceeded);

                        if (isSectorDensityExceeded)
                        {
                            // sector density exceeded for this spawn request
                            activeSpawnRequestsList.Remove(spawnRequest);
                        }

                        if (activeSpawnRequestsList.Count == 0)
                        {
                            // everything for this sector has been spawned
                            break;
                        }
                    }
                }
            }

            stopwatchTotal.Stop();
            var timeSpentSpawnAreas = stopwatchTotal.Elapsed;

            var sb = new StringBuilder("Spawn script \"", capacity: 1024)
                     .Append(this.ShortId)
                     .Append("\" for zone \"")
                     .Append(zone.ProtoGameObject.ShortId)
                     .AppendLine("\": spawn completed. Stats:")
                     .Append("Time spent (including wait time distributed across frames): ")
                     .Append((timeSpentSpawnGlobal + timeSpentSpawnAreas).TotalMilliseconds.ToString("0.#"))
                     .Append("ms (global: ")
                     .Append(timeSpentSpawnGlobal.TotalMilliseconds.ToString("0.#"))
                     .Append("ms, areas: ")
                     .Append(timeSpentSpawnAreas.TotalMilliseconds.ToString("0.#"))
                     .AppendLine("ms)")
                     .AppendLine("Spawned objects:")
                     .AppendLine(
                "[format: * preset: +spawnedNowCount, currentCount/desiredCount=ratio%, currentDensity%/requiredDensity%]");

            foreach (var request in allSpawnRequests)
            {
                var currentDensity = request.CurrentCount / (double)zonePositionsCount;
                sb.Append("* ")
                .Append(request.UseSectorDensity ? "(sector) " : ("(global) "))
                .Append(request.Preset.PrintEntries())
                .AppendLine(":")
                .Append("   +")
                .Append(request.SpawnedCount)
                .Append(", ")
                .Append(request.CurrentCount)
                .Append('/')
                .Append(request.DesiredCount)
                .Append('=')
                // it's normal if we will have NaN if desired count is zero
                .Append((request.CurrentCount / (double)request.DesiredCount * 100d).ToString("0.##"))
                .Append("%, ")
                .Append((currentDensity * 100).ToString("0.###"))
                .Append("%/")
                .Append((request.Density * 100).ToString("0.###"))
                .AppendLine("%");
            }

            Logger.Important(sb);

            void TrySpawn(
                Vector2Ushort spawnPosition,
                bool checkPlayersNearby,
                out SpawnRequest spawnRequest,
                out bool isSectorDensityExceeded)
            {
                spawnRequest = activeSpawnRequestsList.TakeByRandom(Random);
                if (spawnRequest.TileRandomSelector != null &&
                    !spawnRequest.TileRandomSelector
                    .IsMatch(spawnPosition.X, spawnPosition.Y, rangeMultiplier: 1))
                {
                    // the spawn position doesn't satisfy the the random selector
                    isSectorDensityExceeded = false;
                    return;
                }

                var spawnProtoObject = spawnRequest.Preset.GetRandomObjectProto();
                IGameObjectWithProto spawnedObject = null;

                if (ServerIsCanSpawn(
                        spawnRequest,
                        spawnRequest.Preset,
                        spawnZoneAreas,
                        spawnPosition,
                        physicsSpace,
                        out var spawnZoneArea,
                        out isSectorDensityExceeded))
                {
                    if (!checkPlayersNearby ||
                        !ServerIsAnyPlayerNearby(spawnPosition, playersPositions))
                    {
                        spawnedObject = this.ServerSpawn(trigger, zone, spawnProtoObject, spawnPosition);
                    }
                }

                if (spawnedObject == null)
                {
                    // cannot spawn
                    if (++spawnRequest.FailedAttempts >= maxSpawnFailedAttemptsInRow)
                    {
                        // too many attempts failed - stop spawning this preset
                        activeSpawnRequestsList.Remove(spawnRequest);
                    }

                    return;
                }

                if (this.hasServerOnObjectSpawnedMethodOverride)
                {
                    try
                    {
                        this.ServerOnObjectSpawned(spawnedObject);
                    }
                    catch (Exception ex)
                    {
                        Logger.Exception(ex);
                    }
                }

                // spawned successfully
                // register object in zone area
                if (spawnZoneArea == null)
                {
                    throw new Exception("Should be impossible");
                }

                spawnZoneArea.Add(spawnRequest.Preset, spawnPosition);

                if (spawnProtoObject is IProtoCharacterMob)
                {
                    mobsTrackingManager.Add((ICharacter)spawnedObject);
                }

                spawnRequest.OnSpawn();
                spawnRequest.FailedAttempts = 0;
                if (spawnRequest.CountToSpawn == 0)
                {
                    activeSpawnRequestsList.Remove(spawnRequest);
                }
            }
        }
        protected override void PrepareProtoTile(Settings settings)
        {
            settings.AmbientSoundProvider = new TileAmbientSoundProvider(
                new AmbientSoundPreset(new SoundResource("Ambient/Pit")));

            settings.AddGroundTexture(
                new ProtoTileGroundTexture(
                    texture: GroundTexture1,
                    blendMaskTexture: BlendMaskTextureGeneric2Smooth,
                    noiseSelector: null));

            // add formations decals
            var formationsTextures      = ProtoTileDecal.CollectTextures("Terrain/SaltFlats/SaltFlatsFormations01*");
            var formationsSize          = new Vector2Ushort(2, 2);
            var formationsNoiseSelector = new NoiseSelector(
                from: 0.5,
                to: 0.58,
                noise: new PerlinNoise(seed: 470398528,
                                       scale: 5,
                                       octaves: 4,
                                       persistance: 0.9,
                                       lacunarity: 3.9));

            for (ushort x = 0; x <= 1; x++)
            {
                for (ushort y = 0; y <= 1; y++)
                {
                    settings.AddDecal(
                        new ProtoTileDecal(formationsTextures,
                                           size: formationsSize,
                                           offset: (x, y),
                                           noiseSelector: formationsNoiseSelector));
                }
            }

            // add pothole decals
            settings.AddDecal(
                new ProtoTileDecal("Terrain/SaltFlats/SaltFlatsPothole*",
                                   size: (2, 2),
                                   drawOrder: DrawOrder.GroundDecals,
                                   noiseSelector: new CombinedNoiseSelector(
                                       new NoiseSelector(
                                           from: 0.97,
                                           to: 1,
                                           noise: new WhiteNoise(seed: 901267462)),
                                       new NoiseSelector(
                                           from: 0.97,
                                           to: 1,
                                           noise: new WhiteNoise(seed: 68091273)))));

            // add gully decals
            settings.AddDecal(
                new ProtoTileDecal(ProtoTileDecal.CollectTextures("Terrain/SaltFlats/SaltFlatsGully*"),
                                   size: (2, 2),
                                   drawOrder: DrawOrder.GroundDecalsUnder,
                                   noiseSelector: new CombinedNoiseSelector(
                                       new NoiseSelector(
                                           from: 0.98,
                                           to: 1,
                                           noise: new WhiteNoise(seed: 981461233)),
                                       new NoiseSelector(
                                           from: 0.98,
                                           to: 1,
                                           noise: new WhiteNoise(seed: 598236742)),
                                       new NoiseSelector(
                                           from: 0.98,
                                           to: 1,
                                           noise: new WhiteNoise(seed: 98347443)),
                                       new NoiseSelector(
                                           from: 0.98,
                                           to: 1,
                                           noise: new WhiteNoise(seed: 1324234897)),
                                       new NoiseSelector(
                                           from: 0.98,
                                           to: 1,
                                           noise: new WhiteNoise(seed: 324643342)))));
        }