Merge pull request #276 from madsboddum/273

Fixed an issue where dynamic NPCs would spam around players not in God Mode #273
This commit is contained in:
Josh Larson
2020-11-28 12:54:22 -05:00
committed by GitHub

View File

@@ -7,22 +7,22 @@
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of PSWGCommon. *
* This file is part of Holocore. *
* *
* --------------------------------------------------------------------------------*
* *
* PSWGCommon is free software: you can redistribute it and/or modify *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* PSWGCommon is distributed in the hope that it will be useful, *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with PSWGCommon. If not, see <http://www.gnu.org/licenses/>. *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
***********************************************************************************/
package com.projectswg.holocore.services.support.npc.spawn;
@@ -30,6 +30,7 @@ import com.projectswg.common.data.location.Location;
import com.projectswg.common.data.location.Terrain;
import com.projectswg.holocore.intents.gameplay.world.spawn.CreateSpawnIntent;
import com.projectswg.holocore.intents.support.global.zone.PlayerTransformedIntent;
import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent;
import com.projectswg.holocore.resources.support.data.location.ClosestLocationReducer;
import com.projectswg.holocore.resources.support.data.server_info.StandardLog;
import com.projectswg.holocore.resources.support.data.server_info.loader.DynamicSpawnLoader;
@@ -39,10 +40,13 @@ import com.projectswg.holocore.resources.support.data.server_info.loader.Terrain
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcStaticSpawnLoader;
import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase;
import com.projectswg.holocore.resources.support.npc.spawn.SimpleSpawnInfo;
import com.projectswg.holocore.resources.support.npc.spawn.Spawner;
import com.projectswg.holocore.resources.support.npc.spawn.SpawnerType;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty;
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
import com.projectswg.holocore.resources.support.objects.swg.custom.AIBehavior;
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.Service;
import org.jetbrains.annotations.Nullable;
@@ -54,19 +58,21 @@ import java.util.concurrent.ThreadLocalRandom;
public class DynamicSpawnService extends Service {
private static final int SPAWN_DISTANCE_TO_PLAYER = 70; // Spawner is created 70m away from the player and NPCs are spawned around the spawner
private static final SpawnerType SPAWNER_TYPE = SpawnerType.RANDOM;
private static final int MAX_SPAWN_DISTANCE_TO_PLAYER = 110; // Spawner is created up to this amount of meters away from the player
private static final SpawnerType SPAWNER_TYPE = SpawnerType.RANDOM; // Important that this type is only used by dynamic spawns
private final DynamicSpawnLoader dynamicSpawnLoader;
private final NoSpawnZoneLoader noSpawnZoneLoader;
private final TerrainLevelLoader terrainLevelLoader;
private final long spawnsPerArea;
private final long npcSpawnChance; // Chance in % that a NPC is dynamically spawned when a player moves
private final long maxObservedNpcs; // A player should never see more than this amount of alive NPCs
public DynamicSpawnService() {
dynamicSpawnLoader = ServerData.INSTANCE.getDynamicSpawns();
noSpawnZoneLoader = ServerData.INSTANCE.getNoSpawnZones();
terrainLevelLoader = ServerData.INSTANCE.getTerrainLevels();
spawnsPerArea = PswgDatabase.INSTANCE.getConfig().getLong(this, "eggsPerArea", 4) * 3; // Amount of spawns in an area
npcSpawnChance = PswgDatabase.INSTANCE.getConfig().getLong(this, "npcSpawnChance", 1);
maxObservedNpcs = PswgDatabase.INSTANCE.getConfig().getLong(this, "maxObservedNpcs", 5);
}
@IntentHandler
@@ -76,6 +82,22 @@ public class DynamicSpawnService extends Service {
spawnNewNpcs(intent.getPlayer(), location);
}
@IntentHandler
private void handleDestroyObjectIntent(DestroyObjectIntent intent) {
SWGObject object = intent.getObject();
if (object instanceof AIObject) {
AIObject npc = (AIObject) object;
Spawner spawner = npc.getSpawner();
SWGObject egg = spawner.getEgg();
if (SPAWNER_TYPE.getObjectTemplate().equals(egg.getTemplate())) {
// If the dynamic NPC dies, don't let it respawn to prevent overcrowding an area
DestroyObjectIntent.broadcast(egg);
}
}
}
private void spawnNewNpcs(CreatureObject player, Location location) {
Terrain terrain = location.getTerrain();
Collection<DynamicSpawnLoader.DynamicSpawnInfo> spawnInfos = dynamicSpawnLoader.getSpawnInfos(terrain);
@@ -85,15 +107,39 @@ public class DynamicSpawnService extends Service {
return;
}
TerrainLevelLoader.TerrainLevelInfo terrainLevelInfo = terrainLevelLoader.getTerrainLevelInfo(terrain);
if (terrainLevelInfo == null) {
// Terrain has no level range defined, we can't spawn anything without
return;
}
// Random chance to create a spawn
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomChance = random.nextInt(0, 100);
if (randomChance > npcSpawnChance) {
return;
}
if (noSpawnZoneLoader.isInNoSpawnZone(location)) {
// The player is in a no spawn zone. Don't spawn anything.
return;
}
TerrainLevelLoader.TerrainLevelInfo terrainLevelInfo = terrainLevelLoader.getTerrainLevelInfo(terrain);
String dynamicSpawnEggTemplate = SPAWNER_TYPE.getObjectTemplate();
if (terrainLevelInfo == null) {
// Terrain has no level range defined
long dynamicSpawnsWithAliveNpcs = player.getAware().stream()
.filter(swgObject -> swgObject instanceof AIObject)
.map(swgObject -> (AIObject) swgObject)
.map(AIObject::getSpawner)
.map(Spawner::getEgg)
.map(SWGObject::getTemplate)
.filter(dynamicSpawnEggTemplate::equals)
.count();
if (dynamicSpawnsWithAliveNpcs >= maxObservedNpcs) {
// Plenty spawns near this player already - do nothing
return;
}
@@ -102,12 +148,16 @@ public class DynamicSpawnService extends Service {
if (!noSpawnZoneInfos.isEmpty()) {
Optional<Location> closestZoneOpt = noSpawnZoneInfos.stream()
.map(noSpawnZoneInfo -> Location.builder().setX(noSpawnZoneInfo.getX()).setZ(noSpawnZoneInfo.getZ())
.setTerrain(location.getTerrain()).build()).reduce(new ClosestLocationReducer(location));
.map(noSpawnZoneInfo -> Location.builder()
.setX(noSpawnZoneInfo.getX())
.setZ(noSpawnZoneInfo.getZ())
.setTerrain(location.getTerrain())
.build())
.reduce(new ClosestLocationReducer(location));
Location closestZoneLocation = closestZoneOpt.get();
boolean tooCloseToNoSpawnZone = location.isWithinFlatDistance(closestZoneLocation, SPAWN_DISTANCE_TO_PLAYER);
boolean tooCloseToNoSpawnZone = location.isWithinFlatDistance(closestZoneLocation, MAX_SPAWN_DISTANCE_TO_PLAYER);
if (tooCloseToNoSpawnZone) {
// Player is too close to a no spawn zone. Don't spawn anything.
@@ -115,22 +165,14 @@ public class DynamicSpawnService extends Service {
}
}
String eggTemplate = SPAWNER_TYPE.getObjectTemplate();
long nearbyEggs = player.getAware().stream().filter(swgObject -> eggTemplate.equals(swgObject.getTemplate())).count();
if (nearbyEggs >= spawnsPerArea) {
// Plenty spawns near this player already - do nothing
return;
}
// Spawn the egg
ThreadLocalRandom random = ThreadLocalRandom.current();
boolean usePositiveDirectionX = random.nextBoolean();
boolean usePositiveDirectionZ = random.nextBoolean();
double eggX = (usePositiveDirectionX ? SPAWN_DISTANCE_TO_PLAYER : -SPAWN_DISTANCE_TO_PLAYER) + location.getX();
double eggZ = (usePositiveDirectionZ ? SPAWN_DISTANCE_TO_PLAYER : -SPAWN_DISTANCE_TO_PLAYER) + location.getZ();
Location eggLocation = Location.builder(location).setX(eggX).setZ(eggZ)
double randomOffsetX = random.nextDouble(-MAX_SPAWN_DISTANCE_TO_PLAYER, MAX_SPAWN_DISTANCE_TO_PLAYER);
double randomOffsetZ = random.nextDouble(-MAX_SPAWN_DISTANCE_TO_PLAYER, MAX_SPAWN_DISTANCE_TO_PLAYER);
double eggX = location.getX() + randomOffsetX;
double eggZ = location.getZ() + randomOffsetZ;
Location eggLocation = Location.builder(location)
.setX(eggX)
.setZ(eggZ)
.build(); // TODO y parameter should be set and calculated based on X and Z in relevant terrain. Currently they'll spawn in the air or below ground.
int randomSpawnInfoIndex = random.nextInt(0, spawnInfos.size());
DynamicSpawnLoader.DynamicSpawnInfo spawnInfo = new ArrayList<>(spawnInfos).get(randomSpawnInfoIndex);