WIP Some battle logic has been implemented

This commit is contained in:
Stephen Seo 2018-09-05 15:54:06 +09:00
parent c8ce401e4d
commit 2aec173e80
5 changed files with 335 additions and 13 deletions

View file

@ -18,8 +18,10 @@ import com.seodisparate.TurnBasedMinecraft.common.networking.PacketBattleInfo;
import com.seodisparate.TurnBasedMinecraft.common.networking.PacketHandler; import com.seodisparate.TurnBasedMinecraft.common.networking.PacketHandler;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.DamageSource;
public class Battle public class Battle
{ {
@ -38,7 +40,7 @@ public class Battle
public enum State public enum State
{ {
DECISION, DECISION,
ATTACK, ACTION,
HEALTH_CHECK HEALTH_CHECK
} }
@ -96,6 +98,7 @@ public class Battle
continue; continue;
} }
Combatant newCombatant = new Combatant(e, entityInfo); Combatant newCombatant = new Combatant(e, entityInfo);
newCombatant.isSideA = true;
this.sideA.put(e.getEntityId(), newCombatant); this.sideA.put(e.getEntityId(), newCombatant);
if(e instanceof EntityPlayer) if(e instanceof EntityPlayer)
{ {
@ -115,6 +118,7 @@ public class Battle
continue; continue;
} }
Combatant newCombatant = new Combatant(e, entityInfo); Combatant newCombatant = new Combatant(e, entityInfo);
newCombatant.isSideA = false;
this.sideB.put(e.getEntityId(), newCombatant); this.sideB.put(e.getEntityId(), newCombatant);
if(e instanceof EntityPlayer) if(e instanceof EntityPlayer)
{ {
@ -154,6 +158,7 @@ public class Battle
return; return;
} }
Combatant newCombatant = new Combatant(e, entityInfo); Combatant newCombatant = new Combatant(e, entityInfo);
newCombatant.isSideA = true;
sideA.put(e.getEntityId(), newCombatant); sideA.put(e.getEntityId(), newCombatant);
if(e instanceof EntityPlayer) if(e instanceof EntityPlayer)
{ {
@ -175,6 +180,7 @@ public class Battle
return; return;
} }
Combatant newCombatant = new Combatant(e, entityInfo); Combatant newCombatant = new Combatant(e, entityInfo);
newCombatant.isSideA = false;
sideB.put(e.getEntityId(), newCombatant); sideB.put(e.getEntityId(), newCombatant);
if(e instanceof EntityPlayer) if(e instanceof EntityPlayer)
{ {
@ -237,19 +243,26 @@ public class Battle
return combatant; return combatant;
} }
public void setDecision(int entityID, Decision decision, int targetEntityID) public void setDecision(int entityID, Decision decision, int targetIDOrItemID)
{ {
if(state != State.DECISION) if(state != State.DECISION)
{ {
return; return;
} }
Combatant combatant = players.get(entityID); Combatant combatant = players.get(entityID);
if(combatant == null) if(combatant == null || combatant.decision != Decision.UNDECIDED)
{ {
return; return;
} }
combatant.decision = decision; combatant.decision = decision;
combatant.targetEntityID = targetEntityID; if(decision == Decision.ATTACK)
{
combatant.targetEntityID = targetIDOrItemID;
}
else if(decision == Decision.USE_ITEM)
{
combatant.itemToUse = targetIDOrItemID;
}
undecidedCount.decrementAndGet(); undecidedCount.decrementAndGet();
} }
@ -294,7 +307,7 @@ public class Battle
timer = timer.minus(dt); timer = timer.minus(dt);
if(timer.isNegative() || timer.isZero() || undecidedCount.get() <= 0) if(timer.isNegative() || timer.isZero() || undecidedCount.get() <= 0)
{ {
state = State.ATTACK; state = State.ACTION;
timer = TurnBasedMinecraftMod.BattleDecisionTime; timer = TurnBasedMinecraftMod.BattleDecisionTime;
turnOrderQueue.clear(); turnOrderQueue.clear();
for(Combatant c : sideA.values()) for(Combatant c : sideA.values())
@ -306,16 +319,264 @@ public class Battle
turnOrderQueue.add(c); turnOrderQueue.add(c);
} }
update(Duration.ZERO); update(Duration.ZERO);
// TODO assign decisions to non-players
} }
break; break;
case ATTACK: case ACTION:
{
Combatant next = turnOrderQueue.poll(); Combatant next = turnOrderQueue.poll();
while(next != null) 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(); 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; break;
}
case HEALTH_CHECK: case HEALTH_CHECK:
// TODO // TODO
break; break;

View file

