package org.mafagafogigante.dungeon.entity.creatures;
import org.mafagafogigante.dungeon.entity.Entity;
import org.mafagafogigante.dungeon.entity.LightSource;
import org.mafagafogigante.dungeon.entity.Luminosity;
import org.mafagafogigante.dungeon.entity.TagSet;
import org.mafagafogigante.dungeon.entity.items.CreatureInventory;
import org.mafagafogigante.dungeon.entity.items.Item;
import org.mafagafogigante.dungeon.game.Location;
import org.mafagafogigante.dungeon.logging.DungeonLogger;
import org.mafagafogigante.dungeon.stats.CauseOfDeath;
import org.mafagafogigante.dungeon.util.Percentage;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* The Creature class.
*/
public class Creature extends Entity {
private final int attack;
private final AttackAlgorithmId attackAlgorithmId;
private final TagSet<Tag> tagSet;
private final CreatureInventory inventory;
private final LightSource lightSource;
private final CreatureHealth health;
private final Dropper dropper;
private final Observer observer = new Observer(this);
private Item weapon;
private Location location;
/**
* What caused the death of this creature. If getHealth().isAlive() evaluates to true, this should be null.
*/
private CauseOfDeath causeOfDeath;
/**
* Constructs a Creature from the provided CreaturePreset. Most of the application code should obtain new creatures
* from the CreatureFactory.
*/
public Creature(CreaturePreset preset) {
super(preset);
health = CreatureHealth.makeCreatureHealth(preset.getHealth(), this);
attack = preset.getAttack();
tagSet = TagSet.copyTagSet(preset.getTagSet());
attackAlgorithmId = preset.getAttackAlgorithmId();
inventory = new CreatureInventory(this, preset.getInventoryItemLimit(), preset.getInventoryWeightLimit());
lightSource = new LightSource(preset.getLuminosity());
dropper = new Dropper(this, preset.getDropList());
}
public boolean hasTag(Tag tag) {
return tagSet.hasTag(tag);
}
public CreatureHealth getHealth() {
return health;
}
Dropper getDropper() {
return dropper;
}
public int getAttack() {
return attack;
}
public CreatureInventory getInventory() {
return inventory;
}
@Override
public Luminosity getLuminosity() {
if (hasWeapon()) {
double luminosityFromWeapon = getWeapon().getLuminosity().toPercentage().toDouble();
double luminosityFromLightSource = lightSource.getLuminosity().toPercentage().toDouble();
return new Luminosity(new Percentage(luminosityFromWeapon + luminosityFromLightSource));
} else {
return lightSource.getLuminosity();
}
}
public LightSource getLightSource() {
return lightSource;
}
public Item getWeapon() {
return weapon;
}
/**
* Sets an Item as the currently equipped weapon. The Item must be in this Creature's inventory and have the WEAPON
* tag.
*
* @param weapon an Item that must be in this Creature's inventory and have the WEAPON tag
*/
public void setWeapon(Item weapon) {
if (inventory.hasItem(weapon)) {
if (weapon.hasTag(Item.Tag.WEAPON)) {
this.weapon = weapon;
} else {
DungeonLogger.warning(String.format("Tried to equip %s (no WEAPON tag) on %s.", weapon.getName(), getName()));
}
} else {
DungeonLogger.warning("Tried to equip an Item that is not in the inventory of " + getName() + ".");
}
}
/**
* Unequips the currently equipped weapon.
*/
public void unsetWeapon() {
this.weapon = null;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
/**
* Hits the specified target Creature.
*
* @param target the target
*/
public void hit(Creature target) {
AttackAlgorithms.renderAttack(this, target);
}
boolean hasWeapon() {
return getWeapon() != null;
}
/**
* Effectively drops an item.
*
* @param item the Item to be dropped
* @throws IllegalStateException if the item is not in the creature's inventory
*/
void dropItem(Item item) {
if (getInventory().hasItem(item)) {
getInventory().removeItem(item);
getLocation().addItem(item);
} else {
throw new IllegalStateException("item should be in the creature's inventory.");
}
}
public AttackAlgorithmId getAttackAlgorithmId() {
return attackAlgorithmId;
}
/**
* Retrieves what caused the death of this creature. If getHealth().isAlive() evaluates to true, this method returns
* null.
*/
public CauseOfDeath getCauseOfDeath() {
return causeOfDeath;
}
/**
* Sets what caused the death of this creature. Should be called only once and after the creature is dead.
*/
public void setCauseOfDeath(@NotNull CauseOfDeath causeOfDeath) {
if (this.causeOfDeath != null) {
throw new IllegalStateException("creature already has a CauseOfDeath.");
} else if (getHealth().isAlive()) {
throw new IllegalStateException("creature is still alive.");
} else {
this.causeOfDeath = causeOfDeath;
}
}
/**
* Returns a List with all the items this Creature dropped when it died. If this Creature is still alive, returns an
* empty list.
*
* @return a List with the Items this Creature dropped when it died
*/
@NotNull
public List<Item> getDroppedItemsList() {
return getDropper().getDroppedItemsList();
}
@Override
public String toString() {
return getName().getSingular();
}
boolean canSeeTheSky() {
return getLocation().getPoint().getZ() >= 0;
}
/**
* Checks if the Hero can see a given Entity based on the luminosity of the Location the Hero is in and on the
* visibility of the specified Entity.
*/
boolean canSee(Entity entity) {
// The Hero is always able to find himself.
return entity == this || entity.getVisibilityCriteria().isMetBy(observer);
}
<T extends Entity> List<T> filterByVisibility(List<T> list) {
List<T> visible = new ArrayList<>();
for (T entity : list) {
if (canSee(entity)) {
visible.add(entity);
}
}
return visible;
}
public enum Tag {MILKABLE, CORPSE}
}