From 2aec173e8058d1970ccbc11c2e4e3e87454c166e Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 5 Sep 2018 15:54:06 +0900 Subject: [PATCH] WIP Some battle logic has been implemented --- .../TurnBasedMinecraft/common/Battle.java | 275 +++++++++++++++++- .../TurnBasedMinecraft/common/Combatant.java | 4 + .../TurnBasedMinecraft/common/Config.java | 50 ++++ .../networking/PacketBattleDecision.java | 12 +- src/main/resources/TBM_Config.xml | 7 + 5 files changed, 335 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Battle.java b/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Battle.java index 526f5ae..3c2d068 100644 --- a/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Battle.java +++ b/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Battle.java @@ -18,8 +18,10 @@ import com.seodisparate.TurnBasedMinecraft.common.networking.PacketBattleInfo; import com.seodisparate.TurnBasedMinecraft.common.networking.PacketHandler; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.DamageSource; public class Battle { @@ -38,7 +40,7 @@ public class Battle public enum State { DECISION, - ATTACK, + ACTION, HEALTH_CHECK } @@ -96,6 +98,7 @@ public class Battle continue; } Combatant newCombatant = new Combatant(e, entityInfo); + newCombatant.isSideA = true; this.sideA.put(e.getEntityId(), newCombatant); if(e instanceof EntityPlayer) { @@ -115,6 +118,7 @@ public class Battle continue; } Combatant newCombatant = new Combatant(e, entityInfo); + newCombatant.isSideA = false; this.sideB.put(e.getEntityId(), newCombatant); if(e instanceof EntityPlayer) { @@ -154,6 +158,7 @@ public class Battle return; } Combatant newCombatant = new Combatant(e, entityInfo); + newCombatant.isSideA = true; sideA.put(e.getEntityId(), newCombatant); if(e instanceof EntityPlayer) { @@ -175,6 +180,7 @@ public class Battle return; } Combatant newCombatant = new Combatant(e, entityInfo); + newCombatant.isSideA = false; sideB.put(e.getEntityId(), newCombatant); if(e instanceof EntityPlayer) { @@ -237,19 +243,26 @@ public class Battle return combatant; } - public void setDecision(int entityID, Decision decision, int targetEntityID) + public void setDecision(int entityID, Decision decision, int targetIDOrItemID) { if(state != State.DECISION) { return; } Combatant combatant = players.get(entityID); - if(combatant == null) + if(combatant == null || combatant.decision != Decision.UNDECIDED) { return; } combatant.decision = decision; - combatant.targetEntityID = targetEntityID; + if(decision == Decision.ATTACK) + { + combatant.targetEntityID = targetIDOrItemID; + } + else if(decision == Decision.USE_ITEM) + { + combatant.itemToUse = targetIDOrItemID; + } undecidedCount.decrementAndGet(); } @@ -294,7 +307,7 @@ public class Battle timer = timer.minus(dt); if(timer.isNegative() || timer.isZero() || undecidedCount.get() <= 0) { - state = State.ATTACK; + state = State.ACTION; timer = TurnBasedMinecraftMod.BattleDecisionTime; turnOrderQueue.clear(); for(Combatant c : sideA.values()) @@ -306,16 +319,264 @@ public class Battle turnOrderQueue.add(c); } update(Duration.ZERO); + // TODO assign decisions to non-players } break; - case ATTACK: + case ACTION: + { Combatant next = turnOrderQueue.poll(); while(next != null) { - // TODO attack per entity here + if(!next.entity.isEntityAlive()) + { + next = turnOrderQueue.poll(); + continue; + } + switch(next.decision) + { + case UNDECIDED: + next = turnOrderQueue.poll(); + continue; + case ATTACK: + Combatant target = null; + if(next.entity instanceof EntityPlayer) + { + if(next.isSideA) + { + target = sideB.get(next.targetEntityID); + } + else + { + target = sideA.get(next.targetEntityID); + } + if(target == null || !target.entity.isEntityAlive()) + { + next = turnOrderQueue.poll(); + continue; + } + int hitChance = TurnBasedMinecraftMod.config.getPlayerAttackProbability(); + if(target.entity instanceof EntityPlayer) + { + hitChance -= TurnBasedMinecraftMod.config.getPlayerEvasion(); + } + else + { + hitChance -= target.entityInfo.evasion; + } + if((int)(Math.random() * 100) < hitChance) + { + if(target.remainingDefenses <= 0) + { + // attack + // TODO damage via bow and arrow + TurnBasedMinecraftMod.attackingEntity = next.entity; + ((EntityPlayer)next.entity).attackTargetEntityWithCurrentItem(target.entity); + TurnBasedMinecraftMod.attackingEntity = null; + if(!(target.entity instanceof EntityPlayer) && target.entityInfo.defenseDamage > 0) + { + if((int)(Math.random() * 100) < target.entityInfo.defenseDamageProbability) + { + // defense damage + DamageSource defenseDamageSource = DamageSource.causeMobDamage((EntityLivingBase)target.entity); + TurnBasedMinecraftMod.attackingEntity = target.entity; + next.entity.attackEntityFrom(defenseDamageSource, target.entityInfo.defenseDamage); + TurnBasedMinecraftMod.attackingEntity = null; + } + } + } + else + { + // blocked + --target.remainingDefenses; + } + } + else + { + // miss + } + } + else + { + if(next.isSideA) + { + int randomTargetIndex = (int)(Math.random() * sideB.size()); + for(Combatant c : sideB.values()) + { + if(randomTargetIndex-- == 0) + { + target = c; + break; + } + } + } + else + { + int randomTargetIndex = (int)(Math.random() * sideA.size()); + for(Combatant c : sideA.values()) + { + if(randomTargetIndex-- == 0) + { + target = c; + break; + } + } + } + if(target == null || !target.entity.isEntityAlive()) + { + next = turnOrderQueue.poll(); + continue; + } + int hitChance = next.entityInfo.attackProbability; + if(target.entity instanceof EntityPlayer) + { + hitChance -= TurnBasedMinecraftMod.config.getPlayerEvasion(); + } + else + { + hitChance -= target.entityInfo.evasion; + } + if((int)(Math.random() * 100) < hitChance) + { + if(target.remainingDefenses <= 0) + { + DamageSource damageSource = DamageSource.causeMobDamage((EntityLivingBase)next.entity); + int damageAmount = next.entityInfo.attackPower; + if(next.entityInfo.attackVariance > 0) + { + damageAmount += (int)(Math.random() * (next.entityInfo.attackVariance * 2 + 1)) - next.entityInfo.attackVariance; + } + // attack + TurnBasedMinecraftMod.attackingEntity = next.entity; + target.entity.attackEntityFrom(damageSource, next.entityInfo.attackPower); + TurnBasedMinecraftMod.attackingEntity = null; + if(!(target.entity instanceof EntityPlayer) && target.entityInfo.defenseDamage > 0) + { + if((int)(Math.random() * 100) < target.entityInfo.defenseDamageProbability) + { + // defense damage + DamageSource defenseDamageSource = DamageSource.causeMobDamage((EntityLivingBase)target.entity); + TurnBasedMinecraftMod.attackingEntity = target.entity; + next.entity.attackEntityFrom(defenseDamageSource, target.entityInfo.defenseDamage); + TurnBasedMinecraftMod.attackingEntity = null; + } + } + } + else + { + // blocked + --target.remainingDefenses; + } + } + else + { + // miss + } + } + break; + case DEFEND: + next.remainingDefenses = TurnBasedMinecraftMod.config.getDefenseDuration(); + break; + case FLEE: + int fastestEnemySpeed = 0; + if(next.isSideA) + { + for(Combatant c : sideB.values()) + { + if(c.entity instanceof EntityPlayer) + { + if(TurnBasedMinecraftMod.config.getPlayerSpeed() > fastestEnemySpeed) + { + fastestEnemySpeed = TurnBasedMinecraftMod.config.getPlayerSpeed(); + } + } + else + { + if(c.entityInfo.speed > fastestEnemySpeed) + { + fastestEnemySpeed = c.entityInfo.speed; + } + } + } + } + else + { + for(Combatant c : sideA.values()) + { + if(c.entity instanceof EntityPlayer) + { + if(TurnBasedMinecraftMod.config.getPlayerSpeed() > fastestEnemySpeed) + { + fastestEnemySpeed = TurnBasedMinecraftMod.config.getPlayerSpeed(); + } + } + else + { + if(c.entityInfo.speed > fastestEnemySpeed) + { + fastestEnemySpeed = c.entityInfo.speed; + } + } + } + } + int fleeProbability = 0; + if(next.entity instanceof EntityPlayer) + { + if(fastestEnemySpeed >= TurnBasedMinecraftMod.config.getPlayerSpeed()) + { + fleeProbability = TurnBasedMinecraftMod.config.getFleeBadProbability(); + } + else + { + fleeProbability = TurnBasedMinecraftMod.config.getFleeGoodProbability(); + } + } + else + { + if(fastestEnemySpeed >= next.entityInfo.speed) + { + fleeProbability = TurnBasedMinecraftMod.config.getFleeBadProbability(); + } + else + { + fleeProbability = TurnBasedMinecraftMod.config.getFleeGoodProbability(); + } + } + if((int)(Math.random() * 100) < fleeProbability) + { + // flee success + if(next.isSideA) + { + sideA.remove(next.entity.getEntityId()); + } + else + { + sideB.remove(next.entity.getEntityId()); + } + if(next.entity instanceof EntityPlayer) + { + players.remove(next.entity.getEntityId()); + playerCount.decrementAndGet(); + // TODO notify player exited battle + } + } + break; + case USE_ITEM: + break; + } next = turnOrderQueue.poll(); } + for(Combatant c : sideA.values()) + { + c.decision = Decision.UNDECIDED; + } + for(Combatant c : sideB.values()) + { + c.decision = Decision.UNDECIDED; + } + state = State.HEALTH_CHECK; + update(Duration.ZERO); break; + } case HEALTH_CHECK: // TODO break; diff --git a/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Combatant.java b/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Combatant.java index a25db37..efce8ba 100644 --- a/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Combatant.java +++ b/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Combatant.java @@ -18,11 +18,14 @@ public class Combatant public EntityInfo entityInfo; public boolean recalcSpeedOnCompare; public int targetEntityID; + public boolean isSideA; + public int remainingDefenses; public Combatant() { decision = Battle.Decision.UNDECIDED; recalcSpeedOnCompare = false; + remainingDefenses = 0; } public Combatant(Entity e, EntityInfo entityInfo) @@ -31,6 +34,7 @@ public class Combatant decision = Battle.Decision.UNDECIDED; this.entityInfo = entityInfo; recalcSpeedOnCompare = false; + remainingDefenses = 0; } /** diff --git a/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Config.java b/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Config.java index 1e24996..1eb2325 100644 --- a/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Config.java +++ b/src/main/java/com/seodisparate/TurnBasedMinecraft/common/Config.java @@ -30,6 +30,11 @@ public class Config private int playerSpeed; private int playerHasteSpeed; private int playerSlowSpeed; + private int playerAttackProbability = 100; + private int playerEvasion = 10; + private int defenseDuration = 1; + private int fleeGoodProbability = 90; + private int fleeBadProbability = 40; private enum ConfigParseResult { @@ -174,9 +179,29 @@ public class Config { playerSlowSpeed = Integer.parseInt(xmlReader.getElementText()); } + else if(xmlReader.getLocalName().equals("AttackProbability")) + { + playerAttackProbability = Integer.parseInt(xmlReader.getElementText()); + } + else if(xmlReader.getLocalName().equals("Evasion")) + { + playerEvasion = Integer.parseInt(xmlReader.getElementText()); + } } } while(!(xmlReader.isEndElement() && xmlReader.getLocalName().equals("PlayerStats"))); } + else if(xmlReader.getLocalName().equals("DefenseDuration")) + { + defenseDuration = Integer.parseInt(xmlReader.getElementText()); + } + else if(xmlReader.getLocalName().equals("FleeGoodProbability")) + { + fleeGoodProbability = Integer.parseInt(xmlReader.getElementText()); + } + else if(xmlReader.getLocalName().equals("FleeBadProbability")) + { + fleeBadProbability = Integer.parseInt(xmlReader.getElementText()); + } else if(xmlReader.getLocalName().equals("EntityStats")) { do @@ -300,7 +325,32 @@ public class Config { return playerSlowSpeed; } + + public int getPlayerAttackProbability() + { + return playerAttackProbability; + } + public int getPlayerEvasion() + { + return playerEvasion; + } + + public int getDefenseDuration() + { + return defenseDuration; + } + + public int getFleeGoodProbability() + { + return fleeGoodProbability; + } + + public int getFleeBadProbability() + { + return fleeBadProbability; + } + /** * Returns a clone of an EntityInfo (to prevent editing it). * @param classFullName diff --git a/src/main/java/com/seodisparate/TurnBasedMinecraft/common/networking/PacketBattleDecision.java b/src/main/java/com/seodisparate/TurnBasedMinecraft/common/networking/PacketBattleDecision.java index 6e3d622..a2738d2 100644 --- a/src/main/java/com/seodisparate/TurnBasedMinecraft/common/networking/PacketBattleDecision.java +++ b/src/main/java/com/seodisparate/TurnBasedMinecraft/common/networking/PacketBattleDecision.java @@ -14,15 +14,15 @@ public class PacketBattleDecision implements IMessage { private int battleID; private Battle.Decision decision; - private int targetEntityID; + private int targetIDOrItemID; public PacketBattleDecision() {} - public PacketBattleDecision(int battleID, Battle.Decision decision, int targetEntityID) + public PacketBattleDecision(int battleID, Battle.Decision decision, int targetIDOrItemID) { this.battleID = battleID; this.decision = decision; - this.targetEntityID = targetEntityID; + this.targetIDOrItemID = targetIDOrItemID; } @Override @@ -30,7 +30,7 @@ public class PacketBattleDecision implements IMessage { battleID = buf.readInt(); decision = Decision.valueOf(buf.readInt()); - targetEntityID = buf.readInt(); + targetIDOrItemID = buf.readInt(); } @Override @@ -38,7 +38,7 @@ public class PacketBattleDecision implements IMessage { buf.writeInt(battleID); buf.writeInt(decision.getValue()); - buf.writeInt(targetEntityID); + buf.writeInt(targetIDOrItemID); } public static class HandleBattleDecision implements IMessageHandler @@ -50,7 +50,7 @@ public class PacketBattleDecision implements IMessage if(b != null) { EntityPlayerMP player = ctx.getServerHandler().player; - b.setDecision(player.getEntityId(), message.decision, message.targetEntityID); + b.setDecision(player.getEntityId(), message.decision, message.targetIDOrItemID); } return null; } diff --git a/src/main/resources/TBM_Config.xml b/src/main/resources/TBM_Config.xml index 3dc684e..fedad39 100644 --- a/src/main/resources/TBM_Config.xml +++ b/src/main/resources/TBM_Config.xml @@ -10,7 +10,14 @@ 50 80 20 + 90 + 10 + + 1 + + 90 + 40