@ -18,11 +18,14 @@ public class Combatant
public EntityInfo entityInfo; public EntityInfo entityInfo;
public boolean recalcSpeedOnCompare; public boolean recalcSpeedOnCompare;
public int targetEntityID; public int targetEntityID;
public boolean isSideA;
public int remainingDefenses;
public Combatant() public Combatant()
{ {
decision = Battle.Decision.UNDECIDED; decision = Battle.Decision.UNDECIDED;
recalcSpeedOnCompare = false; recalcSpeedOnCompare = false;
remainingDefenses = 0;
} }
public Combatant(Entity e, EntityInfo entityInfo) public Combatant(Entity e, EntityInfo entityInfo)
@ -31,6 +34,7 @@ public class Combatant
decision = Battle.Decision.UNDECIDED; decision = Battle.Decision.UNDECIDED;
this.entityInfo = entityInfo; this.entityInfo = entityInfo;
recalcSpeedOnCompare = false; recalcSpeedOnCompare = false;
remainingDefenses = 0;
} }
/** /**

View file

@ -30,6 +30,11 @@ public class Config
private int playerSpeed; private int playerSpeed;
private int playerHasteSpeed; private int playerHasteSpeed;
private int playerSlowSpeed; 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 private enum ConfigParseResult
{ {
@ -174,9 +179,29 @@ public class Config
{ {
playerSlowSpeed = Integer.parseInt(xmlReader.getElementText()); 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"))); } 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")) else if(xmlReader.getLocalName().equals("EntityStats"))
{ {
do do
@ -300,7 +325,32 @@ public class Config
{ {
return playerSlowSpeed; 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). * Returns a clone of an EntityInfo (to prevent editing it).
* @param classFullName * @param classFullName

View file

@ -14,15 +14,15 @@ public class PacketBattleDecision implements IMessage
{ {
private int battleID; private int battleID;
private Battle.Decision decision; private Battle.Decision decision;
private int targetEntityID; private int targetIDOrItemID;
public PacketBattleDecision() {} public PacketBattleDecision() {}
public PacketBattleDecision(int battleID, Battle.Decision decision, int targetEntityID) public PacketBattleDecision(int battleID, Battle.Decision decision, int targetIDOrItemID)
{ {
this.battleID = battleID; this.battleID = battleID;
this.decision = decision; this.decision = decision;
this.targetEntityID = targetEntityID; this.targetIDOrItemID = targetIDOrItemID;
} }
@Override @Override
@ -30,7 +30,7 @@ public class PacketBattleDecision implements IMessage
{ {
battleID = buf.readInt(); battleID = buf.readInt();
decision = Decision.valueOf(buf.readInt()); decision = Decision.valueOf(buf.readInt());
targetEntityID = buf.readInt(); targetIDOrItemID = buf.readInt();
} }
@Override @Override
@ -38,7 +38,7 @@ public class PacketBattleDecision implements IMessage
{ {
buf.writeInt(battleID); buf.writeInt(battleID);
buf.writeInt(decision.getValue()); buf.writeInt(decision.getValue());
buf.writeInt(targetEntityID); buf.writeInt(targetIDOrItemID);
} }
public static class HandleBattleDecision implements IMessageHandler<PacketBattleDecision, IMessage> public static class HandleBattleDecision implements IMessageHandler<PacketBattleDecision, IMessage>
@ -50,7 +50,7 @@ public class PacketBattleDecision implements IMessage
if(b != null) if(b != null)
{ {
EntityPlayerMP player = ctx.getServerHandler().player; EntityPlayerMP player = ctx.getServerHandler().player;
b.setDecision(player.getEntityId(), message.decision, message.targetEntityID); b.setDecision(player.getEntityId(), message.decision, message.targetIDOrItemID);
} }
return null; return null;
} }

View file

@ -10,7 +10,14 @@
<Speed>50</Speed> <Speed>50</Speed>
<HasteSpeed>80</HasteSpeed> <HasteSpeed>80</HasteSpeed>
<SlowSpeed>20</SlowSpeed> <SlowSpeed>20</SlowSpeed>
<AttackProbability>90</AttackProbability>
<Evasion>10</Evasion>
</PlayerStats> </PlayerStats>
<!-- Determines how many attacks a "defense" move can block before that entity's next turn. -->
<DefenseDuration>1</DefenseDuration>
<!-- Probability of escaping battle. If entity's speed is greater than the enemy team's speediest entity, then good probability is used. -->
<FleeGoodProbability>90</FleeGoodProbability>
<FleeBadProbability>40</FleeBadProbability>
<!-- Battle stats for entities should be specified here. If an entity is not listed it cannot enter battle. --> <!-- Battle stats for entities should be specified here. If an entity is not listed it cannot enter battle. -->
<EntityStats> <EntityStats>
<!-- AttackPower: How much damage an entity does per attack. Usually has a "Probability" attribute between 0 and 100. Also may have a "Variance" attribute that varies the attack power by the specified amount randomly. --> <!-- AttackPower: How much damage an entity does per attack. Usually has a "Probability" attribute between 0 and 100. Also may have a "Variance" attribute that varies the attack power by the specified amount randomly. -->