diff --git a/src/resources/commands/CombatCommand.java b/src/resources/commands/CombatCommand.java index fb910ea6f..145fc20b6 100644 --- a/src/resources/commands/CombatCommand.java +++ b/src/resources/commands/CombatCommand.java @@ -61,6 +61,7 @@ public class CombatCommand extends Command { private float delayAttackInterval; private int delayAttackLoops; private DelayAttackEggPosition eggPosition; + private float coneLength; public CombatCommand(String name) { super(name); @@ -248,4 +249,12 @@ public class CombatCommand extends Command { this.eggPosition = eggPosition; } + public float getConeLength() { + return coneLength; + } + + public void setConeLength(float coneLength) { + this.coneLength = coneLength; + } + } diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index 79c925f91..b26ef8c07 100644 --- a/src/resources/objects/creature/CreatureObject.java +++ b/src/resources/objects/creature/CreatureObject.java @@ -798,6 +798,12 @@ public class CreatureObject extends TangibleObject { return items; } + public boolean isAttackable(CreatureObject otherObject) { + Posture otherPosture = otherObject.getPosture(); + + return isEnemy(otherObject) && otherPosture != Posture.INCAPACITATED && otherPosture != Posture.DEAD; + } + @Override public boolean isEnemy(TangibleObject otherObject) { boolean tangibleEnemy = super.isEnemy(otherObject); diff --git a/src/services/combat/CombatManager.java b/src/services/combat/CombatManager.java index de75ad496..d7f2e1110 100644 --- a/src/services/combat/CombatManager.java +++ b/src/services/combat/CombatManager.java @@ -49,6 +49,7 @@ import intents.object.ObjectCreatedIntent; import java.util.Iterator; import java.util.Random; import java.util.concurrent.Future; +import java.util.stream.Collectors; import network.packets.swg.zone.PlayClientEffectObjectMessage; import network.packets.swg.zone.object_controller.combat.CombatSpam; import resources.Location; @@ -67,6 +68,7 @@ import resources.encodables.ProsePackage; import resources.encodables.StringId; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; +import resources.objects.staticobject.StaticObject; import resources.objects.tangible.TangibleObject; import resources.objects.weapon.WeaponObject; import resources.server_info.Log; @@ -230,7 +232,7 @@ public class CombatManager extends Manager { // TODO implement support for remaining HitTypes switch (c.getHitType()) { - case ATTACK: handleAttack(source, target, c); break; + case ATTACK: handleAttack(source, target, null, c); break; case BUFF: handleBuff(source, target, c); break; case DELAY_ATTACK: handleDelayAttack(source, target, c, intent.getArguments()); break; default: handleStatus(source, CombatStatus.UNKNOWN); break; @@ -251,7 +253,7 @@ public class CombatManager extends Manager { combat.updateLastCombat(); } - private void handleAttack(CreatureObject source, SWGObject target, CombatCommand command) { + private void handleAttack(CreatureObject source, SWGObject target, SWGObject delayEgg, CombatCommand command) { if (!handleStatus(source, canPerform(source, target, command))) return; @@ -260,10 +262,10 @@ public class CombatManager extends Manager { for(int i = 0; i < command.getAttackRolls(); i++) { AttackInfo info = new AttackInfo(); - if (target instanceof CreatureObject) { - switch (command.getAttackType()) { - case SINGLE_TARGET: doCombatSingle(source, (CreatureObject) target, info, weapon, command); break; - } + switch (command.getAttackType()) { + case SINGLE_TARGET: doCombatSingle(source, target, info, weapon, command); break; + case AREA: doCombatArea(source, source, info, weapon, command, false); break; + case TARGET_AREA: doCombatArea(source, delayEgg != null ? delayEgg : target, info, weapon, command, true); break; // Same as AREA, but the target is the destination for the AoE and can take damage } } } @@ -315,7 +317,7 @@ public class CombatManager extends Manager { delayEgg.sendObservers(new PlayClientEffectObjectMessage(delayAttackParticle, "", delayEgg.getObjectId())); // Handle the attack of this loop - handleAttack(source, target, combatCommand); + handleAttack(source, target, delayEgg, combatCommand); if (currentLoop < combatCommand.getDelayAttackLoops()) { // Recursively schedule another loop if that wouldn't exceed the amount of loops we need to perform @@ -326,14 +328,32 @@ public class CombatManager extends Manager { } } - private void doCombatSingle(CreatureObject source, CreatureObject target, AttackInfo info, WeaponObject weapon, CombatCommand command) { + private void doCombatSingle(CreatureObject source, SWGObject target, AttackInfo info, WeaponObject weapon, CombatCommand command) { // TODO single target only defence rolls against target // TODO single target only offence rolls for source // TODO below logic should be in CommandService when target checks are implemented in there Set targets = new HashSet<>(); - targets.add(target); + if (target instanceof CreatureObject) + targets.add((CreatureObject) target); + + doCombat(source, targets, weapon, info, command); + } + + private void doCombatArea(CreatureObject source, SWGObject origin, AttackInfo info, WeaponObject weapon, CombatCommand command, boolean includeOrigin) { + float aoeRange = command.getConeLength(); + + Set targets = origin.getObjectsAware().stream() + .filter(target -> target instanceof CreatureObject) + .map(target -> (CreatureObject) target) + .filter(creature -> source.isAttackable(creature)) + .filter(creature -> origin.getLocation().distanceTo(creature.getLocation()) <= aoeRange) + .collect(Collectors.toSet()); + + // This way, mines or grenades won't try to harm themselves + if (includeOrigin && !(origin instanceof StaticObject) && !(origin instanceof WeaponObject)) + targets.add((CreatureObject) origin); doCombat(source, targets, weapon, info, command); } @@ -619,8 +639,7 @@ public class CombatManager extends Manager { } private CombatStatus canPerformArea(CreatureObject source, CombatCommand c) { - // TODO implement AoE - return CombatStatus.UNKNOWN; + return CombatStatus.SUCCESS; } private void addActionCost(CreatureObject source, CombatCommand command) { diff --git a/src/services/commands/CommandService.java b/src/services/commands/CommandService.java index 504c3f5b2..a45bb12a8 100644 --- a/src/services/commands/CommandService.java +++ b/src/services/commands/CommandService.java @@ -438,6 +438,7 @@ public class CommandService extends Service { int delayAttackInterval = combatCommands.getColumnFromName("delayAttackInterval"); int delayAttackLoops = combatCommands.getColumnFromName("delayAttackLoops"); int delayAttackEggPosition = combatCommands.getColumnFromName("delayAttackEggPosition"); + int coneLength = combatCommands.getColumnFromName("coneLength"); // 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 @@ -471,6 +472,7 @@ public class CommandService extends Service { cc.setDelayAttackInterval((float) cmdRow[delayAttackInterval]); cc.setDelayAttackLoops((int) cmdRow[delayAttackLoops]); cc.setEggPosition(DelayAttackEggPosition.getEggPosition((int) cmdRow[delayAttackEggPosition])); + cc.setConeLength((float) cmdRow[coneLength]); 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]));