diff --git a/scripts/commands/combat/meleeHit.js b/scripts/commands/combat/meleeHit.js
deleted file mode 100644
index cc849b9ec..000000000
--- a/scripts/commands/combat/meleeHit.js
+++ /dev/null
@@ -1,7 +0,0 @@
-function canPerform(source, target, command) {
- return (Java.type("resources.combat.CombatStatus")).SUCCESS;
-}
-
-function doCombat(source, target, command) {
- return 0;
-}
diff --git a/scripts/commands/combat/rangedShotlightRifle.js b/scripts/commands/combat/rangedShotlightRifle.js
deleted file mode 100644
index cc849b9ec..000000000
--- a/scripts/commands/combat/rangedShotlightRifle.js
+++ /dev/null
@@ -1,7 +0,0 @@
-function canPerform(source, target, command) {
- return (Java.type("resources.combat.CombatStatus")).SUCCESS;
-}
-
-function doCombat(source, target, command) {
- return 0;
-}
diff --git a/scripts/commands/combat/rangedShotpistol.js b/scripts/commands/combat/rangedShotpistol.js
deleted file mode 100644
index cc849b9ec..000000000
--- a/scripts/commands/combat/rangedShotpistol.js
+++ /dev/null
@@ -1,7 +0,0 @@
-function canPerform(source, target, command) {
- return (Java.type("resources.combat.CombatStatus")).SUCCESS;
-}
-
-function doCombat(source, target, command) {
- return 0;
-}
diff --git a/scripts/commands/combat/rangedShotrifle.js b/scripts/commands/combat/rangedShotrifle.js
deleted file mode 100644
index cc849b9ec..000000000
--- a/scripts/commands/combat/rangedShotrifle.js
+++ /dev/null
@@ -1,7 +0,0 @@
-function canPerform(source, target, command) {
- return (Java.type("resources.combat.CombatStatus")).SUCCESS;
-}
-
-function doCombat(source, target, command) {
- return 0;
-}
diff --git a/scripts/commands/generic/debug.js b/scripts/commands/generic/debug.js
deleted file mode 100644
index 06fa34414..000000000
--- a/scripts/commands/generic/debug.js
+++ /dev/null
@@ -1,3 +0,0 @@
-function executeCommand(galacticManager, player, target, args) {
- print("Hello");
-}
\ No newline at end of file
diff --git a/scripts/objects/load_creature.js b/scripts/objects/load_creature.js
deleted file mode 100644
index 77109683c..000000000
--- a/scripts/objects/load_creature.js
+++ /dev/null
@@ -1,7 +0,0 @@
-function onLoad(creature) {
- var basicDance = "startDance+basic";
- if(!creature.hasAbility(basicDance)) {
- creature.addAbility(basicDance);
- }
- creature.addOptionFlags((Java.type("resources.objects.tangible.OptionFlag")).HAM_BAR);
-}
\ No newline at end of file
diff --git a/src/intents/BuffIntent.java b/src/intents/BuffIntent.java
new file mode 100644
index 000000000..50e9b0b90
--- /dev/null
+++ b/src/intents/BuffIntent.java
@@ -0,0 +1,65 @@
+/***********************************************************************************
+* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
+* *
+* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
+* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
+* Our goal is to create an emulator which will provide a server for players to *
+* 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 Holocore. *
+* *
+* -------------------------------------------------------------------------------- *
+* *
+* 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. *
+* *
+* 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 Holocore. If not, see . *
+* *
+***********************************************************************************/
+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/network/PacketType.java b/src/network/PacketType.java
index a07b4aade..781655060 100644
--- a/src/network/PacketType.java
+++ b/src/network/PacketType.java
@@ -120,6 +120,7 @@ public enum PacketType {
SERVER_WEATHER_MESSAGE (ServerWeatherMessage.CRC, ServerWeatherMessage.class),
PLAY_MUSIC_MESSAGE (PlayMusicMessage.CRC, PlayMusicMessage.class),
PLAY_CLIENT_EFFECT_OBJECT_MESSAGE (PlayClientEffectObjectMessage.CRC, PlayClientEffectObjectMessage.class),
+ STOP_CLIENT_EFFECT_OBJECT_BY_LABEL (StopClientEffectObjectByLabelMessage.CRC, StopClientEffectObjectByLabelMessage.class),
// Chat
CHAT_CREATE_ROOM (ChatCreateRoom.CRC, ChatCreateRoom.class),
@@ -175,7 +176,6 @@ public enum PacketType {
SPATIAL_CHAT (0x000000f4, SpatialChat.class),
NEW_TICKET_ACTIVITY_RESPONSE_MESSAGE (NewTicketActivityResponseMessage.CRC, NewTicketActivityResponseMessage.class),
ATTRIBUTE_LIST_MESSAGE (AttributeListMessage.CRC, AttributeListMessage.class),
- STOP_CLIENT_EFFECT_OBJECT_BY_LABEL (StopClientEffectObjectByLabelMessage.CRC, StopClientEffectObjectByLabelMessage.class),
OPENED_CONTAINER_MESSAGE (ClientOpenContainerMessage.CRC, ClientOpenContainerMessage.class),
// Combat
diff --git a/src/network/packets/swg/zone/spatial/StopClientEffectObjectByLabelMessage.java b/src/network/packets/swg/zone/StopClientEffectObjectByLabelMessage.java
similarity index 87%
rename from src/network/packets/swg/zone/spatial/StopClientEffectObjectByLabelMessage.java
rename to src/network/packets/swg/zone/StopClientEffectObjectByLabelMessage.java
index d4f065d32..a02f6853f 100644
--- a/src/network/packets/swg/zone/spatial/StopClientEffectObjectByLabelMessage.java
+++ b/src/network/packets/swg/zone/StopClientEffectObjectByLabelMessage.java
@@ -25,7 +25,7 @@
* along with Holocore. If not, see . *
* *
***********************************************************************************/
-package network.packets.swg.zone.spatial;
+package network.packets.swg.zone;
import network.packets.swg.SWGPacket;
@@ -35,36 +35,40 @@ public class StopClientEffectObjectByLabelMessage extends SWGPacket {
public static final int CRC = getCrc("StopClientEffectObjectByLabelMessage");
private long objectId;
- private String effect;
+ private String label;
+ private boolean softStop;
public StopClientEffectObjectByLabelMessage() {
}
- public StopClientEffectObjectByLabelMessage(long objectId, String effect) {
+ public StopClientEffectObjectByLabelMessage(long objectId, String label, boolean softStop) {
this.objectId = objectId;
- this.effect = effect;
+ this.label = label;
+ this.softStop = softStop;
}
public StopClientEffectObjectByLabelMessage(ByteBuffer data) {
decode(data);
}
+ @Override
public void decode(ByteBuffer data) {
if (!super.decode(data, CRC))
return;
objectId = getLong(data);
- effect = getAscii(data);
- getByte(data);
+ label = getAscii(data);
+ softStop = getBoolean(data);
}
+ @Override
public ByteBuffer encode() {
- ByteBuffer data = ByteBuffer.allocate(6);
+ ByteBuffer data = ByteBuffer.allocate(Short.BYTES * 2 + Integer.BYTES + Long.BYTES + Byte.BYTES + label.length());
addShort(data, 4);
addInt (data, CRC);
addLong (data, objectId);
- addAscii(data, effect);
- addByte (data, 1);
+ addAscii(data, label);
+ addBoolean(data, softStop);
return data;
}
diff --git a/src/resources/combat/AttackInfoLight.java b/src/resources/combat/HitType.java
similarity index 81%
rename from src/resources/combat/AttackInfoLight.java
rename to src/resources/combat/HitType.java
index 7e71569ed..e1ab5a767 100644
--- a/src/resources/combat/AttackInfoLight.java
+++ b/src/resources/combat/HitType.java
@@ -27,24 +27,36 @@
***********************************************************************************/
package resources.combat;
-public class AttackInfoLight {
+import java.util.HashMap;
+import java.util.Map;
+
+public enum HitType {
+ ATTACK (-1),
+ BUFF (0),
+ DEBUFF (4),
+ HEAL (5),
+ DELAY_ATTACK (6),
+ REVIVE (7);
- private int damage;
+ private static final Map HIT_TYPES = new HashMap<>();
- public AttackInfoLight() {
- this(0);
+ static {
+ for(HitType hitType : values()) {
+ HIT_TYPES.put(hitType.getNum(), hitType);
+ }
}
- public AttackInfoLight(int damage) {
- setDamage(damage);
+ private final int num;
+
+ HitType(int num) {
+ this.num = num;
}
- public int getDamage() {
- return damage;
+ public int getNum() {
+ return num;
}
- public void setDamage(int damage) {
- this.damage = damage;
+ public static HitType getHitType(int num) {
+ return HIT_TYPES.getOrDefault(num, ATTACK);
}
-
-}
+}
\ No newline at end of file
diff --git a/src/resources/commands/CombatCommand.java b/src/resources/commands/CombatCommand.java
index 28e9eebc8..3e2e82cf9 100644
--- a/src/resources/commands/CombatCommand.java
+++ b/src/resources/commands/CombatCommand.java
@@ -32,6 +32,7 @@ import java.util.Map;
import resources.combat.AttackType;
import resources.combat.DamageType;
+import resources.combat.HitType;
import resources.combat.ValidTarget;
import resources.objects.weapon.WeaponType;
@@ -49,6 +50,10 @@ public class CombatCommand extends Command {
private boolean pvpOnly;
private int attackRolls;
private float percentAddFromWeapon;
+ private int addedDamage;
+ private String buffNameTarget;
+ private String buffNameSelf;
+ private HitType hitType;
public CombatCommand(String name) {
super(name);
@@ -155,5 +160,37 @@ public class CombatCommand extends Command {
public void setPercentAddFromWeapon(float percentAddFromWeapon) {
this.percentAddFromWeapon = percentAddFromWeapon;
}
+
+ public int getAddedDamage() {
+ return addedDamage;
+ }
+ public void setAddedDamage(int addedDamage) {
+ this.addedDamage = addedDamage;
+ }
+
+ public String getBuffNameTarget() {
+ return buffNameTarget;
+ }
+
+ public void setBuffNameTarget(String buffNameTarget) {
+ this.buffNameTarget = buffNameTarget;
+ }
+
+ public String getBuffNameSelf() {
+ return buffNameSelf;
+ }
+
+ public void setBuffNameSelf(String buffNameSelf) {
+ this.buffNameSelf = buffNameSelf;
+ }
+
+ public HitType getHitType() {
+ return hitType;
+ }
+
+ public void setHitType(HitType hitType) {
+ this.hitType = hitType;
+ }
+
}
diff --git a/src/resources/commands/Command.java b/src/resources/commands/Command.java
index a7d319727..64f428b4c 100644
--- a/src/resources/commands/Command.java
+++ b/src/resources/commands/Command.java
@@ -50,7 +50,7 @@ public class Command {
private boolean callOnTarget;
// command group
// disabled
- private int maxRange;
+ private float maxRange;
private int godLevel;
// display group
private boolean combatCommand;
@@ -94,8 +94,8 @@ public class Command {
public void setTargetType(int targetType) { this.targetType = targetType; }
public boolean isCallOnTarget() { return callOnTarget; }
public void setCallOnTarget(boolean callOnTarget) { this.callOnTarget = callOnTarget; }
- public int getMaxRange() { return maxRange; }
- public void setMaxRange(int maxRange) { this.maxRange = maxRange; }
+ public float getMaxRange() { return maxRange; }
+ public void setMaxRange(float maxRange) { this.maxRange = maxRange; }
public int getGodLevel() { return godLevel; }
public void setGodLevel(int godLevel) { this.godLevel = godLevel; }
public boolean isCombatCommand() { return combatCommand; }
diff --git a/src/resources/commands/callbacks/QaToolCmdCallback.java b/src/resources/commands/callbacks/QaToolCmdCallback.java
index e677d2f2a..7724ba57f 100644
--- a/src/resources/commands/callbacks/QaToolCmdCallback.java
+++ b/src/resources/commands/callbacks/QaToolCmdCallback.java
@@ -31,10 +31,10 @@ import intents.chat.ChatBroadcastIntent;
import intents.experience.ExperienceIntent;
import intents.network.CloseConnectionIntent;
import intents.object.DestroyObjectIntent;
-import intents.object.ObjectCreatedIntent;
import intents.object.ObjectTeleportIntent;
import intents.object.CreateStaticItemIntent;
import intents.player.DeleteCharacterIntent;
+import java.io.FileNotFoundException;
import resources.Location;
import resources.Terrain;
import resources.commands.ICmdCallback;
@@ -54,7 +54,6 @@ import resources.sui.SuiInputBox;
import resources.sui.SuiListBox;
import resources.sui.SuiMessageBox;
import services.galaxy.GalacticManager;
-import services.objects.ObjectCreator;
import services.objects.ObjectManager;
import services.player.PlayerManager;
import utilities.Scripts;
@@ -97,7 +96,11 @@ public class QaToolCmdCallback implements ICmdCallback {
recoverPlayer(galacticManager.getObjectManager(), galacticManager.getPlayerManager(), player, args.substring(args.indexOf(' ') + 1));
break;
case "details":
- Scripts.invoke("commands/helper/qatool/details", "sendDetails", player, target, args.split(" "));
+ try {
+ Scripts.invoke("commands/helper/qatool/details", "sendDetails", player, target, args.split(" "));
+ } catch (FileNotFoundException ex) {
+ Log.e("QATool", "sendDetails qatool script not found!");
+ }
break;
case "xp":
if(command.length == 3)
diff --git a/src/resources/commands/callbacks/ServerCmdCallback.java b/src/resources/commands/callbacks/ServerCmdCallback.java
index 8e7b0bd24..bda89f6b8 100644
--- a/src/resources/commands/callbacks/ServerCmdCallback.java
+++ b/src/resources/commands/callbacks/ServerCmdCallback.java
@@ -39,7 +39,6 @@ import resources.sui.SuiEvent;
import resources.sui.SuiInputBox;
import resources.sui.SuiListBox;
import resources.sui.SuiMessageBox;
-import utilities.Scripts;
import services.galaxy.GalacticManager;
import java.util.Map;
@@ -50,10 +49,6 @@ public class ServerCmdCallback implements ICmdCallback {
@Override
public void execute(GalacticManager galacticManager, Player player, SWGObject target, String args) {
- if (args.startsWith("debug")) {
- debug(galacticManager, player, target, args);
- return;
- }
SuiListBox listBox = new SuiListBox(SuiButtons.OK_CANCEL, "Server Management", "Select the management function you wish to perform from the list.");
listBox.addListItem("Kick Player");
@@ -66,10 +61,6 @@ public class ServerCmdCallback implements ICmdCallback {
listBox.display(player);
}
- private void debug(GalacticManager galacticManager, Player player, SWGObject target, String args) {
- Scripts.invoke("commands/generic/debug", "executeCommand", galacticManager, player, target, args);
- }
-
private static class ServerSuiCallback implements ISuiCallback {
@Override
public void handleEvent(Player player, SWGObject actor, SuiEvent event, Map parameters) {
diff --git a/src/resources/common/CRC.java b/src/resources/common/CRC.java
index 1611af3d1..9f22f2032 100644
--- a/src/resources/common/CRC.java
+++ b/src/resources/common/CRC.java
@@ -130,6 +130,28 @@ public class CRC implements Encodable, Persistable {
public String toString() {
return str;
}
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 89 * hash + this.crc;
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ return crc == ((CRC) obj).crc;
+ }
public static int getCrc(String input) {
return getCrc(input.getBytes(StandardCharsets.UTF_8));
diff --git a/src/resources/control/Manager.java b/src/resources/control/Manager.java
index 64f2423aa..4b4b4d1c0 100644
--- a/src/resources/control/Manager.java
+++ b/src/resources/control/Manager.java
@@ -27,8 +27,8 @@
***********************************************************************************/
package resources.control;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
import resources.server_info.Log;
@@ -39,10 +39,10 @@ import resources.server_info.Log;
*/
public abstract class Manager extends Service {
- private List children;
+ private final Set children;
public Manager() {
- children = new ArrayList();
+ children = new HashSet<>();
}
/**
@@ -146,25 +146,21 @@ public abstract class Manager extends Service {
/**
* Adds a child to the manager's list of children. This creates a tree of
- * managers that allows information to propogate freely through the network
+ * services that allows information to propogate freely through the network
* in an easy way.
- * @param m the manager to add as a child.
+ * @param s the service to add as a child.
*/
public void addChildService(Service s) {
if (s == null)
throw new NullPointerException("Child service cannot be null!");
synchronized (children) {
- for (Service child : children) {
- if (s == child || s.equals(child))
- return;
- }
children.add(s);
}
}
/**
- * Removes the sub-manager from the list of children
- * @param m the sub-manager to remove
+ * Removes the service from the list of children
+ * @param s the service to remove
*/
public void removeChildService(Service s) {
if (s == null)
@@ -175,12 +171,12 @@ public abstract class Manager extends Service {
}
/**
- * Returns a copied ArrayList of the children of this manager
- * @return a copied ArrayList of the children of this manager
+ * Returns a copied {@code Set] of the children of this manager
+ * @return a copied {@code Set] of the children of this manager
*/
- public List getManagerChildren() {
+ public Set getManagerChildren() {
synchronized (children) {
- return new ArrayList(children);
+ return new HashSet<>(children);
}
}
diff --git a/src/resources/control/Service.java b/src/resources/control/Service.java
index f9e5c132b..a9c4052ed 100644
--- a/src/resources/control/Service.java
+++ b/src/resources/control/Service.java
@@ -100,7 +100,7 @@ public abstract class Service implements IntentReceiver {
* Registers for the specified intent string
* @param type the intent string
*/
- public void registerForIntent(String type) {
+ protected final void registerForIntent(String type) {
IntentManager.getInstance().registerForIntent(type, this);
}
@@ -108,13 +108,14 @@ public abstract class Service implements IntentReceiver {
* Unregisters for the specified intent string
* @param type the intent string
*/
- public void unregisterForIntent(String type) {
+ protected final void unregisterForIntent(String type) {
IntentManager.getInstance().unregisterForIntent(type, this);
}
/**
* Callback when an intent is received from the system
*/
+ @Override
public void onIntentReceived(Intent i) {
Log.w(this, "Warning: " + getClass().getSimpleName() + " did not override onIntentReceived");
}
diff --git a/src/resources/network/TCPServer.java b/src/resources/network/TCPServer.java
index b3550a5a2..e9db197b2 100644
--- a/src/resources/network/TCPServer.java
+++ b/src/resources/network/TCPServer.java
@@ -33,6 +33,7 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
@@ -248,8 +249,10 @@ public class TCPServer {
if (callback != null)
callbackExecutor.execute(() -> callback.onIncomingConnection(sc.socket()));
}
+ } catch (AsynchronousCloseException e) {
+
} catch (IOException e) {
- e.printStackTrace();
+ Log.a(this, e);
}
}
diff --git a/src/resources/objects/creature/Buff.java b/src/resources/objects/creature/Buff.java
index b4a17783b..c84648338 100644
--- a/src/resources/objects/creature/Buff.java
+++ b/src/resources/objects/creature/Buff.java
@@ -27,14 +27,13 @@
***********************************************************************************/
package resources.objects.creature;
-import java.io.Serializable;
import java.nio.ByteBuffer;
import resources.encodables.Encodable;
import resources.network.NetBuffer;
+import resources.network.NetBufferStream;
+import resources.persistable.Persistable;
-public class Buff implements Encodable, Serializable {
-
- private static final long serialVersionUID = 1;
+public class Buff implements Encodable, Persistable {
private int endTime;
private float value;
@@ -74,6 +73,24 @@ public class Buff implements Encodable, Serializable {
return data.array();
}
+ @Override
+ public void save(NetBufferStream stream) {
+ stream.addInt(endTime);
+ stream.addFloat(value);
+ stream.addInt(duration);
+ stream.addLong(bufferId);
+ stream.addInt(stackCount);
+ }
+
+ @Override
+ public void read(NetBufferStream stream) {
+ endTime = stream.getInt();
+ value = stream.getFloat();
+ duration = stream.getInt();
+ bufferId = stream.getLong();
+ stackCount = stream.getInt();
+ }
+
public int getEndTime() {
return endTime;
}
@@ -122,5 +139,5 @@ public class Buff implements Encodable, Serializable {
public String toString() {
return String.format("Buff[End=%d Value=%f Duration=%d Buffer=%d StackCount=%d]", endTime, value, duration, bufferId, stackCount);
}
-
+
}
diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java
index 4672d1e4b..fa2a5e4f9 100644
--- a/src/resources/objects/creature/CreatureObject.java
+++ b/src/resources/objects/creature/CreatureObject.java
@@ -30,6 +30,11 @@ package resources.objects.creature;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
+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;
@@ -42,6 +47,7 @@ import resources.PvpStatus;
import resources.Race;
import resources.collections.SWGList;
import resources.collections.SWGSet;
+import resources.common.CRC;
import resources.network.BaselineBuilder;
import resources.network.NetBuffer;
import resources.network.NetBufferStream;
@@ -96,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)) {
@@ -577,7 +584,7 @@ public class CreatureObject extends TangibleObject {
sendDelta(3, 18, statesBitmask);
}
- public void adjustSkillmod(String skillModName, int base, int modifier) {
+ public synchronized void adjustSkillmod(String skillModName, int base, int modifier) {
creo4.adjustSkillmod(skillModName, base, modifier, this);
}
@@ -585,6 +592,34 @@ public class CreatureObject extends TangibleObject {
return creo4.getSkillModValue(skillModName);
}
+ public void addBuff(CRC buffCrc, Buff buff) {
+ creo6.putBuff(buffCrc, buff, this);
+ }
+
+ public Buff removeBuff(CRC buffCrc) {
+ return creo6.removeBuff(buffCrc, this);
+ }
+
+ public boolean hasBuff(String buffName) {
+ return getBuffEntries(buffEntry -> new CRC(buffName.toLowerCase(Locale.ENGLISH)).equals(buffEntry.getKey())).count() > 0;
+ }
+
+ public Stream> getBuffEntries(Predicate> predicate) {
+ return creo6.getBuffEntries(predicate);
+ }
+
+ public void adjustBuffStackCount(CRC buffCrc, int adjustment) {
+ creo6.adjustBuffStackCount(buffCrc, adjustment, this);
+ }
+
+ public void setBuffDuration(CRC buffCrc, int playTime, int duration) {
+ creo6.setBuffDuration(buffCrc, playTime, duration, this);
+ }
+
+ public void forEachBuff(BiConsumer action) {
+ creo6.forEachBuff(action);
+ }
+
public boolean isVisible() {
return creo6.isVisible();
}
@@ -760,23 +795,19 @@ public class CreatureObject extends TangibleObject {
@Override
public boolean isEnemy(TangibleObject otherObject) {
- boolean enemy = super.isEnemy(otherObject);
-
- if(enemy) {
- // If these are both creatures, there's a chance both of them are players!
- if (this instanceof CreatureObject && otherObject instanceof CreatureObject) {
- CreatureObject thisCreature = (CreatureObject) this;
- CreatureObject otherCreature = (CreatureObject) otherObject;
-
- // If they're both players, both of them might be special force
- if (thisCreature.isPlayer() && otherCreature.isPlayer()) {
- // They are enemies if they're both players and members of the special force!
- return getPvpStatus() == PvpStatus.SPECIALFORCES && otherObject.getPvpStatus() == PvpStatus.SPECIALFORCES;
- }
- }
+ if (!super.isEnemy(otherObject)) {
+ return false;
}
- return enemy;
+ if (!(otherObject instanceof CreatureObject)) {
+ return false;
+ }
+
+ if (!isPlayer() || !((CreatureObject) otherObject).isPlayer()) {
+ return false;
+ }
+
+ return getPvpStatus() == PvpStatus.SPECIALFORCES && otherObject.getPvpStatus() == PvpStatus.SPECIALFORCES;
}
@Override
diff --git a/src/resources/objects/creature/CreatureObjectSharedNP.java b/src/resources/objects/creature/CreatureObjectSharedNP.java
index 006b18fbd..59e636bc5 100644
--- a/src/resources/objects/creature/CreatureObjectSharedNP.java
+++ b/src/resources/objects/creature/CreatureObjectSharedNP.java
@@ -27,6 +27,10 @@
***********************************************************************************/
package resources.objects.creature;
+import java.util.Map.Entry;
+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;
@@ -456,6 +460,56 @@ class CreatureObjectSharedNP implements Persistable {
}
}
+ public void forEachBuff(BiConsumer action) {
+ synchronized (buffs) {
+ buffs.forEach(action);
+ }
+ }
+
+ public void putBuff(CRC buffCrc, Buff buff, SWGObject target) {
+ synchronized (buffs) {
+ if(!buffs.containsKey(buffCrc)) {
+ buffs.put(buffCrc, buff);
+ buffs.sendDeltaMessage(target);
+ }
+ }
+ }
+
+ public Buff removeBuff(CRC buffCrc, SWGObject target) {
+ synchronized (buffs) {
+ Buff removedBuff = buffs.remove(buffCrc);
+
+ if (removedBuff != null) {
+ buffs.sendDeltaMessage(target);
+ }
+
+ return removedBuff;
+ }
+ }
+
+ public Stream> getBuffEntries(Predicate> predicate) {
+ synchronized (buffs) {
+ return buffs.entrySet().stream()
+ .filter(predicate);
+ }
+ }
+
+ public void adjustBuffStackCount(CRC buffCrc, int adjustment, SWGObject target) {
+ synchronized (buffs) {
+ buffs.get(buffCrc).adjustStackCount(adjustment);
+ buffs.update(buffCrc, target);
+ }
+ }
+
+ public void setBuffDuration(CRC buffCrc, int playTime, int duration, SWGObject target) {
+ synchronized (buffs) {
+ Buff buff = buffs.get(buffCrc);
+ buff.setEndTime(playTime + duration);
+ buff.setDuration(duration);
+ buffs.update(buffCrc, target);
+ }
+ }
+
private void initMaxAttributes() {
maxAttributes.add(0, 1000); // Health
maxAttributes.add(1, 0);
@@ -547,7 +601,7 @@ class CreatureObjectSharedNP implements Persistable {
@Override
public void save(NetBufferStream stream) {
- stream.addByte(1);
+ stream.addByte(2);
stream.addShort(level);
stream.addInt(levelHealthGranted);
stream.addAscii(animation);
@@ -568,6 +622,12 @@ class CreatureObjectSharedNP implements Persistable {
synchronized (maxAttributes) {
stream.addList(maxAttributes, (i) -> stream.addInt(i));
}
+ synchronized (buffs) {
+ stream.addMap(buffs, (e) -> {
+ e.getKey().save(stream);
+ e.getValue().save(stream);
+ });
+ }
}
@Override
@@ -575,6 +635,7 @@ class CreatureObjectSharedNP implements Persistable {
switch(stream.getByte()) {
case 0: readVersion0(stream); break;
case 1: readVersion1(stream); break;
+ case 2: readVersion2(stream); break;
}
}
@@ -623,4 +684,16 @@ class CreatureObjectSharedNP implements Persistable {
});
}
+ private void readVersion2(NetBufferStream stream) {
+ readVersion1(stream);
+ stream.getList((i) -> {
+ CRC crc = new CRC();
+ Buff buff = new Buff();
+
+ crc.read(stream);
+ buff.read(stream);
+ buffs.put(crc, buff);
+ });
+ }
+
}
diff --git a/src/resources/objects/player/PlayerObject.java b/src/resources/objects/player/PlayerObject.java
index 48a0328ce..ab0a5d7e7 100644
--- a/src/resources/objects/player/PlayerObject.java
+++ b/src/resources/objects/player/PlayerObject.java
@@ -93,7 +93,11 @@ public class PlayerObject extends IntangibleObject {
return play3.getPlayTime();
}
- public void setPlayTime(int playTime) {
+ public void updatePlayTime() {
+ 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 9ec24a304..82387002d 100644
--- a/src/resources/objects/tangible/TangibleObject.java
+++ b/src/resources/objects/tangible/TangibleObject.java
@@ -257,14 +257,13 @@ public class TangibleObject extends SWGObject {
// They CAN be enemies if they're not from the same faction and neither of them are neutral
PvpFaction otherFaction = otherObject.getPvpFaction();
- if (otherFaction != PvpFaction.NEUTRAL && getPvpFaction() != otherFaction) {
- if (getPvpStatus() != PvpStatus.ONLEAVE && otherObject.getPvpStatus() != PvpStatus.ONLEAVE) {
- // Both objects are not players. In this case, both just need to be combatants.
- return true;
- }
+ 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 false;
}
-
- return false;
}
public String getCurrentCity() {
diff --git a/src/resources/radial/Radials.java b/src/resources/radial/Radials.java
index fc95a16de..821173a68 100644
--- a/src/resources/radial/Radials.java
+++ b/src/resources/radial/Radials.java
@@ -27,11 +27,15 @@
***********************************************************************************/
package resources.radial;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import resources.objects.SWGObject;
import resources.player.Player;
+import resources.server_info.Log;
import utilities.Scripts;
public class Radials {
@@ -40,12 +44,20 @@ public class Radials {
public static List getRadialOptions(String script, Player player, SWGObject target, Object ... args) {
List options = new ArrayList<>();
- Scripts.invoke(SCRIPT_PREFIX + script, "getOptions", options, player, target, args);
+ try {
+ Scripts.invoke(SCRIPT_PREFIX + script, "getOptions", options, player, target, args);
+ } catch (FileNotFoundException ex) {
+ Log.w("Radials", "Couldn't retrieve radial options from %s for object %s because the script couldn't be found", SCRIPT_PREFIX + script, target);
+ }
return options;
}
public static void handleSelection(String script, Player player, SWGObject target, RadialItem selection, Object ... args) {
- Scripts.invoke(SCRIPT_PREFIX + script, "handleSelection", player, target, selection, args);
+ try {
+ Scripts.invoke(SCRIPT_PREFIX + script, "handleSelection", player, target, selection, args);
+ } catch (FileNotFoundException ex) {
+ Log.w("Radials", "Can't handle selection %s on object %s because the script couldn't be found");
+ }
}
}
diff --git a/src/services/combat/CombatManager.java b/src/services/combat/CombatManager.java
index ba25ef5f0..cd432478d 100644
--- a/src/services/combat/CombatManager.java
+++ b/src/services/combat/CombatManager.java
@@ -27,6 +27,7 @@
***********************************************************************************/
package services.combat;
+import intents.BuffIntent;
import intents.chat.ChatBroadcastIntent;
import java.awt.Color;
import java.util.HashMap;
@@ -49,7 +50,6 @@ import java.util.concurrent.Future;
import resources.Posture;
import resources.PvpFaction;
import resources.PvpFlag;
-import resources.combat.AttackInfoLight;
import resources.combat.AttackType;
import resources.combat.CombatStatus;
import resources.combat.HitLocation;
@@ -71,6 +71,8 @@ import utilities.ThreadUtilities;
public class CombatManager extends Manager {
+ private static final byte INCAP_TIMER = 10; // Amount of seconds to be incapacitated
+
private final Map inCombat;
private final Set regeneratingHealthCreatures; // Only allowed outside of combat
private final Set regeneratingActionCreatures; // Always allowed
@@ -207,28 +209,18 @@ public class CombatManager extends Manager {
}
}
- private void processChatCommand(ChatCommandIntent cci) {
- if (!cci.getCommand().isCombatCommand() || !(cci.getCommand() instanceof CombatCommand))
- return;
- CombatCommand c = (CombatCommand) cci.getCommand();
- CombatStatus status = canPerform(cci.getSource(), cci.getTarget(), c);
- if (!handleStatus(cci.getSource(), status))
- return;
- Object res = Scripts.invoke("commands/combat/"+c.getName(), "doCombat", cci.getSource(), cci.getTarget(), c);
- if (res == null) {
- handleStatus(cci.getSource(), CombatStatus.UNKNOWN);
- return;
- }
- updateCombatList(cci.getSource());
- if (cci.getTarget() instanceof CreatureObject)
- updateCombatList((CreatureObject) cci.getTarget());
- if (res instanceof Number)
- doCombat(cci.getSource(), cci.getTarget(), new AttackInfoLight(((Number) res).intValue()), c);
- else if (res instanceof AttackInfoLight)
- doCombat(cci.getSource(), cci.getTarget(), (AttackInfoLight) res, c);
- else {
- Log.w(this, "Unknown return from combat script: " + res);
+ private void processChatCommand(ChatCommandIntent intent) {
+ if (!intent.getCommand().isCombatCommand() || !(intent.getCommand() instanceof CombatCommand))
return;
+ CombatCommand c = (CombatCommand) intent.getCommand();
+ CreatureObject source = intent.getSource();
+ SWGObject target = intent.getTarget();
+
+ // TODO implement support for remaining HitTypes
+ switch (c.getHitType()) {
+ case ATTACK: handleAttack(source, target, c); break;
+ case BUFF: handleBuff(source, target, c); break;
+ default: handleStatus(source, CombatStatus.UNKNOWN); break;
}
}
@@ -246,7 +238,10 @@ public class CombatManager extends Manager {
combat.updateLastCombat();
}
- private void doCombat(CreatureObject source, SWGObject target, AttackInfoLight info, CombatCommand command) {
+ private void handleAttack(CreatureObject source, SWGObject target, CombatCommand command) {
+ if (!handleStatus(source, canPerform(source, target, command)))
+ return;
+
CombatAction action = new CombatAction(source.getObjectId());
String anim = command.getRandomAnimation(source.getEquippedWeapon().getType());
action.setActionCrc(CRC.getCrc(anim));
@@ -255,15 +250,36 @@ public class CombatManager extends Manager {
action.setCommandCrc(command.getCrc());
action.setTrail(TrailLocation.WEAPON);
action.setUseLocation(false);
- if (target instanceof CreatureObject) {
- if (command.getAttackType() == AttackType.SINGLE_TARGET)
- doCombatSingle(source, (CreatureObject) target, info, command);
- action.addDefender((CreatureObject) target, true, (byte) 0, HitLocation.HIT_LOCATION_BODY, (short) info.getDamage());
+
+ for(int i = 0; i < command.getAttackRolls(); i++) {
+ int damage = 0;
+
+ damage += calculateWeaponDamage(source, command);
+ damage += command.getAddedDamage();
+
+ if (target instanceof CreatureObject) {
+ if (command.getAttackType() == AttackType.SINGLE_TARGET)
+ doCombatSingle(source, (CreatureObject) target, damage, command);
+ action.addDefender((CreatureObject) target, true, (byte) 0, HitLocation.HIT_LOCATION_BODY, (short) damage);
+ }
}
+
source.sendObserversAndSelf(action);
}
- private void doCombatSingle(CreatureObject source, CreatureObject target, AttackInfoLight info, CombatCommand command) {
+ private void handleBuff(CreatureObject source, SWGObject target, CombatCommand combatCommand) {
+ addBuff(source, source, combatCommand.getBuffNameSelf());
+
+ // Only CreatureObjects have buffs
+ if(target instanceof CreatureObject)
+ addBuff(source, (CreatureObject) target, combatCommand.getBuffNameTarget());
+ }
+
+ private void doCombatSingle(CreatureObject source, CreatureObject target, int damage, CombatCommand command) {
+ updateCombatList(source);
+ if (target instanceof CreatureObject)
+ updateCombatList((CreatureObject) target);
+
if (!source.isInCombat())
enterCombat(source);
if (!target.isInCombat())
@@ -271,12 +287,12 @@ public class CombatManager extends Manager {
target.addDefender(source);
source.addDefender(target);
- addWeaponDamage(source, command, info);
+ addBuff(source, target, command.getBuffNameTarget()); // Add target buff
- if (target.getHealth() <= info.getDamage())
+ if (target.getHealth() <= damage)
doCreatureDeath(target, source);
else
- target.modifyHealth(-info.getDamage());
+ target.modifyHealth(-damage);
}
private void enterCombat(CreatureObject creature) {
@@ -317,47 +333,45 @@ public class CombatManager extends Manager {
}
}
- private void doCreatureDeath(CreatureObject killedCreature, CreatureObject killer) {
- killedCreature.setHealth(0);
- killer.removeDefender(killedCreature);
+ private void doCreatureDeath(CreatureObject corpse, CreatureObject killer) {
+ 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);
}
// The creature should not be able to move or turn.
- killedCreature.setTurnScale(0);
- killedCreature.setMovementScale(0);
+ corpse.setTurnScale(0);
+ corpse.setMovementScale(0);
- // We need to handle this differently, depending on whether killedCreature is a player or not
- if(killedCreature.isPlayer()) {
- // TODO account for AI deathblowing players..?
- // If it's a player, they need to be incapacitated
- incapacitatePlayer(killer, killedCreature);
+ if(corpse.isPlayer()) {
+ if (corpse.hasBuff("incapWeaken")) {
+ killCreature(killer, corpse);
+ } else {
+ incapacitatePlayer(killer, corpse);
+ }
} else {
- // This is just a plain ol' NPC. Die!
- killCreature(killer, killedCreature);
+ killCreature(killer, corpse);
}
- exitCombat(killedCreature);
+ exitCombat(corpse);
}
- private void incapacitatePlayer(CreatureObject incapacitator, CreatureObject incapacitatedPlayer) {
- int incapacitationCounter = 15;
- incapacitatedPlayer.setPosture(Posture.INCAPACITATED);
- incapacitatedPlayer.setCounter(incapacitationCounter);
+ private void incapacitatePlayer(CreatureObject incapacitator, CreatureObject incapacitated) {
+ incapacitated.setPosture(Posture.INCAPACITATED);
+ incapacitated.setCounter(INCAP_TIMER);
- Log.i(this, "%s was incapacitated", incapacitatedPlayer);
+ Log.i(this, "%s was incapacitated", incapacitated);
// Once the incapacitation counter expires, revive them.
synchronized(incapacitatedCreatures) {
- incapacitatedCreatures.put(incapacitatedPlayer, executor.schedule(() -> expireIncapacitation(incapacitatedPlayer), incapacitationCounter, TimeUnit.SECONDS));
+ incapacitatedCreatures.put(incapacitated, executor.schedule(() -> expireIncapacitation(incapacitated), INCAP_TIMER, TimeUnit.SECONDS));
}
- new ChatBroadcastIntent(incapacitator.getOwner(), new ProsePackage(new StringId("base_player", "prose_target_incap"), "TT", incapacitatedPlayer.getName())).broadcast();
- new ChatBroadcastIntent(incapacitatedPlayer.getOwner(), new ProsePackage(new StringId("base_player", "prose_victim_incap"), "TT", incapacitator.getName())).broadcast();
+ new BuffIntent("incapWeaken", incapacitator, incapacitated, false).broadcast();
+ new ChatBroadcastIntent(incapacitator.getOwner(), new ProsePackage(new StringId("base_player", "prose_target_incap"), "TT", incapacitated.getName())).broadcast();
+ new ChatBroadcastIntent(incapacitated.getOwner(), new ProsePackage(new StringId("base_player", "prose_victim_incap"), "TT", incapacitator.getName())).broadcast();
}
private void expireIncapacitation(CreatureObject incapacitatedPlayer) {
@@ -392,7 +406,7 @@ public class CombatManager extends Manager {
private void killCreature(CreatureObject killer, CreatureObject corpse) {
corpse.setPosture(Posture.DEAD);
- Log.i(this, "%s was killed", corpse);
+ Log.i(this, "%s was killed by %s", corpse, killer);
new CreatureKilledIntent(killer, corpse).broadcast();
}
@@ -422,7 +436,6 @@ public class CombatManager extends Manager {
if (incapacitationTimer != null) {
if (incapacitationTimer.cancel(false)) { // If the task is running, let them get back up
killCreature(killer, corpse);
- Log.i(this, "%s was deathblown by %s", corpse, killer);
}
} else {
// Can't happen with the current code, but in case it's ever refactored...
@@ -442,8 +455,7 @@ public class CombatManager extends Manager {
showFlyText(source, "@combat_effects:range_too_far", Scale.MEDIUM, Color.CYAN, ShowFlyText.Flag.PRIVATE);
return false;
default:
- showFlyText(source, "@combat_effects:cant_attack_fly", Scale.MEDIUM, Color.WHITE, ShowFlyText.Flag.PRIVATE);
- Log.e(this, "Character unable to attack. Player: %s Reason: %s", source.getName(), status);
+ showFlyText(source, "@combat_effects:action_failed", Scale.MEDIUM, Color.WHITE, ShowFlyText.Flag.PRIVATE);
return false;
}
}
@@ -451,71 +463,72 @@ public class CombatManager extends Manager {
private CombatStatus canPerform(CreatureObject source, SWGObject target, CombatCommand c) {
if (source.getEquippedWeapon() == null)
return CombatStatus.NO_WEAPON;
+
+ if (target == null || source.equals(target))
+ return CombatStatus.SUCCESS;
+
if (!(target instanceof TangibleObject))
return CombatStatus.INVALID_TARGET;
+
TangibleObject tangibleTarget = (TangibleObject) target;
- if(tangibleTarget.getPvpFaction() != PvpFaction.NEUTRAL) {
- if(!tangibleTarget.isEnemy(source)) {
- return CombatStatus.INVALID_TARGET;
- }
- } else if ((tangibleTarget.getPvpFlags() & PvpFlag.ATTACKABLE.getBitmask()) == 0)
+
+ if (!tangibleTarget.isEnemy(source)) {
return CombatStatus.INVALID_TARGET;
+ }
if(target instanceof CreatureObject) {
- CreatureObject creature = (CreatureObject) target;
-
- switch(creature.getPosture()) {
+ switch(((CreatureObject) target).getPosture()) {
case DEAD:
case INCAPACITATED:
return CombatStatus.INVALID_TARGET;
}
}
- CombatStatus status;
switch (c.getAttackType()) {
case AREA:
case TARGET_AREA:
- status = canPerformArea(source, c);
- break;
+ return canPerformArea(source, c);
case SINGLE_TARGET:
- status = canPerformSingle(source, target, c);
- break;
+ return canPerformSingle(source, target, c);
default:
- status = CombatStatus.UNKNOWN;
- break;
+ return CombatStatus.UNKNOWN;
}
- if (status != CombatStatus.SUCCESS)
- return status;
- status = Scripts.invoke("commands/combat/"+c.getName(), "canPerform", source, target, c);
- if (status == null) {
- return CombatStatus.UNKNOWN;
- }
- return status;
}
private CombatStatus canPerformSingle(CreatureObject source, SWGObject target, CombatCommand c) {
- if (target == null || !(target instanceof CreatureObject))
+ if (target == null || !(target instanceof TangibleObject))
return CombatStatus.NO_TARGET;
+
WeaponObject weapon = source.getEquippedWeapon();
double dist = source.getLocation().distanceTo(target.getLocation());
- if (dist > weapon.getMaxRange() || (dist > c.getMaxRange() && c.getMaxRange() > 0))
+ float commandRange = c.getMaxRange();
+ float range = commandRange > 0 ? commandRange : weapon.getMaxRange();
+
+ if (dist > range)
return CombatStatus.TOO_FAR;
+
return CombatStatus.SUCCESS;
}
private CombatStatus canPerformArea(CreatureObject source, CombatCommand c) {
- return CombatStatus.SUCCESS;
+ // TODO implement AoE
+ return CombatStatus.UNKNOWN;
}
- private void addWeaponDamage(CreatureObject source, CombatCommand command, AttackInfoLight info) {
- int abilityDamage = info.getDamage();
+ private int calculateWeaponDamage(CreatureObject source, CombatCommand command) {
WeaponObject weapon = source.getEquippedWeapon();
int minDamage = weapon.getMinDamage();
int weaponDamage = random.nextInt((weapon.getMaxDamage() - minDamage) + 1) + minDamage;
- weaponDamage *= command.getPercentAddFromWeapon();
+ return (int) (weaponDamage * command.getPercentAddFromWeapon());
+ }
+
+ private void addBuff(CreatureObject caster, CreatureObject receiver, String buffName) {
+ if (buffName.isEmpty()) {
+ return;
+ }
- info.setDamage(abilityDamage + weaponDamage);
+ new BuffIntent(buffName, caster, receiver, false).broadcast();
}
private void showFlyText(TangibleObject obj, String text, Scale scale, Color c, ShowFlyText.Flag ... flags) {
diff --git a/src/services/combat/CorpseService.java b/src/services/combat/CorpseService.java
index 0dbc89924..cba24968f 100644
--- a/src/services/combat/CorpseService.java
+++ b/src/services/combat/CorpseService.java
@@ -27,6 +27,7 @@
***********************************************************************************/
package services.combat;
+import intents.BuffIntent;
import intents.FactionIntent;
import intents.PlayerEventIntent;
import intents.chat.ChatBroadcastIntent;
@@ -379,7 +380,10 @@ public final class CorpseService extends Service {
corpse.setTurnScale(1);
corpse.setMovementScale(1);
corpse.setHealth(corpse.getMaxHealth());
- // TODO NGE: cloning debuff
+
+ BuffIntent cloningSickness = new BuffIntent("cloning_sickness", corpse, corpse, false);
+ new BuffIntent("incapweaken", corpse, corpse, true).broadcastAfterIntent(cloningSickness);
+ cloningSickness.broadcast();
}
/**
diff --git a/src/services/commands/BuffService.java b/src/services/commands/BuffService.java
new file mode 100644
index 000000000..08bb04dd5
--- /dev/null
+++ b/src/services/commands/BuffService.java
@@ -0,0 +1,563 @@
+/***********************************************************************************
+* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
+* *
+* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
+* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
+* Our goal is to create an emulator which will provide a server for players to *
+* 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 Holocore. *
+* *
+* -------------------------------------------------------------------------------- *
+* *
+* 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. *
+* *
+* 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 Holocore. If not, see . *
+* *
+***********************************************************************************/
+package services.commands;
+
+import java.util.HashMap;
+import java.util.Map;
+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.io.FileNotFoundException;
+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 java.util.logging.Level;
+import java.util.logging.Logger;
+import network.packets.swg.zone.PlayClientEffectObjectMessage;
+import resources.client_info.ClientFactory;
+import resources.client_info.visitors.DatatableData;
+import resources.common.CRC;
+import resources.control.Intent;
+import resources.control.Service;
+import resources.objects.creature.Buff;
+import resources.objects.creature.CreatureObject;
+import resources.server_info.Log;
+import utilities.Scripts;
+import utilities.ThreadUtilities;
+
+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. Skillmod1 effect name is "group"
+
+ private final ScheduledExecutorService executor;
+ private final Set monitored;
+ private final Map dataMap; // All CRCs are lower-cased buff names!
+
+ public BuffService() {
+ registerForIntent(BuffIntent.TYPE);
+ registerForIntent(PlayerEventIntent.TYPE);
+
+ monitored = new HashSet<>();
+ dataMap = new HashMap<>();
+ executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("buff-service"));
+ }
+
+ @Override
+ public boolean initialize() {
+ long startTime = System.nanoTime();
+ Log.i(this, "Loading buffs...");
+ loadBuffs();
+ Log.i(this, "Finished loading %d buffs in %fms", dataMap.size(), (System.nanoTime() - startTime) / 1E6);
+ return super.initialize();
+ }
+
+ @Override
+ public boolean start() {
+ synchronized (monitored) {
+ executor.scheduleAtFixedRate(() -> monitored.parallelStream().forEach(creature -> checkBuffTimers(creature)), 1, 1, TimeUnit.SECONDS);
+ }
+
+ return super.start();
+ }
+
+ @Override
+ public void onIntentReceived(Intent i) {
+ switch(i.getType()) {
+ case BuffIntent.TYPE: handleBuffIntent((BuffIntent) i); break;
+ case PlayerEventIntent.TYPE: handlePlayerEventIntent((PlayerEventIntent) i); break;
+ case CreatureKilledIntent.TYPE: handleCreatureKilledIntent((CreatureKilledIntent) i); break;
+ }
+ }
+
+ @Override
+ public boolean terminate() {
+ executor.shutdown();
+
+ return super.terminate();
+ }
+
+ private void checkBuffTimers(CreatureObject creature) {
+ creature.getBuffEntries(buffEntry -> isBuffExpired(buffEntry.getValue()))
+ .forEach(buffEntry -> removeBuff(creature, buffEntry.getKey(), true));
+ }
+
+ private void loadBuffs() {
+ DatatableData buffTable = (DatatableData) ClientFactory.getInfoFromFile("datatables/buff/buff.iff");
+
+ int group1 = buffTable.getColumnFromName("GROUP1");
+ int priority = buffTable.getColumnFromName("PRIORITY");
+ int maxStacks = buffTable.getColumnFromName("MAX_STACKS");
+ int effect1Param = buffTable.getColumnFromName("EFFECT1_PARAM");
+ int effect1Value = buffTable.getColumnFromName("EFFECT1_VALUE");
+ int effect2Param = buffTable.getColumnFromName("EFFECT2_PARAM");
+ int effect2Value = buffTable.getColumnFromName("EFFECT2_VALUE");
+ int effect3Param = buffTable.getColumnFromName("EFFECT3_PARAM");
+ int effect3Value = buffTable.getColumnFromName("EFFECT3_VALUE");
+ int effect4Param = buffTable.getColumnFromName("EFFECT4_PARAM");
+ int effect4Value = buffTable.getColumnFromName("EFFECT4_VALUE");
+ int effect5Param = buffTable.getColumnFromName("EFFECT5_PARAM");
+ int effect5Value = buffTable.getColumnFromName("EFFECT5_VALUE");
+ int duration = buffTable.getColumnFromName("DURATION");
+ int particle = buffTable.getColumnFromName("PARTICLE");
+ int particleHardpoint = buffTable.getColumnFromName("PARTICLE_HARDPOINT");
+ int stanceParticle = buffTable.getColumnFromName("STANCE_PARTICLE");
+ int callback = buffTable.getColumnFromName("CALLBACK");
+ int persistent = buffTable.getColumnFromName("IS_PERSISTENT");
+ int removeOnDeath = buffTable.getColumnFromName("REMOVE_ON_DEATH");
+ int decayOnPvpDeath = buffTable.getColumnFromName("DECAY_ON_PVP_DEATH");
+
+ for(int row = 0; row < buffTable.getRowCount(); row++) {
+ dataMap.put(new CRC(((String) buffTable.getCell(row, 0)).toLowerCase(Locale.ENGLISH)), new BuffData(
+ (String) buffTable.getCell(row, group1),
+ (int) buffTable.getCell(row, priority),
+ (int) buffTable.getCell(row, maxStacks),
+ (String) buffTable.getCell(row, effect1Param),
+ (float) buffTable.getCell(row, effect1Value),
+ (String) buffTable.getCell(row, effect2Param),
+ (float) buffTable.getCell(row, effect2Value),
+ (String) buffTable.getCell(row, effect3Param),
+ (float) buffTable.getCell(row, effect3Value),
+ (String) buffTable.getCell(row, effect4Param),
+ (float) buffTable.getCell(row, effect4Value),
+ (String) buffTable.getCell(row, effect5Param),
+ (float) buffTable.getCell(row, effect5Value),
+ (float) buffTable.getCell(row, duration),
+ (String) buffTable.getCell(row, particle),
+ (String) buffTable.getCell(row, particleHardpoint),
+ (String) buffTable.getCell(row, stanceParticle),
+ (String) buffTable.getCell(row, callback),
+ (int) buffTable.getCell(row, persistent) == 1,
+ (int) buffTable.getCell(row, removeOnDeath) == 1,
+ (int) buffTable.getCell(row, decayOnPvpDeath) == 1
+ ));
+ }
+ }
+
+ private void handleBuffIntent(BuffIntent i) {
+ CRC buffCrc = new CRC(i.getBuffName().toLowerCase(Locale.ENGLISH));
+
+ if (i.isRemove()) {
+ removeBuff(i.getReceiver(), buffCrc, false);
+ } else {
+ addBuff(buffCrc, i.getReceiver(), i.getBuffer());
+ }
+ }
+
+ private void handlePlayerEventIntent(PlayerEventIntent pei) {
+ CreatureObject creature = pei.getPlayer().getCreatureObject();
+
+ switch(pei.getEvent()) {
+ case PE_FIRST_ZONE: handleFirstZone(creature); break;
+ case PE_DISAPPEAR: handleDisappear(creature); break;
+ }
+ }
+
+ private void handleFirstZone(CreatureObject creature) {
+ if (hasBuffs(creature)) {
+ synchronized (monitored) {
+ if (!monitored.add(creature)) {
+ Log.w(this, "Redundant addition %s to monitored creatures", creature);
+ }
+ }
+ }
+ }
+
+ private void handleCreatureKilledIntent(CreatureKilledIntent i) {
+ CreatureObject corpse = i.getCorpse();
+
+ synchronized (monitored) {
+ if (!monitored.contains(corpse)) {
+ return;
+ }
+
+ if (i.getKiller().isPlayer()) {
+ // PvP death - decay durations of certain buffs
+ corpse.getBuffEntries(buffEntry -> isBuffDecayable(buffEntry))
+ .forEach(buffEntry -> decayDuration(corpse, buffEntry));
+ } else {
+ // PvE death - remove certain buffs
+ corpse.getBuffEntries(buffEntry -> isBuffRemovedOnDeath(buffEntry.getKey()))
+ .forEach(buffEntry -> removeBuff(corpse, buffEntry.getKey(), true));
+ }
+ }
+ }
+
+ private void handleDisappear(CreatureObject creature) {
+ synchronized (monitored) {
+ if (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) {
+ return !isBuffExpired(buffEntry.getValue()) &&
+ dataMap.get(buffEntry.getKey()).isDecayOnPvpDeath();
+ }
+
+ private boolean isBuffPersistent(CRC crc) {
+ return dataMap.get(crc).isPersistent();
+ }
+
+ private boolean isBuffRemovedOnDeath(CRC crc) {
+ return dataMap.get(crc).isRemovedOnDeath();
+ }
+
+ 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);
+
+ if (buffData == null) {
+ Log.e(this, "Could not add %s to %s - unrecognised buff name", newCrc, receiver);
+ return;
+ }
+
+ String groupName = buffData.getGroupName();
+ Optional> groupBuff = receiver.getBuffEntries(buffEntry -> groupName.equals(dataMap.get(buffEntry.getKey()).getGroupName())).findAny();
+
+ if (receiver.isPlayer()) {
+ receiver.getPlayerObject().updatePlayTime();
+ }
+
+ int playTime = calculatePlayTime(receiver);
+
+ if (groupBuff.isPresent()) {
+ Entry buffEntry = groupBuff.get();
+ CRC oldCrc = buffEntry.getKey();
+
+ if (oldCrc.equals(newCrc)) {
+ if (buffData.getDefaultDuration() == -1) {
+ removeBuff(receiver, oldCrc, true);
+ } else {
+ // 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 {
+ 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;
+ int buffDuration = (int) buffData.getDefaultDuration();
+ Buff buff = new Buff(playTime + buffDuration, buffData.getEffect1Value(), buffDuration, buffer.getObjectId(), stackCount);
+
+ checkSkillMods(buffData, receiver, 1);
+ receiver.addBuff(crc, buff);
+
+ sendParticleEffect(buffData.getEffectFileName(), receiver, buffData.getParticleHardPoint());
+ sendParticleEffect(buffData.getStanceParticle(), receiver, buffData.getParticleHardPoint());
+ }
+
+ private void sendParticleEffect(String effectFileName, CreatureObject receiver, String hardPoint) {
+ if (!effectFileName.isEmpty()) {
+ receiver.sendObserversAndSelf(new PlayClientEffectObjectMessage(effectFileName, hardPoint, receiver.getObjectId()));
+ }
+ }
+
+ private void removeBuff(CreatureObject creature, CRC buffCrc, boolean expired) {
+ BuffData buffData = dataMap.get(buffCrc);
+
+ if (buffData == null) {
+ Log.e(this, "Could not remove %s from %s - buff data for it does not exist", buffCrc, creature);
+ return;
+ }
+
+ Optional> optionalEntry = creature.getBuffEntries(buffEntry -> buffEntry.getKey().equals(buffCrc)).findAny();
+
+ if (!optionalEntry.isPresent()) {
+ 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);
+
+ if (removedBuff == null) {
+ return;
+ }
+
+ checkSkillMods(buffData, creature, -removedBuff.getStackCount());
+ checkCallback(buffCrc, buffData, creature);
+
+ // If they have no more expirable buffs, we can stop monitoring them
+ if (hasBuffs(creature)) {
+ synchronized (monitored) {
+ monitored.remove(creature);
+ }
+ }
+ }
+ }
+
+ private void checkCallback(CRC crc, BuffData buffData, CreatureObject creature) {
+ String callback = buffData.getCallback();
+
+ if (callback.equals("none")) {
+ return;
+ }
+
+ CRC callbackCrc = new CRC(callback.toLowerCase(Locale.ENGLISH));
+
+ if (dataMap.containsKey(callbackCrc)) {
+ addBuff(callbackCrc, creature, creature);
+ } else {
+ try {
+ Scripts.invoke("buffs/callback" + callback, callback, creature);
+ } catch (FileNotFoundException ex) {
+ Log.w(this, "Callback script %s doesn't exist - buff %s won't behave as expected", callback, crc);
+ }
+ }
+ }
+
+ private void checkSkillMods(BuffData buffData, CreatureObject creature, int valueFactor) {
+ // TODO Check effect1name == "group". If yes, every group member within 100m range (maybe just the ones aware of the buffer) receive the buff.
+ // TODO once outside range, buff needs removal
+ sendSkillModIntent(creature, buffData.getEffect1Name(), buffData.getEffect1Value(), valueFactor);
+ sendSkillModIntent(creature, buffData.getEffect2Name(), buffData.getEffect2Value(), valueFactor);
+ sendSkillModIntent(creature, buffData.getEffect3Name(), buffData.getEffect3Value(), valueFactor);
+ sendSkillModIntent(creature, buffData.getEffect4Name(), buffData.getEffect4Value(), valueFactor);
+ sendSkillModIntent(creature, buffData.getEffect5Name(), buffData.getEffect5Value(), valueFactor);
+ }
+
+ private void sendSkillModIntent(CreatureObject creature, String effectName, float effectValue, int valueFactor) {
+ if (!effectName.isEmpty())
+ new SkillModIntent(effectName, 0, (int) effectValue * valueFactor, creature).broadcast();
+ }
+
+ /**
+ * @author Mads
+ * Each instance of this class holds the base information
+ * for a specific buff name.
+ *
+ * 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 a noticeable
+ * memory usage reduction.
+ */
+ private static class BuffData {
+
+ private final String groupName;
+ private final int groupPriority;
+ private final int maxStackCount;
+ private final String effect1Name;
+ private final float effect1Value;
+ private final String effect2Name;
+ private final float effect2Value;
+ private final String effect3Name;
+ private final float effect3Value;
+ private final String effect4Name;
+ private final float effect4Value;
+ private final String effect5Name;
+ private final float effect5Value;
+ private final float defaultDuration;
+ private final String effectFileName;
+ private final String particleHardPoint;
+ private final String stanceParticle;
+ private final String callback;
+ private final boolean persistent;
+ private final boolean removedOnDeath;
+ private final boolean decayOnPvpDeath;
+
+ private BuffData(String groupName, int groupPriority, int maxStackCount, String effect1Name, float effect1Value, String effect2Name, float effect2Value, String effect3Name, float effect3Value, String effect4Name, float effect4Value, String effect5Name, float effect5Value, float defaultDuration, String effectFileName, String particleHardPoint, String stanceParticle, String callback, boolean persistent, boolean removedOnDeath, boolean decayOnPvpDeath) {
+ this.groupName = groupName;
+ this.groupPriority = groupPriority;
+ this.maxStackCount = maxStackCount;
+ this.effect1Name = effect1Name;
+ this.effect1Value = effect1Value;
+ this.effect2Name = effect2Name;
+ this.effect2Value = effect2Value;
+ this.effect3Name = effect3Name;
+ this.effect3Value = effect3Value;
+ this.effect4Name = effect4Name;
+ this.effect4Value = effect4Value;
+ this.effect5Name = effect5Name;
+ this.effect5Value = effect5Value;
+ this.defaultDuration = defaultDuration;
+ this.effectFileName = effectFileName;
+ this.particleHardPoint = particleHardPoint;
+ this.stanceParticle = stanceParticle;
+ this.callback = callback;
+ this.persistent = persistent;
+ this.removedOnDeath = removedOnDeath;
+ this.decayOnPvpDeath = decayOnPvpDeath;
+ }
+
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public int getGroupPriority() {
+ return groupPriority;
+ }
+
+ private int getMaxStackCount() {
+ return maxStackCount;
+ }
+
+ private String getEffect1Name() {
+ return effect1Name;
+ }
+
+ private float getEffect1Value() {
+ return effect1Value;
+ }
+
+ private String getEffect2Name() {
+ return effect2Name;
+ }
+
+ private float getEffect2Value() {
+ return effect2Value;
+ }
+
+ private String getEffect3Name() {
+ return effect3Name;
+ }
+
+ private float getEffect3Value() {
+ return effect3Value;
+ }
+
+ private String getEffect4Name() {
+ return effect4Name;
+ }
+
+ private float getEffect4Value() {
+ return effect4Value;
+ }
+
+ private String getEffect5Name() {
+ return effect5Name;
+ }
+
+ private float getEffect5Value() {
+ return effect5Value;
+ }
+
+ private float getDefaultDuration() {
+ return defaultDuration;
+ }
+
+ private String getEffectFileName() {
+ return effectFileName;
+ }
+
+ private String getParticleHardPoint() {
+ return particleHardPoint;
+ }
+
+ public String getStanceParticle() {
+ return stanceParticle;
+ }
+
+ public String getCallback() {
+ return callback;
+ }
+
+ public boolean isPersistent() {
+ return persistent;
+ }
+
+ public boolean isRemovedOnDeath() {
+ return removedOnDeath;
+ }
+
+ public boolean isDecayOnPvpDeath() {
+ return decayOnPvpDeath;
+ }
+
+ }
+
+}
diff --git a/src/services/commands/CommandService.java b/src/services/commands/CommandService.java
index ef7a20bea..ca3188582 100644
--- a/src/services/commands/CommandService.java
+++ b/src/services/commands/CommandService.java
@@ -32,6 +32,7 @@ import intents.chat.ChatBroadcastIntent;
import intents.chat.ChatCommandIntent;
import intents.network.GalacticPacketIntent;
import intents.player.PlayerTransformedIntent;
+import java.io.FileNotFoundException;
import network.packets.Packet;
import network.packets.swg.zone.object_controller.CommandQueueDequeue;
import network.packets.swg.zone.object_controller.CommandQueueEnqueue;
@@ -71,7 +72,10 @@ import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import network.packets.swg.zone.object_controller.CommandTimer;
+import resources.combat.HitType;
import resources.commands.DefaultPriority;
import resources.objects.creature.CreatureObject;
import utilities.ThreadUtilities;
@@ -328,7 +332,10 @@ public class CommandService extends Service {
}
}
else
- Scripts.invoke("commands/generic/" + command.getDefaultScriptCallback(), "executeCommand", galacticManager, player, target, args);
+ try {
+ Scripts.invoke("commands/generic/" + command.getDefaultScriptCallback(), "executeCommand", galacticManager, player, target, args);
+ } catch (FileNotFoundException ex) {
+ }
}
private void loadBaseCommands() {
@@ -400,6 +407,7 @@ public class CommandService extends Service {
cc.setCooldownGroup2(c.getCooldownGroup2());
cc.setCooldownTime(c.getCooldownTime());
cc.setCooldownTime2(c.getCooldownTime2());
+ cc.setMaxRange(c.getMaxRange());
return cc;
}
@@ -416,6 +424,11 @@ public class CommandService extends Service {
int attackRolls = combatCommands.getColumnFromName("attack_rolls");
int animDefault = combatCommands.getColumnFromName("animDefault");
int percentAddFromWeapon = combatCommands.getColumnFromName("percentAddFromWeapon");
+ int addedDamage = combatCommands.getColumnFromName("addedDamage");
+ int buffNameTarget = combatCommands.getColumnFromName("buffNameTarget");
+ int buffNameSelf = combatCommands.getColumnFromName("buffNameSelf");
+ int maxRange = combatCommands.getColumnFromName("maxRange");
+ int hitType = combatCommands.getColumnFromName("hitType");
// animDefault anim_unarmed anim_onehandmelee anim_twohandmelee anim_polearm
// anim_pistol anim_lightRifle anim_carbine anim_rifle anim_heavyweapon
// anim_thrown anim_onehandlightsaber anim_twohandlightsaber anim_polearmlightsaber
@@ -438,6 +451,11 @@ public class CommandService extends Service {
cc.setAttackRolls((int) cmdRow[attackRolls]);
cc.setDefaultAnimation(getAnimationList((String) cmdRow[animDefault]));
cc.setPercentAddFromWeapon((float) cmdRow[percentAddFromWeapon]);
+ cc.setAddedDamage((int) cmdRow[addedDamage]);
+ cc.setBuffNameTarget((String) cmdRow[buffNameTarget]);
+ cc.setBuffNameSelf((String) cmdRow[buffNameSelf]);
+ cc.setMaxRange((float) cmdRow[maxRange]);
+ cc.setHitType(HitType.getHitType((Integer) cmdRow[hitType]));
cc.setAnimations(WeaponType.UNARMED, getAnimationList((String) cmdRow[animDefault+1]));
cc.setAnimations(WeaponType.ONE_HANDED_MELEE, getAnimationList((String) cmdRow[animDefault+2]));
cc.setAnimations(WeaponType.TWO_HANDED_MELEE, getAnimationList((String) cmdRow[animDefault+3]));
diff --git a/src/services/faction/FactionService.java b/src/services/faction/FactionService.java
index 6036c3a89..9dfa54b59 100644
--- a/src/services/faction/FactionService.java
+++ b/src/services/faction/FactionService.java
@@ -248,7 +248,7 @@ public final class FactionService extends Service {
int pvpBitmask = 0;
if(target.isEnemy(observer)) {
- pvpBitmask |= PvpFlag.AGGRESSIVE.getBitmask() | PvpFlag.ATTACKABLE.getBitmask();
+ pvpBitmask |= PvpFlag.AGGRESSIVE.getBitmask() | PvpFlag.ATTACKABLE.getBitmask() | PvpFlag.ENEMY.getBitmask();
}
return pvpBitmask;
diff --git a/src/services/galaxy/ConnectionService.java b/src/services/galaxy/ConnectionService.java
index 9403ce35a..efb45358a 100644
--- a/src/services/galaxy/ConnectionService.java
+++ b/src/services/galaxy/ConnectionService.java
@@ -260,11 +260,7 @@ public class ConnectionService extends Service {
if (playerObject == null)
return;
- int currentTime = playerObject.getPlayTime();
- int startTime = playerObject.getStartPlayTime();
- int deltaTime = (int) ((System.currentTimeMillis()) - startTime);
- int newTotalTime = currentTime + (int) TimeUnit.MILLISECONDS.toSeconds(deltaTime);
- playerObject.setPlayTime(newTotalTime);
+ playerObject.updatePlayTime();
}
private static class DisappearPlayer {
diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java
index dde109ff6..3a8f3800d 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.collections.CollectionService;
import services.collections.CollectionBadgeManager;
import services.combat.CombatManager;
@@ -54,6 +55,7 @@ public class GameManager extends Manager {
private final EntertainmentService entertainmentService;
private final CombatManager combatManager;
private final ExperienceManager experienceManager;
+ private final BuffService buffService;
public GameManager() {
commandService = new CommandService();
@@ -69,6 +71,7 @@ public class GameManager extends Manager {
entertainmentService = new EntertainmentService();
combatManager = new CombatManager();
experienceManager = new ExperienceManager();
+ buffService = new BuffService();
addChildService(commandService);
addChildService(connectionService);
@@ -83,5 +86,6 @@ public class GameManager extends Manager {
addChildService(entertainmentService);
addChildService(combatManager);
addChildService(experienceManager);
+ addChildService(buffService);
}
}
diff --git a/src/services/objects/ObjectManager.java b/src/services/objects/ObjectManager.java
index d2e707818..ee18ec11a 100644
--- a/src/services/objects/ObjectManager.java
+++ b/src/services/objects/ObjectManager.java
@@ -128,9 +128,6 @@ public class ObjectManager extends Manager {
}
private void loadObject(SWGObject obj) {
- if (obj instanceof CreatureObject && ((CreatureObject) obj).isPlayer())
- Scripts.invoke("objects/load_creature", "onLoad", obj);
-
updateBuildoutParent(obj);
addChildrenObjects(obj);
}
diff --git a/src/services/sui/SuiService.java b/src/services/sui/SuiService.java
index 435c6132a..4a90ca42f 100644
--- a/src/services/sui/SuiService.java
+++ b/src/services/sui/SuiService.java
@@ -29,6 +29,7 @@ package services.sui;
import intents.network.GalacticPacketIntent;
import intents.sui.SuiWindowIntent;
+import java.io.FileNotFoundException;
import network.packets.Packet;
import network.packets.swg.SWGPacket;
import network.packets.swg.zone.server_ui.SuiCreatePageMessage;
@@ -49,6 +50,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
public class SuiService extends Service {
@@ -149,7 +152,11 @@ public class SuiService extends Service {
if (window.hasCallbackFunction(callback)) {
String script = window.getCallbackScript(callback);
- Scripts.invoke(script, callback, player, player.getCreatureObject(), event, parameters);
+ try {
+ Scripts.invoke(script, callback, player, player.getCreatureObject(), event, parameters);
+ } catch (FileNotFoundException ex) {
+ Log.e(this, "Callback script %s not found", script);
+ }
} else if (window.hasJavaCallback(callback)) {
ISuiCallback suiCallback = window.getJavaCallback(callback);
suiCallback.handleEvent(player, player.getCreatureObject(), event, parameters);
diff --git a/src/utilities/DebugUtilities.java b/src/utilities/DebugUtilities.java
deleted file mode 100644
index 3c8ad24ee..000000000
--- a/src/utilities/DebugUtilities.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/************************************************************************************
- * Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
- * *
- * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
- * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
- * Our goal is to create an emulator which will provide a server for players to *
- * 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 Holocore. *
- * *
- * -------------------------------------------------------------------------------- *
- * *
- * 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. *
- * *
- * 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 Holocore. If not, see . *
- * *
- ***********************************************************************************/
-package utilities;
-
-import resources.control.Service;
-import resources.objects.creature.CreatureObject;
-import resources.player.Player;
-import resources.server_info.Log;
-
-public class DebugUtilities {
-
- public static void printPlayerCharacterDebug(Service service, Player p, String identifyingText) {
- CreatureObject c = p.getCreatureObject();
- boolean hasGhost = c.hasSlot("ghost");
- Log.d(service, "[%s] PLAY=%s/%d CREO=%s/%d hasGhost=%b", identifyingText, p.getUsername(), p.getUserId(), c.getName(), c.getObjectId(), hasGhost);
- }
-
- public static void printPlayerCharacterDebug(Service service, CreatureObject c, String identifyingText) {
- Player p = c.getOwner();
- String username = "null";
- int id = 0;
- if (p != null) {
- username = p.getUsername();
- id = p.getUserId();
- }
- boolean hasGhost = c.hasSlot("ghost");
- Log.d(service, "[%s] PLAY=%s/%d CREO=%s/%d hasGhost=%b", identifyingText, username, id, c.getName(), c.getObjectId(), hasGhost);
- }
-
-}
diff --git a/src/utilities/Scripts.java b/src/utilities/Scripts.java
index 876e5a586..40a3efd16 100644
--- a/src/utilities/Scripts.java
+++ b/src/utilities/Scripts.java
@@ -70,17 +70,14 @@ public class Scripts {
* @param args to pass to the function.
* @return whatever the function returns. If the function doesn't have a return statement, this method returns {@code null}.
* If an exception occurs, {@code null} is returned.
+ * @throws java.io.FileNotFoundException if the script file wasn't found
*/
@SuppressWarnings("unchecked")
- public static T invoke(String script, String function, Object... args) {
+ public static T invoke(String script, String function, Object... args) throws FileNotFoundException {
try {
ENGINE.eval(new InputStreamReader(new FileInputStream(SCRIPTS + script + EXTENSION), StandardCharsets.UTF_8));
return (T) INVOCABLE.invokeFunction(function, args);
- } catch (FileNotFoundException e) {
- // No need to print anything, this is a common error.
- // Returning null is all that's necessary
- return null;
- } catch (Throwable t) {
+ } catch (ScriptException | NoSuchMethodException t) {
Log.e("Scripts", "Error invoking script: " + script + " with function: " + function);
Log.e("Scripts", " Args: " + Arrays.toString(args));
Log.e("Scripts", t);