Jump to content

[Java] DnD Initiative tracker


Freak
 Share

Recommended Posts

In my DnD group we've always tracked initiative on a white board, and it's always been a pain in the ass. We'd write down the names of everyone in the encounter, take note of their initiative scores, rewrite the whole list in order, and then we'd do all damage calculation by hand. It took way too long, and was always very anticlimactic. We'd be rushing through a cave to some epic music and, at the peak of excitement, the DM shouts "You're greeted by 5 viscous ancient dragons!!" and then we'd have to pause for 5-10 minutes while we fumble around with our white board, and even then the encounter itself would be a bit clumsy as we haphazardly try to figure out damage and whose turn it was.

No more! Now there's a tool which will do all of that for you! (though soon after finishing this program I found out there are dozens of free mobile apps that do the same thing...)

This tool is Object Oriented, and it keeps track of Mob objects in a linked list. 

Here is a screenshot:

B6GnMNI.png

Clicking the bottom 3 buttons creates popup dialogues that you can use to enter the information.

Here is the code:
Main.java:

Spoiler

import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class Main {
	public static Mob head = null;
	public static int initiativeSpot = 0;
	
	public static void main(String args[]){
		GUI myInterface = new GUI();
		myInterface.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		myInterface.setSize(370,600);
		myInterface.setVisible(true);
		myInterface.setResizable(false);
	}
	
	public static String genDisplayText(){
		String displayText = "";
		Mob tempMob = head;
		for(int i = 1; tempMob != null; i++){
			displayText += i + ": " + tempMob.getName() + "\t" + "( " + (tempMob.getHp()-tempMob.getDmg()) + " / " + tempMob.getHp() + " )";
			if(i == initiativeSpot){
				displayText += "***";
			}
			displayText += "\n";
			tempMob = tempMob.getNextMob();
		}
		return displayText;
	}
	
	public static void damage(int num, int dmg, Mob thisMob){
		num--;
		if(num == 0){
			thisMob.addDmg(dmg);
			if(! thisMob.isAlive()){
				JOptionPane.showMessageDialog(null, thisMob.getName() + " is dead!");
			}else if(thisMob.getDmg() > thisMob.getHp()/2){
				JOptionPane.showMessageDialog(null, thisMob.getName() + " is looking bloody! (Past half HP)");
			}
		}else{
			damage(num, dmg, thisMob.getNextMob());
		}
	}
}

 

GUI.java

Spoiler

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

@SuppressWarnings("serial")
public class GUI extends JFrame{
	private Mob currentMob = null;
	
	private JButton add;
	private JButton damage;
	private JButton next;
	private JButton clear;
	
	private JTextArea display;
	
	public GUI(){
		super("Initiative Tracker");
		setLayout(new GridBagLayout());
		GridBagConstraints c=new GridBagConstraints();
		c.fill = GridBagConstraints.HORIZONTAL;
		
/*----------------------------------
 * 			Display stuff
 */
		display = new JTextArea(30, 30);
		display.setText("<Mob info will appear here>");
		display.setEditable(false);
		c.gridx = 0;
		c.gridy = 0;
		c.gridwidth = 3;
		c.gridheight = 2;
		add(display, c);
		
/*----------------------------------
 * 			Button stuff
 */
		add = new JButton("Add a mob");
		add.setToolTipText("Add another enemy, NPC, or player character to the encounter.");
		c.gridx = 0;
		c.gridy = 3;
		c.gridwidth = 1;
		c.gridheight = 1;
		add(add, c);
		
		damage = new JButton("Do damage");
		damage.setToolTipText("Record damage that was done to an enemy, NPC, or player character.");
		c.gridx = 2;
		c.gridy = 3;
		c.gridwidth = 1;
		c.gridheight = 1;
		add(damage, c);
		
		next = new JButton("Next ->");
		next.setToolTipText("Skip to the next enemy, NPC, or player character in initiative.");
		c.gridx = 0;
		c.gridy = 2;
		c.gridwidth = 3;
		c.gridheight = 1;
		add(next, c);
		
		clear = new JButton("Clear");
		next.setToolTipText("Delete this encounter.");
		c.gridx = 1;
		c.gridy = 3;
		c.gridwidth = 1;
		c.gridheight = 1;
		add(clear, c);
		
/*---------------------------------
 * event handlers and action listeners
 */
		EventHandler handler = new EventHandler();
		add.addActionListener(handler);
		damage.addActionListener(handler);
		next.addActionListener(handler);
		clear.addActionListener(handler);
	}
	private class EventHandler implements ActionListener{
		public void actionPerformed(ActionEvent event){
			if(event.getSource()==add){
				if(Main.initiativeSpot == 0){
					Mob newMob = new Mob();
					newMob.setName(JOptionPane.showInputDialog("Enter the name: "));
					newMob.setHp(Integer.parseInt(JOptionPane.showInputDialog("Enter the max HP: ")));
					newMob.addInitiative(Integer.parseInt(JOptionPane.showInputDialog("Enter their initiative: ")));
					if(Main.head == null){
						Main.head = newMob;
					}else{
						newMob.insertSelf(Main.head, null);
					}
					display.setText(Main.genDisplayText());
				}else{
					JOptionPane.showMessageDialog(null, "You may only add new participants at the top of initiative.");
				}
			}else if(event.getSource()==damage){
				int n = Integer.parseInt(JOptionPane.showInputDialog("Mob number: "));
				int d = Integer.parseInt(JOptionPane.showInputDialog("Enter the amount of damage to be done: "));
				Main.damage(n, d, Main.head);
				display.setText(Main.genDisplayText());
			}else if(event.getSource()==next){
				if(Main.head != null){
					do{
						if(currentMob == null){
							currentMob = Main.head;
							Main.initiativeSpot++;
						}else{
							if(currentMob.getNextMob() == null){
								currentMob = null;
								Main.initiativeSpot = 0;
							}else{
								currentMob = currentMob.getNextMob();
								Main.initiativeSpot++;
							}
						}
						if(currentMob == null){ //ugh...
							break;
						}
					}while(!currentMob.isAlive());
					display.setText(Main.genDisplayText());
					if(Main.initiativeSpot == 0){
						JOptionPane.showMessageDialog(null, "Top of initiative! You may now add new mobs to the encounter, or press Next again to keep playing.");
					}else{
						JOptionPane.showMessageDialog(null, "It's " + currentMob.getName() + "'s turn!");
					}
				}
			}else if(event.getSource()==clear){
				if(JOptionPane.showConfirmDialog(null, "Are you SURE you want to clear?") == 0){
					Main.head = null;
					Main.initiativeSpot = 0;
					display.setText(Main.genDisplayText());
				}
			}
		}
	}
}

 

Mob.java

Spoiler

import javax.swing.JOptionPane;

public class Mob {
	private int hp;
	private String name;
	private int initiative[] = {};
	private int dmgTaken = 0;
	private Mob nextMob = null;
	
	public Mob(){
		
	}
/*----------------------------------
 * 		  		Getters
 */
	
	public int getDmg(){
		return dmgTaken;
	}
	public int getHp(){
		return hp;
	}
	public boolean isAlive(){
		if(dmgTaken < hp){
			return true;
		}else{
			return false;
		}
	}
	public String getName(){
		return name;
	}
	public Mob getNextMob(){
		return nextMob;
	}
	public int[] getInitiative(){
		return initiative;
	}
/*----------------------------------
 * 		  		Setters
 */
	public void setHp(int theHp){
		hp = theHp;
	}
	public void addDmg(int dmg){
		dmgTaken += dmg;
		if(dmgTaken > hp){
			dmgTaken = hp;
		}else if(dmgTaken < 0){
			dmgTaken = 0;
		}
	}
	public void addInitiative(int num){
		int temp[] = initiative;
		initiative = new int[temp.length + 1];
		for(int i = 0; i<temp.length; i++){
			initiative[i] = temp[i];
		}
		initiative[initiative.length - 1] = num;
	}
	public void setName(String theName){
		name = theName;
	}
	public void setNextMob(Mob theNextMob){
		nextMob = theNextMob;
	}
	public void insertSelf(Mob current, Mob last){
		for(int i = 0;; i++){
			if(this.getInitiative()[i] > current.getInitiative()[i]){
				if(last == null){
					this.nextMob = Main.head;
					Main.head = this;
				}else{
					this.nextMob = current;
					last.setNextMob(this);
				}
				break;
			}else if(this.getInitiative()[i] < current.getInitiative()[i]){
				if(current.nextMob == null){
					current.setNextMob(this);
				}else{
					insertSelf(current.nextMob, current);
				}
				break;
			}else{
				if(i+1 == this.getInitiative().length ){
					this.addInitiative(Integer.parseInt(JOptionPane.showInputDialog(this.getName() + " must roll again for initiative:")));
				}
				if(i+1 == current.getInitiative().length){
					current.addInitiative(Integer.parseInt(JOptionPane.showInputDialog(current.getName() + " must roll again for initiative:")));
				}
			}
		}
	}
}

 

 

There are some small limitations:

  1. There is no healing button. What you can do instead is just enter a negative number for damage. I could have easily added a healing button with only a few lines of code overall, but I felt that it would clutter the UI a bit for something that is virtually identical to the damage button.
  2. The program does not distinguish between NPCs and players. The only downside of this is that if a player "dies" then it doesn't prompt them to do their death saves. Hopefully your DM can pay enough attention to notice when the player is skipped in the order and just ask them to do it themselves though.
  • I Like This! 2
Link to comment
Share on other sites

Awesome! I personally never played that game but it is great to see this. I know there are other apps out there but good work. I wonder what other kind of games this application can apply to...

 

Link to comment
Share on other sites

  • 2 weeks later...

Probably lots of games. Could probably transfer over to some form of trackers for card games like magic and others where you have to keep track of damage to players and monsters. Would be cool to deck this out to a full tracker for all sorts of these kinds of game, one stop-shop. I'd be interested in building this up on github for multiple games.

Link to comment
Share on other sites

That would be pretty neat! I'd be down for that. We could just add a JMenuBar with a section called "Games" that would drop down with all available options. We could even add a "File" section that allows you to save in the middle of a game and load it back up later.

Link to comment
Share on other sites

I was thinking... As a program like this can apply to so much, what about non role playing games? I guess my idea is to have this count cards in blackjack or even calculate odds in holdem or five card draw. Something like this with a good GUI may make you some money...

Link to comment
Share on other sites

We can perhaps get some design going, find a good layout and structure and code this up, I'm in.

I've done some stuff with loading files in a primitive way. I built an application for logging ham radio contacts. It saves by writing the information to the file, then pulls it back up by reading the file. I can share that with ya'll.

Edited by WarFox
Link to comment
Share on other sites

On 4/11/2019 at 9:17 PM, Aeo said:

I was thinking... As a program like this can apply to so much, what about non role playing games? I guess my idea is to have this count cards in blackjack or even calculate odds in holdem or five card draw. Something like this with a good GUI may make you some money...

Quiet a few programming books use a card counting program. I think Head First C does, along with Beginning C++Through Game Programming. But yea, Freak and I are talking about some stuff we can do, probably first is to create a main menu to select a game from there. From that, we could probably build out anything into this.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...