From ad557b14e4c2d11314a10e2f8c22c72f43fcb930 Mon Sep 17 00:00:00 2001 From: Obique PSWG Date: Sat, 7 May 2016 23:23:20 -0500 Subject: [PATCH] Created CombatService, the supporting items for combat, as well as basic combat --- scripts/commands/combat/meleeHit.js | 7 + src/intents/chat/ChatCommandIntent.java | 11 +- .../swg/zone/deltas/DeltasMessage.java | 3 + .../CommandQueueEnqueue.java | 2 + .../object_controller/ObjectController.java | 3 + .../combat/CombatAction.java | 252 ++++++++++++++++ .../object_controller/combat/CombatSpam.java | 279 ++++++++++++++++++ src/resources/combat/AttackInfo.java | 212 +++++++++++++ src/resources/combat/AttackInfoLight.java | 23 ++ src/resources/combat/AttackType.java | 36 +++ src/resources/combat/CombatStatus.java | 9 + src/resources/combat/DamageType.java | 45 +++ src/resources/combat/HitLocation.java | 29 ++ src/resources/combat/TrailLocation.java | 42 +++ src/resources/combat/ValidTarget.java | 33 +++ src/resources/commands/CombatCommand.java | 95 ++++++ .../encodables/player/Equipment.java | 5 +- .../objects/creature/CreatureObject.java | 110 +++++-- .../objects/tangible/TangibleObject.java | 61 ++-- src/services/combat/CombatService.java | 235 +++++++++++++++ src/services/commands/CommandService.java | 74 ++++- src/services/dev/DeveloperService.java | 36 +++ src/services/galaxy/GalacticManager.java | 4 + src/services/galaxy/GameManager.java | 4 + .../player/CharacterCreationService.java | 2 +- 25 files changed, 1553 insertions(+), 59 deletions(-) create mode 100644 scripts/commands/combat/meleeHit.js create mode 100644 src/network/packets/swg/zone/object_controller/combat/CombatAction.java create mode 100644 src/network/packets/swg/zone/object_controller/combat/CombatSpam.java create mode 100644 src/resources/combat/AttackInfo.java create mode 100644 src/resources/combat/AttackInfoLight.java create mode 100644 src/resources/combat/AttackType.java create mode 100644 src/resources/combat/CombatStatus.java create mode 100644 src/resources/combat/DamageType.java create mode 100644 src/resources/combat/HitLocation.java create mode 100644 src/resources/combat/TrailLocation.java create mode 100644 src/resources/combat/ValidTarget.java create mode 100644 src/resources/commands/CombatCommand.java create mode 100644 src/services/combat/CombatService.java create mode 100644 src/services/dev/DeveloperService.java diff --git a/scripts/commands/combat/meleeHit.js b/scripts/commands/combat/meleeHit.js new file mode 100644 index 000000000..1b9e95447 --- /dev/null +++ b/scripts/commands/combat/meleeHit.js @@ -0,0 +1,7 @@ +function canPerform(source, target, command) { + return (Java.type("resources.combat.CombatStatus")).SUCCESS; +} + +function doCombat(source, target, command) { + return new (Java.type("resources.combat.AttackInfoLight"))(100); +} diff --git a/src/intents/chat/ChatCommandIntent.java b/src/intents/chat/ChatCommandIntent.java index c6a55711b..8a7969c41 100644 --- a/src/intents/chat/ChatCommandIntent.java +++ b/src/intents/chat/ChatCommandIntent.java @@ -29,6 +29,7 @@ package intents.chat; import resources.commands.Command; import resources.control.Intent; +import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; public class ChatCommandIntent extends Intent { @@ -36,19 +37,19 @@ public class ChatCommandIntent extends Intent { public static final String TYPE = "ChatCommandIntent"; private CreatureObject source; - private long target; + private SWGObject target; private Command command; private String [] arguments; public ChatCommandIntent() { super(TYPE); setSource(null); - setTarget(0); + setTarget(null); setCommand(null); setArguments(new String[0]); } - public ChatCommandIntent(CreatureObject source, long target, Command command, String [] arguments) { + public ChatCommandIntent(CreatureObject source, SWGObject target, Command command, String [] arguments) { super(TYPE); setSource(source); setTarget(target); @@ -60,7 +61,7 @@ public class ChatCommandIntent extends Intent { this.source = source; } - public void setTarget(long target) { + public void setTarget(SWGObject target) { this.target = target; } @@ -77,7 +78,7 @@ public class ChatCommandIntent extends Intent { return source; } - public long getTarget() { + public SWGObject getTarget() { return target; } diff --git a/src/network/packets/swg/zone/deltas/DeltasMessage.java b/src/network/packets/swg/zone/deltas/DeltasMessage.java index 2e2fffaed..d4e9f068e 100644 --- a/src/network/packets/swg/zone/deltas/DeltasMessage.java +++ b/src/network/packets/swg/zone/deltas/DeltasMessage.java @@ -66,6 +66,9 @@ public class DeltasMessage extends SWGPacket { num = getByte(data); int length = getInt(data); this.deltaData = getArray(data, length); + data = ByteBuffer.wrap(deltaData); + getShort(data); + update = getShort(data); } public ByteBuffer encode() { diff --git a/src/network/packets/swg/zone/object_controller/CommandQueueEnqueue.java b/src/network/packets/swg/zone/object_controller/CommandQueueEnqueue.java index 35015df51..13809d30a 100644 --- a/src/network/packets/swg/zone/object_controller/CommandQueueEnqueue.java +++ b/src/network/packets/swg/zone/object_controller/CommandQueueEnqueue.java @@ -70,10 +70,12 @@ public class CommandQueueEnqueue extends ObjectController { return data; } + public int getCounter() { return counter; } public int getCommandCrc() { return crc; } public String getArguments() { return arguments; } public long getTargetId() { return targetId; } + public void setCounter(int counter) { this.counter = counter; } public void setCommandCrc(int crc) { this.crc = crc; } public void setArguments(String args) { this.arguments = args; } public void setTargetId(long targetId) { this.targetId = targetId; } diff --git a/src/network/packets/swg/zone/object_controller/ObjectController.java b/src/network/packets/swg/zone/object_controller/ObjectController.java index ba6650474..9f44d7940 100644 --- a/src/network/packets/swg/zone/object_controller/ObjectController.java +++ b/src/network/packets/swg/zone/object_controller/ObjectController.java @@ -31,6 +31,7 @@ import java.nio.ByteBuffer; import resources.server_info.Log; import network.packets.swg.SWGPacket; +import network.packets.swg.zone.object_controller.combat.*; public abstract class ObjectController extends SWGPacket { @@ -90,12 +91,14 @@ public abstract class ObjectController extends SWGPacket { int crc = data.getInt(10); switch (crc) { case 0x0071: return new DataTransform(data); + case 0x00CC: return new CombatAction(data); case 0x00F1: return new DataTransformWithParent(data); case 0x0116: return new CommandQueueEnqueue(data); case 0x0117: return new CommandQueueDequeue(data); case 0x0126: return new LookAtTarget(data); case 0x012E: return new PlayerEmote(data); case 0x0131: return new PostureUpdate(data); + case 0x0134: return new CombatSpam(data); case 0x0146: return new ObjectMenuRequest(data); case 0x04C5: return new IntendedTarget(data); } diff --git a/src/network/packets/swg/zone/object_controller/combat/CombatAction.java b/src/network/packets/swg/zone/object_controller/combat/CombatAction.java new file mode 100644 index 000000000..28dfddaa0 --- /dev/null +++ b/src/network/packets/swg/zone/object_controller/combat/CombatAction.java @@ -0,0 +1,252 @@ +package network.packets.swg.zone.object_controller.combat; + +import java.nio.ByteBuffer; +import java.util.HashSet; +import java.util.Set; + +import resources.Point3D; +import resources.Posture; +import resources.combat.HitLocation; +import resources.combat.TrailLocation; +import resources.network.NetBuffer; +import resources.objects.creature.CreatureObject; +import network.packets.swg.zone.object_controller.ObjectController; + +public class CombatAction extends ObjectController { + + public static final int CRC = 0x00CC; + + private long attackerId; + private Posture posture; + private Point3D position; + private long cell; + private TrailLocation trail; + private byte clientEffectId; + private int actionCrc; + private boolean useLocation; + private Set defenders; + + public CombatAction(long objectId) { + super(objectId, CRC); + defenders = new HashSet<>(); + } + + public CombatAction(ByteBuffer data) { + super(CRC); + defenders = new HashSet<>(); + decode(data); + } + + @Override + public void decode(ByteBuffer data) { + decodeHeader(data); + attackerId = getLong(data); + posture = Posture.getFromId(getByte(data)); + trail = TrailLocation.getTrailLocation(getByte(data)); + clientEffectId = getByte(data); + actionCrc = getInt(data); + useLocation = getBoolean(data); + if (useLocation) { + position = getEncodable(data, Point3D.class); + cell = getLong(data); + } + int count = getInt(data); + for (int i = 0; i < count; i++) { + Defender d = new Defender(); + d.setCreatureId(getLong(data)); + d.setPosture(Posture.getFromId(getByte(data))); + d.setDefense(getBoolean(data)); + d.setClientEffectId(getByte(data)); + d.setHitLocation(HitLocation.getHitLocation(getByte(data))); + d.setDamage(getShort(data)); + defenders.add(d); + } + } + + @Override + public ByteBuffer encode() { + NetBuffer data = NetBuffer.allocate(HEADER_LENGTH + 20 + defenders.size() * 14 + (useLocation ? 20 : 0)); + data.addLong(attackerId); + data.addByte(posture.getId()); + data.addByte(trail.getNum()); + data.addByte(clientEffectId); + data.addInt(actionCrc); + data.addBoolean(useLocation); + if (useLocation) { + data.addEncodable(position); + data.addLong(cell); + } + data.addInt(defenders.size()); + for (Defender d : defenders) { + data.addLong(d.getCreatureId()); + data.addByte(d.getPosture().getId()); + data.addBoolean(d.isDefense()); + data.addByte(d.getClientEffectId()); + data.addByte(d.getHitLocation().getNum()); + data.addShort(d.getDamage()); + } + return data.getBuffer(); + } + + public static int getCrc() { + return CRC; + } + + public long getAttackerId() { + return attackerId; + } + + public Posture getPosture() { + return posture; + } + + public Point3D getPosition() { + return position; + } + + public long getCell() { + return cell; + } + + public TrailLocation getTrail() { + return trail; + } + + public byte getClientEffectId() { + return clientEffectId; + } + + public int getActionCrc() { + return actionCrc; + } + + public boolean isUseLocation() { + return useLocation; + } + + public Set getDefenders() { + return defenders; + } + + public void setAttacker(CreatureObject attacker) { + setAttackerId(attacker.getObjectId()); + setPosture(attacker.getPosture()); + } + + public void setAttackerId(long attackerId) { + this.attackerId = attackerId; + } + + public void setPosture(Posture posture) { + this.posture = posture; + } + + public void setPosition(Point3D position) { + this.position = position; + } + + public void setCell(long cell) { + this.cell = cell; + } + + public void setTrail(TrailLocation trail) { + this.trail = trail; + } + + public void setClientEffectId(byte clientEffectId) { + this.clientEffectId = clientEffectId; + } + + public void setActionCrc(int actionCrc) { + this.actionCrc = actionCrc; + } + + public void setUseLocation(boolean useLocation) { + this.useLocation = useLocation; + } + + public void addDefender(CreatureObject creature, boolean defense, byte clientEffectId, HitLocation location, short damage) { + defenders.add(new Defender(creature.getObjectId(), creature.getPosture(), defense, clientEffectId, location, damage)); + } + + public static class Defender { + + private long creatureId; + private Posture posture; + private boolean defense; + private byte clientEffectId; + private HitLocation hitLocation; + private short damage; + + public Defender() { + this(0, Posture.UPRIGHT, false, (byte) 0, HitLocation.HIT_LOCATION_BODY, (short) 0); + } + + public Defender(long creatureId, Posture p, boolean defense, byte clientEffectId, HitLocation location, short damage) { + setCreatureId(creatureId); + setPosture(p); + setDefense(defense); + setClientEffectId(clientEffectId); + setHitLocation(location); + setDamage(damage); + } + + public long getCreatureId() { + return creatureId; + } + + public Posture getPosture() { + return posture; + } + + public boolean isDefense() { + return defense; + } + + public byte getClientEffectId() { + return clientEffectId; + } + + public HitLocation getHitLocation() { + return hitLocation; + } + + public short getDamage() { + return damage; + } + + public void setCreatureId(long creatureId) { + this.creatureId = creatureId; + } + + public void setPosture(Posture p) { + this.posture = p; + } + + public void setDefense(boolean defense) { + this.defense = defense; + } + + public void setClientEffectId(byte clientEffectId) { + this.clientEffectId = clientEffectId; + } + + public void setHitLocation(HitLocation hitLocation) { + this.hitLocation = hitLocation; + } + + public void setDamage(short damage) { + this.damage = damage; + } + + public int hashCode() { + return Long.hashCode(creatureId); + } + + public boolean equals(Defender d) { + return creatureId == d.getCreatureId(); + } + } + +} + diff --git a/src/network/packets/swg/zone/object_controller/combat/CombatSpam.java b/src/network/packets/swg/zone/object_controller/combat/CombatSpam.java new file mode 100644 index 000000000..a855998c9 --- /dev/null +++ b/src/network/packets/swg/zone/object_controller/combat/CombatSpam.java @@ -0,0 +1,279 @@ +package network.packets.swg.zone.object_controller.combat; + +import java.nio.ByteBuffer; + +import network.packets.swg.zone.object_controller.ObjectController; +import resources.Point3D; +import resources.combat.AttackInfo; +import resources.combat.DamageType; +import resources.combat.HitLocation; +import resources.encodables.StringId; +import resources.network.NetBuffer; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; + +public class CombatSpam extends ObjectController { + + public static final int CRC = 0x0134; + + private byte dataType; + private long attacker; + private Point3D attackerPosition; + private long defender; + private Point3D defenderPosition; + private long weapon; + private StringId weaponName; + private StringId attackName; + private AttackInfo info; + private String spamMessage; + private int spamType; + + public CombatSpam(long objectId) { + super(objectId, CRC); + } + + public CombatSpam(ByteBuffer data) { + super(CRC); + decode(data); + } + + @Override + public void decode(ByteBuffer data) { + decodeHeader(data); + info = new AttackInfo(); + dataType = getByte(data); + attacker = getLong(data); + attackerPosition = getEncodable(data, Point3D.class); + defender = getLong(data); + defenderPosition = getEncodable(data, Point3D.class); + if (isAttackDataWeaponObject(dataType) || isAttackWeaponName(dataType)) { + if (isAttackDataWeaponObject(dataType)) + weapon = getLong(data); + else + weaponName = getEncodable(data, StringId.class); + attackName = getEncodable(data, StringId.class); + info.setSuccess(getBoolean(data)); + if (info.isSuccess()) { + info.setArmor(getLong(data)); + info.setRawDamage(getInt(data)); + info.setDamageType(DamageType.getDamageType(getInt(data))); + info.setElementalDamage(getInt(data)); + info.setElementalDamageType(DamageType.getDamageType(getInt(data))); + info.setBleedDamage(getInt(data)); + info.setCriticalDamage(getInt(data)); + info.setBlockedDamage(getInt(data)); + info.setFinalDamage(getInt(data)); + info.setHitLocation(HitLocation.getHitLocation(getInt(data))); + info.setCrushing(getBoolean(data)); + info.setStrikethrough(getBoolean(data)); + info.setStrikethroughAmount(getFloat(data)); + info.setEvadeResult(getBoolean(data)); + info.setEvadeAmount(getFloat(data)); + info.setBlockResult(getBoolean(data)); + info.setBlock(getInt(data)); + } else { + info.setDodge(getBoolean(data)); + info.setParry(getBoolean(data)); + } + } else { + // spamMessage = getUnicode(data); + getInt(data); + } + info.setCritical(getBoolean(data)); + info.setGlancing(getBoolean(data)); + info.setProc(getBoolean(data)); + spamType = getInt(data); + if (isMessageData(dataType)) { + // Extra stuff? + } + } + + @Override + public ByteBuffer encode() { + NetBuffer data = NetBuffer.allocate(getEncodeSize()); + data.addByte(dataType); + data.addLong(attacker); + data.addEncodable(attackerPosition); + data.addLong(defender); + data.addEncodable(defenderPosition); + if (isAttackDataWeaponObject(dataType) || isAttackWeaponName(dataType)) { + if (isAttackDataWeaponObject(dataType)) + data.addLong(weapon); + else + data.addEncodable(weaponName); + data.addEncodable(attackName); + data.addBoolean(info.isSuccess()); + if (info.isSuccess()) { + data.addLong(info.getArmor()); + data.addInt(info.getRawDamage()); + data.addInt(info.getDamageType().getNum()); + data.addInt(info.getElementalDamage()); + data.addInt(info.getElementalDamageType().getNum()); + data.addInt(info.getBleedDamage());; + data.addInt(info.getCriticalDamage()); + data.addInt(info.getBlockedDamage()); + data.addInt(info.getFinalDamage()); + data.addInt(info.getHitLocation().getNum()); + data.addBoolean(info.isCrushing()); + data.addBoolean(info.isStrikethrough()); + data.addFloat((float) info.getStrikethroughAmount()); + data.addBoolean(info.isEvadeResult()); + data.addFloat((float) info.getEvadeAmount()); + data.addBoolean(info.isBlockResult()); + data.addInt(info.getBlock()); + } else { + data.addBoolean(info.isDodge()); + data.addBoolean(info.isParry()); + } + } else { + data.addInt(0); // ihnfk + } + data.addBoolean(info.isCritical()); + data.addBoolean(info.isGlancing()); + data.addBoolean(info.isProc()); + data.addInt(spamType); + return data.getBuffer(); + } + + private int getEncodeSize() { + int size = 48; + if (isAttackDataWeaponObject(dataType)) + size += 9 + getStringIdSize(attackName) + (info.isSuccess() ? 97 : 2); + else if (isAttackWeaponName(dataType)) + size += 1 + getStringIdSize(attackName) + getStringIdSize(weaponName) + (info.isSuccess() ? 97 : 2); + else + size += 4; // I have no idea what's in this struct + return size; + } + + private int getStringIdSize(StringId sid) { + return 8 + sid.getFile().length() + sid.getKey().length(); + } + + /** + * Sets the various fields based off of this creature object + * + * @param attacker the attacker + */ + public void setAttacker(CreatureObject attacker) { + setDataType((byte) 0); + setAttacker(attacker.getObjectId()); + setAttackerPosition(attacker.getLocation().getPosition()); + if (attacker.getEquippedWeapon() != null) + setWeapon(attacker.getEquippedWeapon().getObjectId()); + else + setWeapon(0); + } + + /** + * Sets the various fields based off of this creature object + * + * @param defender the defender + */ + public void setDefender(SWGObject defender) { + setDefender(defender.getObjectId()); + setDefenderPosition(defender.getLocation().getPosition()); + } + + public byte getDataType() { + return dataType; + } + + public long getAttacker() { + return attacker; + } + + public Point3D getAttackerPosition() { + return attackerPosition; + } + + public long getDefender() { + return defender; + } + + public Point3D getDefenderPosition() { + return defenderPosition; + } + + public long getWeapon() { + return weapon; + } + + public StringId getWeaponName() { + return weaponName; + } + + public StringId getAttackName() { + return attackName; + } + + public AttackInfo getInfo() { + return info; + } + + public String getSpamMessage() { + return spamMessage; + } + + public int getSpamType() { + return spamType; + } + + public void setDataType(byte dataType) { + this.dataType = dataType; + } + + public void setAttacker(long attacker) { + this.attacker = attacker; + } + + public void setAttackerPosition(Point3D attackerPosition) { + this.attackerPosition = attackerPosition; + } + + public void setDefender(long defender) { + this.defender = defender; + } + + public void setDefenderPosition(Point3D defenderPosition) { + this.defenderPosition = defenderPosition; + } + + public void setWeapon(long weapon) { + this.weapon = weapon; + } + + public void setWeaponName(StringId weaponName) { + this.weaponName = weaponName; + } + + public void setAttackName(StringId attackName) { + this.attackName = attackName; + } + + public void setInfo(AttackInfo info) { + this.info = info; + } + + public void setSpamMessage(String spamMessage) { + this.spamMessage = spamMessage; + } + + public void setSpamType(int spamType) { + this.spamType = spamType; + } + + private boolean isAttackDataWeaponObject(byte b) { + return b == 0; + } + + private boolean isAttackWeaponName(byte b) { + return b == 1; + } + + private boolean isMessageData(byte b) { + return b == 2; + } + +} + diff --git a/src/resources/combat/AttackInfo.java b/src/resources/combat/AttackInfo.java new file mode 100644 index 000000000..7b0b4f4ef --- /dev/null +++ b/src/resources/combat/AttackInfo.java @@ -0,0 +1,212 @@ +package resources.combat; + +public class AttackInfo { + + private boolean success = true; + private long armor = 0; + private int rawDamage = 0; + private DamageType damageType = DamageType.KINETIC; + private int elementalDamage = 0; + private DamageType elementalDamageType = DamageType.KINETIC; + private int bleedDamage = 0; + private int criticalDamage = 0; + private int blockedDamage = 0; + private int finalDamage = 0; + private HitLocation hitLocation = HitLocation.HIT_LOCATION_BODY; + private boolean crushing = false; + private boolean strikethrough = false; + private double strikethroughAmount = 0; + private boolean evadeResult = false; + private double evadeAmount = 0; + private boolean blockResult = false; + private int block = 0; + private boolean dodge = false; + private boolean parry = false; + private boolean critical = false; + private boolean glancing = false; + private boolean proc = false; + + public boolean isSuccess() { + return success; + } + + public long getArmor() { + return armor; + } + + public int getRawDamage() { + return rawDamage; + } + + public DamageType getDamageType() { + return damageType; + } + + public int getElementalDamage() { + return elementalDamage; + } + + public DamageType getElementalDamageType() { + return elementalDamageType; + } + + public int getBleedDamage() { + return bleedDamage; + } + + public int getCriticalDamage() { + return criticalDamage; + } + + public int getBlockedDamage() { + return blockedDamage; + } + + public int getFinalDamage() { + return finalDamage; + } + + public HitLocation getHitLocation() { + return hitLocation; + } + + public boolean isCrushing() { + return crushing; + } + + public boolean isStrikethrough() { + return strikethrough; + } + + public double getStrikethroughAmount() { + return strikethroughAmount; + } + + public boolean isEvadeResult() { + return evadeResult; + } + + public double getEvadeAmount() { + return evadeAmount; + } + + public boolean isBlockResult() { + return blockResult; + } + + public int getBlock() { + return block; + } + + public boolean isDodge() { + return dodge; + } + + public boolean isParry() { + return parry; + } + + public boolean isCritical() { + return critical; + } + + public boolean isGlancing() { + return glancing; + } + + public boolean isProc() { + return proc; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public void setArmor(long armor) { + this.armor = armor; + } + + public void setRawDamage(int rawDamage) { + this.rawDamage = rawDamage; + } + + public void setDamageType(DamageType damageType) { + this.damageType = damageType; + } + + public void setElementalDamage(int elementalDamage) { + this.elementalDamage = elementalDamage; + } + + public void setElementalDamageType(DamageType elementalDamageType) { + this.elementalDamageType = elementalDamageType; + } + + public void setBleedDamage(int bleedDamage) { + this.bleedDamage = bleedDamage; + } + + public void setCriticalDamage(int criticalDamage) { + this.criticalDamage = criticalDamage; + } + + public void setBlockedDamage(int blockedDamage) { + this.blockedDamage = blockedDamage; + } + + public void setFinalDamage(int finalDamage) { + this.finalDamage = finalDamage; + } + + public void setHitLocation(HitLocation hitLocation) { + this.hitLocation = hitLocation; + } + + public void setCrushing(boolean crushing) { + this.crushing = crushing; + } + + public void setStrikethrough(boolean strikethrough) { + this.strikethrough = strikethrough; + } + + public void setStrikethroughAmount(double strikethroughAmount) { + this.strikethroughAmount = strikethroughAmount; + } + + public void setEvadeResult(boolean evadeResult) { + this.evadeResult = evadeResult; + } + + public void setEvadeAmount(double evadeAmount) { + this.evadeAmount = evadeAmount; + } + + public void setBlockResult(boolean blockResult) { + this.blockResult = blockResult; + } + + public void setBlock(int block) { + this.block = block; + } + + public void setDodge(boolean dodge) { + this.dodge = dodge; + } + + public void setParry(boolean parry) { + this.parry = parry; + } + + public void setCritical(boolean critical) { + this.critical = critical; + } + + public void setGlancing(boolean glancing) { + this.glancing = glancing; + } + + public void setProc(boolean proc) { + this.proc = proc; + } +} diff --git a/src/resources/combat/AttackInfoLight.java b/src/resources/combat/AttackInfoLight.java new file mode 100644 index 000000000..43fb06ef1 --- /dev/null +++ b/src/resources/combat/AttackInfoLight.java @@ -0,0 +1,23 @@ +package resources.combat; + +public class AttackInfoLight { + + private int damage; + + public AttackInfoLight() { + this(0); + } + + public AttackInfoLight(int damage) { + setDamage(damage); + } + + public int getDamage() { + return damage; + } + + public void setDamage(int damage) { + this.damage = damage; + } + +} diff --git a/src/resources/combat/AttackType.java b/src/resources/combat/AttackType.java new file mode 100644 index 000000000..7ca81fca7 --- /dev/null +++ b/src/resources/combat/AttackType.java @@ -0,0 +1,36 @@ +package resources.combat; + +public enum AttackType { + CONE (0), + SINGLE_TARGET (1), + AREA (2), + TARGET_AREA (3), + DUAL_WIELD (4), + RAMPAGE (5), + RANDOM_HATE_TARGET (6), + RANDOM_HATE_TARGET_CONE (7), + RANDOM_HATE_TARGET_CONE_TERMINUS (8), + HATE_LIST (9), + RANDOM_HATE_MULTI (10), + AREA_PROGRESSIVE (11), + SPLIT_DAMAGE_TARGET_AREA (12), + DISTANCE_FARTHEST (13); + + private static final AttackType [] VALUES = values(); + + private int num; + + AttackType(int num) { + this.num = num; + } + + public int getNum() { + return num; + } + + public static AttackType getAttackType(int num) { + if (num < 0 || num >= VALUES.length) + return AttackType.SINGLE_TARGET; + return VALUES[num]; + } +} diff --git a/src/resources/combat/CombatStatus.java b/src/resources/combat/CombatStatus.java new file mode 100644 index 000000000..c947d2071 --- /dev/null +++ b/src/resources/combat/CombatStatus.java @@ -0,0 +1,9 @@ +package resources.combat; + +public enum CombatStatus { + UNKNOWN, + NO_WEAPON, + NO_TARGET, + TOO_FAR, + SUCCESS +} diff --git a/src/resources/combat/DamageType.java b/src/resources/combat/DamageType.java new file mode 100644 index 000000000..abd15b704 --- /dev/null +++ b/src/resources/combat/DamageType.java @@ -0,0 +1,45 @@ +package resources.combat; + +import java.util.EnumSet; +import java.util.Set; + +public enum DamageType { + KINETIC (1), + ENERGY (2), + BLAST (4), + STUN (8), + RESTAINT (16), + ELEMENTAL_HEAT (32), + ELEMENTAL_COLD (64), + ELEMENTAL_ACID (128), + ELEMENTAL_ELECTRICAL (256); + + private static final DamageType [] VALUES = values(); + + private int num; + + DamageType(int num) { + this.num = num; + } + + public int getNum() { + return num; + } + + public static DamageType getDamageType(int num) { + for (DamageType type : VALUES) { + if ((num & type.getNum()) != 0) + return type; + } + return KINETIC; + } + + public static Set getDamageTypes(int num) { + Set types = EnumSet.noneOf(DamageType.class); + for (DamageType type : VALUES) { + if ((num & type.getNum()) != 0) + types.add(type); + } + return types; + } +} diff --git a/src/resources/combat/HitLocation.java b/src/resources/combat/HitLocation.java new file mode 100644 index 000000000..c494da622 --- /dev/null +++ b/src/resources/combat/HitLocation.java @@ -0,0 +1,29 @@ +package resources.combat; + +public enum HitLocation { + HIT_LOCATION_BODY (0), + HIT_LOCATION_HEAD (1), + HIT_LOCATION_R_ARM (2), + HIT_LOCATION_L_ARM (3), + HIT_LOCATION_R_LEG (4), + HIT_LOCATION_L_LEG (5), + HIT_LOCATION_NUM_LOCATIONS (6); + + private static final HitLocation [] VALUES = values(); + + private int num; + + HitLocation(int num) { + this.num = num; + } + + public int getNum() { + return num; + } + + public static HitLocation getHitLocation(int num) { + if (num < 0 || num >= VALUES.length) + return HIT_LOCATION_BODY; + return VALUES[num]; + } +} diff --git a/src/resources/combat/TrailLocation.java b/src/resources/combat/TrailLocation.java new file mode 100644 index 000000000..712be342e --- /dev/null +++ b/src/resources/combat/TrailLocation.java @@ -0,0 +1,42 @@ +package resources.combat; + +import java.util.EnumSet; +import java.util.Set; + +public enum TrailLocation { + LEFT_FOOT (0x01), + RIGHT_FOOT (0x02), + LEFT_HAND (0x04), + RIGHT_HAND (0x08), + WEAPON (0x10); + + private static final TrailLocation [] VALUES = values(); + + private int num; + + TrailLocation(int num) { + this.num = num; + } + + public int getNum() { + return num; + } + + public static TrailLocation getTrailLocation(int num) { + for (TrailLocation type : VALUES) { + if ((num & type.getNum()) != 0) + return type; + } + return WEAPON; + } + + public static Set getTrailLocations(int num) { + Set types = EnumSet.noneOf(TrailLocation.class); + for (TrailLocation type : VALUES) { + if ((num & type.getNum()) != 0) + types.add(type); + } + return types; + } + +} diff --git a/src/resources/combat/ValidTarget.java b/src/resources/combat/ValidTarget.java new file mode 100644 index 000000000..c6a0cf99b --- /dev/null +++ b/src/resources/combat/ValidTarget.java @@ -0,0 +1,33 @@ +package resources.combat; + +public enum ValidTarget { + NONE (-1), + STANDARD(0), + MOB (1), + CREATURE(2), + NPC (3), + DROID (4), + PVP (5), + JEDI (6), + DEAD (7), + FRIEND (8); + + private static final ValidTarget [] VALUES = values(); + + private int num; + + ValidTarget(int num) { + this.num = num; + } + + public int getNum() { + return num; + } + + public static ValidTarget getValidTarget(int num) { + if (num < 0 || num >= VALUES.length) + return STANDARD; + return VALUES[num]; + } + +} diff --git a/src/resources/commands/CombatCommand.java b/src/resources/commands/CombatCommand.java new file mode 100644 index 000000000..a4297dde9 --- /dev/null +++ b/src/resources/commands/CombatCommand.java @@ -0,0 +1,95 @@ +package resources.commands; + +import resources.combat.AttackType; +import resources.combat.DamageType; +import resources.combat.ValidTarget; + +public class CombatCommand extends Command { + + private ValidTarget validTarget; + private boolean forceCombat; + private AttackType attackType; + private double healthCost; + private double actionCost; + private DamageType damageType; + private boolean ignoreDistance; + private boolean pvpOnly; + private int attackRolls; + + public CombatCommand(String name) { + super(name); + } + + public ValidTarget getValidTarget() { + return validTarget; + } + + public boolean isForceCombat() { + return forceCombat; + } + + public AttackType getAttackType() { + return attackType; + } + + public double getHealthCost() { + return healthCost; + } + + public double getActionCost() { + return actionCost; + } + + public DamageType getDamageType() { + return damageType; + } + + public boolean isIgnoreDistance() { + return ignoreDistance; + } + + public boolean isPvpOnly() { + return pvpOnly; + } + + public int getAttackRolls() { + return attackRolls; + } + + public void setValidTarget(ValidTarget validTarget) { + this.validTarget = validTarget; + } + + public void setForceCombat(boolean forceCombat) { + this.forceCombat = forceCombat; + } + + public void setAttackType(AttackType attackType) { + this.attackType = attackType; + } + + public void setHealthCost(double healthCost) { + this.healthCost = healthCost; + } + + public void setActionCost(double actionCost) { + this.actionCost = actionCost; + } + + public void setDamageType(DamageType damageType) { + this.damageType = damageType; + } + + public void setIgnoreDistance(boolean ignoreDistance) { + this.ignoreDistance = ignoreDistance; + } + + public void setPvpOnly(boolean pvpOnly) { + this.pvpOnly = pvpOnly; + } + + public void setAttackRolls(int attackRolls) { + this.attackRolls = attackRolls; + } + +} diff --git a/src/resources/encodables/player/Equipment.java b/src/resources/encodables/player/Equipment.java index 62a728436..65478cc07 100644 --- a/src/resources/encodables/player/Equipment.java +++ b/src/resources/encodables/player/Equipment.java @@ -107,7 +107,10 @@ public class Equipment implements Encodable, Serializable { public String getTemplate() { return template.getString(); } public void setTemplate(String template) { this.template = new CRC(template); } - + + public TangibleObject getWeapon() { return weapon; } + public void setWeapon(TangibleObject weapon) { this.weapon = weapon; } + private byte[] getWeaponData() { Player target = weapon.getOwner(); ByteBuffer data3 = weapon.createBaseline3(target).encode(); diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index 13930b4c5..462824bdc 100644 --- a/src/resources/objects/creature/CreatureObject.java +++ b/src/resources/objects/creature/CreatureObject.java @@ -90,7 +90,7 @@ public class CreatureObject extends TangibleObject { private long reserveBalance = 0; // Galactic Reserve - capped at 3 billion private String moodAnimation = "neutral"; private String animation = ""; - private long equippedWeaponId = 0; + private WeaponObject equippedWeapon = null; private byte moodId = 0; private long lookAtTargetId = 0; private long intendedTargetId = 0; @@ -122,7 +122,7 @@ public class CreatureObject extends TangibleObject { private SWGMap skillMods = new SWGMap<>(4, 3, StringType.ASCII); // TODO: SkillMod structure private SWGMap abilities = new SWGMap<>(4, 14, StringType.ASCII); private SWGMap buffs = new SWGMap<>(6, 26); - + public CreatureObject(long objectId) { super(objectId, BaselineType.CREO); initMaxAttributes(); @@ -130,7 +130,7 @@ public class CreatureObject extends TangibleObject { initBaseAttributes(); setOptionFlags(OptionFlag.HAM_BAR); } - + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); // Transient Variables @@ -496,13 +496,13 @@ public class CreatureObject extends TangibleObject { sendDelta(6, 10, animation, StringType.ASCII); } - public long getEquippedWeaponId() { - return equippedWeaponId; + public WeaponObject getEquippedWeapon() { + return equippedWeapon; } - public void setEquippedWeaponId(long equippedWeaponId) { - this.equippedWeaponId = equippedWeaponId; - sendDelta(6, 12, equippedWeaponId); + public void setEquippedWeapon(WeaponObject weapon) { + this.equippedWeapon = weapon; + sendDelta(6, 12, weapon.getObjectId()); } public byte getMoodId() { @@ -690,27 +690,57 @@ public class CreatureObject extends TangibleObject { } public int getHealth() { - return attributes.get(0); + synchronized (attributes) { + return attributes.get(0); + } } public int getMaxHealth() { - return maxAttributes.get(0); + synchronized (maxAttributes) { + return maxAttributes.get(0); + } } public int getBaseHealth() { - return baseAttributes.get(0); + synchronized (baseAttributes) { + return baseAttributes.get(0); + } } public int getAction() { - return attributes.get(2); + synchronized (attributes) { + return attributes.get(2); + } } public int getMaxAction() { - return attributes.get(2); + synchronized (maxAttributes) { + return maxAttributes.get(2); + } } public int getBaseAction() { - return attributes.get(2); + synchronized (baseAttributes) { + return baseAttributes.get(2); + } + } + + public int getMind() { + synchronized (attributes) { + return attributes.get(4); + } + } + + public int getMaxMind() { + synchronized (maxAttributes) { + return maxAttributes.get(4); + } + } + + public int getBaseMind() { + synchronized (baseAttributes) { + return baseAttributes.get(4); + } } public void addAbility(String abilityName){ abilities.put(abilityName, 1); }//TODO: Figure out what the integer value should be for each ability @@ -728,6 +758,13 @@ public class CreatureObject extends TangibleObject { } } + public void modifyHealth(int mod) { + synchronized(attributes) { + attributes.set(0, getHealth() + mod); + attributes.sendDeltaMessage(this); + } + } + public void setMaxHealth(int maxHealth) { synchronized(maxAttributes) { maxAttributes.set(0, maxHealth); @@ -742,6 +779,13 @@ public class CreatureObject extends TangibleObject { } } + public void modifyAction(int mod) { + synchronized(attributes) { + attributes.set(2, getAction() + mod); + attributes.sendDeltaMessage(this); + } + } + public void setMaxAction(int maxAction) { synchronized(maxAttributes) { maxAttributes.set(2, maxAction); @@ -749,12 +793,33 @@ public class CreatureObject extends TangibleObject { } } + public void setMind(int mind) { + synchronized(attributes) { + attributes.set(4, mind); + attributes.sendDeltaMessage(this); + } + } + + public void modifyMind(int mod) { + synchronized(attributes) { + attributes.set(4, getMind() + mod); + attributes.sendDeltaMessage(this); + } + } + + public void setMaxMind(int maxMind) { + synchronized(maxAttributes) { + maxAttributes.set(4, maxMind); + maxAttributes.sendDeltaMessage(this); + } + } + private void initMaxAttributes() { maxAttributes.add(0, 1000); // Health maxAttributes.add(1, 0); maxAttributes.add(2, 300); // Action maxAttributes.add(3, 0); - maxAttributes.add(4, 300); // ?? + maxAttributes.add(4, 300); // Mind maxAttributes.add(5, 0); maxAttributes.clearDeltaQueue(); } @@ -764,7 +829,7 @@ public class CreatureObject extends TangibleObject { attributes.add(1, 0); attributes.add(2, 300); // Action attributes.add(3, 0); - attributes.add(4, 300); // ?? + attributes.add(4, 300); // Mind attributes.add(5, 0); attributes.clearDeltaQueue(); } @@ -774,7 +839,7 @@ public class CreatureObject extends TangibleObject { baseAttributes.add(1, 0); baseAttributes.add(2, 300); // Action baseAttributes.add(3, 0); - baseAttributes.add(4, 300); // ?? + baseAttributes.add(4, 300); // Mind baseAttributes.add(5, 0); baseAttributes.clearDeltaQueue(); } @@ -901,7 +966,7 @@ public class CreatureObject extends TangibleObject { bb.addInt(levelHealthGranted); // 9 bb.addAscii(animation); // 10 bb.addAscii(moodAnimation); // 11 - bb.addLong(equippedWeaponId); // 12 + bb.addLong(equippedWeapon == null ? 0 : equippedWeapon.getObjectId()); // 12 bb.addLong(groupId); // 13 bb.addObject(inviterData); // TODO: Check structure -- 14 bb.addInt(guildId); // 15 @@ -980,7 +1045,7 @@ public class CreatureObject extends TangibleObject { levelHealthGranted = buffer.getInt(); animation = buffer.getAscii(); moodAnimation = buffer.getAscii(); - equippedWeaponId = buffer.getLong(); + long weaponId = buffer.getLong(); groupId = buffer.getLong(); inviterData = buffer.getEncodable(GroupInviterData.class); guildId = buffer.getInt(); @@ -1003,6 +1068,13 @@ public class CreatureObject extends TangibleObject { buffer.getBoolean(); appearanceList = buffer.getSwgList(6, 33, Equipment.class); buffer.getLong(); + equippedWeapon = null; + for (Equipment e : equipmentList) { + if (e.getObjectId() == weaponId && e.getWeapon() instanceof WeaponObject) { + equippedWeapon = (WeaponObject) e.getWeapon(); + break; + } + } } } diff --git a/src/resources/objects/tangible/TangibleObject.java b/src/resources/objects/tangible/TangibleObject.java index b4020034e..8b68ec806 100644 --- a/src/resources/objects/tangible/TangibleObject.java +++ b/src/resources/objects/tangible/TangibleObject.java @@ -36,9 +36,12 @@ import network.packets.swg.zone.baselines.Baseline.BaselineType; import resources.PvpFaction; import resources.PvpFlag; import resources.PvpStatus; +import resources.collections.SWGMap; +import resources.collections.SWGSet; import resources.network.BaselineBuilder; import resources.network.NetBuffer; import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; import resources.player.Player; import utilities.Encoder.StringType; @@ -47,7 +50,6 @@ public class TangibleObject extends SWGObject { private static final long serialVersionUID = 1L; private byte [] appearanceData = new byte[0]; - private int damageTaken = 0; private int maxHitPoints = 0; private int components = 0; private boolean inCombat = false; @@ -55,10 +57,14 @@ public class TangibleObject extends SWGObject { private int pvpFlags = 0; private PvpStatus pvpStatus = PvpStatus.COMBATANT; private PvpFaction pvpFaction = PvpFaction.NEUTRAL; - private boolean visibleGmOnly = false; + private boolean visibleGmOnly = true; private byte [] objectEffects = new byte[0]; private int optionFlags = 0; + private SWGSet defenders = new SWGSet<>(6, 3); + + private SWGMap effectsMap = new SWGMap<>(6, 7); + public TangibleObject(long objectId) { super(objectId, BaselineType.TANO); addOptionFlags(OptionFlag.INVULNERABLE); @@ -72,16 +78,16 @@ public class TangibleObject extends SWGObject { pvpStatus = PvpStatus.COMBATANT; pvpFaction = PvpFaction.NEUTRAL; ois.defaultReadObject(); + effectsMap = new SWGMap<>(6, 7); + defenders.clear(); + defenders.resetUpdateCount(); + inCombat = false; } public byte [] getAppearanceData() { return appearanceData; } - public int getDamageTaken() { - return damageTaken; - } - public int getMaxHitPoints() { return maxHitPoints; } @@ -152,12 +158,9 @@ public class TangibleObject extends SWGObject { this.appearanceData = appearanceData; } - public void setDamageTaken(int damageTaken) { - this.damageTaken = damageTaken; - } - public void setMaxHitPoints(int maxHitPoints) { this.maxHitPoints = maxHitPoints; + sendDelta(3, 11, maxHitPoints); } public void setComponents(int components) { @@ -166,6 +169,7 @@ public class TangibleObject extends SWGObject { public void setInCombat(boolean inCombat) { this.inCombat = inCombat; + sendDelta(6, 2, inCombat); } public void setCondition(int condition) { @@ -219,7 +223,22 @@ public class TangibleObject extends SWGObject { return passCount == options.length; } - + + public void addDefender(CreatureObject creature) { + if (defenders.add(creature.getObjectId())) + defenders.sendDeltaMessage(this); + } + + public void removeDefender(CreatureObject creature) { + if (defenders.remove(creature.getObjectId())) + defenders.sendDeltaMessage(this); + } + + public void clearDefenders() { + defenders.clear(); + defenders.sendDeltaMessage(this); + } + @Override public boolean equals(Object o) { return super.equals(o); @@ -238,12 +257,12 @@ public class TangibleObject extends SWGObject { bb.addInt(pvpStatus.getValue()); // Faction Status - 5 bb.addArray(appearanceData); // - 6 bb.addInt(0); // Component customization (Set, Integer) - 7 - bb.addInt(0); //updates + bb.addInt(0); bb.addInt(optionFlags); // 8 bb.addInt(0); // Generic Counter -- use count and incap timer - 9 bb.addInt(condition); // 10 - bb.addInt(100); // maxHitPoints - 11 - bb.addBoolean(true); // isVisible - 12 + bb.addInt(maxHitPoints); // maxHitPoints - 11 + bb.addBoolean(visibleGmOnly); // isVisible - 12 bb.incrementOperandCount(9); } @@ -253,15 +272,13 @@ public class TangibleObject extends SWGObject { if (getStringId().toString().equals("@obj_n:unknown_object")) return; bb.addBoolean(inCombat); // 2 - Combat flag - bb.addInt(0); // 3 - Defenders List (Set, Long) - bb.addInt(0); + bb.addObject(defenders); // 3 - Defenders List (Set, Long) bb.addInt(0); // 4 - Map color bb.addInt(0); // 5 - Access List bb.addInt(0); bb.addInt(0); // 6 - Guild Access Set bb.addInt(0); - bb.addInt(0); // 7 - Effects Map - bb.addInt(0); + bb.addObject(effectsMap); // 7 - Effects Map bb.incrementOperandCount(6); } @@ -277,8 +294,8 @@ public class TangibleObject extends SWGObject { optionFlags = buffer.getInt(); buffer.getInt(); condition = buffer.getInt(); - buffer.getInt(); - buffer.getBoolean(); + maxHitPoints = buffer.getInt(); + visibleGmOnly = buffer.getBoolean(); } protected void parseBaseline6(NetBuffer buffer) { @@ -286,11 +303,11 @@ public class TangibleObject extends SWGObject { if (getStringId().toString().equals("@obj_n:unknown_object")) return; inCombat = buffer.getBoolean(); - buffer.getSwgSet(6, 3, Long.TYPE); + defenders = buffer.getSwgSet(6, 3, Long.TYPE); buffer.getInt(); buffer.getSwgSet(6, 5, StringType.ASCII); buffer.getSwgSet(6, 6, StringType.ASCII); - buffer.getSwgMap(6, 7, StringType.ASCII, StringType.ASCII); + effectsMap = buffer.getSwgMap(6, 7, StringType.ASCII, StringType.ASCII); } @Override diff --git a/src/services/combat/CombatService.java b/src/services/combat/CombatService.java new file mode 100644 index 000000000..46efde770 --- /dev/null +++ b/src/services/combat/CombatService.java @@ -0,0 +1,235 @@ +package services.combat; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import network.packets.swg.zone.object_controller.combat.CombatAction; +import intents.chat.ChatBroadcastIntent; +import intents.chat.ChatCommandIntent; +import resources.combat.AttackInfoLight; +import resources.combat.AttackType; +import resources.combat.CombatStatus; +import resources.combat.HitLocation; +import resources.combat.TrailLocation; +import resources.commands.CombatCommand; +import resources.control.Intent; +import resources.control.Service; +import resources.encodables.ProsePackage; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; +import resources.objects.weapon.WeaponObject; +import resources.server_info.Log; +import utilities.Scripts; +import utilities.ThreadUtilities; + +public class CombatService extends Service { + + private ScheduledExecutorService executor; + + private Map inCombat; + + public CombatService() { + inCombat = new HashMap<>(); + } + + @Override + public boolean initialize() { + registerForIntent(ChatCommandIntent.TYPE); + executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("combat-service")); + return super.initialize(); + } + + @Override + public boolean start() { + executor.scheduleAtFixedRate(() -> periodicChecks(), 0, 5, TimeUnit.SECONDS); + return super.start(); + } + + @Override + public boolean terminate() { + executor.shutdownNow(); + try { + executor.awaitTermination(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + + } + return super.terminate(); + } + + @Override + public void onIntentReceived(Intent i) { + if (i instanceof ChatCommandIntent) + processChatCommand((ChatCommandIntent) i); + } + + private void periodicChecks() { + Set removeSet = new HashSet<>(); + synchronized (inCombat) { + for (CombatCreature combat : inCombat.values()) { + if (combat.getTimeSinceCombat() >= 10E3) { // 10 sec + removeSet.add(combat); + } + } + } + for (CombatCreature combat : removeSet) + removeFromCombat(combat); + } + + private void removeFromCombat(CombatCreature combat) { + synchronized (inCombat) { + inCombat.remove(combat.getCreature().getObjectId()); + } + combat.getCreature().setInCombat(false); + combat.getCreature().clearDefenders(); + } + + 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 AttackInfoLight) + doCombat(cci.getSource(), cci.getTarget(), (AttackInfoLight) res, c); + else { + Log.w(this, "Unknown return from combat script: " + res); + return; + } + } + + private void updateCombatList(CreatureObject creature) { + CombatCreature combat; + synchronized (inCombat) { + combat = inCombat.get(creature.getObjectId()); + } + if (combat == null) { + combat = new CombatCreature(creature); + synchronized (inCombat) { + inCombat.put(creature.getObjectId(), combat); + } + } + combat.updateLastCombat(); + } + + private void doCombat(CreatureObject source, SWGObject target, AttackInfoLight info, CombatCommand command) { + CombatAction action = new CombatAction(source.getObjectId()); + action.setAttacker(source); + action.setClientEffectId((byte) 0); + action.setActionCrc(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()); + } + source.sendObserversAndSelf(action); + } + + private void doCombatSingle(CreatureObject source, CreatureObject target, AttackInfoLight info, CombatCommand command) { + if (!source.isInCombat()) + source.setInCombat(true); + if (!target.isInCombat()) + target.setInCombat(true); + target.addDefender(source); + // Note: This will not kill anyone + if (target.getHealth() <= info.getDamage()) + target.setHealth(target.getMaxHealth()); + else + target.modifyHealth(-info.getDamage()); + } + + private boolean handleStatus(CreatureObject source, CombatStatus status) { + switch (status) { + case SUCCESS: + return true; + case NO_TARGET: + new ChatBroadcastIntent(source.getOwner(), new ProsePackage("combat_effects", "target_invalid_fly")).broadcast(); + return false; + case TOO_FAR: + new ChatBroadcastIntent(source.getOwner(), new ProsePackage("combat_effects", "range_too_far")).broadcast(); + return false; + default: + new ChatBroadcastIntent(source.getOwner(), new ProsePackage("combat_effects", "cant_attack_fly")).broadcast(); + Log.e(this, "Character unable to attack. Player: %s Reason: %s", source.getName(), status); + return false; + } + } + + private CombatStatus canPerform(CreatureObject source, SWGObject target, CombatCommand c) { + if (source.getEquippedWeapon() == null) + return CombatStatus.NO_WEAPON; + CombatStatus status; + switch (c.getAttackType()) { + case AREA: + case TARGET_AREA: + status = canPerformArea(source, c); + break; + case SINGLE_TARGET: + status = canPerformSingle(source, target, c); + break; + default: + status = CombatStatus.UNKNOWN; + break; + } + 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)) + 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)) + return CombatStatus.TOO_FAR; + return CombatStatus.SUCCESS; + } + + private CombatStatus canPerformArea(CreatureObject source, CombatCommand c) { + return CombatStatus.SUCCESS; + } + + private static class CombatCreature { + + private final CreatureObject creature; + private long lastCombat; + + public CombatCreature(CreatureObject creature) { + this.creature = creature; + } + + public void updateLastCombat() { + lastCombat = System.nanoTime(); + } + + public double getTimeSinceCombat() { + return (System.nanoTime() - lastCombat) / 1E6; + } + + public CreatureObject getCreature() { + return creature; + } + + } + +} diff --git a/src/services/commands/CommandService.java b/src/services/commands/CommandService.java index dbda38024..0eba98151 100644 --- a/src/services/commands/CommandService.java +++ b/src/services/commands/CommandService.java @@ -30,12 +30,15 @@ package services.commands; import intents.chat.ChatBroadcastIntent; import intents.chat.ChatCommandIntent; import intents.network.GalacticPacketIntent; - - import network.packets.Packet; +import network.packets.swg.zone.object_controller.CommandQueueDequeue; import network.packets.swg.zone.object_controller.CommandQueueEnqueue; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; +import resources.combat.AttackType; +import resources.combat.DamageType; +import resources.combat.ValidTarget; +import resources.commands.CombatCommand; import resources.commands.Command; import resources.commands.ICmdCallback; import resources.commands.callbacks.*; @@ -75,6 +78,7 @@ public class CommandService extends Service { @Override public boolean initialize() { loadBaseCommands(); + loadCombatCommands(); registerCallbacks(); return super.initialize(); } @@ -108,7 +112,14 @@ public class CommandService extends Service { } executeCommand(galacticManager, player, command, target, request.getArguments()); - new ChatCommandIntent(player.getCreatureObject(), request.getTargetId(), command, arguments).broadcast(); + new ChatCommandIntent(player.getCreatureObject(), target, command, arguments).broadcast(); + + CommandQueueDequeue dequeue = new CommandQueueDequeue(player.getCreatureObject().getObjectId()); + dequeue.setCounter(request.getCounter()); + dequeue.setAction(0); + dequeue.setError(0); + dequeue.setTimer(0); + player.sendPacket(dequeue); } private void executeCommand(GalacticManager galacticManager, Player player, Command command, SWGObject target, String args) { @@ -168,29 +179,70 @@ public class CommandService extends Service { DatatableData baseCommands = (DatatableData) ClientFactory.getInfoFromFile("datatables/command/"+table+".iff"); int godLevel = baseCommands.getColumnFromName("godLevel"); - int combatCommand = baseCommands.getColumnFromName("addToCombatQueue"); for (int row = 0; row < baseCommands.getRowCount(); row++) { Object [] cmdRow = baseCommands.getRow(row); - + Command command = new Command((String) cmdRow[0]); command.setCrc(CRC.getCrc(command.getName().toLowerCase(Locale.ENGLISH))); command.setScriptHook((String) cmdRow[2]); command.setCppHook((String)cmdRow[4]); command.setDefaultTime((float) cmdRow[6]); command.setCharacterAbility((String) cmdRow[7]); - if (combatCommand != -1) - command.setCombatCommand((Boolean) cmdRow[combatCommand]); - else - command.setCombatCommand(false); - + command.setCombatCommand(false); + if(godLevel >= 0){ command.setGodLevel((int) cmdRow[godLevel]); } - + addCommand(command); } } + private CombatCommand createAsCombatCommand(Command c) { + CombatCommand cc = new CombatCommand(c.getName()); + cc.setCrc(c.getCrc()); + cc.setScriptHook(c.getScriptHook()); + cc.setCppHook(c.getScriptHook()); + cc.setDefaultTime(c.getDefaultTime()); + cc.setCharacterAbility(c.getCharacterAbility()); + cc.setGodLevel(c.getGodLevel()); + cc.setCombatCommand(true); + return cc; + } + + private void loadCombatCommands() { + DatatableData combatCommands = (DatatableData) ClientFactory.getInfoFromFile("datatables/combat/combat_data.iff"); + // validTarget forcesCharacterIntoCombat attackType healthCost actionCost damageType ignore_distance pvp_only attack_rolls + int validTarget = combatCommands.getColumnFromName("validTarget"); + int forceCombat = combatCommands.getColumnFromName("forcesCharacterIntoCombat"); + int attackType = combatCommands.getColumnFromName("attackType"); + int healthCost = combatCommands.getColumnFromName("healthCost"); + int actionCost = combatCommands.getColumnFromName("actionCost"); + int damageType = combatCommands.getColumnFromName("damageType"); + int ignoreDistance = combatCommands.getColumnFromName("ignore_distance"); + int pvpOnly = combatCommands.getColumnFromName("pvp_only"); + int attackRolls = combatCommands.getColumnFromName("attack_rolls"); + for (int row = 0; row < combatCommands.getRowCount(); row++) { + Object [] cmdRow = combatCommands.getRow(row); + + Command c = commands.get(CRC.getCrc(((String) cmdRow[0]).toLowerCase(Locale.ENGLISH))); + if (c == null) + continue; + CombatCommand cc = createAsCombatCommand(c); + commands.remove(c.getCrc()); + cc.setValidTarget(ValidTarget.getValidTarget((Integer) cmdRow[validTarget])); + cc.setForceCombat(((int) cmdRow[forceCombat]) != 0); + cc.setAttackType(AttackType.getAttackType((Integer) cmdRow[attackType])); + cc.setHealthCost((float) cmdRow[healthCost]); + cc.setActionCost((float) cmdRow[actionCost]); + cc.setDamageType(DamageType.getDamageType((Integer) cmdRow[damageType])); + cc.setIgnoreDistance(((int) cmdRow[ignoreDistance]) != 0); + cc.setPvpOnly(((int) cmdRow[pvpOnly]) != 0); + cc.setAttackRolls((int) cmdRow[attackRolls]); + addCommand(cc); + } + } + private Command registerCallback(String command, Class callback) { Command comand = getCommand(command); registerCallback(comand, callback); diff --git a/src/services/dev/DeveloperService.java b/src/services/dev/DeveloperService.java new file mode 100644 index 000000000..84f6b14b1 --- /dev/null +++ b/src/services/dev/DeveloperService.java @@ -0,0 +1,36 @@ +package services.dev; + +import intents.object.ObjectCreatedIntent; +import resources.Location; +import resources.PvpFlag; +import resources.Terrain; +import resources.control.Service; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; +import services.objects.ObjectCreator; + +public class DeveloperService extends Service { + + public DeveloperService() { + + } + + @Override + public boolean start() { + setupDeveloperArea(); + return super.start(); + } + + private void setupDeveloperArea() { + CreatureObject dummy = (CreatureObject) spawnObject("object/mobile/shared_target_dummy_blacksun.iff", new Location(3500, 5, -4800, Terrain.DEV_AREA)); + dummy.setPvpFlags(PvpFlag.ATTACKABLE); + } + + private SWGObject spawnObject(String template, Location l) { + SWGObject obj = ObjectCreator.createObjectFromTemplate(template); + obj.setLocation(l); + new ObjectCreatedIntent(obj).broadcast(); + return obj; + } + +} diff --git a/src/services/galaxy/GalacticManager.java b/src/services/galaxy/GalacticManager.java index 2db4caaa6..41a6373ef 100644 --- a/src/services/galaxy/GalacticManager.java +++ b/src/services/galaxy/GalacticManager.java @@ -38,6 +38,7 @@ import resources.control.Intent; import resources.control.Manager; import services.CoreManager; import services.chat.ChatManager; +import services.dev.DeveloperService; import services.galaxy.travel.TravelService; import services.objects.ObjectManager; import services.objects.UniformBoxService; @@ -50,6 +51,7 @@ public class GalacticManager extends Manager { private final GameManager gameManager; private final ChatManager chatManager; private final TravelService travelService; + private final DeveloperService developerService; private final UniformBoxService uniformBox; private final Map prevIntentMap; @@ -59,6 +61,7 @@ public class GalacticManager extends Manager { gameManager = new GameManager(); chatManager = new ChatManager(); travelService = new TravelService(objectManager); + developerService = new DeveloperService(); uniformBox = new UniformBoxService(); prevIntentMap = new Hashtable<>(); @@ -67,6 +70,7 @@ public class GalacticManager extends Manager { addChildService(gameManager); addChildService(chatManager); addChildService(travelService); + addChildService(developerService); addChildService(uniformBox); registerForIntent(InboundPacketIntent.TYPE); diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index 792ecb043..bd364bcb1 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -29,6 +29,7 @@ package services.galaxy; import resources.control.Manager; import services.collections.CollectionBadgeManager; +import services.combat.CombatService; import services.commands.CommandService; import services.commands.EntertainmentService; import services.faction.FactionService; @@ -47,6 +48,7 @@ public class GameManager extends Manager { //private final GroupService groupService; private final SkillModService skillModService; private final EntertainmentService entertainmentService; + private final CombatService combatService; public GameManager() { commandService = new CommandService(); @@ -59,6 +61,7 @@ public class GameManager extends Manager { // groupService = new GroupService(); skillModService = new SkillModService(); entertainmentService = new EntertainmentService(); + combatService = new CombatService(); addChildService(commandService); addChildService(connectionService); @@ -70,5 +73,6 @@ public class GameManager extends Manager { // addChildService(groupService); addChildService(skillModService); addChildService(entertainmentService); + addChildService(combatService); } } diff --git a/src/services/player/CharacterCreationService.java b/src/services/player/CharacterCreationService.java index a587bb533..cf1271b3d 100644 --- a/src/services/player/CharacterCreationService.java +++ b/src/services/player/CharacterCreationService.java @@ -396,7 +396,7 @@ public class CharacterCreationService extends Service { WeaponObject defWeapon = (WeaponObject) createInventoryObject(objManager, creatureObj, "object/weapon/melee/unarmed/shared_unarmed_default_player.iff"); defWeapon.setMaxRange(5); - creatureObj.setEquippedWeaponId(defWeapon.getObjectId()); + creatureObj.setEquippedWeapon(defWeapon); creatureObj.addEquipment(createInventoryObject(objManager, creatureObj, "object/tangible/inventory/shared_character_inventory.iff")); creatureObj.addEquipment(createInventoryObject(objManager, creatureObj, "object/tangible/datapad/shared_character_datapad.iff")); creatureObj.addEquipment(createInventoryObject(objManager, creatureObj, "object/tangible/inventory/shared_appearance_inventory.iff"));