package worm.effects;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import net.puppygames.applet.Factory;
import net.puppygames.applet.Game;
import net.puppygames.applet.Pool;
import net.puppygames.applet.Screen;
import net.puppygames.applet.SimplePool;
import net.puppygames.applet.Tickable;
import org.lwjgl.util.Color;
import org.lwjgl.util.Point;
import worm.GameMap;
import worm.MapRenderer;
import worm.Tile;
import worm.Worm;
import worm.screens.GameScreen;
import com.shavenpuppy.jglib.interpolators.LinearInterpolator;
import com.shavenpuppy.jglib.resources.Feature;
import com.shavenpuppy.jglib.resources.Range;
import com.shavenpuppy.jglib.sprites.Animation;
import com.shavenpuppy.jglib.sprites.Sprite;
import com.shavenpuppy.jglib.util.FPMath;
import com.shavenpuppy.jglib.util.Util;
/**
* Handles rain and snow weather effects.
*/
public class WeatherEmitter extends Feature implements Tickable {
private static final long serialVersionUID = 1L;
/** Layer */
private int layer, subLayer;
/** Initial particle height */
private float height;
/** Particle angle */
private Range angle;
/** Animation for the particle */
private Animation animation;
/** Minimum gravity */
private float minGravity;
/** Scale */
private Range startScale, endScale;
/** Falling velocity */
private Range gravity;
/** Acceleration */
private Range acceleration;
/** Modulate X position by cos * time * this value */
private Range xmod;
/** HSV */
private Range startHue, startBrightness, startSaturation;
/** HSV */
private Range endHue, endBrightness, endSaturation;
/** Modulation rate */
private float modRate;
/** Wind - x velocity range */
private Range wind;
/** Ground animation, when the particle hits the ground */
private Animation groundAnimation;
/** Max particles : screen size ratio. Screen logical size area * density = max particles. */
private float density;
/** Screen border size (logical units): spawn particles this far outside the visible screen area */
private int border;
/*
* Transient
*/
private transient boolean active;
private transient Screen screen;
private transient Pool<WeatherParticle> pool;
private transient List<WeatherParticle> particles;
/**
* A single weather particle
*/
private class WeatherParticle implements Serializable {
private static final long serialVersionUID = 1L;
private boolean finished;
private Sprite sprite;
private float x, y, z;
private int tick;
private float vx, vz;
private float ox;
private float accel;
private float startH, startS, startB;
private float initialScale, finalScale;
private float endH, endS, endB;
private final Color color = new Color();
/**
* C'tor
*/
WeatherParticle() {
}
void init() {
finished = false;
}
void spawn() {
finished = false;
Point spriteOffset = GameScreen.getSpriteOffset();
x = Util.random(-border, Game.getWidth() + border) - spriteOffset.getX();
y = Util.random(-border, Game.getHeight() + border) - spriteOffset.getY();
GameMap map = Worm.getGameState().getMap();
for (int z = 0; z < GameMap.LAYERS; z ++) {
Tile t = map.getTile((int) (x / MapRenderer.TILE_SIZE), (int) (y / MapRenderer.TILE_SIZE), z);
if (t != null && (t.isSolid() || t.isImpassable())) {
// Don't spawn at all
return;
}
}
z = height;
sprite = screen.allocateSprite(screen);
sprite.setLayer(layer);
sprite.setSubLayer(subLayer);
sprite.setAnimation(animation);
initialScale = startScale.getValue();
finalScale = endScale.getValue();
sprite.setAngle(FPMath.fpYaklyDegrees(Math.toRadians(angle.getValue())));
vx = wind.getValue();
vz = gravity.getValue();
ox = xmod.getValue();
accel = acceleration.getValue();
startH = startHue.getValue();
startS = startSaturation.getValue();
startB = startBrightness.getValue();
endH = endHue.getValue();
endS = endSaturation.getValue();
endB = endBrightness.getValue();
particles.add(this);
}
boolean isActive() {
return !finished;
}
void remove() {
if (finished) {
return;
}
finished = true;
if (sprite != null) {
sprite.deallocate();
sprite = null;
}
}
void tick() {
if (sprite.getEvent() == 1) {
remove();
} else {
Point spriteOffset = GameScreen.getSpriteOffset();
int xoffset = spriteOffset.getX();
int yoffset = spriteOffset.getY();
if (x + xoffset < -border * 2 || y + yoffset < -border * 2 || x + xoffset >= border * 2 + Game.getWidth() || y + yoffset >= border * 2 + Game.getHeight()) {
remove();
} else if (z != 0.0f) {
vz += accel;
if (vz < minGravity) {
vz = minGravity;
}
x += vx;
z -= vz;
tick ++;
if (z <= 0.0f) {
z = 0.0f;
if (groundAnimation == null) {
remove();
} else {
sprite.setAnimation(groundAnimation);
}
}
if (sprite != null) {
float ratio = z / height;
color.fromHSB
(
LinearInterpolator.instance.interpolate(endH, startH, ratio),
LinearInterpolator.instance.interpolate(endS, startS, ratio),
LinearInterpolator.instance.interpolate(endB, startB, ratio)
);
sprite.setColors(color);
float currentScale = LinearInterpolator.instance.interpolate(finalScale, initialScale, ratio);
sprite.setScale(FPMath.fpValue(currentScale));
}
}
}
}
void update() {
Point spriteOffset = GameScreen.getSpriteOffset();
sprite.setLocation(x + spriteOffset.getX(), y + spriteOffset.getY());
float xpos = (float) Math.cos(Math.toRadians(tick * modRate)) * ox * vz;
sprite.setOffset(xpos, z);
}
}
/**
* C'tor
* @param name
*/
public WeatherEmitter(String name) {
super(name);
}
@Override
public void spawn(Screen screen) {
this.screen = screen;
active = true;
particles = new ArrayList<WeatherParticle>();
pool = new SimplePool<WeatherParticle>(new Factory<WeatherParticle>() {
@Override
public WeatherParticle createNew() {
return new WeatherParticle();
}
}, 0);
screen.addTickable(this);
}
/**
* @return the maximum number of particles we should have onscreen
*/
private int getMaxParticles() {
int fps = Game.getFPS();
if (fps >= Game.getFrameRate() - 2) {
return (int) (density * Game.getWidth() * Game.getHeight());
} else {
// Cut down snow to 1/10th if framerate falls
return (int) (0.1f * density * Game.getWidth() * Game.getHeight());
}
}
@Override
public void tick() {
// Spawn more weather particles
int numToSpawn = Math.min(200, getMaxParticles() - particles.size());
for (int i = 0; i < numToSpawn; i ++) {
pool.obtain().spawn();
}
int n = particles.size();
for (int i = 0; i < n; ) {
WeatherParticle p = particles.get(i);
p.tick();
if (!p.isActive()) {
particles.remove(i);
pool.release(p);
n --;
} else {
i ++;
}
}
}
@Override
public void update() {
int n = particles.size();
for (int i = 0; i < n; i ++) {
particles.get(i).update();
}
}
@Override
public boolean isActive() {
return active;
}
@Override
public void remove() {
if (!active) {
return;
}
active = false;
for (WeatherParticle p : particles) {
p.remove();
}
particles = null;
pool = null;
}
}