I was working on a simple RPG type game in C#, it is mainly a prototype, just testing ideas out (I am aware that the code could be improved and is crude in places, however the bulk of it does work).
I have currently hit a roadblock with the battle system.
Currently, you control 1 character. You can face up to 3 enemies. It is a turn based, ATB system, and this part is working fine. Once it is the players turn, they can select an attack and use it.
I wanted to add a new feature to have damage over time effects. However, this has broken the combat.
I made a class to represent a DOT effect:
public class DoT
{
public int Duration { get; set; }
public int Elapsed { get; set; }
public int Damage { get; set; }
public Enemy Target { get; set; }
public String DName { get; set; }
public DoT()
{
Duration = 3;
Elapsed = 0;
DName = "";
}
public void Tick()
{
Elapsed++;
}
}
This is added to abilities when they are created, and this part works, I can add a DOT effect to an ability.
In the combat system, when selecting an attack, I create a list of DOT effects called DOTS. When the user selects an attack, if the attack has a DOT effect attached to it, it will be added to the list of DOT effects:
try
{
string s = ((Control)sender).Text;//Stores name of label that triggered the event
int i = s.Length;//can't remember why I added this
if (playerTarget != null)//check that we have a target
{
if (currentTurn.owner == c && canAttack)//if the current turn is ours and we can attack
{
if (c.GetAbility(s).AbilityCost <= c.currentMP)//if we have enough MP for the attack
{
if(c.GetAbility(s).DamageOverTime!=null)//checks if the attack has a DOT effect
{
c.GetAbility(s).DamageOverTime.Target = playerTarget;//the DOT now targets the target
AddDOT(c.GetAbility(s).DamageOverTime );//Adds the DOT effect to DOTS
//Trace statements I was using for debugging
UpdateTest("Added "+ c.GetAbility(s).AbilityName + " to DOT");
UpdateTest("Time left- " + (c.GetAbility(s).DamageOverTime.Duration-c.GetAbility(s).DamageOverTime.Elapsed).ToString() );
}
currentTurn.ability = c.GetAbility(s);//Sets the current ability to be used
UseTurn();//Executes a turn
}
else
{
MessageBox.Show("Not enough MP!");
}
}
}
else
{
MessageBox.Show("You must select a target!");
}
}
catch (Exception ex)
{
MessageBox.Show("Error" + ex.ToString());
}
The method that adds a DOT effect is this:
public void AddDOT(DoT d)
{
int exists = 0;
if (DOTS.Count > 0)
{
foreach (DoT dot in DOTS)
{
if (dot.Equals(d))
{
dot.Elapsed = 0;
exists++;
}
}
if(exists==0)
{
DOTS.Add(d);
}
}
else
{
DOTS.Add(d);
}
This is to stop multiples of the same DOT being applied, if it is already there, we just reset the time elapsed on the current DOT.
I also have a method to apply DOT effects on each turn:
public void UpdateDots()
{
try
{
if (DOTS.Count > 0)//Check that we have a DOT
{
foreach (DoT d in DOTS)//Loop through each DOT
{
DotDamage(d);//Apply the DOT damage to the DOT's target
d.Tick();//call the tick method of DOT to add 1 to elapsed
//Trace statement to help debug
UpdateTest("Duration on " + d.DName + " is " + (d.Duration - d.Elapsed).ToString());
if (d.Elapsed == d.Duration)//Check if we have ticks left
{
DOTS.Remove(d);//If not, remove the DOT effect
}
}
}
else
{
UpdateTest("No DOTS active");//Trace statement
}
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
The UpdateDots method is called here in the code to use a turn:
public void UseTurn()
{
UpdateDots();//Execute any DOT effects
pause.Tick += new EventHandler(PauseTick);//just a timer to delay each turn, I need to move this
UpdateTest("Owner is..."+currentTurn.owner.characterName );//Trace statement
if (currentTurn.owner == c)//Checks if the current turn is by the player (c=player)
{
if (currentTurn.ability.DamageOverTime == null)//if we aren't applying a DOT
{
if (currentTurn.ability.MultiTarget == false)//If it is single target
{
Attack(c, playerTarget, currentTurn.ability);//Single target attack
}
else
{
MultiAttack(playerTargets, currentTurn.ability);//Multi target attack
}
}
lblPlayerTimer.Width = 0;//Resets the label showing the players ATB bar
currentTurn.owner = null;//Free the current owner to be claimed by other characters
canAttack = false;//Can no longer attack
playerTimer.Start();//Start the players timer again
UpdateAbilities();//Simply used to update the display of abilities
}
else if(currentTurn.owner is Enemy)//If an enemy is attacking
{
Enemy en = (Enemy)currentTurn.owner;//Get the details of the enemy attacking
string n = en.characterName;//this was from previous code
Label l = new Label();
l = Controls.Find(en.timer.lblName ,true).FirstOrDefault() as Label;//Get the label being used as the enemy timer
PickEnemyAttack(en);//Select an attack for the enemy
Attack(en, c, currentTurn.ability);//Execute attack for enemy
l.Width = 0;//Reset the enemy ATB bar
currentTurn.owner = null;//Free up the current turn
//This part is just used to cause a short delay before the next turn
pause.Start();
enemyCanAttack = false;
en.timer.Start();
}
ShowTurn();//Updates display of the current turn
turnCount += 1;
CheckWinner();//Checks if the player has won, or lost
}
The issue is, when I apply a DOT, it is applied, and damages the enemy. It continues to tick until the DOT expires, at which point all of the timers stop. If there are no DOT effects, then the combat works as normal.
It seems to be caused when the DOT is removed from DOTS. It is throwing an invalidoperation exception, stating that the collection was modified. Is it because I removed an item, but as I am in a ForEach loop, removing this messes with the enumeration?
As you can tell, I am not an expert with lists, and I can't see the issue, I was hoping someone with a more expert eye could help me fix this issue.
Thanks.