mirror of
https://github.com/ProjectSWGCore/Holocore.git
synced 2026-10-07 22:13:40 -04:00
Fixed deathblow NPCs and directly attached the spawner to each AIObject
This commit is contained in:
@@ -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();
|
||||
|
||||
+11
@@ -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);
|
||||
}
|
||||
|
||||
+5
@@ -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);
|
||||
|
||||
+9
-3
@@ -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);
|
||||
|
||||
+6
-37
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user