mirror of
https://github.com/ProjectSWGCore/Holocore.git
synced 2026-01-17 00:06:00 -05:00
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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user