package net.fe.unit;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import net.fe.Command;
import net.fe.FEResources;
import net.fe.PaletteSwapper;
import net.fe.Party;
import net.fe.fightStage.CombatTrigger;
import net.fe.overworldStage.Corpse;
import net.fe.overworldStage.DoNotDestroy;
import net.fe.overworldStage.Grid;
import net.fe.overworldStage.Node;
import net.fe.overworldStage.ClientOverworldStage;
import net.fe.overworldStage.OverworldStage;
import net.fe.overworldStage.Path;
import net.fe.overworldStage.Terrain;
import org.newdawn.slick.Color;
import org.newdawn.slick.opengl.Texture;
import chu.engine.Game;
import chu.engine.GriddedEntity;
import chu.engine.anim.Renderer;
import chu.engine.anim.ShaderArgs;
import chu.engine.anim.Transform;
public class Unit extends GriddedEntity implements Serializable, DoNotDestroy{
public HashMap<String, Integer> bases;
public HashMap<String, Integer> growths;
private static final long serialVersionUID = -5101031417704315547L;
private HashMap<String, Float> stats;
private ArrayList<CombatTrigger> skills;
private int hp;
private Class clazz;
public final char gender;
private Weapon weapon;
private ArrayList<Item> inventory;
public final String name;
private Party team;
private transient HashMap<String, Integer> tempMods;
private transient HashMap<String, Integer> battleStats;
private transient Set<Unit> assist;
private transient Unit rescuedUnit;
private transient boolean moved;
private transient Path path;
private transient float rX, rY;
private transient Command callback;
private boolean rescued;
private float counter;
private int origX, origY;
public static final float MAP_ANIM_SPEED = 0.2f;
public static final int MOVE_SPEED = 250;
public static Texture rescue;
static {
if(Game.glContextExists())
rescue = FEResources.getTexture("rescue");
}
public Unit(String name, Class c, char gender, HashMap<String, Integer> bases,
HashMap<String, Integer> growths) {
super(0, 0);
this.bases = bases;
this.growths = growths;
this.gender = gender;
inventory = new ArrayList<Item>();
tempMods = new HashMap<String, Integer>();
assist = new HashSet<Unit>();
skills = new ArrayList<CombatTrigger>();
battleStats = new HashMap<String, Integer>();
battleStats.put("Kills", 0);
battleStats.put("Assists", 0);
battleStats.put("Damage", 0);
battleStats.put("Healing", 0);
this.name = name;
clazz = c;
stats = new HashMap<String, Float>();
for (String s : bases.keySet()) {
stats.put(s, bases.get(s).floatValue());
}
fillHp();
renderDepth = ClientOverworldStage.UNIT_DEPTH;
}
public void loadMapSprites(){
sprite.addAnimation("IDLE", new MapAnimation(functionalClassName() +
"_map_idle", false));
sprite.addAnimation("SELECTED", new MapAnimation(functionalClassName() +
"_map_selected", false));
sprite.addAnimation("LEFT", new MapAnimation(functionalClassName() +
"_map_side", true));
sprite.addAnimation("RIGHT", new MapAnimation(functionalClassName() +
"_map_side", true));
sprite.addAnimation("UP", new MapAnimation(functionalClassName() +
"_map_up", true));
sprite.addAnimation("DOWN", new MapAnimation(functionalClassName() +
"_map_down", true));
sprite.setAnimation("IDLE");
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
tempMods = new HashMap<String, Integer>();
assist = new HashSet<Unit>();
skills = new ArrayList<CombatTrigger>();
battleStats = new HashMap<String, Integer>();
battleStats.put("Kills", 0);
battleStats.put("Assists", 0);
battleStats.put("Damage", 0);
battleStats.put("Healing", 0);
}
public String functionalClassName(){
String prefix = clazz.name;
if(prefix.equals("Lord")){
prefix = name;
}
if(gender != '-'){
prefix += gender;
}
return prefix.toLowerCase();
}
public String noGenderName() {
String prefix = clazz.name;
if(prefix.equals("Lord")){
prefix = name;
}
return prefix;
}
public void move(Path p, Command callback) {
this.path = p.getCopy();
this.callback = callback;
}
public void rescue(Unit u){
final int oldX = u.xcoord;
final int oldY = u.ycoord;
rescuedUnit = u;
rescuedUnit.rescued = true;
final OverworldStage grid = (OverworldStage) stage;
Path p = new Path();
p.add(new Node(this.xcoord, this.ycoord));
rescuedUnit.move(p, new Command(){
@Override
public void execute() {
rescuedUnit.xcoord = oldX;
rescuedUnit.ycoord = oldY;
grid.removeUnit(rescuedUnit);
}
});
}
public void drop(int x, int y){
if(rescuedUnit == null) return;
rescuedUnit.rescued = false;
rescuedUnit.setMoved(true);
final OverworldStage grid = (OverworldStage) stage;
grid.addUnit(rescuedUnit, x, y);
rescuedUnit.rX = this.x - x * 16;
rescuedUnit.rY = this.y - y * 16;
rescuedUnit = null;
}
public void give(Unit u){
if(rescuedUnit == null) return;
if(u.rescuedUnit() != null) return;
u.setRescuedUnit(rescuedUnit);
rescuedUnit = null;
}
public void beginStep(){
super.beginStep();
if(Game.glContextExists() && !sprite.hasAnimation("IDLE")) {
loadMapSprites();
}
if(path != null){
String name;
if(rX > 0) name = "left";
else if(rX < 0) name = "right";
else if(rY > 0) name = "up";
else name = "down";
sprite.setAnimation(name);
}
renderDepth = calcRenderDepth();
}
private float calcRenderDepth(){
float depth = ClientOverworldStage.UNIT_DEPTH;
if(rescued){
return depth-0.0001f;
}
float highlightDiff = (ClientOverworldStage.UNIT_DEPTH - ClientOverworldStage.UNIT_MAX_DEPTH)/2;
Grid g = ((ClientOverworldStage) stage).grid;
float yDiff = highlightDiff/g.width;
float xDiff = yDiff/g.height;
if(path!=null) depth -= highlightDiff;
if(((ClientOverworldStage) stage).getHoveredUnit() == this) depth -= highlightDiff;
depth -= ycoord*yDiff;
depth -= (g.width-xcoord)*xDiff;
return depth;
}
public void onStep() {
super.onStep();
float rXOld = rX;
float rYOld = rY;
rX = rX - Math.signum(rX) * Game.getDeltaSeconds() * 250;
rY = rY - Math.signum(rY) * Game.getDeltaSeconds() * 250;
if(rXOld * rX < 0 || rYOld * rY < 0 || (rXOld == rX && rYOld == rY)){
rX = 0;
rY = 0;
if (path != null) {
if (path.size() == 0) {
// We made it to destination
path = null;
callback.execute();
} else {
Node next = path.removeFirst();
rX = -(next.x - xcoord) * 16;
rY = -(next.y - ycoord) * 16;
xcoord = next.x;
ycoord = next.y;
x = xcoord * 16;
y = ycoord * 16;
}
}
}
}
Unit getCopy() {
Unit copy = new Unit(name, clazz, gender, bases, growths);
copy.setLevel(stats.get("Lvl").intValue());
for (Item i : inventory) {
copy.addToInventory(i);
}
return copy;
}
public void render() {
ClientOverworldStage cs = (ClientOverworldStage)stage;
Renderer.translate(-cs.camX, -cs.camY);
Renderer.addClip(0, 0, 368, 240, true);
if(FEResources.hasTexture(functionalClassName().toLowerCase() + "_map_idle")){
Transform t = new Transform();
if(sprite.getAnimationName().equals("RIGHT")){
t.flipHorizontal();
t.setTranslation(14, 0); //Why do we have to do this?
}
Color mod = new Color(1.0f, 1.0f, 1.0f, 1.0f);
t.setColor(mod);
if(moved) {
sprite.render(x+1+rX, y+1+rY, renderDepth, t, new ShaderArgs("greyscale"));
} else {
ShaderArgs args = PaletteSwapper.setup(this);
sprite.render(x+1+rX, y+1+rY, renderDepth, t, args);
}
} else {
Color c = !moved ? new Color(getPartyColor()) : new Color(128, 128, 128);
Renderer.drawRectangle(x + 1 + rX, y + 1 + rY, x + 14 + rX,
y + 14 + rY, ClientOverworldStage.UNIT_DEPTH, c);
Renderer.drawString("default_med",
name.charAt(0) + "" + name.charAt(1), x + 2 + rX, y + 1 + rY,
ClientOverworldStage.UNIT_DEPTH);
}
if(rescuedUnit!=null){
counter+=Game.getDeltaSeconds();
counter%=1;
if(counter > 0.5){
Renderer.render(rescue,
0, 0, 1, 1, x+9, y+7, x+9+8, y+7+8, renderDepth);
}
}
Renderer.removeClip();
Renderer.translate(cs.camX, cs.camY);
}
//Skills
public void addSkill(CombatTrigger t) {
skills.add(t);
}
public List<String> getAttackAnims(){
ArrayList<String> ans = new ArrayList<String>();
ans.add("attack");
ans.add("critical");
for(CombatTrigger skill: getTriggers()){
for(String a: skill.attackAnims){
ans.add(a);
}
}
return ans;
}
//Inventory
public List<Item> getInventory() {
return inventory;
}
public int findItem(Item i){
return inventory.indexOf(i);
}
public void removeFromInventory(Item item){
inventory.remove(item);
}
public void addToInventory(Item item) {
if(inventory.size() < 4)
inventory.add(item);
}
public Set<Integer> getTotalWepRange(boolean staff) {
Set<Integer> range = new HashSet<Integer>();
for (Item i : getInventory()) {
if (!(i instanceof Weapon))
continue;
Weapon w = (Weapon) i;
if (staff == (w.type == Weapon.Type.STAFF) && equippable(w))
range.addAll(w.range);
}
return range;
}
public void equip(Weapon w) {
if (equippable(w)) {
weapon = w;
if(stage != null){
((ClientOverworldStage) stage).addCmd("EQUIP");
((ClientOverworldStage) stage).addCmd(new UnitIdentifier(this));
((ClientOverworldStage) stage).addCmd(findItem(w));
}
inventory.remove(w);
inventory.add(0, w);
}
}
// For use in command message processing only
public void equip(int i) {
Weapon w = (Weapon)inventory.get(i);
if (equippable(w)) {
weapon = w;
inventory.remove(w);
inventory.add(0, w);
}
}
public void unequip(){
weapon = null;
}
public boolean equippable(Weapon w) {
if(w.pref!= null){
return name.equals(w.pref);
}
return clazz.usableWeapon.contains(w.type);
}
public ArrayList<Weapon> equippableWeapons(int range) {
ArrayList<Weapon> weps = new ArrayList<Weapon>();
for (Item i : inventory) {
if (i instanceof Weapon) {
Weapon w = (Weapon) i;
if (equippable(w) && w.type != Weapon.Type.STAFF
&& w.range.contains(range)) {
weps.add(w);
}
}
}
return weps;
}
public ArrayList<Weapon> equippableStaves(int range) {
ArrayList<Weapon> weps = new ArrayList<Weapon>();
for (Item i : inventory) {
if (i instanceof Weapon) {
Weapon w = (Weapon) i;
if (equippable(w) && w.type == Weapon.Type.STAFF
&& w.range.contains(range)) {
weps.add(w);
}
}
}
return weps;
}
public void initializeEquipment(){
for(Item it: inventory){
if(it instanceof Weapon){
if(equippable((Weapon)it)){
equip((Weapon) it);
break;
}
}
}
}
public int equipFirstWeapon(int range) {
for (int i = 0; i < inventory.size(); i++) {
Item it = inventory.get(i);
if (it instanceof Weapon) {
Weapon w = (Weapon) it;
if (equippable(w) && w.type != Weapon.Type.STAFF
&& w.range.contains(range)) {
equip(w);
return i;
}
}
}
return -1;
}
public void reEquip(){
for (int i = 0; i < inventory.size(); i++) {
Item it = inventory.get(i);
if (it instanceof Weapon) {
Weapon w = (Weapon) it;
if (equippable(w)) {
weapon = w;
inventory.remove(w);
inventory.add(0, w);
return;
}
}
}
}
public int use(int index) {
return use(inventory.get(index), true);
}
public int use(int index, boolean destroy){
return use(inventory.get(index), destroy);
}
public int use(Item i){
return use(i, true);
}
public int use(Item i, boolean destroy) {
int ans = i.use(this);
if(i.getUses() <= 0 && destroy){
inventory.remove(i);
if(i == weapon){
weapon = null;
reEquip();
}
}
return ans;
}
public ArrayList<CombatTrigger> getTriggers() {
ArrayList<CombatTrigger> triggers = new ArrayList<CombatTrigger>();
triggers.addAll(skills);
if (clazz.masterSkill != null)
triggers.add(clazz.masterSkill);
if(weapon!=null)
triggers.addAll(weapon.getTriggers());
return triggers;
}
//Development
public void setLevel(int lv) {
if (lv > 20 || lv < 1) {
return;
}
stats.put("Lvl", (float) lv);
lv--;
for (String stat : growths.keySet()) {
float newStat = bases.get(stat)
+ (float) (lv * growths.get(stat) / 100.0);
float max = stat.equals("HP") ? 60 : 35;
stats.put(stat, Math.min(newStat, max));
}
fillHp();
}
public void fillHp() {
setHp(get("HP"));
}
public static int getExpCost(int level){
return level * 50 + 500;
}
public int squeezeExp(){
int exp = 0;
while(get("Lvl") != 1){
exp += getExpCost(get("Lvl"));
setLevel(get("Lvl") - 1);
}
return exp;
}
public int squeezeGold(){
int gold = 0;
ListIterator<Item> items = inventory.listIterator();
while(items.hasNext()){
Item i = items.next();
boolean remove = true;
if(i instanceof Weapon){
Weapon w = (Weapon) i;
if(w.pref != null){
remove = false;
}
}
if(remove){
items.remove();
gold+= i.getCost();
}
}
return gold;
}
public void addBattleStat(String stat, int add) {
battleStats.put(stat, battleStats.get(stat) + add);
}
public int getBattleStat(String stat) {
return battleStats.get(stat);
}
public void reportBattleStats() {
for(String s : battleStats.keySet()) {
System.out.print(s+": ");
System.out.print(battleStats.get(s)+" ");
}
System.out.println();
}
public Set<Unit> getAssisters() {
return assist;
}
// Combat statistics
public int hit() {
if(weapon == null) return 0;
return weapon.hit + 2 * get("Skl") + get("Lck") / 2
+ (tempMods.get("Hit") != null ? tempMods.get("Hit") : 0);
}
public int avoid() {
return 2 * get("Spd") + get("Lck") / 2
+ (tempMods.get("Avo") != null ? tempMods.get("Avo") : 0)
+ getTerrain().getAvoidBonus(this);
}
public int crit() {
if(weapon == null) return 0;
return weapon.crit + get("Skl") / 2 + clazz.crit
+ (tempMods.get("Crit") != null ? tempMods.get("Crit") : 0);
}
public int dodge() { // Critical avoid
return get("Lck")
+ (tempMods.get("Dodge") != null ? tempMods.get("Dodge") : 0);
}
// Getter/Setter
public Class getTheClass() {
return clazz;
}
public int getHp() {
return hp;
}
public void setHp(int hp) {
this.hp = Math.max(hp, 0);
if(hp == 0) {
((OverworldStage) stage).removeUnit(xcoord, ycoord);
if(rescuedUnit != null) {
drop(xcoord, ycoord);
}
if(Game.glContextExists()) {
((ClientOverworldStage) stage).setControl(false);
stage.addEntity(new Corpse(this));
}
}
}
public int get(String stat) {
int ans = stats.get(stat).intValue()
+ (weapon != null ? weapon.modifiers.get(stat) : 0)
+ (tempMods.get(stat) != null ? tempMods.get(stat) : 0);
if (Arrays.asList("Def", "Res").contains(stat)) {
ans += getTerrain().getDefenseBonus(this);
}
if((stat.equals("Spd") || stat.equals("Skl")) && rescuedUnit!=null){
ans/=2;
}
return ans;
}
public int getBase(String stat) {
return stats.get(stat).intValue();
}
public void setTempMod(String stat, int val) {
tempMods.put(stat, val);
}
public void clearTempMods() {
tempMods.clear();
}
public Weapon getWeapon() {
return weapon;
}
public Terrain getTerrain() {
if(stage == null) return Terrain.PLAIN;
return ((OverworldStage) stage).getTerrain(xcoord, ycoord);
}
public String toString() {
return name + " HP" + hp + " #" + hashCode() + " \n" + stats;
}
public Color getPartyColor() {
if(team == null) return Party.TEAM_BLUE;
return team.getColor();
}
public void setParty(Party t) {
team = t;
}
public Party getParty() {
return team;
}
public void setMoved(boolean status) {
moved = status;
if(moved) {
sprite.setAnimation("IDLE");
origX = xcoord;
origY = ycoord;
}
}
public boolean hasMoved() {
return moved;
}
public int getOrigX() {
return origX;
}
public void setOrigX(int origX) {
this.origX = origX;
}
public void setOrigY(int origY) {
this.origY = origY;
}
public int getOrigY() {
return origY;
}
public Unit rescuedUnit(){
return rescuedUnit;
}
public void setRescuedUnit(Unit unit) {
rescuedUnit = unit;
}
}