Been writing a basic base for a combat system that I might use in a framework.
This is by no means done and something can be approached differently and more efficiently. I've been working on this combat base for aprox 3 hours now but kinda ran dead into it. Thought i'd release the base here, as stated already it's by no means superior but it is a different approach to what i've seen over the years, so someone might benefit from it.
NOTE: THERE'S ALOT OF CLASSES
Code:
package com.game.character.combat;
import java.util.Objects;
import com.game.character.Character;
import com.game.character.combat.impl.MagicCombatAction;
import com.game.character.combat.impl.MeleeCombatAction;
import com.game.character.combat.impl.RangedCombatAction;
/**
*
* @author kronn
*
* Representive of combat management
*
*/
public class Combat {
/**
* The character
*/
private final Character character;
/**
* Combat constructor
*
* @param character
* the character
*/
public Combat(final Character character) {
this.character = character;
}
/**
* Executes the combat action
*/
public void execute() {
final CombatBuilder bldr = character.getCombatBuilder();
if (character.getHitpoints() <= 0 || bldr.getVictim().getHitpoints() <= 0) {
reset();
return;
}
bldr.setAttackTimer(5);
CombatAction combat_action = null;
if (bldr.getAttackType() == AttackType.MELEE) {
combat_action = new MeleeCombatAction();
} else if (bldr.getAttackType() == AttackType.RANGED) {
combat_action = new RangedCombatAction(1);
} else if (bldr.getAttackType() == AttackType.MAGIC) {
combat_action = new MagicCombatAction(1);
}
if (Objects.isNull(combat_action)) {
reset();
return;
}
bldr.getContainer().getInitAnimation().ifPresent(bldr.getContainer().getCharacter()::playAnimation);
bldr.getContainer().getInitGraphic().ifPresent(bldr.getContainer().getCharacter()::playGraphics);
combat_action.execute(bldr.getContainer());
}
/**
* Resets the character's combat
*/
private void reset() {
final CombatBuilder bldr = new CombatBuilder();
if (bldr != null) {
bldr.setAttackType(null);
bldr.setVictim(null);
bldr.setAttackTimer(-1);
}
character.setInteractingCharacter(null);
}
/**
* The combat sequence
*/
public void sequence() {
if (character.getCombatBuilder().getAttackTimer() > 0) {
character.getCombatBuilder().decrementAttackTimer();
}
if (character.getCombatBuilder().getAttackTimer() <= 0 && character.getCombatBuilder().getVictim() != null) {
execute();
}
}
}
Code:
package com.game.character.combat;
import com.game.character.Character;
/**
*
* @author kronn
*
* Representive of the combat container
*
*/
public class CombatBuilder {
/**
* The character's attack timer
*/
private int attackTimer;
/**
* The character's victim
*/
private Character victim;
/**
* The character's attack type
*/
private AttackType attackType;
/**
* The character's combat container
*/
private CombatActionContainer container;
/**
* @return the attackTimer
*/
public int getAttackTimer() {
return attackTimer;
}
/**
* @param attackTimer
* the attackTimer to set
*/
public void setAttackTimer(int attackTimer) {
this.attackTimer = attackTimer;
}
/**
* Decrements the attack timer
*/
public void decrementAttackTimer() {
this.attackTimer--;
}
/**
* @return the victim
*/
public Character getVictim() {
return victim;
}
/**
* @param victim
* the victim to set
*/
public void setVictim(Character victim) {
this.victim = victim;
}
/**
* @return the attackType
*/
public AttackType getAttackType() {
return attackType;
}
/**
* @param attackType
* the attackType to set
*/
public void setAttackType(AttackType attackType) {
this.attackType = attackType;
}
/**
* @return the container
*/
public CombatActionContainer getContainer() {
return container;
}
/**
* @param container
* the container to set
*/
public void setContainer(CombatActionContainer container) {
this.container = container;
}
}
Code:
package com.game.character.combat;
import java.util.Optional;
import com.game.character.Animation;
import com.game.character.Character;
import com.game.character.Graphic;
/**
*
* @author kronn
*
* Representive of combat action container
*
*/
public class CombatActionContainer {
/**
* @param character
* @param victim
* @param initAnimation
* @param initGraphic
* @param projectileId
* @param finishGraphic
*/
public CombatActionContainer(Character character, Character victim, Optional<Animation> initAnimation,
Optional<Graphic> initGraphic, Optional<Integer> projectileId, final Optional<Graphic> finishGraphic) {
this.character = character;
this.victim = victim;
this.initAnimation = initAnimation;
this.initGraphic = initGraphic;
this.projectileId = projectileId;
this.finishGraphic = finishGraphic;
}
/**
* The character
*/
private final Character character;
/**
* The {@link #character}'s victim
*/
private final Character victim;
/**
* The action's init animation
*/
private final Optional<Animation> initAnimation;
/**
* The action's initiation graphic
*/
private final Optional<Graphic> initGraphic;
/**
* The action's projectile id
*/
private final Optional<Integer> projectileId;
/**
* The action's finish graphic
*/
private final Optional<Graphic> finishGraphic;
/**
* @return the character
*/
public Character getCharacter() {
return character;
}
/**
* @return the victim
*/
public Character getVictim() {
return victim;
}
/**
* @return the initAnimation
*/
public Optional<Animation> getInitAnimation() {
return initAnimation;
}
/**
* @return the initGraphic
*/
public Optional<Graphic> getInitGraphic() {
return initGraphic;
}
/**
* @return the projectileId
*/
public Optional<Integer> getProjectileId() {
return projectileId;
}
/**
* @return the finishGraphic
*/
public Optional<Graphic> getFinishGraphic() {
return finishGraphic;
}
}
Code:
package com.game.character.combat;
import com.game.character.player.Player;
/**
*
* @author kronn
*
* An abstract class that represents combat actions
*
*/
public abstract class CombatAction {
/**
* Execution of the combat action
*
* @param attacker
* the attacker
* @param victim
* the victim
*/
public abstract void execute(CombatActionContainer container);
/**
* Adds experience to the player character
*
* @param character
* the character
* @param damage
* the damage
*/
public abstract void addExperience(final Player player, final int damage);
}
Code:
package com.game.character.combat;
/**
*
* @author kronn
*
* An enum representive of various attack types
*
*/
public enum AttackType {
MELEE, RANGED, MAGIC
}
Code:
package com.game.character.combat.damage;
import java.util.Optional;
import com.game.character.combat.AttackType;
/**
*
* @author kronn
*
* Representive of damage
*
*/
public class Damage {
/**
* @param count
* @param type
* @param cycles
* @param damageEffect
*/
public Damage(int count, AttackType type, int cycles, Optional<DamageEffect> damageEffect) {
this.count = count;
this.type = type;
this.cycles = cycles;
this.damageEffect = damageEffect;
}
/**
* The count of the damage
*/
private int count;
/**
* The type of damage
*/
private AttackType type;
/**
* The amount of cycles before the damage appears to the character
*/
private int cycles;
/**
* The damage action
*/
private Optional<DamageEffect> damageEffect;
/**
* @return the count
*/
public int getCount() {
return count;
}
/**
* @return the type
*/
public AttackType getType() {
return type;
}
/**
* @return the cycles
*/
public int getCycles() {
return cycles;
}
/**
* @return the damageEffect
*/
public Optional<DamageEffect> getDamageAction() {
return damageEffect;
}
/**
* @param count
* the count to set
*/
public void setCount(int count) {
this.count = count;
}
/**
* @param type
* the type to set
*/
public void setType(AttackType type) {
this.type = type;
}
/**
* @param cycles
* the cycles to set
*/
public void setCycles(int cycles) {
this.cycles = cycles;
}
}
Code:
package com.game.character.combat.damage;
import com.game.character.Character;
/**
*
* @author kronn
*
* An abstract class representive of damage effects
*
*/
public abstract class DamageEffect {
public abstract void execute(final Character character);
}
Code:
package com.game.character.combat.damage;
/**
*
* @author kronn
*
* An enum representive of various hitsplat icons
*
*/
public enum HitSplatIcon {
BLOCK((byte) 0), HIT((byte) 1), POISON((byte) 2), DISEASE((byte) 3);
/**
* @param clientImage
*/
private HitSplatIcon(byte clientImage) {
this.clientImage = clientImage;
}
/**
* @return the clientImage
*/
public byte getClientImage() {
return clientImage;
}
/**
* The id for the client image
*/
private final byte clientImage;
}
Code:
package com.game.character.combat.damage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Queue;
import com.game.character.Character;
import com.game.character.UpdateFlags.UpdateFlag;
/**
*
* @author kronn
*
* Representive of a character's damage queue
*
*/
public class DamageQueue {
/**
* The maximum amount of damage objects the damage queue can hold per cycle
*/
private static final int MAXIMUM_DAMAGE_PER_CYCLE = 4;
/**
* The character
*/
private final Character character;
/**
* Damage Queue constructor
*
* @param character
* the character
*/
public DamageQueue(final Character character) {
this.character = character;
}
/**
* The character's damage queue
*/
private final Queue<Damage> damageQueue = new LinkedList<>();
/**
* Submits a new damage object to the damage queue
*
* @param damage
* the damage object
*/
public void submit(final Damage damage) {
damageQueue.add(damage);
}
/**
* Polls damage objects from the damage queue
*/
public void poll() {
if (damageQueue.size() <= 0) {
return;
}
Collection<Damage> dmg_col = new ArrayList<Damage>();
int loop_count = damageQueue.size() > MAXIMUM_DAMAGE_PER_CYCLE ? MAXIMUM_DAMAGE_PER_CYCLE : damageQueue.size();
for (int index = 0; index < loop_count; index++) {
final Damage damage = damageQueue.poll();
if (damage != null) {
if (damage.getCycles() > 0) {
damage.setCycles(damage.getCycles() - 1);
dmg_col.add(damage);
} else if (damage.getCycles() <= 0) {
execute(damage);
}
}
}
if (dmg_col.size() > 0) {
damageQueue.addAll(dmg_col);
}
}
/**
* Executes the incoming damage
*
* @param damage
* the damage object
*/
public void execute(final Damage damage) {
if (character.getHitpoints() <= 0) {
return;
}
HitSplatIcon icon = null;
if (damage.getCount() <= 0) {
icon = HitSplatIcon.BLOCK;
} else if (damage.getCount() > 0) {
icon = HitSplatIcon.HIT;
}
if (!character.getUpdateFlags().get(UpdateFlag.HIT)) {
character.setHitMaskDamage(damage.getCount());
character.setHitIcon(icon);
character.getUpdateFlags().flag(UpdateFlag.HIT);
} else if (!character.getUpdateFlags().get(UpdateFlag.HIT_2)) {
character.setSecondaryHitMaskDamage(damage.getCount());
character.setSecondaryHitIcon(icon);
character.getUpdateFlags().flag(UpdateFlag.HIT_2);
}
if (!damage.getEffects().isEmpty()) {
damage.getEffects().forEach(effect -> effect.execute(character));
}
character.setHitpoints(character.getHitpoints() - damage.getCount());
if (character.getHitpoints() < 0) {
character.setHitpoints(0);
}
}
}
Code:
package com.game.character.combat.damage.effects;
import java.util.ArrayList;
import com.game.character.Character;
import com.game.character.combat.AttackType;
import com.game.character.combat.damage.Damage;
import com.game.character.combat.damage.DamageEffect;
import com.game.character.player.Player;
/**
*
* @author kronn
*
* Representive of ring effects
*
*/
public class RingEffect extends DamageEffect {
/**
* The id of the ring
*/
private final Rings ring;
/**
* The victim of the character
*/
private final Character victim;
/**
* The amount of damage
*/
private final int damage;
/**
* RingEffect constructor
*
* @param id
* the id of the ring
*/
public RingEffect(final Rings ring, final Character victim, final int damage) {
this.ring = ring;
this.victim = victim;
this.damage = damage;
}
@Override
public void execute(Character character) {
if (character instanceof Player) {
final Player player = (Player) character;
if (player != null) {
if (player.getEquipment().contains(ring.getId())) {
if (ring == Rings.RING_OF_RECOIL) {
final int rec_damage = (int) (damage * .25);
victim.getDamageQueue().submit(new Damage(rec_damage, AttackType.NON_SPECIFIED, 1, new ArrayList<>()));
}
}
}
}
}
public enum Rings {
RING_OF_RECOIL(2550), RING_OF_LIFE(2570);
/**
* @param id
*/
private Rings(int id) {
this.id = id;
}
/**
* @return the id
*/
public int getId() {
return id;
}
/**
* The id of the ring
*/
private final int id;
}
}
Example of combat action implementation
Code:
package com.game.character.combat.impl;
import java.util.Arrays;
import com.game.character.Character;
import com.game.character.combat.AttackType;
import com.game.character.combat.CombatAction;
import com.game.character.combat.CombatActionContainer;
import com.game.character.combat.damage.Damage;
import com.game.character.combat.damage.DamageEffect;
import com.game.character.combat.damage.effects.RingEffect;
import com.game.character.combat.damage.effects.RingEffect.Rings;
import com.game.character.player.Player;
public class RangedCombatAction extends CombatAction {
/**
* The distance to the victim
*/
private final int distanceToVictim;
/**
* RangedCombatAction constructor
*
* @param distanceToVictim
* the distance
*/
public RangedCombatAction(final int distanceToVictim) {
this.distanceToVictim = distanceToVictim;
}
@Override
public void execute(CombatActionContainer container) {
if (container.getProjectileId().isPresent()) {
final int id = container.getProjectileId().get();
}
int dmg = 10;
final Damage damage = new Damage(dmg, AttackType.RANGED, getHitDelay(distanceToVictim),
Arrays.asList(new RingEffect(Rings.RING_OF_RECOIL, container.getVictim(), dmg)));
if (damage != null) {
container.getVictim().getDamageQueue().submit(damage);
}
container.getFinishGraphic().ifPresent(container.getVictim()::playGraphics);
if (container.getCharacter() instanceof Player) {
addExperience((Player) container.getCharacter(), damage.getCount());
}
}
@Override
public void addExperience(Player player, int damage) {
// TODO add experience
}
/**
* Damage effects
*/
private final DamageEffect effects = new DamageEffect() {
@Override
public void execute(Character character) {
// TODO damage effects
}
};
/**
* Gets the hit delay
*
* @param distanceToVictim
* the distance to the attackers victim
* @return the hit delay
*/
private final int getHitDelay(final int distanceToVictim) {
switch (distanceToVictim) {
case 1:
case 2:
return 2;
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
return 3;
case 9:
case 10:
return 4;
}
return 1;
}
}
ITT: We don't messure cocks, if you got some constructive critism, feel free to post it.