public class Globals {
    // Game Information
    public static final String VERSION = "1.0";
    public static final int W_WIDTH = 800; // The width of the game window.
    public static final int W_HEIGHT = 640; // The height of the game window.
    public static final long SLEEP_TIME = 20; // The sleep time of the animation thread.
    public static final long WAVE_BREAK_TIME = 30 * 1000;
    public static final Random r = new Random();
    public static List<Message> GAME_MESSAGES = new ArrayList<Message>();
    
    // Zombie Information
    public static final int ZOMBIE_REGULAR_TYPE = 1;
    public static final long ZOMBIE_REGULAR_SPAWN = 1000;
    public static final int ZOMBIE_DOG_TYPE = 2;
    public static final long ZOMBIE_DOG_SPAWN = 10000;
    public static final int ZOMBIE_ACID_TYPE = 3;
    public static final long ZOMBIE_ACID_SPAWN = 30000;
    public static final int ZOMBIE_POISONFOG_TYPE = 4;
    public static final long ZOMBIE_POISONFOG_SPAWN = 20000;
    public static final int ZOMBIE_MATRON_TYPE = 5;
    public static final long ZOMBIE_MATRON_SPAWN = 50000;
    public static final int ZOMBIE_TINY_TYPE = 6;
    
    // Boss Information
    public static final int ZOMBIE_BOSS_ABERRATION_TYPE = 7;
    public static final int ZOMBIE_BOSS_ZOMBAT_TYPE = 8;
    public static final int ZOMBIE_BOSS_STITCHES_TYPE = 9;
    
    // Game-State Related
    public static Runnable animation; // The primary animation thread.
    public static GameTime gameTime; // Used to keep track of the time.
    
    public static boolean running; // Whether or not the game is currently running.
    public static boolean started;
    public static boolean crashed; // Tells the game whether or not there was a crash.
    public static boolean paused;
    public static boolean storeOpen;
    public static boolean levelScreenOpen;
    public static boolean deathScreen;
    public static boolean waveInProgress; // Whether the player is fighting or waiting for another wave.
    public static long nextWave;
    
    // Input Related
    public static boolean [] keys; // The state of the game key controls.
    public static boolean [] buttons; // The state of the game mouse button controls.
    public static Point mousePos; // The current position of the mouse on the screen.
    
    // Static Weapons
    public static Handgun HANDGUN = new Handgun();
    public static AssaultRifle ASSAULT_RIFLE = new AssaultRifle();
    public static Shotgun SHOTGUN = new Shotgun();
    public static Flamethrower FLAMETHROWER = new Flamethrower();
    public static Grenade GRENADE = new Grenade();
    public static Landmine LANDMINE = new Landmine();
    public static Flare FLARE = new Flare();
    public static LaserWire LASERWIRE = new LaserWire();
    public static TurretWeapon TURRETWEAPON = new TurretWeapon();
    public static Teleporter TELEPORTER = new Teleporter();
    
    public static Weapon getWeaponByName(String name) {
        if(name.equals(Globals.HANDGUN.getName())) return Globals.HANDGUN;
        else if(name.equals(Globals.ASSAULT_RIFLE.getName())) return Globals.ASSAULT_RIFLE;
        else if(name.equals(Globals.SHOTGUN.getName())) return Globals.SHOTGUN;
        else if(name.equals(Globals.FLAMETHROWER.getName())) return Globals.FLAMETHROWER;
        else if(name.equals(Globals.GRENADE.getName())) return Globals.GRENADE;
        else if(name.equals(Globals.LANDMINE.getName())) return Globals.LANDMINE;
        else if(name.equals(Globals.FLARE.getName())) return Globals.FLARE;
        else if(name.equals(Globals.LASERWIRE.getName())) return Globals.LASERWIRE;
        else if(name.equals(Globals.TURRETWEAPON.getName())) return Globals.TURRETWEAPON;
        else if(name.equals(Globals.TELEPORTER.getName())) return Globals.TELEPORTER;
        else return null;
    }
    
