Fixed the health system for the player
This commit is contained in:
parent
a107afd05d
commit
5ab2075f29
|
|
@ -70,6 +70,9 @@ public class DisplayWindow implements IMainloopTask
|
||||||
GLFW.glfwSetKeyCallback(this.window, new KeyCallback());
|
GLFW.glfwSetKeyCallback(this.window, new KeyCallback());
|
||||||
GLFW.glfwSetJoystickCallback(JoystickCallback.JOYSTICK_CALLBACK);
|
GLFW.glfwSetJoystickCallback(JoystickCallback.JOYSTICK_CALLBACK);
|
||||||
|
|
||||||
|
// Make the cursor invisible
|
||||||
|
GLFW.glfwSetInputMode(this.window, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_HIDDEN);
|
||||||
|
|
||||||
// Show the window
|
// Show the window
|
||||||
GLFW.glfwShowWindow(this.window);
|
GLFW.glfwShowWindow(this.window);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package shootergame.entity;
|
package shootergame.entity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import shootergame.Main;
|
import shootergame.Main;
|
||||||
import shootergame.display.Camera;
|
import shootergame.display.Camera;
|
||||||
import shootergame.display.transparent.ITransparentObject;
|
import shootergame.display.transparent.ITransparentObject;
|
||||||
|
|
@ -9,6 +11,7 @@ import shootergame.tiles.Tile;
|
||||||
import shootergame.util.math.MathHelpers;
|
import shootergame.util.math.MathHelpers;
|
||||||
import shootergame.util.math.vec.Vec2d;
|
import shootergame.util.math.vec.Vec2d;
|
||||||
import shootergame.util.math.vec.Vec2i;
|
import shootergame.util.math.vec.Vec2i;
|
||||||
|
import shootergame.world.World;
|
||||||
import shootergame.world.chunk.Chunk;
|
import shootergame.world.chunk.Chunk;
|
||||||
import shootergame.world.layer.Layer;
|
import shootergame.world.layer.Layer;
|
||||||
|
|
||||||
|
|
@ -19,6 +22,7 @@ public class Entity implements ITransparentObject
|
||||||
public boolean opaqueTile = true;
|
public boolean opaqueTile = true;
|
||||||
public double hitbox = 1;
|
public double hitbox = 1;
|
||||||
public boolean isSolid = false;
|
public boolean isSolid = false;
|
||||||
|
public Chunk chunk;
|
||||||
|
|
||||||
public Entity(Vec2d pos, double angle)
|
public Entity(Vec2d pos, double angle)
|
||||||
{
|
{
|
||||||
|
|
@ -77,8 +81,70 @@ public class Entity implements ITransparentObject
|
||||||
this.moveBackward(0.1);
|
this.moveBackward(0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void kill() {
|
||||||
|
chunk.killEntity(this);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean moveIsLegal(Vec2d pos)
|
public boolean moveIsLegal(Vec2d pos)
|
||||||
{
|
{
|
||||||
|
/*// Is this entity solid
|
||||||
|
if(isSolid)
|
||||||
|
{
|
||||||
|
// Get the layer
|
||||||
|
Layer l = Main.world.getLayer();
|
||||||
|
|
||||||
|
// Get some tiles
|
||||||
|
Vec2i[] tiles = {
|
||||||
|
new Vec2i(MathHelpers.floor(pos.x)+0, MathHelpers.floor(pos.y)+0),
|
||||||
|
new Vec2i(MathHelpers.floor(pos.x)+1, MathHelpers.floor(pos.y)+0),
|
||||||
|
new Vec2i(MathHelpers.floor(pos.x)+1, MathHelpers.floor(pos.y)+1),
|
||||||
|
new Vec2i(MathHelpers.floor(pos.x)+0, MathHelpers.floor(pos.y)+1)
|
||||||
|
};
|
||||||
|
|
||||||
|
for(Vec2i tpos : tiles)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// Get the tile
|
||||||
|
Tile t = l.getBackTile(tpos);
|
||||||
|
|
||||||
|
// Send false if the tile isn't walkable
|
||||||
|
if(!t.tileWalkable) {
|
||||||
|
System.out.println("1");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the front tile
|
||||||
|
Tile t = l.getFrontTile(tpos);
|
||||||
|
|
||||||
|
// Send false if the tile isn't walkable
|
||||||
|
if(!t.tileWalkable) {
|
||||||
|
System.out.println("2");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the tiles hitbox if the tile is solid
|
||||||
|
if(t.tileSolid)
|
||||||
|
{
|
||||||
|
// Is the entity in the tiles hitbox
|
||||||
|
if(
|
||||||
|
tpos.x + t.tileHitbox < pos.x ||
|
||||||
|
tpos.x - t.tileHitbox > pos.x ||
|
||||||
|
tpos.y + t.tileHitbox < pos.y ||
|
||||||
|
tpos.y - t.tileHitbox > pos.y
|
||||||
|
) {
|
||||||
|
System.out.println("3");
|
||||||
|
|
||||||
|
// Send back false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Send back true if everything passes
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package shootergame.entity;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import shootergame.display.Camera;
|
import shootergame.display.Camera;
|
||||||
|
import shootergame.entity.particle.ParticleBlood;
|
||||||
import shootergame.util.gl.GlHelpers;
|
import shootergame.util.gl.GlHelpers;
|
||||||
import shootergame.util.math.random.RandomHelpers;
|
import shootergame.util.math.random.RandomHelpers;
|
||||||
import shootergame.util.math.vec.Vec2d;
|
import shootergame.util.math.vec.Vec2d;
|
||||||
|
|
@ -42,8 +43,8 @@ public class EntityBullet extends EntityParticle
|
||||||
EntityAlive ea = (EntityAlive)e;
|
EntityAlive ea = (EntityAlive)e;
|
||||||
|
|
||||||
// Spawn some blood particles
|
// Spawn some blood particles
|
||||||
for(int i=0;i<20;i++) {
|
for(int i=0;i<10;i++) {
|
||||||
chunk.spawnEntity(new EntityBlood(rand, pos.copy(), angle));
|
chunk.spawnEntity(new ParticleBlood(rand, pos.copy(), angle));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Harm the entity
|
// Harm the entity
|
||||||
|
|
@ -67,7 +68,7 @@ public class EntityBullet extends EntityParticle
|
||||||
public void render(Vec2d pos, Camera camera)
|
public void render(Vec2d pos, Camera camera)
|
||||||
{
|
{
|
||||||
// Set the colour
|
// Set the colour
|
||||||
GlHelpers.color3(0.6, 0.6, 0);
|
GlHelpers.color3(1, 0.8, 0.3);
|
||||||
|
|
||||||
// Call super
|
// Call super
|
||||||
super.render(pos, camera);
|
super.render(pos, camera);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
package shootergame.entity;
|
package shootergame.entity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import mainloop.task.IMainloopTask;
|
import mainloop.task.IMainloopTask;
|
||||||
import shootergame.Main;
|
import shootergame.Main;
|
||||||
import shootergame.init.Entities;
|
import shootergame.init.Entities;
|
||||||
|
import shootergame.mainloop.MainloopEventHandler;
|
||||||
|
import shootergame.util.math.map.Map2DElement;
|
||||||
|
import shootergame.util.math.random.RandomHelpers;
|
||||||
|
import shootergame.world.chunk.Chunk;
|
||||||
|
import shootergame.world.layer.Layer;
|
||||||
|
|
||||||
public class EntityEventHandler implements IMainloopTask
|
public class EntityEventHandler implements IMainloopTask
|
||||||
{
|
{
|
||||||
public static final EntityEventHandler ENTITY_EVENT_HANDLER = new EntityEventHandler();
|
public static final EntityEventHandler ENTITY_EVENT_HANDLER = new EntityEventHandler();
|
||||||
|
|
||||||
|
private Random rand = new Random();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean MainLoopDelay(long millis) {
|
public boolean MainLoopDelay(long millis) {
|
||||||
return millis > 10;
|
return millis > 10;
|
||||||
|
|
@ -27,7 +35,5 @@ public class EntityEventHandler implements IMainloopTask
|
||||||
Main.world.tickEntities();
|
Main.world.tickEntities();
|
||||||
Main.world.spawnRandomEntities();
|
Main.world.spawnRandomEntities();
|
||||||
Main.player.tick(null, Main.world.getLayer());
|
Main.player.tick(null, Main.world.getLayer());
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ public class EntityParticle extends Entity
|
||||||
this.size = size;
|
this.size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSize(double size) {
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(Vec2d pos, Camera camera)
|
public void render(Vec2d pos, Camera camera)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package shootergame.entity;
|
||||||
|
|
||||||
|
public class EntityTnt extends EntityVertical
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -11,15 +11,20 @@ import shootergame.world.layer.Layer;
|
||||||
public class EntityZombie extends EntityVertical implements EntityAlive
|
public class EntityZombie extends EntityVertical implements EntityAlive
|
||||||
{
|
{
|
||||||
private Random rand;
|
private Random rand;
|
||||||
private OpenSimplexNoise noise;
|
private OpenSimplexNoise noise_movement;
|
||||||
|
private OpenSimplexNoise noise_gun_fire;
|
||||||
|
private OpenSimplexNoise noise_gun_angle;
|
||||||
private double time;
|
private double time;
|
||||||
private double health_max = 100;
|
private double health_max = 100;
|
||||||
private double health = health_max;
|
private double health = health_max;
|
||||||
|
private int gun_interval = 0;
|
||||||
|
|
||||||
public EntityZombie() {
|
public EntityZombie() {
|
||||||
super(Textures.ENTITY_ZOMBIE, 1);
|
super(Textures.ENTITY_ZOMBIE, 1);
|
||||||
rand = new Random();
|
rand = new Random();
|
||||||
noise = new OpenSimplexNoise(rand.nextLong());
|
noise_movement = new OpenSimplexNoise(rand.nextLong());
|
||||||
|
noise_gun_fire = new OpenSimplexNoise(rand.nextLong());
|
||||||
|
noise_gun_angle = new OpenSimplexNoise(rand.nextLong());
|
||||||
time = 0;
|
time = 0;
|
||||||
|
|
||||||
// Set some settings
|
// Set some settings
|
||||||
|
|
@ -31,13 +36,33 @@ public class EntityZombie extends EntityVertical implements EntityAlive
|
||||||
public void tick(Chunk chunk, Layer layer) {
|
public void tick(Chunk chunk, Layer layer) {
|
||||||
super.tick(chunk, layer);
|
super.tick(chunk, layer);
|
||||||
|
|
||||||
|
// Get the angle between the player and the zombie
|
||||||
double angle = Math.atan2(pos.x - Main.player.pos.x, pos.y - Main.player.pos.y);
|
double angle = Math.atan2(pos.x - Main.player.pos.x, pos.y - Main.player.pos.y);
|
||||||
|
|
||||||
|
// Move forward towards the player
|
||||||
this.angle = Math.toDegrees(angle) + 180;
|
this.angle = Math.toDegrees(angle) + 180;
|
||||||
this.angle += noise.eval(time, 0)*60;
|
this.angle += noise_movement.eval(time, 0)*60;
|
||||||
|
this.moveForward(0.05);
|
||||||
|
|
||||||
|
if(noise_gun_fire.eval(time, 0) > 0)
|
||||||
|
{
|
||||||
|
gun_interval += 1;
|
||||||
|
gun_interval %= 10;
|
||||||
|
|
||||||
|
if(gun_interval == 0)
|
||||||
|
{
|
||||||
|
// Aim the gun at the player
|
||||||
|
double angle_gun = Math.toDegrees(angle) + 180;
|
||||||
|
angle_gun += noise_gun_angle.eval(time, 0)*20;
|
||||||
|
|
||||||
|
// Fire the gun
|
||||||
|
layer.spawnEntity(new EntityBullet(rand, pos.copy(), this, angle_gun));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase time
|
||||||
time += 0.001;
|
time += 0.001;
|
||||||
|
|
||||||
this.moveForward(0.05);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
package shootergame.entity;
|
package shootergame.entity.particle;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import shootergame.display.Camera;
|
import shootergame.display.Camera;
|
||||||
|
import shootergame.entity.EntityParticle;
|
||||||
import shootergame.util.gl.GlHelpers;
|
import shootergame.util.gl.GlHelpers;
|
||||||
import shootergame.util.math.random.RandomHelpers;
|
import shootergame.util.math.random.RandomHelpers;
|
||||||
import shootergame.util.math.vec.Vec2d;
|
import shootergame.util.math.vec.Vec2d;
|
||||||
import shootergame.world.chunk.Chunk;
|
import shootergame.world.chunk.Chunk;
|
||||||
import shootergame.world.layer.Layer;
|
import shootergame.world.layer.Layer;
|
||||||
|
|
||||||
public class EntityBlood extends EntityParticle
|
public class ParticleBlood extends EntityParticle
|
||||||
{
|
{
|
||||||
private double r_color;
|
private double r_color;
|
||||||
private double speed = 0.1;
|
private double speed = 0.1;
|
||||||
|
|
@ -17,7 +18,7 @@ public class EntityBlood extends EntityParticle
|
||||||
private double height = 0;
|
private double height = 0;
|
||||||
private double height_velocity = 0;
|
private double height_velocity = 0;
|
||||||
|
|
||||||
public EntityBlood(Random rand, Vec2d pos, double angle) {
|
public ParticleBlood(Random rand, Vec2d pos, double angle) {
|
||||||
super(rand.nextDouble() / 5, 0);
|
super(rand.nextDouble() / 5, 0);
|
||||||
|
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package shootergame.entity.particle;
|
||||||
|
|
||||||
|
import shootergame.display.Camera;
|
||||||
|
import shootergame.entity.EntityParticle;
|
||||||
|
import shootergame.util.gl.GlHelpers;
|
||||||
|
import shootergame.util.math.vec.Vec2d;
|
||||||
|
import shootergame.world.chunk.Chunk;
|
||||||
|
import shootergame.world.layer.Layer;
|
||||||
|
|
||||||
|
public class ParticleSpark extends EntityParticle
|
||||||
|
{
|
||||||
|
private double size = 1;
|
||||||
|
|
||||||
|
public ParticleSpark(int height) {
|
||||||
|
super(1, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(Chunk chunk, Layer layer) {
|
||||||
|
super.tick(chunk, layer);
|
||||||
|
|
||||||
|
// Reduce the size
|
||||||
|
size -= 0.01;
|
||||||
|
setSize(size);
|
||||||
|
|
||||||
|
// Is the size zero
|
||||||
|
if(size <= 0)
|
||||||
|
{
|
||||||
|
// Destroy this particle
|
||||||
|
kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(Vec2d pos, Camera camera)
|
||||||
|
{
|
||||||
|
// Set some settings
|
||||||
|
GlHelpers.color3(1, 1, 0);
|
||||||
|
|
||||||
|
// Call super
|
||||||
|
super.render(pos, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ public class EntityPlayer extends EntityVertical implements EntityAlive
|
||||||
|
|
||||||
private int bullet_frequency = 0;
|
private int bullet_frequency = 0;
|
||||||
private Random rand;
|
private Random rand;
|
||||||
private double health_max = 100;
|
private double health_max = 1000;
|
||||||
private double health = health_max;
|
private double health = health_max;
|
||||||
|
|
||||||
public EntityPlayer() {
|
public EntityPlayer() {
|
||||||
|
|
@ -41,6 +41,9 @@ public class EntityPlayer extends EntityVertical implements EntityAlive
|
||||||
@Override
|
@Override
|
||||||
public void tick(Chunk chunk, Layer layer)
|
public void tick(Chunk chunk, Layer layer)
|
||||||
{
|
{
|
||||||
|
// Don't tick if the player is dead
|
||||||
|
if(this.getHealth() < 0) return;
|
||||||
|
|
||||||
// Call super
|
// Call super
|
||||||
super.tick(chunk, layer);
|
super.tick(chunk, layer);
|
||||||
|
|
||||||
|
|
@ -70,6 +73,9 @@ public class EntityPlayer extends EntityVertical implements EntityAlive
|
||||||
@Override
|
@Override
|
||||||
public void render(Vec2d pos, Camera camera)
|
public void render(Vec2d pos, Camera camera)
|
||||||
{
|
{
|
||||||
|
// Don't render if the player is dead
|
||||||
|
if(this.getHealth() < 0) return;
|
||||||
|
|
||||||
// Moving
|
// Moving
|
||||||
if(MOVE_BACKWARD || MOVE_FORWARD || moving)
|
if(MOVE_BACKWARD || MOVE_FORWARD || moving)
|
||||||
super.render(pos, camera, Textures.ENTITY_PLAYER_MOVING, 1);
|
super.render(pos, camera, Textures.ENTITY_PLAYER_MOVING, 1);
|
||||||
|
|
|
||||||
|
|
@ -164,20 +164,30 @@ public class JoystickCallback implements GLFWJoystickCallbackI, IMainloopTask
|
||||||
// Set the players moving to false
|
// Set the players moving to false
|
||||||
else Main.player.moving = false;
|
else Main.player.moving = false;
|
||||||
|
|
||||||
// Is the right stick moved into a position (gun stick)
|
// Is the right x axis stick moved into a position (camera stick)
|
||||||
if(right_x > 0.3 || right_x < -0.3 || right_y > 0.3 || right_y < -0.3)
|
if(right_x > 0.3 || right_x < -0.3) {
|
||||||
{
|
Main.player.angle += right_x;
|
||||||
// Get the the angle and fire the bullet
|
|
||||||
double angle = Math.toDegrees(Math.atan2(right_y, right_x)) + 90;
|
|
||||||
Main.player.fireBullet(angle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(shoulder_left) {
|
// Gun trigger
|
||||||
Main.player.angle -= 1;
|
if(right_trigger > 0.3) {
|
||||||
|
Main.player.fireBullet(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(shoulder_right) {
|
if(dpad_up) {
|
||||||
Main.player.angle += 1;
|
Main.player.fireBullet(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dpad_down) {
|
||||||
|
Main.player.fireBullet(180);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dpad_left) {
|
||||||
|
Main.player.fireBullet(270);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dpad_right) {
|
||||||
|
Main.player.fireBullet(90);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ public class Tile implements ITransparentObject
|
||||||
public boolean opaqueTile = false;
|
public boolean opaqueTile = false;
|
||||||
public boolean tileSolid = false;
|
public boolean tileSolid = false;
|
||||||
public boolean tileWalkable = true;
|
public boolean tileWalkable = true;
|
||||||
|
public double tileHitbox = 0;
|
||||||
|
|
||||||
public Tile(String id) {
|
public Tile(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public class Chunk
|
||||||
|
|
||||||
private Tile tiles_back[] = new Tile[CHUNK_INDEX];
|
private Tile tiles_back[] = new Tile[CHUNK_INDEX];
|
||||||
private Tile tiles_front[] = new Tile[CHUNK_INDEX];
|
private Tile tiles_front[] = new Tile[CHUNK_INDEX];
|
||||||
private ArrayList<Entity> entities = new ArrayList<Entity>();
|
public ArrayList<Entity> entities = new ArrayList<Entity>();
|
||||||
private Random rand;
|
private Random rand;
|
||||||
private Layer layer;
|
private Layer layer;
|
||||||
private Vec2i c_pos;
|
private Vec2i c_pos;
|
||||||
|
|
@ -65,6 +65,7 @@ public class Chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
public void spawnEntity(Entity e) {
|
public void spawnEntity(Entity e) {
|
||||||
|
e.chunk = this;
|
||||||
entities.add(e);
|
entities.add(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ public class Layer
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vec2i getChunkPosFromPos(Vec2i pos) {
|
private Vec2i getChunkPosFromPos(Vec2i pos) {
|
||||||
return this.getChunkPosFromPos(new Vec2i(pos.x, pos.y));
|
return this.getChunkPosFromPos(new Vec2d(pos.x, pos.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vec2i getChunkPosFromPos(Vec2d pos) {
|
private Vec2i getChunkPosFromPos(Vec2d pos) {
|
||||||
|
|
@ -148,6 +148,17 @@ public class Layer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the player is close enough
|
||||||
|
if(
|
||||||
|
Main.player.pos.x + distance > pos.x &&
|
||||||
|
Main.player.pos.x - distance < pos.x &&
|
||||||
|
Main.player.pos.y + distance > pos.y &&
|
||||||
|
Main.player.pos.y - distance < pos.y
|
||||||
|
) {
|
||||||
|
// Add the player to the list of entities
|
||||||
|
entities.add(Main.player);
|
||||||
|
}
|
||||||
|
|
||||||
// Send back the list of nearby entities
|
// Send back the list of nearby entities
|
||||||
return entities;
|
return entities;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue