From 4bd8aa8fdcfe6805e5ad0813afe8306466c12bb9 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 25 Oct 2016 10:20:29 +0200 Subject: [PATCH] Refactored buffs slightly --- .../objects/creature/CreatureObject.java | 17 +- .../creature/CreatureObjectSharedNP.java | 41 ++-- .../objects/player/PlayerObject.java | 5 +- .../objects/tangible/TangibleObject.java | 5 +- src/services/combat/CombatManager.java | 9 +- src/services/commands/BuffService.java | 231 ++++++++++-------- 6 files changed, 166 insertions(+), 142 deletions(-) diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index ee4be70bc..ff386e057 100644 --- a/src/resources/objects/creature/CreatureObject.java +++ b/src/resources/objects/creature/CreatureObject.java @@ -30,9 +30,11 @@ package resources.objects.creature; import java.util.ArrayList; import java.util.Collection; import java.util.Set; -import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import java.util.stream.Stream; import network.packets.swg.zone.UpdatePostureMessage; import network.packets.swg.zone.UpdatePvpStatusMessage; @@ -100,6 +102,7 @@ public class CreatureObject extends TangibleObject { removeEquipment(obj); } + @Override protected void handleSlotReplacement(SWGObject oldParent, SWGObject obj, int arrangement) { SWGObject inventory = getSlottedObject("inventory"); for (String slot : obj.getArrangement().get(arrangement-4)) { @@ -590,7 +593,7 @@ public class CreatureObject extends TangibleObject { } public void addBuff(CRC buffCrc, Buff buff) { - creo6.addBuff(buffCrc, buff, this); + creo6.putBuff(buffCrc, buff, this); } public Buff removeBuff(CRC buffCrc) { @@ -598,11 +601,11 @@ public class CreatureObject extends TangibleObject { } public boolean hasBuff(String buffName) { - return getBuffByCrc(new CRC(buffName.toLowerCase(Locale.ENGLISH))) != null; + return getBuffEntries(buffEntry -> new CRC(buffName.toLowerCase(Locale.ENGLISH)).equals(buffEntry.getKey())).count() > 0; } - public Buff getBuffByCrc(CRC buffCrc) { - return creo6.getBuffByCrc(buffCrc); + public Stream> getBuffEntries(Predicate> predicate) { + return creo6.getBuffEntries(predicate); } public void adjustBuffStackCount(CRC buffCrc, int adjustment) { @@ -613,8 +616,8 @@ public class CreatureObject extends TangibleObject { creo6.setBuffDuration(buffCrc, playTime, duration, this); } - public Map getBuffs() { - return new HashMap<>(creo6.getBuffs()); + public void forEachBuff(BiConsumer action) { + creo6.forEachBuff(action); } public boolean isVisible() { diff --git a/src/resources/objects/creature/CreatureObjectSharedNP.java b/src/resources/objects/creature/CreatureObjectSharedNP.java index 38712d135..1056edaff 100644 --- a/src/resources/objects/creature/CreatureObjectSharedNP.java +++ b/src/resources/objects/creature/CreatureObjectSharedNP.java @@ -29,6 +29,11 @@ package resources.objects.creature; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import java.util.stream.Stream; import resources.HologramColour; import resources.collections.SWGList; import resources.collections.SWGMap; @@ -457,20 +462,14 @@ class CreatureObjectSharedNP implements Persistable { maxAttributes.sendDeltaMessage(target); } } - - /** - * @return a copy of the buffs map. Removing and adding entries in this - * map will not affect the internal {@code SWGMap}. Do not edit the - * {@code Buff} values in the belief that deltas will be sent because - * they won't - this is incorrect usage. - */ - public Map getBuffs() { + + public void forEachBuff(BiConsumer action) { synchronized (buffs) { - return new HashMap<>(buffs); + buffs.forEach(action); } } - public void addBuff(CRC buffCrc, Buff buff, SWGObject target) { + public void putBuff(CRC buffCrc, Buff buff, SWGObject target) { synchronized (buffs) { if(!buffs.containsKey(buffCrc)) { buffs.put(buffCrc, buff); @@ -481,30 +480,26 @@ class CreatureObjectSharedNP implements Persistable { public Buff removeBuff(CRC buffCrc, SWGObject target) { synchronized (buffs) { - if(buffs.containsKey(buffCrc)) { - Buff removedBuff = buffs.remove(buffCrc); - - if(removedBuff != null) { - buffs.sendDeltaMessage(target); - } - - return removedBuff; + Buff removedBuff = buffs.remove(buffCrc); + + if (removedBuff != null) { + buffs.sendDeltaMessage(target); } - return null; + return removedBuff; } } - public Buff getBuffByCrc(CRC buffCrc) { + public Stream> getBuffEntries(Predicate> predicate) { synchronized (buffs) { - return buffs.get(buffCrc); + return buffs.entrySet().stream() + .filter(predicate); } } public void adjustBuffStackCount(CRC buffCrc, int adjustment, SWGObject target) { synchronized (buffs) { - Buff buff = buffs.get(buffCrc); - buff.adjustStackCount(adjustment); + buffs.get(buffCrc).adjustStackCount(adjustment); buffs.update(buffCrc, target); } } diff --git a/src/resources/objects/player/PlayerObject.java b/src/resources/objects/player/PlayerObject.java index c67f5f159..84a0fed0b 100644 --- a/src/resources/objects/player/PlayerObject.java +++ b/src/resources/objects/player/PlayerObject.java @@ -95,7 +95,10 @@ public class PlayerObject extends IntangibleObject { } public void updatePlayTime() { - int playTime = (int) (System.currentTimeMillis() / 1000); + int currentTime = (int) (System.currentTimeMillis() / 1000); + int oldPlayTime = getPlayTime(); + int playTime = oldPlayTime + (currentTime - oldPlayTime); + play3.setPlayTime(playTime); sendDelta(3, 9, playTime); } diff --git a/src/resources/objects/tangible/TangibleObject.java b/src/resources/objects/tangible/TangibleObject.java index 6bd236ccd..82387002d 100644 --- a/src/resources/objects/tangible/TangibleObject.java +++ b/src/resources/objects/tangible/TangibleObject.java @@ -259,9 +259,10 @@ public class TangibleObject extends SWGObject { if (hasPvpFlag(PvpFlag.ATTACKABLE) || otherObject.hasPvpFlag(PvpFlag.ATTACKABLE)) { return true; + } else if (otherFaction != PvpFaction.NEUTRAL && getPvpFaction() != otherFaction) { + return getPvpStatus() != PvpStatus.ONLEAVE && otherObject.getPvpStatus() != PvpStatus.ONLEAVE; } else { - return otherFaction != PvpFaction.NEUTRAL && getPvpFaction() != otherFaction - && getPvpStatus() != PvpStatus.ONLEAVE && otherObject.getPvpStatus() != PvpStatus.ONLEAVE; + return false; } } diff --git a/src/services/combat/CombatManager.java b/src/services/combat/CombatManager.java index f6d10ac61..cd432478d 100644 --- a/src/services/combat/CombatManager.java +++ b/src/services/combat/CombatManager.java @@ -239,9 +239,7 @@ public class CombatManager extends Manager { } private void handleAttack(CreatureObject source, SWGObject target, CombatCommand command) { - CombatStatus status = canPerform(source, target, command); - - if (!handleStatus(source, status)) + if (!handleStatus(source, canPerform(source, target, command))) return; CombatAction action = new CombatAction(source.getObjectId()); @@ -339,9 +337,7 @@ public class CombatManager extends Manager { corpse.setHealth(0); killer.removeDefender(corpse); - // Let's check if the killer needs to remain in-combat... if(!killer.hasDefenders()) { - // They have no active targets they're in combat with, make them exit combat exitCombat(killer); } @@ -349,16 +345,13 @@ public class CombatManager extends Manager { corpse.setTurnScale(0); corpse.setMovementScale(0); - // We need to handle this differently, depending on whether killedCreature is a player or not if(corpse.isPlayer()) { if (corpse.hasBuff("incapWeaken")) { - // Kill this sucka immediately killCreature(killer, corpse); } else { incapacitatePlayer(killer, corpse); } } else { - // This is just a plain ol' NPC. Die! killCreature(killer, corpse); } diff --git a/src/services/commands/BuffService.java b/src/services/commands/BuffService.java index 90798e801..b9ff2b960 100644 --- a/src/services/commands/BuffService.java +++ b/src/services/commands/BuffService.java @@ -29,16 +29,18 @@ package services.commands; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.DelayQueue; -import java.util.concurrent.Delayed; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import intents.BuffIntent; import intents.PlayerEventIntent; import intents.SkillModIntent; +import intents.combat.CreatureKilledIntent; +import java.util.HashSet; import java.util.Locale; +import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import network.packets.swg.zone.PlayClientEffectObjectMessage; import resources.client_info.ClientFactory; @@ -56,17 +58,16 @@ public class BuffService extends Service { // TODO allow removal of buffs with BuffData where PLAYER_REMOVABLE == 1 // TODO remove buffs on respec. Listen for respec event and remove buffs with BuffData where REMOVE_ON_RESPEC == 1 // TODO remove group buff(s) from receiver when distance between caster and receiver is 100m. Perform same check upon zoning in - // TODO on deathblow, decay buffs with BuffData where DECAY_ON_PVP_DEATH == 1 private final ScheduledExecutorService executor; - private final Map> buffRemoval; + private final Set monitored; private final Map dataMap; // All CRCs are lower-cased buff names! public BuffService() { registerForIntent(BuffIntent.TYPE); registerForIntent(PlayerEventIntent.TYPE); - buffRemoval = new HashMap<>(); + monitored = new HashSet<>(); dataMap = new HashMap<>(); executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("buff-service")); } @@ -82,8 +83,9 @@ public class BuffService extends Service { @Override public boolean start() { - // Polls buff queues every second - executor.scheduleAtFixedRate(() -> buffRemoval.values().forEach(buffQueue -> checkBuffQueue(buffQueue)), 1, 1, TimeUnit.SECONDS); + synchronized (monitored) { + executor.scheduleAtFixedRate(() -> monitored.parallelStream().forEach(creature -> checkBuffTimers(creature)), 1, 1, TimeUnit.SECONDS); + } return super.start(); } @@ -93,6 +95,7 @@ public class BuffService extends Service { switch(i.getType()) { case BuffIntent.TYPE: handleBuffIntent((BuffIntent) i); break; case PlayerEventIntent.TYPE: handlePlayerEventIntent((PlayerEventIntent) i); break; + case CreatureKilledIntent.TYPE: handleCreatureKilledIntent((CreatureKilledIntent) i); break; } } @@ -103,12 +106,9 @@ public class BuffService extends Service { return super.stop(); } - private void checkBuffQueue(DelayQueue buffQueue) { - BuffDelayed buffToRemove = buffQueue.poll(); - - if (buffToRemove != null) { - removeBuff(buffToRemove.getCreature(), buffToRemove.getBuffCrc(), true); - } + private void checkBuffTimers(CreatureObject creature) { + creature.getBuffEntries(buffEntry -> isBuffExpired(buffEntry.getValue())) + .forEach(buffEntry -> removeBuff(creature, buffEntry.getKey(), true)); } private void loadBuffs() { @@ -177,15 +177,69 @@ public class BuffService extends Service { } private void handleFirstZone(CreatureObject creature) { - creature.getBuffs().forEach((crc, buff) -> manageBuff(buff, crc, creature)); + if (hasBuffs(creature)) { + synchronized (monitored) { + monitored.add(creature); + } + } + } + + private void handleCreatureKilledIntent(CreatureKilledIntent i) { + CreatureObject corpse = i.getCorpse(); + + if (!monitored.contains(corpse)) { + return; + } + + synchronized (monitored) { + // TODO remove buffs where REMOVE_ON_DEATH == 1 + + if (corpse.isPlayer()) { + corpse.getBuffEntries(buffEntry -> isBuffDecayable(buffEntry)) + .forEach(buffEntry -> decayDuration(corpse, buffEntry)); + } + } } private void handleDisappear(CreatureObject creature) { - synchronized(buffRemoval) { - buffRemoval.remove(creature); + synchronized (monitored) { + monitored.remove(creature); + + // Buffs that aren't persistable should be removed at this point + creature.getBuffEntries(buffEntry -> isBuffPersistent(buffEntry.getKey())) + .forEach(buffEntry -> removeBuff(creature, buffEntry.getKey(), true)); } } + private boolean isBuffExpired(Buff buff) { + return buff.getDuration() >= 0 && System.currentTimeMillis() / 1000 >= buff.getEndTime(); + } + + private boolean isBuffDecayable(Entry buffEntry) { + // DECAY_ON_PVP_DEATH == 1 + return !isBuffExpired(buffEntry.getValue()) /*&& dataMap.get(buffEntry.getKey()).isDecayable()*/; + } + + private boolean isBuffPersistent(CRC crc) { +// IS_PERSISTENT == 0 + return false; + } + + private boolean hasBuffs(CreatureObject creature) { + return creature.getBuffEntries(buffEntry -> true).count() > 0; + } + + private void decayDuration(CreatureObject creature, Entry buffEntry) { + int currentDuration = buffEntry.getValue().getDuration(); + int newDuration = (int) (currentDuration * 0.10); // Duration decays with 10% + + creature.setBuffDuration(buffEntry.getKey(), creature.getPlayerObject().getPlayTime(), newDuration); + } + + private int calculatePlayTime(CreatureObject creature) { + return creature.isPlayer() ? creature.getPlayerObject().getPlayTime() : 0; + } + private void addBuff(CRC newCrc, CreatureObject receiver, CreatureObject buffer) { BuffData buffData = dataMap.get(newCrc); @@ -195,42 +249,55 @@ public class BuffService extends Service { } String groupName = buffData.getGroupName(); - Optional groupBuff = receiver.getBuffs().keySet().stream().filter(candidate -> groupName.equals(dataMap.get(candidate).groupName)).findFirst(); - int playTime = 0; + Optional> groupBuff = receiver.getBuffEntries(buffEntry -> groupName.equals(dataMap.get(buffEntry.getKey()).getGroupName())).findAny(); - if(receiver.isPlayer()) { + if (receiver.isPlayer()) { receiver.getPlayerObject().updatePlayTime(); - playTime = receiver.getPlayerObject().getPlayTime(); } - if (groupBuff.isPresent()) { // They already have a buff of this group - CRC oldCrc = groupBuff.get(); + int playTime = calculatePlayTime(receiver); + + if (groupBuff.isPresent()) { + Entry buffEntry = groupBuff.get(); + CRC oldCrc = buffEntry.getKey(); if (oldCrc.equals(newCrc)) { - int maxStackCount = buffData.getMaxStackCount(); - - if (maxStackCount > 1) { - // TODO buffs can, based on skillmods, adjust with different values - int addedStacks = 1; - int currentStacks = receiver.getBuffByCrc(oldCrc).getStackCount(); - - if (addedStacks + currentStacks <= maxStackCount) { - receiver.adjustBuffStackCount(oldCrc, addedStacks); - checkSkillMods(buffData, receiver, addedStacks); - } - } - - // We reset the duration - receiver.setBuffDuration(newCrc, playTime, (int) buffData.getDefaultDuration()); + // TODO skillmods influencing stack increment + checkStackCount(receiver, buffData, buffEntry, playTime, 1); } else if (buffData.getGroupPriority() >= dataMap.get(oldCrc).getGroupPriority()) { removeBuff(receiver, oldCrc, true); applyBuff(receiver, buffer, buffData, playTime, newCrc); } - } else { // They had no buff from this group already + } else { applyBuff(receiver, buffer, buffData, playTime, newCrc); } } + private void checkStackCount(CreatureObject receiver, BuffData buffData, Entry buffEntry, int playTime, int stackMod) { + // If it's the same buff, we need to check for stacks + int maxStackCount = buffData.getMaxStackCount(); + + if (maxStackCount < 2) { + return; + } + + int currentStacks = buffEntry.getValue().getStackCount(); + + if (stackMod + currentStacks > maxStackCount) { + stackMod = maxStackCount; + } + + CRC crc = buffEntry.getKey(); + + receiver.adjustBuffStackCount(crc, stackMod); + checkSkillMods(buffData, receiver, stackMod); + + // If the stack count was incremented, also renew the duration + if (stackMod > 0) { + receiver.setBuffDuration(crc, playTime, (int) buffData.getDefaultDuration()); + } + } + private void applyBuff(CreatureObject receiver, CreatureObject buffer, BuffData buffData, int playTime, CRC crc) { // TODO stack counts upon add/remove need to be defined on a per-buff basis due to skillmod influence. Scripts might not be a bad idea. int stackCount = 1; @@ -239,36 +306,17 @@ public class BuffService extends Service { checkSkillMods(buffData, receiver, 1); receiver.addBuff(crc, buff); - manageBuff(buff, crc, receiver); - - sendObjectEffect(buffData.getEffectFileName(), receiver, buffData.getParticleHardPoint()); - sendObjectEffect(buffData.getStanceParticle(), receiver, buffData.getParticleHardPoint()); + + sendParticleEffect(buffData.getEffectFileName(), receiver, buffData.getParticleHardPoint()); + sendParticleEffect(buffData.getStanceParticle(), receiver, buffData.getParticleHardPoint()); } - private void sendObjectEffect(String effectFileName, CreatureObject receiver, String hardPoint) { + private void sendParticleEffect(String effectFileName, CreatureObject receiver, String hardPoint) { if (!effectFileName.isEmpty()) { receiver.sendObserversAndSelf(new PlayClientEffectObjectMessage(effectFileName, hardPoint, receiver.getObjectId())); } } - private void manageBuff(Buff buff, CRC buffCrc, CreatureObject creature) { - if (dataMap.get(buffCrc).getDefaultDuration() > -1 && buff.getEndTime() <= 0) { - removeBuff(creature, buffCrc, true); - } else if(buff.getDuration() >= 0) { - // If this buff doesn't last forever or hasn't expired, we'll schedule it for removal in the future - synchronized (buffRemoval) { - DelayQueue buffQueue = buffRemoval.get(creature); - - if(buffQueue == null) { - buffQueue = new DelayQueue<>(); - buffRemoval.put(creature, buffQueue); - } - - buffQueue.add(new BuffDelayed(buff, buffCrc, creature)); - } - } - } - private void removeBuff(CreatureObject creature, CRC buffCrc, boolean expired) { BuffData buffData = dataMap.get(buffCrc); @@ -277,10 +325,20 @@ public class BuffService extends Service { return; } - Buff buff = creature.getBuffByCrc(buffCrc); + Optional> optionalEntry = creature.getBuffEntries(buffEntry -> buffEntry.getKey().equals(buffCrc)).findAny(); - if (buffData.getMaxStackCount() > 1 && !expired && buff.getStackCount() > 1) { - creature.adjustBuffStackCount(buffCrc, -1); + if (!optionalEntry.isPresent()) { + // It's hard for us to remove a buff that the creature doesn't have... + return; + } + + Entry buffEntry = optionalEntry.get(); + Buff buff = buffEntry.getValue(); + int maxStackCount = buffData.getMaxStackCount(); + int currentStacks = buff.getStackCount(); + + if (maxStackCount > 1 && !expired && currentStacks > 1) { + checkStackCount(creature, buffData, buffEntry, calculatePlayTime(creature), maxStackCount); } else { Buff removedBuff = creature.removeBuff(buffCrc); @@ -298,13 +356,20 @@ public class BuffService extends Service { CRC callbackCrc = new CRC(callback.toLowerCase(Locale.ENGLISH)); - if(dataMap.containsKey(callbackCrc)) { + if (dataMap.containsKey(callbackCrc)) { // Apply the callback buff addBuff(callbackCrc, creature, creature); } else { // Call the callback command script // TODO } + + // If they have no more expirable buffs, we can stop monitoring them + if (hasBuffs(creature)) { + synchronized (monitored) { + monitored.remove(creature); + } + } } } @@ -321,42 +386,6 @@ public class BuffService extends Service { new SkillModIntent(effectName, 0, (int) effectValue * valueFactor, creature).broadcast(); } - private static class BuffDelayed implements Delayed { - - private final Buff buff; - private final CRC buffCrc; - private final CreatureObject creature; - - private BuffDelayed(Buff buff, CRC buffCrc, CreatureObject creature) { - this.buff = buff; - this.buffCrc = buffCrc; - this.creature = creature; - } - - @Override - public int compareTo(Delayed o) { - return Integer.compare(buff.getEndTime(), ((BuffDelayed) o).buff.getEndTime()); - } - - @Override - public long getDelay(TimeUnit timeUnit) { - return timeUnit.convert(buff.getEndTime() - System.currentTimeMillis() / 1000, TimeUnit.MILLISECONDS); - } - - public Buff getBuff() { - return buff; - } - - public CRC getBuffCrc() { - return buffCrc; - } - - public CreatureObject getCreature() { - return creature; - } - - } - /** * @author Mads * Each instance of this class holds the base information