Fixed deathblow NPCs and directly attached the spawner to each AIObject

This commit is contained in:
Obique
2018-07-01 14:07:12 -05:00
parent 696698f741
commit d8be013421
7 changed files with 44 additions and 70 deletions
@@ -4,7 +4,6 @@ import com.projectswg.common.data.encodables.tangible.Posture;
import com.projectswg.common.data.location.Location;
import com.projectswg.holocore.intents.support.global.command.QueueCommandIntent;
import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader;
import com.projectswg.holocore.resources.support.npc.spawn.Spawner;
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject;
import com.projectswg.holocore.resources.support.objects.swg.custom.NpcMode;
@@ -27,25 +26,25 @@ public class NpcCombatMode extends NpcMode {
private final AtomicReference<Location> returnLocation;
private final Collection<CreatureObject> targets;
private final AtomicInteger attackCountdown;
private final Spawner spawner;
public NpcCombatMode(Spawner spawner) {
public NpcCombatMode() {
this.returnLocation = new AtomicReference<>(null);
this.targets = new CopyOnWriteArraySet<>();
this.attackCountdown = new AtomicInteger(0);
this.spawner = spawner;
}
@Override
public void onPlayerMoveInAware(CreatureObject player, double distance) {
if (distance < spawner.getAggressiveRadius()) {
if (distance < getSpawner().getAggressiveRadius()) {
if (targets.add(player)) {
requestAssistance();
if (!isExecuting())
requestModeStart();
}
} else {
targets.remove(player);
// If out of aggressive range, and not actively fighting
if (!getAI().getDefenders().contains(player.getObjectId()))
targets.remove(player);
}
}
@@ -68,7 +67,7 @@ public class NpcCombatMode extends NpcMode {
}
syncTargets();
if (isInActiveCombat()) {
if (!targets.isEmpty()) {
performCombatAction();
queueNextLoop(1000);
} else if (isReturning()) {
@@ -84,17 +83,6 @@ public class NpcCombatMode extends NpcMode {
return getAI().isInCombat() || ret == null || getAI().getLocation().distanceTo(ret) >= RETURN_THRESHOLD;
}
private boolean isInActiveCombat() {
if (getAI().isInCombat())
return true;
for (CreatureObject target : targets) {
if (!target.hasBuff("incapWeaken") || spawner.isDeathblow())
return true;
}
return false;
}
private void performCombatAction() {
CreatureObject target = getPrimaryTarget();
if (target == null)
@@ -108,9 +96,9 @@ public class NpcCombatMode extends NpcMode {
if (target.getPosture() != Posture.INCAPACITATED && target.getPosture() != Posture.DEAD && attackCountdown.decrementAndGet() <= 0) {
attack(target, weapon);
if (getAI().getPrimaryWeapons().contains(weapon))
attackCountdown.set((int) spawner.getPrimaryWeaponSpeed());
attackCountdown.set((int) getSpawner().getPrimaryWeaponSpeed());
else
attackCountdown.set((int) spawner.getSecondaryWeaponSpeed());
attackCountdown.set((int) getSpawner().getSecondaryWeaponSpeed());
}
}
@@ -161,15 +149,8 @@ public class NpcCombatMode extends NpcMode {
@Nullable
private CreatureObject getPrimaryTarget() {
if (spawner.isDeathblow()) {
return targets.stream()
.filter(creo -> creo.getHealth() > 0) // Don't attack if they're already dead
.min(Comparator.comparingInt(CreatureObject::getHealth)).orElse(null);
}
return targets.stream()
.filter(creo -> creo.getHealth() > 0) // Don't attack if they're already dead
.filter(creo -> !creo.hasBuff("incapWeaken")) // Don't attack if it'll be a DB
.min(Comparator.comparingInt(CreatureObject::getHealth)).orElse(null);
}
@@ -184,7 +165,7 @@ public class NpcCombatMode extends NpcMode {
private void requestAssistance() {
Location myLocation = getAI().getWorldLocation();
double assistRange = spawner.getAssistRadius();
double assistRange = getSpawner().getAssistRadius();
getAI().getAware().stream()
.filter(AIObject.class::isInstance) // get nearby AI
.filter(ai -> ai.getWorldLocation().flatDistanceTo(myLocation) < assistRange) // that can assist
@@ -58,6 +58,7 @@ public class NPCCreator {
public static long createNPC(Spawner spawner) {
AIObject object = ObjectCreator.createObjectFromTemplate(spawner.getRandomIffTemplate(), AIObject.class);
object.setSpawner(spawner);
object.setLocation(behaviorLocation(spawner));
object.setObjectName(spawner.getName());
object.setLevel(spawner.getCombatLevel());
@@ -110,7 +111,7 @@ public class NPCCreator {
case ATTACKABLE:
creature.setPvpFlags(PvpFlag.ATTACKABLE);
creature.addOptionFlags(OptionFlag.HAM_BAR);
creature.setCombatMode(new NpcCombatMode(spawner));
creature.setCombatMode(new NpcCombatMode());
break;
case INVULNERABLE:
creature.addOptionFlags(OptionFlag.INVULNERABLE);
@@ -79,7 +79,8 @@ public final class Spawner {
if (spawn.getPatrolId() < 1000) {
this.waypoints = null;
} else {
this.waypoints = DataLoader.npcPatrolRoutes().getPatrolRoute(spawn.getPatrolId()).stream().map(ResolvedPatrolWaypoint::new).collect(Collectors.toList());
List<PatrolRouteWaypoint> waypoints = Objects.requireNonNull(DataLoader.npcPatrolRoutes().getPatrolRoute(spawn.getPatrolId()), "Invalid patrol route: " + spawn.getPatrolId());
this.waypoints = waypoints.stream().map(ResolvedPatrolWaypoint::new).collect(Collectors.toList());
}
this.egg = Objects.requireNonNull(egg, "egg");
this.random = new Random();
@@ -29,6 +29,7 @@ package com.projectswg.holocore.resources.support.objects.swg.custom;
import com.projectswg.common.data.encodables.tangible.Posture;
import com.projectswg.common.data.encodables.tangible.PvpFlag;
import com.projectswg.common.network.packets.swg.zone.baselines.Baseline.BaselineType;
import com.projectswg.holocore.resources.support.npc.spawn.Spawner;
import com.projectswg.holocore.resources.support.objects.ObjectCreator;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
@@ -49,6 +50,7 @@ public class AIObject extends CreatureObject {
private final EnumMap<ScheduledMode, NpcMode> modes;
private final SWGObject hiddenInventory;
private Spawner spawner;
private ScheduledThreadPool executor;
private ScheduledFuture<?> previousScheduled;
private ScheduledMode mode;
@@ -62,6 +64,7 @@ public class AIObject extends CreatureObject {
this.modes = new EnumMap<>(ScheduledMode.class);
this.hiddenInventory = ObjectCreator.createObjectFromTemplate("object/tangible/inventory/shared_character_inventory.iff");
this.spawner = null;
this.executor = null;
this.previousScheduled = null;
this.mode = ScheduledMode.DEFAULT;
@@ -105,6 +108,10 @@ public class AIObject extends CreatureObject {
return (theirPosture != Posture.INCAPACITATED || hasPvpFlag(PvpFlag.AGGRESSIVE)) && theirPosture != Posture.DEAD;
}
public void setSpawner(Spawner spawner) {
this.spawner = spawner;
}
public void addPrimaryWeapon(WeaponObject weapon) {
this.primaryWeapons.add(weapon);
weapon.systemMove(hiddenInventory);
@@ -124,6 +131,10 @@ public class AIObject extends CreatureObject {
super.setEquippedWeapon(weapon);
}
public Spawner getSpawner() {
return spawner;
}
public List<WeaponObject> getPrimaryWeapons() {
return Collections.unmodifiableList(primaryWeapons);
}
@@ -2,6 +2,7 @@ package com.projectswg.holocore.resources.support.objects.swg.custom;
import com.projectswg.common.data.location.Location;
import com.projectswg.holocore.intents.support.objects.swg.MoveObjectIntent;
import com.projectswg.holocore.resources.support.npc.spawn.Spawner;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject.ScheduledMode;
@@ -88,6 +89,10 @@ public abstract class NpcMode {
return random;
}
public final Spawner getSpawner() {
return obj.getSpawner();
}
@Nullable
public final NpcMode requestPeerMode(AIObject obj) {
return obj.getMode(mode);
@@ -7,6 +7,7 @@ import com.projectswg.holocore.intents.gameplay.combat.*;
import com.projectswg.holocore.intents.gameplay.combat.buffs.BuffIntent;
import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent;
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject;
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.Service;
@@ -91,11 +92,16 @@ public class CombatDeathblowService extends Service {
corpse.setHealth(0);
corpse.setTurnScale(0);
corpse.setMovementScale(0);
CreatureObject killer = rcdi.getKiller();
if (corpse.isPlayer() && !corpse.hasBuff("incapWeaken")) {
incapacitatePlayer(rcdi.getKiller(), corpse);
} else {
boolean deathblow = !corpse.isPlayer() || corpse.hasBuff("incapWeaken");
if (!deathblow && killer instanceof AIObject)
deathblow = ((AIObject) killer).getSpawner().isDeathblow();
if (deathblow) {
killCreature(rcdi.getKiller(), corpse);
} else {
incapacitatePlayer(rcdi.getKiller(), corpse);
}
ExitCombatIntent.broadcast(corpse);
@@ -27,7 +27,6 @@
package com.projectswg.holocore.services.support.npc.spawn;
import com.projectswg.common.data.location.Location;
import com.projectswg.holocore.intents.support.data.config.ConfigChangedIntent;
import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent;
import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent;
import com.projectswg.holocore.resources.support.data.config.ConfigFile;
@@ -52,17 +51,13 @@ import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class SpawnerService extends Service {
private final Map<Long, Spawner> spawnerMap;
private final ScheduledThreadPool executor;
public SpawnerService() {
this.spawnerMap = new HashMap<>();
this.executor = new ScheduledThreadPool(1, "spawner-service");
}
@@ -82,71 +77,45 @@ public final class SpawnerService extends Service {
return true;
}
@IntentHandler
private void handleConfigChangedIntent(ConfigChangedIntent cci) {
String newValue, oldValue;
if (cci.getChangedConfig().equals(ConfigFile.FEATURES) && cci.getKey().equals("SPAWN-EGGS-ENABLED")) {
newValue = cci.getNewValue();
oldValue = cci.getOldValue();
if (!newValue.equals(oldValue)) {
if (Boolean.valueOf(newValue) && spawnerMap.isEmpty()) { // If nothing's been spawned, create it.
loadSpawners();
} else { // If anything's been spawned, delete it.
removeSpawners();
}
}
}
}
@IntentHandler
private void handleDestroyObjectIntent(DestroyObjectIntent doi) {
SWGObject destroyedObject = doi.getObject();
if (!(destroyedObject instanceof AIObject))
return;
Spawner spawner = spawnerMap.remove(destroyedObject.getObjectId());
Spawner spawner = ((AIObject) destroyedObject).getSpawner();
if (spawner == null) {
Log.e("Killed AI object %s has no linked Spawner - it cannot respawn!", destroyedObject);
return;
}
executor.execute(spawner.getRespawnDelay() * 1000, () -> spawnNPC(spawner));
}
private void removeSpawners() {
spawnerMap.values().forEach(spawner -> new DestroyObjectIntent(spawner.getEgg()).broadcast());
spawnerMap.clear();
}
private void spawnNPC(Spawner spawner) {
spawnerMap.put(NPCCreator.createNPC(spawner), spawner);
executor.execute(spawner.getRespawnDelay() * 1000, () -> NPCCreator.createNPC(spawner));
}
private void loadSpawners() {
long startTime = StandardLog.onStartLoad("spawners");
List<DataLoader> forceNoGC = List.of(DataLoader.npcs(), DataLoader.npcWeapons(), DataLoader.npcStaticSpawns(), DataLoader.npcStats(), DataLoader.npcPatrolRoutes());
int count = 0;
for (StaticSpawnInfo spawn : DataLoader.npcStaticSpawns().getSpawns()) {
try {
spawn(spawn);
count++;
} catch (Throwable t) {
Log.e("Failed to load spawner. %s: %s", t.getClass().getName(), t.getMessage());
Log.e(t);
}
}
createPatrolRouteWaypoints();
StandardLog.onEndLoad(spawnerMap.size(), "spawners", startTime);
StandardLog.onEndLoad(count, "spawners", startTime);
}
private void spawn(StaticSpawnInfo spawn) {
Spawner spawner = new Spawner(spawn, createEgg(spawn));
for (int i = 0; i < spawner.getAmount(); i++) {
spawnNPC(spawner);
NPCCreator.createNPC(spawner);
}
}