private static bool IsAreaLocalDensityExceeded( SpawnRequest spawnRequest, ObjectSpawnPreset preset, SpawnZoneArea area, out double countToSpawnRemains) { var spawnedObjectsCount = area.WorldObjectsByPreset.Find(preset)?.Count ?? 0; var desiredObjectsCount = (int)Math.Round( area.ZoneTilesCount * spawnRequest.Density, MidpointRounding.AwayFromZero); countToSpawnRemains = desiredObjectsCount - spawnedObjectsCount; return(countToSpawnRemains <= 0); }
private static bool ServerIsAnyPlayerNearby(SpawnZoneArea area, List <Vector2Ushort> playersPositions) { // check 5 locations of the area - 4 corners and center var start = area.StartPosition; const int size = SpawnZoneAreaSize; var topLeft = start; var topRight = new Vector2Ushort((ushort)(start.X + size), start.Y); var bottomLeft = new Vector2Ushort(start.X, (ushort)(start.Y + size)); var bottomRight = new Vector2Ushort((ushort)(start.X + size), (ushort)(start.Y + size)); var center = new Vector2Ushort((ushort)(start.X + size / 2), (ushort)(start.Y + size / 2)); return(ServerIsAnyPlayerNearby(center, playersPositions) || ServerIsAnyPlayerNearby(topLeft, playersPositions) || ServerIsAnyPlayerNearby(topRight, playersPositions) || ServerIsAnyPlayerNearby(bottomLeft, playersPositions) || ServerIsAnyPlayerNearby(bottomRight, playersPositions)); }
private static bool IsAreaLocalDensityExceeded( SpawnRequest spawnRequest, ObjectSpawnPreset preset, SpawnZoneArea area, out double countToSpawnRemains) { var spawnedObjectsCount = area.WorldObjectsByPreset.Find(preset)?.Count ?? 0; var desiredObjectsCountFloat = area.ZoneTilesCount * spawnRequest.Density; var desiredObjectsCount = (int)Math.Round(desiredObjectsCountFloat, MidpointRounding.AwayFromZero); if (desiredObjectsCount == 0 && preset.SpawnAtLeastOnePerSector && preset.PresetUseSectorDensity && desiredObjectsCountFloat > 0) { // spawn at least a single object desiredObjectsCount = 1; } countToSpawnRemains = desiredObjectsCount - spawnedObjectsCount; return(countToSpawnRemains <= 0); }
/// <summary> /// Get all (already populated) areas with their objects inside the zone /// </summary> private async Task ServerFillSpawnAreasInZoneAsync( IServerZone zone, IReadOnlyDictionary <Vector2Ushort, SpawnZoneArea> areas, Func <Task> callbackYieldIfOutOfTime) { // this is a heavy method so we will try to yield every 100 objects to reduce the load const int defaultCounterToYieldValue = 100; var counterToYield = defaultCounterToYieldValue; // this check has a problem - it returns only objects strictly inside the zone, // but we also need to consider objects nearby the zone for restriction presets //await zone.PopulateStaticObjectsInZone(tempList, callbackYieldIfOutOfTime); TempListAllStaticWorldObjects.Clear(); await Api.Server.World.GetStaticWorldObjectsAsync(TempListAllStaticWorldObjects); foreach (var staticObject in TempListAllStaticWorldObjects) { await YieldIfOutOfTime(); if (staticObject.IsDestroyed) { continue; } var position = staticObject.TilePosition; var area = GetArea(position, isMobTrackingEnumeration: false); if (area is null) { continue; } var protoStaticWorldObject = staticObject.ProtoStaticWorldObject; if (!(protoStaticWorldObject is ObjectGroundItemsContainer)) { if (protoStaticWorldObject.IsIgnoredBySpawnScripts) { // we don't consider padding to certain objects such as ground decals // (though they still might affect spawn during the tiles check) continue; } // create entry for regular static object var preset = this.FindPreset(protoStaticWorldObject); if (preset != null && preset.Density > 0 && !zone.IsContainsPosition(staticObject.TilePosition)) { // this object is a part of the preset spawn list but it's not present in the zone // don't consider this object // TODO: this might cause a problem if there is a padding to this object check continue; } area.Add(preset, position); continue; } // ground container object var itemsContainer = ObjectGroundItemsContainer.GetPublicState(staticObject).ItemsContainer; foreach (var item in itemsContainer.Items) { // create entry for each item in the ground container var preset = this.FindPreset(item.ProtoItem); if (preset != null && preset.Density > 0 && !zone.IsContainsPosition(staticObject.TilePosition)) { // this object is a part of the preset spawn list but it's not present in the zone // don't consider this object continue; } area.Add(preset, position); } } var mobsTrackingManager = SpawnedMobsTrackingManagersStore.Get(this, zone); foreach (var mob in mobsTrackingManager.EnumerateAll()) { await YieldIfOutOfTime(); var position = mob.TilePosition; var area = GetArea(position, isMobTrackingEnumeration: true); area?.Add(this.FindPreset(mob.ProtoCharacter), position); } return; SpawnZoneArea GetArea(Vector2Ushort tilePosition, bool isMobTrackingEnumeration) { var zoneChunkStartPosition = SpawnZoneArea.CalculateStartPosition(tilePosition); if (areas.TryGetValue(zoneChunkStartPosition, out var area)) { return(area); } if (isMobTrackingEnumeration) { return(null); } return(null); // throw new Exception("No zone area found for " + tilePosition); //var newArea = new SpawnZoneArea(zoneChunkStartPosition, zoneChunk); //areas.Add(newArea); //return newArea; } Task YieldIfOutOfTime() { if (--counterToYield > 0) { return(Task.CompletedTask); } counterToYield = defaultCounterToYieldValue; return(callbackYieldIfOutOfTime()); } }
private static bool ServerIsCanSpawn( SpawnRequest spawnRequest, ObjectSpawnPreset preset, IReadOnlyDictionary <Vector2Ushort, SpawnZoneArea> spawnZoneAreas, Vector2Ushort spawnPosition, IPhysicsSpace physicsSpace, out SpawnZoneArea resultSpawnArea, out bool isSectorDensityExceeded) { if (ServerWorldService.GetTile(spawnPosition) .IsCliffOrSlope) { // quick discard - don't spawn on cliff or slope resultSpawnArea = null; isSectorDensityExceeded = false; return(false); } var presetPadding = preset.Padding; var presetCustomObjectPadding = preset.CustomObjectPadding; resultSpawnArea = null; var resultSpawnAreaStartPosition = SpawnZoneArea.CalculateStartPosition(spawnPosition); foreach (var area in EnumerateAdjacentZoneAreas(resultSpawnAreaStartPosition, spawnZoneAreas)) { foreach (var nearbyPreset in area.WorldObjectsByPreset) { var nearbyObjectPreset = nearbyPreset.Key; double padding; if (nearbyObjectPreset != null) { // preset found if (presetCustomObjectPadding.TryGetValue(nearbyObjectPreset, out padding)) { // use custom padding value } else { // don't have custom padding padding = Math.Max(presetPadding, nearbyObjectPreset.Padding); } } else { // preset of another object not defined in this spawn list - use default object spawn padding padding = DefaultObjectSpawnPadding; } foreach (var nearbyObjectTilePosition in nearbyPreset.Value) { var distance = spawnPosition.TileSqrDistanceTo(nearbyObjectTilePosition); // Actually using < will be more correct, but it will produce not so nice-looking result // (objects could touch each other on each side). // So we insist objects must don't even touch each other on their // left/up/right/down edges (but the diagonal corners touch is ok). if (distance <= padding * padding) { // too close isSectorDensityExceeded = false; return(false); } } } if (resultSpawnAreaStartPosition == area.StartPosition) { resultSpawnArea = area; } } var needToCheckLandClaimPresence = true; if (preset.IsContainsOnlyStaticObjects) { needToCheckLandClaimPresence = !ServerWorldService.GetTile(spawnPosition) .ProtoTile .IsRestrictingConstruction; } if (needToCheckLandClaimPresence && ServerCheckLandClaimAreaPresence(spawnPosition, preset.PaddingToLandClaimAreas)) { // the land is claimed by players resultSpawnArea = null; isSectorDensityExceeded = false; return(false); } if (preset.CustomCanSpawnCheckCallback != null && !preset.CustomCanSpawnCheckCallback(physicsSpace, spawnPosition)) { // custom spawn check failed resultSpawnArea = null; isSectorDensityExceeded = false; return(false); } if (resultSpawnArea == null) { // no area exist (will be created) isSectorDensityExceeded = false; return(true); } if (!spawnRequest.UseSectorDensity) { isSectorDensityExceeded = false; return(true); } // ensure that the area/sector density is not exceeded for this spawn preset isSectorDensityExceeded = IsAreaLocalDensityExceeded(spawnRequest, preset, resultSpawnArea, out var countToSpawnRemains); if (isSectorDensityExceeded) { // already spawned too many objects of the required type in the area return(false); } if (countToSpawnRemains < 1) { // density allows to spawn an extra object of this type with some small probability if (!RandomHelper.RollWithProbability(countToSpawnRemains)) { return(false); } } return(true); }
/// <summary> /// Get all (already populated) areas with their objects inside the zone /// </summary> private async Task ServerFillSpawnAreasInZoneAsync( IServerZone zone, IReadOnlyDictionary <Vector2Ushort, SpawnZoneArea> areas, Func <Task> callbackYieldIfOutOfTime) { // this is a heavy method so we will try to yield every 100 objects to reduce the load const int defaultCounterToYieldValue = 100; var counterToYield = defaultCounterToYieldValue; using (var tempList = Api.Shared.GetTempList <IStaticWorldObject>()) { await zone.PopulateStaticObjectsInZone(tempList, callbackYieldIfOutOfTime); foreach (var staticObject in tempList) { await YieldIfOutOfTime(); if (staticObject.IsDestroyed) { continue; } var position = staticObject.TilePosition; var area = GetArea(position, isMobTrackingEnumeration: false); var protoStaticWorldObject = staticObject.ProtoStaticWorldObject; if (!(protoStaticWorldObject is ObjectGroundItemsContainer)) { if (protoStaticWorldObject.Kind == StaticObjectKind.FloorDecal) { // we don't consider padding to decal objects // (though they still might affect spawn during tile check) continue; } // create entry for regular static object area.Add(this.FindPreset(protoStaticWorldObject), position); continue; } // ground container object var itemsContainer = ObjectGroundItemsContainer.GetPublicState(staticObject).ItemsContainer; foreach (var item in itemsContainer.Items) { // create entry for each item in the ground container area.Add(this.FindPreset(item.ProtoItem), position); } } } var mobsTrackingManager = SpawnedMobsTrackingManagersStore.Get(this, zone); foreach (var mob in mobsTrackingManager.EnumerateAll()) { await YieldIfOutOfTime(); var position = mob.TilePosition; var area = GetArea(position, isMobTrackingEnumeration: true); area?.Add(this.FindPreset(mob.ProtoCharacter), position); } return; SpawnZoneArea GetArea(Vector2Ushort tilePosition, bool isMobTrackingEnumeration) { var zoneChunkStartPosition = SpawnZoneArea.CalculateStartPosition(tilePosition); if (areas.TryGetValue(zoneChunkStartPosition, out var area)) { return(area); } if (isMobTrackingEnumeration) { return(null); } throw new Exception("No zone area found for " + tilePosition); //var newArea = new SpawnZoneArea(zoneChunkStartPosition, zoneChunk); //areas.Add(newArea); //return newArea; } Task YieldIfOutOfTime() { if (--counterToYield > 0) { return(Task.CompletedTask); } counterToYield = defaultCounterToYieldValue; return(callbackYieldIfOutOfTime()); } }