    public static void resetWeapons() {
        Globals.HANDGUN.resetAmmo();
        Globals.ASSAULT_RIFLE.resetAmmo();
        Globals.SHOTGUN.resetAmmo();
        Globals.FLAMETHROWER.resetAmmo();
        Globals.GRENADE.resetAmmo();
        Globals.LANDMINE.resetAmmo();
        Globals.FLARE.resetAmmo();
        Globals.LASERWIRE.resetAmmo();
        Globals.TURRETWEAPON.resetAmmo();
        Globals.TELEPORTER.resetAmmo();
    }
} public class Images {
    // Background Images
    public static final BufferedImage START_SCREEN = GZSFramework.loadImage("/resources/images/GZS_Splash.png");
    public static final BufferedImage BACKGROUND = GZSFramework.loadImage("/resources/images/GZS_Background6.png");
    public static final BufferedImage DEATH_SCREEN = GZSFramework.loadImage("/resources/images/GZS_DeathScreen.png");
    
    // Player-Related
    public static final BufferedImage PLAYER = GZSFramework.loadImage("/resources/images/GZS_Player.png");
    public static final BufferedImage CROSSHAIR = GZSFramework.loadImage("/resources/images/GZS_Crosshair.png");
    
    // Zombie-Related
    public static final BufferedImage ZOMBIE_REGULAR = GZSFramework.loadImage("/resources/images/GZS_Zumby2.png");
    public static final BufferedImage ZOMBIE_DOG = GZSFramework.loadImage("/resources/images/GZS_Rotdog2.png");
    public static final BufferedImage ZOMBIE_ACID = GZSFramework.loadImage("/resources/images/GZS_Upchuck2.png");
    public static final BufferedImage ZOMBIE_POISONFOG = GZSFramework.loadImage("/resources/images/GZS_Gasbag2.png");
    public static final BufferedImage ZOMBIE_MATRON = GZSFramework.loadImage("/resources/images/GZS_BigMama2.png");
    public static final BufferedImage ZOMBIE_TINY = GZSFramework.loadImage("/resources/images/GZS_TinyZumby.png");
    public static final BufferedImage BOSS_ABERRATION = GZSFramework.loadImage("/resources/images/GZS_Aberration2.png");
    public static final BufferedImage BOSS_ZOMBAT = GZSFramework.loadImage("/resources/images/GZS_Zombat.png");
    public static final BufferedImage BOSS_STITCHES = GZSFramework.loadImage("/resources/images/GZS_Stitches.png");
    
    public static final BufferedImage ACID_PARTICLE = GZSFramework.loadImage("/resources/images/GZS_AcidParticle.png");
    public static final BufferedImage STITCHES_HOOK = GZSFramework.loadImage("/resources/images/GZS_Hook.png");
    public static final BufferedImage POISON_GAS_SHEET = GZSFramework.loadImage("/resources/images/GZS_PoisonExplosion.png");
    public static final BufferedImage POISON_STATUS_ICON = GZSFramework.loadImage("/resources/images/GZS_PoisonIcon.png");
    public static final BufferedImage BLOOD_SHEET = GZSFramework.loadImage("/resources/images/GZS_BloodExplosion.png");
    
    // Power-Up Related
    public static final BufferedImage HEALTH_PACK = GZSFramework.loadImage("/resources/images/GZS_Health.png");
    public static final BufferedImage AMMO_PACK = GZSFramework.loadImage("/resources/images/GZS_Ammo.png");
    public static final BufferedImage SPEED_UP = GZSFramework.loadImage("/resources/images/GZS_SpeedUp.png");
    public static final BufferedImage UNLIMITED_AMMO = GZSFramework.loadImage("/resources/images/GZS_UnlimitedAmmo.png");
    public static final BufferedImage EXTRA_LIFE = GZSFramework.loadImage("/resources/images/GZS_ExtraLife.png");
    public static final BufferedImage EXP_MULTIPLIER = GZSFramework.loadImage("/resources/images/GZS_ExpMultiplier.png");
    public static final BufferedImage INVULNERABILITY = GZSFramework.loadImage("/resources/images/GZS_Invulnerability.png");
    public static final BufferedImage NIGHT_VISION = GZSFramework.loadImage("/resources/images/GZS_NightVision.png");
    
    // Ammo-Related
    public static final BufferedImage POPGUN_BULLET = GZSFramework.loadImage("/resources/images/GZS_Bullet.png");
    public static final BufferedImage RTPS_BULLET = GZSFramework.loadImage("/resources/images/GZS_Bullet2.png");
    public static final BufferedImage FIRE_PARTICLE = GZSFramework.loadImage("/resources/images/GZS_FireParticle2.png");
    public static final BufferedImage GRENADE_PARTICLE = GZSFramework.loadImage("/resources/images/GZS_HandEggParticle.png");
    public static final BufferedImage LANDMINE_PARTICLE = GZSFramework.loadImage("/resources/images/GZS_FlipFlopParticle.png");
    public static final BufferedImage FLARE_PARTICLE = GZSFramework.loadImage("/resources/images/GZS_FlareParticle.png");
    public static final BufferedImage LASER_TERMINAL = GZSFramework.loadImage("/resources/images/GZS_LaserTerminal.png");
    public static final BufferedImage EXPLOSION_SHEET = GZSFramework.loadImage("/resources/images/GZS_Explosion.png");
}
 public enum Sounds {
    // Weapon-Related
    POPGUN("shoot4.wav", false, true),
    RTPS("shoot3.wav", false, false),
    BOOMSTICK("shotgun2.wav", false, false),
    FLAMETHROWER("flamethrower2.wav", true, false),
    THROW("throw2.wav", false, false),
    EXPLOSION("explosion2.wav", false, false),
    LANDMINE_ARMED("landmine_armed.wav", false, false),
    TELEPORT("teleport2.wav", false, false),
    
    // Zombie-Related
    MOAN1("zombie_moan_01.wav", false, true),
    MOAN2("zombie_moan_02.wav", false, true),
    MOAN3("zombie_moan_03.wav", false, true),
    MOAN4("zombie_moan_04.wav", false, true),
    MOAN5("zombie_moan_05.wav", false, true),
    MOAN6("zombie_moan_06.wav", false, true),
    MOAN7("zombie_moan_07.wav", false, true),
    MOAN8("zombie_moan_08.wav", false, true),
    POISONCLOUD("poison_cloud.wav", false, false),
    
    // Game Sounds
    POWERUP("powerup2.wav", false, false),
    PURCHASEWEAPON("purchase_weapon2.wav", false, true),
    BUYAMMO("buy_ammo2.wav", false, true),
    POINTBUY("point_buy.wav", false, true),
    PAUSE("pause.wav", false, false),
    UNPAUSE("unpause.wav", false, false);
    
    private String file;
    private Music audio;
    public Music getAudio() { return this.audio; }
    private boolean looped; 
    private boolean overlap;

    Sounds(String filename, boolean loop, boolean over) {
        openClip(filename, loop, over);
    }

    private synchronized void openClip(String filename, boolean loop, boolean over) {
        file = filename;
        audio = TinySound.loadMusic("/resources/sounds/" + filename);
        looped = loop;
        overlap = over;
    }
    
    public synchronized void play() {
        play(1.0);
    }
    
    public synchronized void play(final double gain) {
        // If the clip supports overlapping, create a new Music object to use.
        if(overlap) {
            Music m = TinySound.loadMusic("/resources/sounds/" + file);
            m.rewind();
            m.play(looped, gain);
        } else {
            audio.rewind();
            audio.play(looped, gain);
        }
    }

    public static void init() {
        values();
    }
} public class LightSource extends Point2D.Double {
    // Member Variables
    private long timeExpiring;
    private long life;
    public long getLife() { return this.life; }
    private float radius;
    public float getIntensity() { return this.radius; }
    private float [] distance;
    private Color [] colors;
    
    /**
     * Creates a new light source at the center point with the specified intensity.
     * @param center The center point of the light source.
     * @param intensity The radius of the light source.
     **/
    public LightSource(Point2D.Double center, float intensity) {
        this(center, 0, intensity, new float[]{0.0f, 1.0f}, new Color[]{new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK});
    }
    
    /**
     * Creates a new light source at the center point with the specified intensity
     * using the colors specified.
     * @param center The center point of the light source.
     * @param life Indicates how long the light source lasts. Zero if permanent.
     * @param intensity The radius of the light source.
     * @param distance The distance parameters of the radial gradient.
     * @param colors The colors of the radial gradient.
     **/
    public LightSource(Point2D.Double center, long life, float intensity, float [] distance, Color [] colors) {
        super(center.x, center.y);
        this.timeExpiring = Globals.gameTime.getElapsedMillis() + life;
        this.life = life;
        this.radius = intensity;
        this.distance = distance;
        this.colors = colors;
    }
    
    public boolean isAlive() {
        return (this.life > 0) && (Globals.gameTime.getElapsedMillis() <= this.timeExpiring);
    }
    
    /**
     * Moves the center of the light source to the specified center point.
     * @param center The point to move the center of the light source to.
     **/
    public void move(Point2D.Double center) {
        this.x = center.x;
        this.y = center.y;
    }
    
    public void draw(Graphics2D g2d) {
        RadialGradientPaint rgp = new RadialGradientPaint(this, this.radius, this.distance, this.colors);
        g2d.setPaint(rgp);
        g2d.fillRect((int)(this.x - this.radius), (int)(this.y - this.radius), 
                     (int)(this.radius * 2), (int)(this.radius * 2));
    }
}
 public class Message {
    // Member Variables
    private String text;
    public String getText() { return this.text; }
    private long expirationTime;
    
    public Message(String text, long duration) {
        this.text = text;
        this.expirationTime = Globals.gameTime.getElapsedMillis() + duration;
    }
    
    public boolean isAlive() { return (Globals.gameTime.getElapsedMillis() < this.expirationTime); }
    
    public void draw(Graphics2D g2d, Point2D.Double pos) {
        Composite savedComp = g2d.getComposite();
        
        double opacity = 1.0f;
        if(Globals.gameTime.getElapsedMillis() >= (this.expirationTime - 3000)) {
            opacity = ((double)this.expirationTime - (double)Globals.gameTime.getElapsedMillis()) / 3000;
        }
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)opacity));
        g2d.drawString(this.text, (int)pos.x, (int)pos.y);
        
        g2d.setComposite(savedComp);
    }
}
 public class StatusEffect {
    // Member Variables
    private BufferedImage img;
    public BufferedImage getImage() { return this.img; }
    private long endTime;
    public long getEndTime() { return this.endTime; }
    public void refresh(long duration) { this.endTime = Globals.gameTime.getElapsedMillis() + duration; }
    public boolean isActive() { return (Globals.gameTime.getElapsedMillis() < this.endTime); }
    private int value;
    public long getValue() { return this.value; }
    
    public StatusEffect(BufferedImage img, long duration, int value) {
        this.img = img;
        this.endTime = Globals.gameTime.getElapsedMillis() + duration;
        this.value = value;
    }
}
 public class ExpMultiplier extends Item {
    // Final Variables
    public static final int ID = 6;
    public static final String EFFECT_NAME = "Exp Multiplier";
    public static final long ITEM_DURATION = 15 * 1000;
    public static final long DURATION = 20 * 1000;
    
    public ExpMultiplier(Point2D.Double p) {
        super(ExpMultiplier.ID, ExpMultiplier.EFFECT_NAME, Images.EXP_MULTIPLIER, ExpMultiplier.DURATION);
        this.x = p.x;
        this.y = p.y;
    }
    
    @Override
    public void applyEffect(Player p) {
        p.addStatusEffect(ExpMultiplier.ID, ExpMultiplier.EFFECT_NAME, Images.EXP_MULTIPLIER, ExpMultiplier.DURATION, 0);
        synchronized(Globals.GAME_MESSAGES) { Globals.GAME_MESSAGES.add(new Message("Player experience doubled for " + (ExpMultiplier.DURATION / 1000) +" seconds!", 5000)); }
        Sounds.POWERUP.play();
    }
}
 public class Invulnerability extends Item {
    // Final Variables
    public static final int ID = 7;
    public static final String EFFECT_NAME = "Invulnerability";
    public static final long ITEM_DURATION = 15 * 1000;
    public static final long DURATION = 10 * 1000;
    
    public Invulnerability(Point2D.Double p) {
        super(Invulnerability.ID, Invulnerability.EFFECT_NAME, Images.INVULNERABILITY, Invulnerability.DURATION);
        this.x = p.x;
        this.y = p.y;
    }
    
    @Override
    public void applyEffect(Player p) {
        p.addStatusEffect(Invulnerability.ID, this.getName(), Images.INVULNERABILITY, Invulnerability.DURATION, 0);
        synchronized(Globals.GAME_MESSAGES) {
            Globals.GAME_MESSAGES.add(new Message(("Player has invulnerability for " + (Invulnerability.DURATION / 1000) + " seconds!"), 5000));
        }
        Sounds.POWERUP.play();
    }
}
 public class NightVision extends Item {
    // Final Variables
    public static final int ID = 6;
    public static final String EFFECT_NAME = "Night Vision";
    public static final long ITEM_DURATION = 15 * 1000;
    public static final long DURATION = 30 * 1000;
    
    public NightVision(Point2D.Double p) {
        super(NightVision.ID, NightVision.EFFECT_NAME, Images.NIGHT_VISION, NightVision.DURATION);
        this.x = p.x;
        this.y = p.y;
    }
    
    @Override
    public void applyEffect(Player p) {
        p.addStatusEffect(NightVision.ID, NightVision.EFFECT_NAME, Images.NIGHT_VISION, NightVision.DURATION, 0);
        synchronized(Globals.GAME_MESSAGES) { Globals.GAME_MESSAGES.add(new Message("Player has night vision for " + (NightVision.DURATION / 1000) +" seconds!", 5000)); }
        Sounds.POWERUP.play();
    }
}
 public class SpeedUp extends Item {
    // Final Variables
    public static final int ID = 3;
    public static final String EFFECT_NAME = "SpeedUp";
    public static final long ITEM_DURATION = 15 * 1000;
    public static final long DURATION = 10 * 1000;
    public static final int SPEED_MULTIPLIER = 2;
    
    public SpeedUp(Point2D.Double p) {
        super(SpeedUp.ID, SpeedUp.EFFECT_NAME, Images.SPEED_UP, SpeedUp.DURATION);
        this.x = p.x;
        this.y = p.y;
    }
    
    @Override
    public void applyEffect(Player p) {
        p.addStatusEffect(SpeedUp.ID, this.getName(), Images.SPEED_UP, SpeedUp.DURATION, SpeedUp.SPEED_MULTIPLIER);
        synchronized(Globals.GAME_MESSAGES) {
            Globals.GAME_MESSAGES.add(new Message("Player speed doubled for " + (SpeedUp.DURATION / 1000) + " seconds!", 5000));
        }
        Sounds.POWERUP.play();
    }
}
 public class UnlimitedAmmo extends Item {
    // Final Variables
    public static final int ID = 4;
    public static final String EFFECT_NAME = "Unlimited Ammo";
    public static final long ITEM_DURATION = 15 * 1000;
    public static final long DURATION = 10 * 1000;
    
    public UnlimitedAmmo(Point2D.Double p) {
        super(UnlimitedAmmo.ID, UnlimitedAmmo.EFFECT_NAME, Images.UNLIMITED_AMMO, UnlimitedAmmo.DURATION);
        this.x = p.x;
        this.y = p.y;
    }
    
    @Override
    public void applyEffect(Player p) {
        p.addStatusEffect(UnlimitedAmmo.ID, this.getName(), Images.UNLIMITED_AMMO, UnlimitedAmmo.DURATION, 0);
        synchronized(Globals.GAME_MESSAGES) {
            Globals.GAME_MESSAGES.add(new Message(("Player has unlimited ammo for " + (UnlimitedAmmo.DURATION / 1000) + " seconds!"), 5000));
        }
        Sounds.POWERUP.play();
    }
}
 public class Weapon {
    private String name;
    public String getName() { return this.name; }
    private int key;
    public int getKey() { return this.key; }
    private BufferedImage image;
    public BufferedImage getImage() { return this.image; }
    
    protected int ammoLeft;
    public int getAmmoLeft() { return this.ammoLeft; }
    private int maxAmmo;
    public int getMaxAmmo() { return this.maxAmmo; }
    private int ammoPerUse;
    
    public int getWeaponPrice() { return 0; }
    public int getAmmoPrice() { return 0; }
    
    private boolean automatic; // Indicates if the weapon can be fired continuously.
    public boolean isAutomatic() { return this.automatic; }
    protected boolean fired; // Used with automatic to determine if the weapon needs to be fired again.
    public boolean hasFired() { return this.fired; }
    public void resetFire() { this.fired = false; }
    private int cooldown;
    private int coolPeriod;
    public double getCooldownPercentage() { return ((double)cooldown / (double)coolPeriod); }
    public void resetCooldown() { this.cooldown = this.coolPeriod; }
    public void cool() { if(this.cooldown > 0) this.cooldown--; }
    
    public boolean canFire() {
        boolean a = this.ammoLeft >= this.ammoPerUse;
        boolean b = this.cooldown == 0;
        boolean c = this.automatic || (!this.automatic && !this.fired);
        //System.out.println("Enough Ammo? " + a + ", Cooled? " + b + ", Automatic or Manual and Cool? " + c);
        return (a) && (b) && (c); 
    }
    public boolean ammoFull() { return this.ammoLeft == this.maxAmmo; }
    public void addAmmo(int amount) { 
        if((this.ammoLeft + amount) > this.maxAmmo) this.ammoLeft = this.maxAmmo;
        else this.ammoLeft += amount;
    }
    public int getAmmoPackAmount() {
        // To be overridden...
        return 0;
    }
    public void resetAmmo() {
        synchronized(this.particles) { this.particles.clear(); }
    }
    
    protected List<Particle> particles;
    public List<Particle> getParticles() { return this.particles; }
    
    public List<LightSource> getLights() { return null; }
    
    public Weapon(String name, int key, String filename, int ammoLeft, int maxAmmo, int ammoPerUse, int cooldown, boolean automatic) {
        this.name = name;
        this.key = key;
        
        this.image = GZSFramework.loadImage(filename);
        
        this.ammoLeft = ammoLeft;
        this.maxAmmo = maxAmmo;
        this.ammoPerUse = ammoPerUse;
        
        this.automatic = automatic;
        this.fired = false;
        this.cooldown = cooldown;
        this.coolPeriod = cooldown;
        
        this.particles = Collections.synchronizedList(new ArrayList<Particle>());
    }
    
    public void updateWeapon(List<Zombie> zombies) {
        // To be overridden.
    }
    
    public void drawAmmo(Graphics2D g2d) {
        // To be overridden.
    }
    
    public void fire(double theta, Point2D.Double pos, Player player) {
        // To be overridden.
    }
    
    public int checkForDamage(Rectangle2D.Double rect) {
        // To be overridden.
        return 0;
    }
    
    public void consumeAmmo() {
        this.ammoLeft -= this.ammoPerUse;
    }
}

