diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..90f172f --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/GamePlay/Actions/Attack.java b/GamePlay/Actions/Attack.java new file mode 100644 index 0000000..4e769c4 --- /dev/null +++ b/GamePlay/Actions/Attack.java @@ -0,0 +1,17 @@ +package Actions; +import input.*; + +public class Attack { + /** + * Defines if the attack is a special one (E.G. a fireball) or a normal one (a punch) + */ + private static boolean isSpecial; + + /** + * The suite of Inputs to have the move come out. + * For example, a classic fireball would be something like + * {{DOWN},{DOWN,RIGHT},{RIGHT},{A}} + */ + private static Button[][] command; + +} diff --git a/GamePlay/Entities/Character.java b/GamePlay/Entities/Character.java new file mode 100644 index 0000000..f4d2366 --- /dev/null +++ b/GamePlay/Entities/Character.java @@ -0,0 +1,31 @@ +package Entities; + +import Frames.Frame; + +/** + * Character class, which is a sub-class of an entity + * @author Victor + * + */ +public class Character extends Entity { + private static int maxHP; + + /** + * Main constructor for a character. By default its max health is 1000 if not specified + */ + public Character() { + super(); + this.maxHP = 1000; + } + + public Character(int posx, int posy, Frame f, int maxHP) { + super(posx, posy, f); + this.maxHP = maxHP; + } + + public void setMaxHP(int HP) { + this.maxHP = HP; + } + + public int getMaxHP() { return this.getMaxHP();} +} diff --git a/GamePlay/Entities/Entity.java b/GamePlay/Entities/Entity.java new file mode 100644 index 0000000..0ddf6c6 --- /dev/null +++ b/GamePlay/Entities/Entity.java @@ -0,0 +1,50 @@ +package Entities; + +import Frames.Frame; + +/** + * Entity class, which is the main class regrouping characters and projectiles + * @author Victor + * + */ +public class Entity { + private int posx; + private int posy; + private Frame currentFrame; + + /** + * base constructor of the entity class + */ + public Entity() { + this.posx = 0; + this.posy = 0; + this.currentFrame = new Frame(); + } + + /** + * constructor of the entity class with parameters + * @param posx the position of the entity on the x axis + * @param posy the position of the entity on the y axis + * @param f the current frame of the new entity + */ + public Entity(int posx, int posy, Frame f) { + this.posx = posx; + this.posy = posy; + this.currentFrame = f; + } + + public void setPos(int x, int y) { + this.posx = x; + this.posy = y; + } + + public void setCurrentFrame(Frame f) { + this.currentFrame = f; + } + + public int getPosX() {return this.posx;} + + public int getPosY() {return this.posy;} + + public Frame getCurrentframe() {return this.currentFrame;} +} diff --git a/GamePlay/Entities/Projectile.java b/GamePlay/Entities/Projectile.java new file mode 100644 index 0000000..2feb351 --- /dev/null +++ b/GamePlay/Entities/Projectile.java @@ -0,0 +1,4 @@ +package Entities; + +public class Projectile extends Entity{ +} diff --git a/GamePlay/Frames/Frame.java b/GamePlay/Frames/Frame.java new file mode 100644 index 0000000..177603d --- /dev/null +++ b/GamePlay/Frames/Frame.java @@ -0,0 +1,55 @@ +package Frames; + +import java.util.ArrayList; +import Hitboxes.*; +/** + * Main class for frames + * @author Victor Azra + * + */ +public class Frame { + + private Double move_y; + private Double move_x; + private ArrayList passHitBox; + private ArrayList actHitBox; + private ArrayList passThrowHitBox; + private ArrayList actThrowHitBox; + private Push_HitBox pushHitBox; + + public Frame() { + this.move_y = 0.0; + this.move_x = 0.0; + this.passHitBox = new ArrayList(); + this.actHitBox = new ArrayList(); + this.passThrowHitBox = new ArrayList(); + this.actThrowHitBox = new ArrayList(); + this.pushHitBox = new Push_HitBox(); + } + + public Frame(Double move_y, Double move_x, ArrayList passHitBox, ArrayList actHitBox, + ArrayList passThrowHitBox, ArrayList actThrowHitBox, + Push_HitBox pushHitBox) { + this.move_y = move_y; + this.move_x = move_x; + this.passHitBox = passHitBox; + this.actHitBox = actHitBox; + this.passThrowHitBox = passThrowHitBox; + this.actThrowHitBox = actThrowHitBox; + this.pushHitBox = pushHitBox; + } + + /* + * Mainly use for projectiles + */ + public Frame(Double move_y, Double move_x, ArrayList passHitBox, ArrayList actHitBox) { + this.move_y = move_y; + this.move_x = move_x; + this.passHitBox = passHitBox; + this.actHitBox = actHitBox; + this.passThrowHitBox = new ArrayList(); + this.actThrowHitBox = new ArrayList(); + this.pushHitBox = new Push_HitBox(); + } + +} diff --git a/GamePlay/Frames/nextFrameBuffer.java b/GamePlay/Frames/nextFrameBuffer.java new file mode 100644 index 0000000..87d7f43 --- /dev/null +++ b/GamePlay/Frames/nextFrameBuffer.java @@ -0,0 +1,70 @@ +package Frames; + +/** + * This will handle the next frames to be played by each entity. + * @author Victor Azra + */ +public class nextFrameBuffer { + + private Frame current; + private nextFrameBuffer next; + + /** + * creates a new framebuffer, empty for now + */ + public nextFrameBuffer() { + this.current = null; + this.next = null; + } + + public void setCurrentFrame(Frame f) { + this.current = f; + } + + public void clone(nextFrameBuffer f) { + this.current = f.current; + this.next = f.next; + } + + public void setNext(nextFrameBuffer f) { + this.next.clone(f); + } + + public void emptyQueue() { + this.next = null; + } + + public void empty() { + this.current = null; + this.next = null; + } + + public void goToNext() { + this.current = this.next.current; + this.next = this.next.next; + } + + public Frame getCurrentFrame() { + return this.current; + } + + public Frame getNextframe() { + return this.next.current; + } + + /** + * Adds a frame at the end of the buffer + * @param f the frame to add at the end + */ + public void addFrameToQueue(Frame f) { + if(this.current == null) { + this.current = f; + } else if(this.next == null){ + nextFrameBuffer fb = new nextFrameBuffer(); + fb.current = f; + this.next = fb; + } else { + this.next.addFrameToQueue(f); + } + } +} diff --git a/GamePlay/Hitboxes/Active_HitBox.java b/GamePlay/Hitboxes/Active_HitBox.java new file mode 100644 index 0000000..af4b970 --- /dev/null +++ b/GamePlay/Hitboxes/Active_HitBox.java @@ -0,0 +1,5 @@ +package Hitboxes; + +public class Active_HitBox extends HitBox { + +} diff --git a/GamePlay/Hitboxes/Active_throw_Hitbox.java b/GamePlay/Hitboxes/Active_throw_Hitbox.java new file mode 100644 index 0000000..2bcd36b --- /dev/null +++ b/GamePlay/Hitboxes/Active_throw_Hitbox.java @@ -0,0 +1,5 @@ +package Hitboxes; + +public class Active_throw_Hitbox extends HitBox { + +} diff --git a/GamePlay/Hitboxes/HitBox.java b/GamePlay/Hitboxes/HitBox.java new file mode 100644 index 0000000..ae2e01f --- /dev/null +++ b/GamePlay/Hitboxes/HitBox.java @@ -0,0 +1,100 @@ +package Hitboxes; + +public class HitBox { + + private Double position_x; + private Double position_y; + private Double size_x; + private Double size_y; + + public HitBox() { + this.position_x = 0.0; + this.position_y = 0.0; + this.size_x = 0.0; + this.size_y = 0.0; + } + + public void setPosition_x(Double position_x){ + this.position_x = position_x; + } + + public void setPosition_y(Double position_y){ + this.position_y = position_y; + } + + public HitBox(Double position_x, Double position_y, Double size_x, Double size_y) { + + if(position_x < 0.0) { + position_x = 0.0; + }else if(position_x > 1.0) { + position_x = 1.0; + } + this.position_x = position_x; + + if(position_y < 0.0) { + position_y = 0.0; + }else if(position_y > 1.0) { + position_y = 1.0; + } + this.position_y = position_y; + + if(size_x < 0.0) { + size_x = 0.0; + }else if(size_x > 1.0) { + size_x = 1.0; + } + this.size_x = size_x; + + if(size_y < 0.0) { + size_y = 0.0; + }else if(size_y > 1.0) { + size_y = 1.0; + } + this.size_y = size_y; + + } + + /* + * @param hb an Hitbox + * @return true if HitBox overlap else return false + */ + public Boolean hit(HitBox hb) { + Boolean horiz = false; + Boolean ver = false; + Double horizontal1 = this.position_x + this.size_x; + Double vertical1 = this.position_y + this.size_y; + Double horizontal2 = hb.position_x + hb.size_x; + Double vertical2 = hb.position_y + hb.size_y; + + + /* + * HitBox overlap horizontally + */ + if(this.position_x < hb.position_x) { //this is at left of hb + if(hb.position_x < horizontal1) { + horiz = true; + } + }else {//this is at left of hb + if(this.position_x < horizontal2) { + horiz = true; + } + } + + /* + * HitBox overlap vertically + */ + if(this.position_y < hb.position_y) { //this is at top of hb + if(hb.position_y < vertical1) { + ver = true; + } + }else {//this is at left of hb + if(this.position_x < vertical2) { + ver = true; + } + } + + + return horiz && ver; + } + +} \ No newline at end of file diff --git a/GamePlay/Hitboxes/Passive_HitBox.java b/GamePlay/Hitboxes/Passive_HitBox.java new file mode 100644 index 0000000..2a2582b --- /dev/null +++ b/GamePlay/Hitboxes/Passive_HitBox.java @@ -0,0 +1,5 @@ +package Hitboxes; + +public class Passive_HitBox extends HitBox { + +} diff --git a/GamePlay/Hitboxes/Passive_throw_HitBox.java b/GamePlay/Hitboxes/Passive_throw_HitBox.java new file mode 100644 index 0000000..8ffe132 --- /dev/null +++ b/GamePlay/Hitboxes/Passive_throw_HitBox.java @@ -0,0 +1,5 @@ +package Hitboxes; + +public class Passive_throw_HitBox extends HitBox { + +} diff --git a/GamePlay/Hitboxes/Push_HitBox.java b/GamePlay/Hitboxes/Push_HitBox.java new file mode 100644 index 0000000..28d05b8 --- /dev/null +++ b/GamePlay/Hitboxes/Push_HitBox.java @@ -0,0 +1,13 @@ +package Hitboxes; + +public class Push_HitBox extends HitBox { + + public Push_HitBox() { + super(); + } + + public Push_HitBox(Double position_x, Double position_y, Double size_x, Double size_y) { + super(position_x, position_y, size_x, size_y); + } + +} diff --git a/GamePlay/Match/.gitignore b/GamePlay/Match/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/GamePlay/Match/match.java b/GamePlay/Match/match.java new file mode 100644 index 0000000..34705ee --- /dev/null +++ b/GamePlay/Match/match.java @@ -0,0 +1,41 @@ +package Match; + +import input.InputBuffer; +import Entities.*; + +/** + * Main class that describes the base structure of the match, with characters, timer and such + * @author Victor Azra + * + */ +public class match { + + /** + * the number of inputs read for each character, a.k.a. for how many frames the inputs are saved in memory. + */ + private static final int inputBufferSize = 120; + + private int timer; + private InputBuffer inputsP1, inputsP2; + private int hpP1, hpP2; + private int roundsWonP1, roundsWonP2; + private Entities.Character p1, p2; //characters of player 1 and 2 + + /** + * base constructor of the match class. + * Initiates a new match with with two given characters + */ + public match(Entities.Character p1, Entities.Character p2) { + this.timer = 99; + this.inputsP1 = new input.InputBuffer(inputBufferSize); + this.inputsP2 = new input.InputBuffer(inputBufferSize); + this.p1 = p1; + this.p2 = p2; + this.hpP1 = p1.getMaxHP(); + this.hpP2 = p2.getMaxHP(); + this.roundsWonP1 = 0; + this.roundsWonP2 = 0; + } + + +} diff --git a/GamePlay/input/Button.java b/GamePlay/input/Button.java new file mode 100644 index 0000000..85a90c7 --- /dev/null +++ b/GamePlay/input/Button.java @@ -0,0 +1,19 @@ +package input; + +public enum Button { + UP, DOWN, LEFT, RIGHT, A, B, C, D; + + public int toInt() { + switch (this) { + case UP : return 0; + case DOWN : return 1; + case LEFT : return 2; + case RIGHT : return 3; + case A : return 4; + case B : return 5; + case C : return 6; + case D : return 7; + default : return -1; + } + } +} diff --git a/GamePlay/input/InputBuffer.java b/GamePlay/input/InputBuffer.java new file mode 100644 index 0000000..058e4b0 --- /dev/null +++ b/GamePlay/input/InputBuffer.java @@ -0,0 +1,80 @@ +package input; + +public class InputBuffer { + + /** + * a list of various inputs being recorded, such as inputs pressed at each frame + * Each element is a tab where each element represent a possible input + * (UP, Down, Right, Left, A, B, C, D) + * if the value at the corresponding index is true, then the input is pressed + * By default, no input is pressed. + */ + private Inputs[] inputList; + + /* + * the size of the input buffer + */ + private int size; + + /* + * the current position in the tab of the last newest input recorded + * This should never bee accessed outside of this class. + */ + private int pos; + + + /** + * base constructor of the InputBuffer. Creates a buffer of size 1, with every input empty + */ + public InputBuffer() { + this.size = 1; + this.pos = 0; + this.inputList = new Inputs[1]; + } + + public InputBuffer(int size) { + this.size = size; + this.pos = 0; + this.inputList = new Inputs[this.size]; + + for(int i = 0; i < this.size; i++) { + this.inputList[i] = new Inputs(); + } + } + + + + /** + * @return the latest added inputs + */ + public Inputs getLatestInputs() { + return this.inputList[this.pos]; + } + + + /** + * Sets the last input without moving the current position + * @param inputs + */ + private void setLatestInputs(Inputs inputs) { + this.inputList[pos] = inputs; + } + + /** + * advances the current position to the next one (goes back to the first if at the end + */ + private void nextPos() { + this.pos++; + if(this.pos == size) {this.pos = 0;} + } + + /** + * record a new input in the + * @param inputs a size 8 tab of inputs + */ + private void recordInputs(Inputs inputs) { + this.nextPos(); + this.setLatestInputs(inputs); + } +} + diff --git a/GamePlay/input/Inputs.java b/GamePlay/input/Inputs.java new file mode 100644 index 0000000..769a029 --- /dev/null +++ b/GamePlay/input/Inputs.java @@ -0,0 +1,66 @@ +package input; + +/** + * The class handling the parsing of one input. + * @author Victor + * + */ +public class Inputs { + private static final int numberOfInputs = 8; + private static boolean[] tab; + + public Inputs() { + this.tab = new boolean[numberOfInputs]; + for(int i = 0; i < numberOfInputs; i ++) { + this.tab[i] = false; + } + } + + /** + * record one input + * @param i the integer value corresponding to the tab location + */ + public void recordOneInput(Button b) { + int i = b.toInt(); + this.tab[i] = true; + } + + /** + * Check if a specific input (for example UP) has been recorded + * @param i the integer value corresponding to the input + * @return + */ + public boolean containsInput(Button b) { + return this.tab[b.toInt()]; + } + + /** + * Check if a number of inputs are contained simultaneously + * @param in a number of inputs. Check if those are containes in this + * @return true if all inputs of in are also in this + */ + public boolean containsInputs(Inputs in) { + for(int i = 0; i < numberOfInputs; i++) { + if(this.tab[i] != in.getInputs()[i]) {return false;} + } + return true; + } + + /** + * Check if a number of inputs are contained simultaneously, in the form of an array of Buttons + * @param bs a number of inputs. Check if those are contained in this + * @return true if all inputs of in are also in this + */ + public boolean containsButtonTab(Button[] bs) { + for(int i = 0; i < bs.length; i++) { + if(!this.containsInput(bs[i])) { return false;} + } + return true; + } + + public boolean[] getInputs() { + return this.tab; + } + + +} diff --git a/docs/class-diagram.jpeg b/docs/class-diagram.jpeg new file mode 100644 index 0000000..4ba0174 Binary files /dev/null and b/docs/class-diagram.jpeg differ diff --git a/docs/gameplay_loop.drawio b/docs/gameplay_loop.drawio new file mode 100644 index 0000000..c09a3eb --- /dev/null +++ b/docs/gameplay_loop.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/gameplay_loop.png b/docs/gameplay_loop.png new file mode 100644 index 0000000..2a627e8 Binary files /dev/null and b/docs/gameplay_loop.png differ