package soundfriend.types;
import java.io.File;
import javax.swing.JOptionPane;
import soundfriend.SoundController;
import soundfriend.SoundController.Sounds;
import toritools.debug.Debug;
import toritools.entity.Entity;
import toritools.entity.Level;
import toritools.entity.sprite.ImageSprite;
import toritools.math.MidpointChain;
import toritools.math.Vector2;
import toritools.scripting.EntityScript;
import toritools.scripting.ScriptUtils;
public class Creature extends Entity implements EntityScript {
/**
* Important state stuff.
*/
private float mood, energy;
private long age;
private String name;
private State state;
private boolean isSick = false;
private float maxEnergy = 1f;
// Movement internals
private Vector2 moveTarget;
private float moveTargetSpeed = .6f;
private MidpointChain moveChain;
int sneezeTimer = 0;
private int genTimer = 0;
public Creature() {
this.setType("pet");
setDim(new Vector2(64, 64));
setPos(new Vector2(100, 100));
addScript(this);
setSprite(new ImageSprite(new File("tamodatchi/kitten.png"), 4, 6));
getSprite().setTimeStretch(40);
}
@Override
public void onSpawn(Entity self, Level level) {
age = 0;
mood = .5f;
energy = .5f;
name = "Mozart";
moveTarget = self.getPos();
moveChain = new MidpointChain(self.getPos(), 20);
state = State.ROAM;
}
@Override
public void onUpdate(Entity self, float time, Level level) {
genTimer = (genTimer + 1) % (60 * 2);
Debug.print("Mood: " + mood + " | Energy: " + energy);
// State Transitions
stateTransitions(level);
// Actions
float energyExpended = stateActions(level);
// Handle stat shanges
maintenence(energyExpended);
// Check for issues
checkForHealthIssues();
}
private void maintenence(float energyExpended) {
if (energy > 0) {
energy -= .00002 * energyExpended * energyExpended * (energy > 1 ? 8f * energy : 1f);
if (energy > 1 || isSick) {
isSick = energy > maxEnergy;
}
}
energy = Math.min(energy, maxEnergy * 2f);
// mood
if (mood >= 0 && state != State.SICK_INCAP && state != State.SLEEP) {
mood -= (1f / energy) * .00001f * (isSick ? energy * 30 : 1);
}
mood = Math.min(1, Math.max(0, mood));
}
private float stateActions(final Level level) {
/*
* State Actions
*/
float moveTargetSpeed = this.moveTargetSpeed;
if (state == State.ROAM && Math.random() < .006 * energy * energy) {
moveTarget = new Vector2((level.getDim().getWidth() - getDim().getWidth() / 2) * Math.random(), (level
.getDim().getHeight() - getDim().getHeight() / 2) * Math.random());
}
if (state == State.PLAYING) {
moveTargetSpeed *= 4;
if (level.getEntitiesWithType("ball").isEmpty()) {
state = State.ROAM;
}
Vector2 closest = null;
float bestDist = Float.MAX_VALUE;
for (Entity e : level.getEntitiesWithType("ball")) {
if (closest == null || getPos().dist(e.getPos()) < bestDist) {
closest = e.getPos().add(e.getDim().scale(.5f));
bestDist = getPos().dist(e.getPos());
}
}
mood = Math.min(1, mood + energy * (1f / bestDist) * .004f);
moveTarget = closest;
}
if (state == State.HUNTING) {
moveTargetSpeed *= 2;
Vector2 closest = null;
float bestDist = Float.MAX_VALUE;
for (Entity e : level.getEntitiesWithType("food")) {
if (ScriptUtils.isColliding(this, e)) {
eatFood((Food) e, level);
break;
}
if (closest == null || getPos().dist(e.getPos()) < bestDist) {
closest = e.getPos().add(e.getDim().scale(.5f));
bestDist = getPos().dist(e.getPos());
}
}
moveTarget = closest;
}
if (state == State.SICK_INCAP || state == State.SLEEP)
moveTarget = null;
if (moveTarget != null) {
Vector2 move = moveTarget.sub(getPos().add(getDim().scale(.5f)));
if (move.mag() > moveTargetSpeed) {
move = move.unit().scale(moveTargetSpeed * Math.min(1f, energy));
moveChain.setA(moveChain.getA().add(move));
moveChain.smoothTowardA();
setPos(moveChain.getB().sub(getDim().scale(.5f)));
} else {
moveTarget = null;
}
}
return moveTargetSpeed;
}
private void stateTransitions(final Level level) {
/*
* Transitions
*/
if (state == State.ROAM || state == State.PLAYING || state == State.HUNTING) {
if (sneezeTimer <= 0)
getSprite().setCycle(mood >= .5 ? 0 : 1);
if(state == State.HUNTING && level.getEntitiesWithType("food").isEmpty())
state = State.ROAM;
if ((energy < .5f || mood < .5f) && !level.getEntitiesWithType("food").isEmpty()) {
state = State.HUNTING;
}
if (state != State.HUNTING && state != State.PLAYING && !level.getEntitiesWithType("ball").isEmpty()) {
SoundController.play(mood < .5f ? Sounds.GRUMBLE : Sounds.HAPPY, energy);
state = State.PLAYING;
}
if (!state.equals(State.SLEEP) && energy < .1) {
SoundController.play(Sounds.SLEEPY, .9f);
state = State.SLEEP;
}
if (genTimer == 0 && mood < .5f && !state.equals(State.SLEEP)) {
if (Math.random() < .02)
SoundController.play(Sounds.GRUMBLE, energy);
}
if (genTimer == 0 && energy < .5f && !state.equals(State.SLEEP)
&& level.getEntitiesWithType("food").isEmpty()) {
if (Math.random() < .05)
SoundController.play(Sounds.HUNGRY, .8f);
} else if (Math.random() < .0005 && mood > .5f) {
SoundController.play(Math.random() < .5 ? Sounds.HAPPY2 : Sounds.HAPPY, energy);
}
}
if (state == State.SICK_INCAP && sickPercentage() < 100) {
state = State.ROAM;
}
if (state == State.SLEEP) {
sprite.set(0, 5);
energy += .0001;
if (energy > .5f) {
state = State.ROAM;
}
}
// Play a cute little animation from time to time
if (state != State.SICK_INCAP && state != State.SLEEP) {
getSprite().setTimeStretch((int) (40 * (1f/energy)));
sprite.nextFrame();
if (--sneezeTimer <= 0 && Math.random() < .001) {
sprite.setCycle((Math.random() < .95) ? 4 : 2);
sneezeTimer = 60;
}
}
}
private void checkForHealthIssues() {
if (energy > maxEnergy * 1.9f) {
getSprite().setTimeStretch(1);
getSprite().set(1, 5);
state = State.SICK_INCAP;
}
if (mood <= 0) {
JOptionPane.showMessageDialog(null, name + " hates you! It has run away.");
System.exit(0);
}
}
private void eatFood(final Food e, final Level level) {
level.despawnEntity(e);
energy += .3f;
state = State.ROAM;
mood = mood + .2f * mood;
}
@Override
public void onDeath(Entity self, Level level, boolean isRoomExit) {
}
public float getMood() {
return mood;
}
/**
* Energy relational to maxEnergy
*/
public float getEnergy() {
return energy / maxEnergy;
}
public long getAge() {
return age;
}
public String getName() {
return name;
}
public State getState() {
return state;
}
public int sickPercentage() {
return (int) ((energy - maxEnergy) / ((maxEnergy * 1.9f) - maxEnergy) * 100f);
}
public boolean isSick() {
return isSick;
}
public static enum State {
SLEEP, ROAM, HUNTING, PLAYING, SICK_INCAP;
}
}