import genericzombieshooter.misc.Globals;
import genericzombieshooter.misc.Images;
import genericzombieshooter.misc.Sounds;
import genericzombieshooter.structures.LightSource;
import genericzombieshooter.structures.Message;
import genericzombieshooter.structures.StatusEffect;
import genericzombieshooter.structures.items.ExpMultiplier;
import genericzombieshooter.structures.items.Invulnerability;
import genericzombieshooter.structures.items.NightVision;
import genericzombieshooter.structures.items.SpeedUp;
import genericzombieshooter.structures.items.UnlimitedAmmo;
import genericzombieshooter.structures.weapons.Weapon;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;

/**
    This file is part of Generic Zombie Shooter.

    Generic Zombie Shooter is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Generic Zombie Shooter is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Generic Zombie Shooter.  If not, see <http://www.gnu.org/licenses/>.
 **/
package genericzombieshooter.actors;


/**
 * Represents the player character.
 * @author Darin Beaudreau
 **/
public class Player extends Rectangle2D.Double {
    // Final variables.
    public static final int DEFAULT_HEALTH = 150;
    public static final long INVINCIBILITY_LENGTH = 3000;
    private static final double MOVE_SPEED = 2; // How many pixels per tick the player moves.
    public static final double AUDIO_RANGE = 500;
    
    public static final int MAX_HEALTH_ID = 1;
    private static final int MAX_HEALTH_INC = 50;
    public static final int DAMAGE_ID = 2;
    private static final double DAMAGE_INC = 0.2;
    public static final int SPEED_ID = 3;
    private static final double SPEED_INC = 0.2;
    
    // Member variables.
    private AffineTransform af;
    private double theta;
    private BufferedImage img;
    private LightSource light;
    
    private int health;
    private int maxHealth;
    private double damageBonus;
    private double speed;
    private double speedBonus;
    private int cash;
    
    private int experience;
    private int experienceMultiplier;
    private int level;
    private int skillPoints;
    
    private int lives;
    
    private boolean blink;
    private long nextBlinkChange;
    

    private HashMap<String, StatusEffect> statusEffects;
