From 9d624a95644aaa58c47a378f47ef2d6d687736e2 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Sun, 6 Dec 2015 09:19:57 +0100 Subject: [PATCH] Implemented very simple buff capabilities --- src/intents/BuffIntent.java | 38 ++++ src/resources/Buff.java | 86 ++++++++ .../objects/creature/CreatureObject.java | 32 ++- .../objects/player/PlayerObject.java | 5 + src/services/commands/BuffService.java | 194 ++++++++++++++++++ src/services/galaxy/GameManager.java | 6 +- 6 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 src/intents/BuffIntent.java create mode 100644 src/resources/Buff.java create mode 100644 src/services/commands/BuffService.java diff --git a/src/intents/BuffIntent.java b/src/intents/BuffIntent.java new file mode 100644 index 000000000..46b9a4222 --- /dev/null +++ b/src/intents/BuffIntent.java @@ -0,0 +1,38 @@ +package intents; + +import resources.control.Intent; +import resources.objects.creature.CreatureObject; + +public class BuffIntent extends Intent { + + public static final String TYPE = "BuffIntent"; + + private final String buffName; + private final CreatureObject buffer, receiver; + private final boolean remove; + + public BuffIntent(String buffName, CreatureObject buffer, CreatureObject receiver, boolean remove) { + super(TYPE); + this.buffName = buffName; + this.buffer = buffer; + this.receiver = receiver; + this.remove = remove; + } + + public CreatureObject getReceiver() { + return receiver; + } + + public CreatureObject getBuffer() { + return buffer; + } + + public String getBuffName() { + return buffName; + } + + public boolean isRemove() { + return remove; + } + +} diff --git a/src/resources/Buff.java b/src/resources/Buff.java new file mode 100644 index 000000000..22566c873 --- /dev/null +++ b/src/resources/Buff.java @@ -0,0 +1,86 @@ +package resources; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.concurrent.TimeUnit; + +import resources.encodables.Encodable; +import resources.objects.creature.CreatureObject; +import resources.objects.player.PlayerObject; + +public class Buff implements Encodable, Serializable { + + private static final long serialVersionUID = 1; + + private int duration; + private final CreatureObject buffer, receiver; + private final long endTime; + private long stackCount; + private float skillMod1Value; + + public Buff(CreatureObject buffer, CreatureObject receiver, int duration, float skillMod1Value) { + this.buffer = buffer; + this.receiver = receiver; + this.duration = duration; + this.skillMod1Value = skillMod1Value; + endTime = getEndTimeFromNow(); + stackCount = 1; + } + + @Override + public byte[] encode() { + ByteBuffer data = ByteBuffer.allocate(Integer.BYTES * 2 + Float.BYTES + Long.BYTES * 2 + Short.BYTES ).order(ByteOrder.LITTLE_ENDIAN); + int sentDuration = duration; + PlayerObject playerObject = receiver.getPlayerObject(); + + if(playerObject != null) { + // TODO how will this work with NPCs? + sentDuration += playerObject.getPlayTime(); + } + + data.putInt(sentDuration); // Buff duration + time played on character + data.putFloat(skillMod1Value); // The value for skillMod #1 on the buff. Displayed on the client as skillMod1Value * stackCount. + data.putInt(duration); // Icon shadow "clock" overlay. + data.putLong(buffer.getObjectId()); // Object ID of the caster + data.putLong(stackCount); // Stack count + data.putShort((short) 0); // Unknown. Could also be two bytes. + + return data.array(); + } + + @Override + public void decode(ByteBuffer data) { + // TODO Auto-generated method stub + + } + + public int getDuration() { + return duration; + } + + public long getStackCount() { + return stackCount; + } + + /** + * Adjusts the stack count. This method doesn't send any delta. + * @param adjustment + */ + public void adjustStackCount(int adjustment) { + stackCount += adjustment; + } + + private long getEndTimeFromNow() { + return System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS); + } + + public long getRemainingTime() { + return endTime - System.currentTimeMillis(); + } + + public long getEndTime() { + return endTime; + } + +} diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index 45b40e27a..bfcd7fe54 100644 --- a/src/resources/objects/creature/CreatureObject.java +++ b/src/resources/objects/creature/CreatureObject.java @@ -36,6 +36,7 @@ import network.packets.swg.zone.UpdatePostureMessage; import network.packets.swg.zone.UpdatePvpStatusMessage; import network.packets.swg.zone.baselines.Baseline.BaselineType; import network.packets.swg.zone.object_controller.PostureUpdate; +import resources.Buff; import resources.HologramColour; import resources.Posture; import resources.PvpFlag; @@ -43,6 +44,7 @@ import resources.Race; import resources.SkillMod; import resources.collections.SWGList; import resources.collections.SWGMap; +import resources.common.CRC; import resources.encodables.player.Equipment; import resources.network.BaselineBuilder; import resources.objects.SWGObject; @@ -56,7 +58,7 @@ import utilities.Encoder.StringType; public class CreatureObject extends TangibleObject { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; private transient GroupInviterData inviterData = new GroupInviterData(0, null, "", 0); private transient long lastReserveOperation = 0; @@ -115,7 +117,7 @@ public class CreatureObject extends TangibleObject { private SWGMap skillMods = new SWGMap<>(BaselineType.CREO, 4, 3, StringType.ASCII); private SWGMap missionCriticalObjs = new SWGMap<>(BaselineType.CREO, 4, 13); private SWGMap abilities = new SWGMap<>(BaselineType.CREO, 4, 14, StringType.ASCII); - private SWGMap buffs = new SWGMap<>(BaselineType.CREO, 6, 26); // TODO: Buff structure + private SWGMap buffs = new SWGMap<>(BaselineType.CREO, 6, 26); public CreatureObject(long objectId) { @@ -628,6 +630,32 @@ public class CreatureObject extends TangibleObject { return skillMod != null ? skillMod.getValue() : 0; } + public void addBuff(String buffName, Buff buff) { + buffs.put(CRC.getCrc(buffName), buff); + buffs.sendDeltaMessage(this); + } + + public void removeBuff(String buffName) { + buffs.remove(CRC.getCrc(buffName)); + buffs.sendDeltaMessage(this); + } + + public boolean hasBuff(String buffName) { + return buffs.containsKey(CRC.getCrc(buffName)); + } + + public Buff getBuffByName(String buffName) { + return buffs.get(CRC.getCrc(buffName)); + } + + public void adjustBuffStackCount(String buffName, int adjustment) { + int buffCrc = CRC.getCrc(buffName); + Buff buff = buffs.get(buffCrc); + buff.adjustStackCount(adjustment); // Adjust the stack count + // TODO reset time remaining? + buffs.update(buffCrc, this); // Send deltas for this key. + } + public boolean isVisible() { return visible; } diff --git a/src/resources/objects/player/PlayerObject.java b/src/resources/objects/player/PlayerObject.java index 946633519..c5c9f750d 100644 --- a/src/resources/objects/player/PlayerObject.java +++ b/src/resources/objects/player/PlayerObject.java @@ -47,6 +47,7 @@ import utilities.Encoder.StringType; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; public class PlayerObject extends IntangibleObject { @@ -153,6 +154,10 @@ public class PlayerObject extends IntangibleObject { } public int getPlayTime() { + int startTime = getStartPlayTime(); + int deltaTime = (int) ((System.currentTimeMillis()) - startTime); + setPlayTime((int) TimeUnit.MILLISECONDS.toSeconds(deltaTime)); // Set new time and send delta + return playTime; } diff --git a/src/services/commands/BuffService.java b/src/services/commands/BuffService.java new file mode 100644 index 000000000..f0dd5168e --- /dev/null +++ b/src/services/commands/BuffService.java @@ -0,0 +1,194 @@ +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.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import intents.BuffIntent; +import intents.SkillModIntent; +import intents.object.ObjectCreatedIntent; +import resources.Buff; +import resources.control.Intent; +import resources.control.Service; +import resources.objects.creature.CreatureObject; +import utilities.Scripts; + +public class BuffService extends Service { + + // 100 metres is the max distance you can be away from the source of the buff before it's removed. +// private static final byte GROUP_BUFF_RANGE = 100; + + private final DelayQueue buffRemoval; + private final ExecutorService executor; + private boolean stopBuffRemover; + private final Map dataMap; + + public BuffService() { + registerForIntent(BuffIntent.TYPE); + registerForIntent(ObjectCreatedIntent.TYPE); + + buffRemoval = new DelayQueue<>(); + executor = Executors.newSingleThreadScheduledExecutor(); + dataMap = new HashMap<>(); + } + + @Override + public boolean initialize() { + // TODO read buff datatable into dataMap?? + + executor.execute(new BuffRemover()); + return super.initialize(); + } + + @Override + public void onIntentReceived(Intent i) { + switch(i.getType()) { + case BuffIntent.TYPE: + BuffIntent bi = (BuffIntent) i; + + if(bi.isRemove()) { + handleBuffRemove(bi); + } else { + handleBuffAdd(bi); + } + + break; + case ObjectCreatedIntent.TYPE: handleObjectCreation((ObjectCreatedIntent) i); break; + } + } + + @Override + public boolean terminate() { + stopBuffRemover = true; + executor.shutdown(); + + return super.terminate(); + } + + private void handleObjectCreation(ObjectCreatedIntent oci) { + if(oci.getObject() instanceof CreatureObject) { + // If this is a creature, we'll want to check if they have any buffs that we should start managing. + } + } + + private void handleBuffAdd(BuffIntent bi) { + CreatureObject receiver = bi.getReceiver(); + CreatureObject buffer = bi.getBuffer(); + int duration = 600; + Buff buff = new Buff(buffer, receiver, duration, 1337f); + String buffName = bi.getBuffName(); + + receiver.addBuff(buffName, buff); + + if(duration >= 0) { + // If this buff doesn't last forever, we'll schedule it for removal in the future + buffRemoval.put(new BuffDelayed(buff, buffName, receiver)); + } + } + + private void handleBuffRemove(BuffIntent bi) { + removeBuff(bi.getReceiver(), bi.getBuffName(), false); + } + + private void removeBuff(CreatureObject creature, String buffName, boolean expired) { + // Get the BuffData for this buff name. + BuffData buffData = dataMap.get(buffName); + Buff buff = creature.getBuffByName(buffName); + + // Check if this buff can be stacked + if(buffData.maxStackCount > 1 && !expired) { + // Check if this buff has been stacked + if(buff.getStackCount() > 1) { + // Get the adjustment from the + int adjustment = Scripts.invoke(buffName, "onStackChange", creature, buffName); + + // If it has, reduce the stack count and reset the duration. + creature.adjustBuffStackCount(buffName, adjustment); + } + } else { + // If this is a group buff, skillmods and buffs need to be removed from the group as well. + CreatureObject[] affectedCreatures = null; + + // Remove the buff from the creature(s) + creature.removeBuff(buffName); + + // TODO remove skillmods + new SkillModIntent("skillModName", 0, -10, creature); + } + } + + private class BuffRemover implements Runnable { + @Override + public void run() { + while(!stopBuffRemover) { + try { + BuffDelayed buffToRemove = buffRemoval.take(); // Block here until a buff expires + + removeBuff(buffToRemove.owner, buffToRemove.buffName, true); + } catch (InterruptedException e) { + + } + } + } + } + + private class BuffDelayed implements Delayed { + + private final Buff buff; + private final String buffName; + private final CreatureObject owner; + + private BuffDelayed(Buff buff, String buffName, CreatureObject owner) { + this.buff = buff; + this.buffName = buffName; + this.owner = owner; + } + + @Override + public int compareTo(Delayed o) { + return Long.compare(buff.getEndTime(), ((BuffDelayed) o).buff.getEndTime()); + } + + @Override + public long getDelay(TimeUnit timeUnit) { + return timeUnit.convert(buff.getRemainingTime(), TimeUnit.MILLISECONDS); + } + + } + + /** + * @author Mads + * Each instance of this class holds the base information + * for a specific buff name. Thus, we only store shared data that each {@code Buff} instance + * would otherwise have been holding. + * + * Example: Instead of each {@code Buff} instance storing the max amount of times you can + * stack it, a shared class stores that information. + * + * With many {@code Buff} instances in play, this will result in memory + * usage reduction. + */ + private class BuffData { + private final long maxStackCount; +// private final String effect1Name; +// private final int effect1Value; +// private final String effect2Name; +// private final int effect2Value; +// private final String effect3Name; +// private final int effect3Value; +// private final String effect4Name; +// private final int effect4Value; +// private final String effect5Name; +// private final int effect5Value; + + private BuffData(long maxStackCount) { + this.maxStackCount = maxStackCount; + } + + } + +} diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index 05109b13d..e01d472b4 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -28,6 +28,7 @@ package services.galaxy; import resources.control.Manager; +import services.commands.BuffService; import services.commands.CommandService; import services.faction.FactionService; import services.galaxy.terminals.TerminalService; @@ -42,6 +43,7 @@ public class GameManager extends Manager { private final TerminalService terminalManager; private final FactionService factionService; // private final GroupService groupService; + private final BuffService buffService; public GameManager() { commandService = new CommandService(); @@ -51,7 +53,8 @@ public class GameManager extends Manager { terminalManager = new TerminalService(); factionService = new FactionService(); // groupService = new GroupService(); - + buffService = new BuffService(); + addChildService(commandService); addChildService(connectionService); addChildService(suiService); @@ -59,5 +62,6 @@ public class GameManager extends Manager { addChildService(terminalManager); addChildService(factionService); // addChildService(groupService); + addChildService(buffService); } }