Merged in M4SS/holocore/ability_buffs (pull request #335)

NGE-449 - Abilities applying Buffs
This commit is contained in:
Obique PSWG
2016-10-27 01:54:36 +00:00
35 changed files with 1062 additions and 286 deletions
-7
View File
@@ -1,7 +0,0 @@
function canPerform(source, target, command) {
return (Java.type("resources.combat.CombatStatus")).SUCCESS;
}
function doCombat(source, target, command) {
return 0;
}
@@ -1,7 +0,0 @@
function canPerform(source, target, command) {
return (Java.type("resources.combat.CombatStatus")).SUCCESS;
}
function doCombat(source, target, command) {
return 0;
}
@@ -1,7 +0,0 @@
function canPerform(source, target, command) {
return (Java.type("resources.combat.CombatStatus")).SUCCESS;
}
function doCombat(source, target, command) {
return 0;
}
@@ -1,7 +0,0 @@
function canPerform(source, target, command) {
return (Java.type("resources.combat.CombatStatus")).SUCCESS;
}
function doCombat(source, target, command) {
return 0;
}
-3
View File
@@ -1,3 +0,0 @@
function executeCommand(galacticManager, player, target, args) {
print("Hello");
}
-7
View File
@@ -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);
}
+65
View File
@@ -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 <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
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;
}
}
+1 -1
View File
@@ -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
@@ -25,7 +25,7 @@
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
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;
}
@@ -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<Integer, HitType> 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);
}
}
}
+37
View File
@@ -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;
}
}
+3 -3
View File
@@ -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; }
@@ -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)
@@ -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<String, String> parameters) {
+22
View File
@@ -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));
+12 -16
View File
@@ -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 <Service> children;
private final Set<Service> children;
public Manager() {
children = new ArrayList<Service>();
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<Service> getManagerChildren() {
public Set<Service> getManagerChildren() {
synchronized (children) {
return new ArrayList<Service>(children);
return new HashSet<>(children);
}
}
+3 -2
View File
@@ -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");
}
+4 -1
View File
@@ -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);
}
}
+22 -5
View File
@@ -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);
}
}
@@ -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<Map.Entry<CRC, Buff>> getBuffEntries(Predicate<Map.Entry<CRC, Buff>> 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<CRC, Buff> 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
@@ -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<CRC, Buff> 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<Entry<CRC, Buff>> getBuffEntries(Predicate<Entry<CRC, Buff>> 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);
});
}
}
@@ -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);
}
@@ -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() {
+14 -2
View File
@@ -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<RadialOption> getRadialOptions(String script, Player player, SWGObject target, Object ... args) {
List<RadialOption> 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");
}
}
}
+100 -87
View File
@@ -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<Long, CombatCreature> inCombat;
private final Set<CreatureObject> regeneratingHealthCreatures; // Only allowed outside of combat
private final Set<CreatureObject> 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) {
+5 -1
View File
@@ -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();
}
/**
+563
View File
@@ -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 <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
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<CreatureObject> monitored;
private final Map<CRC, BuffData> 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<CRC, Buff> 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<CRC, Buff> 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<Entry<CRC, Buff>> 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<CRC, Buff> 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<CRC, Buff> 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<Entry<CRC, Buff>> optionalEntry = creature.getBuffEntries(buffEntry -> buffEntry.getKey().equals(buffCrc)).findAny();
if (!optionalEntry.isPresent()) {
return;
}
Entry<CRC, Buff> 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;
}
}
}
+19 -1
View File
@@ -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]));
+1 -1
View File
@@ -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;
+1 -5
View File
@@ -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 {
+4
View File
@@ -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);
}
}
-3
View File
@@ -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);
}
+8 -1
View File
@@ -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);
-55
View File
@@ -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 <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
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);
}
}
+3 -6
View File
@@ -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> T invoke(String script, String function, Object... args) {
public static <T> 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);