/*
* Copyright (C) 2012 maartenl
*
* This program 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.
*
* This program 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.
*g
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package mmud.database.entities.items;
import com.google.gwt.thirdparty.guava.common.escape.Escaper;
import com.google.gwt.thirdparty.guava.common.html.HtmlEscapers;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import mmud.Attributes;
import mmud.Constants;
import mmud.HtmlEscaped;
import mmud.Utils;
import mmud.database.entities.Ownage;
import mmud.database.entities.characters.Person;
import mmud.database.entities.game.Admin;
import mmud.database.entities.game.Attribute;
import mmud.database.entities.game.AttributeWrangler;
import mmud.database.entities.game.DisplayInterface;
import mmud.database.entities.game.Room;
import mmud.database.enums.Wearing;
import mmud.database.enums.Wielding;
import mmud.exceptions.ItemException;
import mmud.exceptions.MudException;
/**
* An item. To be more precise an instance of an item definition.
* An item can either reside in a room, on a person or in another item.
*
* @author maartenl
*/
// TODO: create subclass named NormalItem.
// TODO: create subclass named ContainerItem.
// TODO: create subclass named ShopKeeperItem.
// TODO: fix named queries.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name = "discriminator",
discriminatorType = DiscriminatorType.INTEGER)
@Table(name = "mm_itemtable")
@NamedQueries(
{
@NamedQuery(name = "Item.findAll", query = "SELECT i FROM Item i"),
@NamedQuery(name = "Item.findById", query = "SELECT i FROM Item i WHERE i.id = :id"),
@NamedQuery(name = "Item.drop", query = "UPDATE Item i SET i.belongsto = null, i.room = :room WHERE i = :item and i.belongsto = :person and i.room is null and i.container is null"),// and i.itemDefinition.dropable <> 0"),
@NamedQuery(name = "Item.get", query = "UPDATE Item i SET i.room = null, i.belongsto = :person WHERE i = :item and i.belongsto is null and i.room = :room and i.container is null"),// and i.itemDefinition.getable <> 0")
@NamedQuery(name = "Item.give", query = "UPDATE Item i SET i.belongsto = :toperson WHERE i = :item and i.belongsto = :fromperson and i.room is null and i.container is null"),// and i.itemDefinition.getable <> 0")
@NamedQuery(name = "Item.put", query = "UPDATE Item i SET i.container = :container, i.belongsto = null, i.room = null WHERE i = :item and i.belongsto = :person and i.room is null and i.container is null"),// and i.itemDefinition.getable <> 0")
@NamedQuery(name = "Item.retrieve", query = "UPDATE Item i SET i.container = null, i.belongsto = :person, i.room = null WHERE i = :item and i.belongsto is null and i.room is null and i.container = :container")// and i.itemDefinition.getable <> 0")
})
abstract public class Item implements Serializable, DisplayInterface, AttributeWrangler, ItemWrangler, Ownage
{
private static final Logger itsLog = Logger.getLogger(Item.class.getName());
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Basic(optional = false)
@NotNull
@Column(name = "creation")
@Temporal(TemporalType.TIMESTAMP)
private Date creation;
/**
* Indicates what kind of item it is. Current values are used:
* <ul><li>0, a normal item</li>
* <li>1, a shopkeeper list</li>
* <li>2, a writable item like a board</li></ul>
*/
@Basic(optional = false)
@NotNull
@Column(name = "discriminator")
private Integer discriminator;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "container", orphanRemoval = true)
private Set<Item> items = new HashSet<>();
@JoinColumn(name = "containerid", referencedColumnName = "id")
@ManyToOne
private Item container;
@ManyToOne
@JoinColumn(name = "room", referencedColumnName = "id")
private Room room;
@ManyToOne
@JoinColumn(name = "belongsto", referencedColumnName = "name")
private Person belongsto;
@ManyToOne
@JoinColumn(name = "itemid", referencedColumnName = "id")
private ItemDefinition itemDefinition;
@ManyToOne
@JoinColumn(name = "owner", referencedColumnName = "name")
private Admin owner;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "item", orphanRemoval = true)
private List<Itemattribute> itemattributeCollection;
/**
* Constructor. Creates a completely empty item.
* Usually required by ORM.
*/
public Item()
{
}
/**
* Constructor.
*
* @param id the item definition used as a template of this (new) item.
*/
public Item(ItemDefinition id)
{
this.itemDefinition = id;
this.creation = new Date();
this.discriminator = 0;
}
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public Date getCreation()
{
return creation;
}
public void setCreation(Date creation)
{
this.creation = creation;
}
/**
* Indicates the items that are contained (if possible) inside this item.
* Will in most cases return an empty list.
*
* @return set of items.
*/
@Override
public Set<Item> getItems()
{
return items;
}
public void setItems(Set<Item> items)
{
this.items = items;
}
/**
* Retrieves the item that contains this item (if possible). Might be null.
*
* @return the container
* @see #getBelongsTo()
* @see #getRoom()
*/
public Item getContainer()
{
return container;
}
public boolean isContainer()
{
return getItemDefinition().isContainer();
}
public void setContainer(Item container)
{
if (!container.isContainer())
{
throw new ItemException("Item is not a container.");
}
this.container = container;
}
public ItemDefinition getItemDefinition()
{
return itemDefinition;
}
public void setItemDefinition(ItemDefinition itemDefinition)
{
this.itemDefinition = itemDefinition;
}
@Override
public Admin getOwner()
{
return owner;
}
@Override
public void setOwner(Admin owner)
{
this.owner = owner;
}
/**
* Will return the room in which this item can be found. Might be null.
*
* @return the room which contains this item.
* @see #getBelongsTo()
* @see #getContainer()
*/
public Room getRoom()
{
return room;
}
/**
* Will return the person that owns this item. Might be null.
*
* @return the person who owns this item.
* @see #getRoom()
* @see #getContainer()
*/
public Person getBelongsTo()
{
return belongsto;
}
@Override
public int hashCode()
{
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object)
{
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Item))
{
return false;
}
Item other = (Item) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id)))
{
return false;
}
return true;
}
@Override
public String toString()
{
return "mmud.database.entities.items.Item[ id=" + id + " ]";
}
/**
* Description of the item, for example "a white, cloth pants".
*
* @return a description of the item.
*/
@HtmlEscaped
public String getDescription()
{
Escaper htmlEscaper = HtmlEscapers.htmlEscaper();
return htmlEscaper.escape(getItemDefinition().getShortDescription());
}
/**
* @see ItemDefinition#isDescribedBy(java.util.List)
* @param parsed
* @return true if the item is properly described.
*/
public boolean isDescribedBy(List<String> parsed)
{
return getItemDefinition().isDescribedBy(parsed);
}
public String getTitle() throws MudException
{
if (getItemDefinition().getTitle() != null)
{
return getItemDefinition().getTitle();
}
return getItemDefinition().getShortDescription();
}
@Override
public String getMainTitle() throws MudException
{
return Utils.startWithCapital(getTitle());
}
@Override
public String getImage() throws MudException
{
return getItemDefinition().getImage();
}
public String getLongDescription()
{
Attribute description = getAttribute(Attributes.DESCRIPTION);
if (description == null || description.getValue() == null || description.getValue().trim().equals(""))
{
return getItemDefinition().getDescription();
}
return description.getValue();
}
@Override
public String getBody() throws MudException
{
StringBuilder builder = new StringBuilder(getLongDescription());
if (getItemDefinition().getId() > 0)
{
builder.append("With your expert eye your judge this item to be worth ").
append(Constants.getDescriptionOfMoney(getCopper())).
append(".<br/>\r\n");
if (isDrinkable())
{
builder.append("You can try drinking it.<br/>\r\n");
}
if (isEatable())
{
builder.append("You can try eating it.<br/>\r\n");
}
Set<Wielding> wieldable = getItemDefinition().getWieldable();
Set<Wearing> wearable = getItemDefinition().getWearable();
for (Wielding wield : wieldable)
{
builder.append("It can be wielded ").append(wield.toString().replace("%SHISHER", "your")).append(".<br/>\r\n");
}
for (Wearing wear : wearable)
{
builder.append("It can be worn ").append(wear.toString().replace("%SHISHER", "your")).append(".<br/>\r\n");
}
}
if (isContainer())
{
if (hasLid())
{
if (isOpen())
{
builder.append("It is open.<br/>\r\n");
} else
{
builder.append("It is closed.<br/>\r\n");
}
}
if (hasLock())
{
if (isLocked())
{
builder.append("It is locked.<br/>\r\n");
} else
{
builder.append("It is unlocked.<br/>\r\n");
}
}
}
return builder.toString();
}
public Integer getCopper()
{
return getItemDefinition().getCopper();
}
@Override
public boolean removeAttribute(String name)
{
Itemattribute attr = getItemattribute(name);
if (attr == null)
{
return false;
}
itemattributeCollection.remove(attr);
return true;
}
private Itemattribute getItemattribute(String name)
{
if (itemattributeCollection == null)
{
itsLog.finer("getItemattribute name=" + name + " collection is null");
return null;
}
for (Itemattribute attr : itemattributeCollection)
{
itsLog.finer("getItemattribute name=" + name + " attr=" + attr);
if (attr.getName().equals(name))
{
return attr;
}
}
itsLog.finer("getItemattribute name=" + name + " not found");
return null;
}
@Override
public Attribute getAttribute(String name)
{
return getItemattribute(name);
}
@Override
public void setAttribute(String name, String value)
{
itsLog.finer("setAttribute name=" + name + " value=" + value);
Itemattribute attr = getItemattribute(name);
if (attr == null)
{
attr = new Itemattribute(name, getId());
attr.setItem(this);
}
attr.setValue(value);
attr.setValueType(Attributes.VALUETYPE_STRING);
itemattributeCollection.add(attr);
}
@Override
public boolean verifyAttribute(String name, String value)
{
Itemattribute attr = getItemattribute(name);
if (attr == null)
{
itsLog.finer("verifyAttribute (name=" + name + ", value=" + value + ") not found on item " + getId() + ".");
return false;
}
if (attr.getValue() == value)
{
itsLog.finer("verifyAttribute (name=" + name + ", value=" + value + ") same object on item " + getId() + "!");
return true;
}
if (attr.getValue().equals(value))
{
itsLog.finer("verifyAttribute (name=" + name + ", value=" + value + ") matches on item " + getId() + "!");
return true;
}
itsLog.finer("verifyAttribute (name=" + name + ", value=" + value + ") with (name=" + attr.getName() + ", value=" + attr.getValue() + ") no match on item " + getId() + ".");
return false;
}
/**
* Indicates if the container is open or closed.
* Only makes sense if this item is in fact a container.
*
* @return true if there is an attribute named "isopen".
*/
public boolean isOpen()
{
return verifyAttribute("isopen", "true");
}
/**
* Returns true if the container has a lid, if the
* container can be opened.
*
* @return boolean true if the container has a lid.
*/
public boolean hasLid()
{
return isOpenable();
}
/**
* Returns true if the container has a lid, if the
* container can be opened.
*
* @return boolean true if the container has a lid.
*/
public boolean isOpenable()
{
if (getAttribute("isopenable") != null)
{
return verifyAttribute("isopenable", "true");
}
return getItemDefinition().isOpenable();
}
/**
* Returns whether or not the container is locked. If it can be locked, it
* needs a key.
*
* @return boolean true if the container is locked.
* @see ItemDefinition#getKey()
*/
public boolean isLocked()
{
return verifyAttribute(Attributes.LOCKED, "true");
}
public void open()
{
if (!isContainer())
{
throw new ItemException("Item is not a container, and cannot be opened.");
}
if (!isOpenable())
{
throw new ItemException(getDescription()
+ "cannot be opened.");
}
if (isOpen())
{
throw new ItemException(getDescription()
+ " is already open.");
}
if (isLocked())
{
throw new ItemException(getDescription()
+ " is locked.");
}
setAttribute("isopen", "true");
}
public void close()
{
if (!isContainer())
{
throw new ItemException("Item is not a container, and cannot be closed.");
}
if (!isOpenable())
{
throw new ItemException(getDescription()
+ "cannot be closed.");
}
if (!isOpen())
{
throw new ItemException(getDescription()
+ " is already closed.");
}
if (isLocked())
{
throw new ItemException(getDescription()
+ " is locked.");
}
setAttribute("isopen", "false");
}
/**
* Returns true if the item can be eaten. This is better than just using
* a compare on the getEatable.
*
* @return true if eatable, false otherwise
* @see #getEatable
*/
public boolean isEatable()
{
if (getAttribute("eatable") != null)
{
return verifyAttribute("eatable", "true");
}
return getItemDefinition().getEatable() != null && !getItemDefinition().getEatable().trim().equals("");
}
/**
* Provides the description of what happens when you try to eat it.
* Can be null or empty, for example if the item cannot be eaten.
* Eating an item will always destroy that item.
*
* @return
*/
public String getEatable()
{
return getItemDefinition().getEatable();
}
@Override
public List<Item> findItems(List<String> parsed)
{
List<Item> result = new ArrayList<>();
for (Item item : getItems())
{
if (item.isDescribedBy(parsed))
{
result.add(item);
}
}
return result;
}
@Override
public boolean destroyItem(Item item)
{
// TODO: move this to a container
// return items.remove(item);
// note: as the collection is an orphan, the delete
// on the set will take place automatically.
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
/**
* Returns true if the item can be drunk. This is better than just using
* a compare on the getDrinkable.
*
* @return true if drinkable, false otherwise
* @see #getDrinkable
*/
public boolean isDrinkable()
{
if (getAttribute("drinkable") != null)
{
return verifyAttribute("drinkable", "true");
}
return getItemDefinition().getDrinkable() != null && !getItemDefinition().getDrinkable().trim().equals("");
}
/**
* Provides the description of what happens when you try to drink it.
* Can be null or empty, for example if the item cannot be drunk.
* Drinking an item will always destroy that item.
*
* @return
*/
public String getDrinkable()
{
return getItemDefinition().getDrinkable();
}
public boolean isWearable(Wearing position)
{
return getItemDefinition().isWearable(position);
}
public boolean isWieldable(Wielding position)
{
return getItemDefinition().isWieldable(position);
}
/**
* Whether or not you are able to drop this item.
*
* @return true, in case you can, false otherwise.
*/
public boolean isDroppable()
{
if (isBound())
{
return false;
}
if (getItemDefinition().getId() < 0)
{
return false;
}
if (getAttribute("notdropable") != null)
{
return !verifyAttribute("notdropable", "true");
}
return getItemDefinition().getDropable();
}
/**
* Whether or not you are able to drop this item or give this item, or in
* some other way dispose of this item, besides selling it to a vendor or
* just destroying it.
*
* @return true, in case you cannot, false otherwise.
*/
public boolean isBound()
{
if (getAttribute("bound") != null)
{
return verifyAttribute("bound", "true");
}
return getItemDefinition().isBound();
}
/**
* Whether or not you are able to retrieve this item from the floor of the room.
*
* @return true, in case you can, false otherwise.
*/
public boolean isGetable()
{
if (isBound())
{
return false;
}
if (getItemDefinition().getId() < 0)
{
return false;
}
if (getAttribute("notgetable") != null)
{
return !verifyAttribute("notgetable", "true");
}
return getItemDefinition().getGetable();
}
public void drop(Person person, Room room)
{
if (isBound())
{
throw new ItemException("You are not allowed to drop this item.");
}
if (getBelongsTo() == null || !getBelongsTo().equals(person))
{
throw new ItemException("Cannot drop the item, it's not yours.");
}
// TODO: equals directly on room doesn't seem to work right. Different objects
// same ids.
if (!person.getRoom().getId().equals(room.getId()))
{
throw new ItemException("You are not in the room.");
}
belongsto = null;
this.room = room;
}
/**
* Specialty method for the creation of new items in a room. Only used by
* {@link Room#createItem(mmud.database.entities.items.ItemDefinition) }
*
* @param room the room to drop the new item into.
*/
public void drop(Room room)
{
if (isBound())
{
throw new ItemException("You are not allowed to drop this item.");
}
// TODO: equals directly on room doesn't seem to work right. Different objects
// same ids.
belongsto = null;
this.room = room;
}
public void get(Person person, Room room)
{
if (isBound())
{
throw new ItemException("You are not allowed to drop this item.");
}
if (getRoom() == null || !getRoom().equals(room))
{
throw new ItemException("Item not in the room.");
}
if (!person.getRoom().equals(room))
{
throw new ItemException("You are not in the room.");
}
belongsto = person;
this.room = null;
}
public void give(Person toperson)
{
if (isBound())
{
throw new ItemException("You are not allowed to give this item.");
}
if (room != null)
{
throw new ItemException("Cannot give an item that is in a room.");
}
if (container != null)
{
throw new ItemException("Cannot give an item that is stored in a container.");
}
belongsto = toperson;
}
public boolean isReadable()
{
boolean foundAttribute = getAttribute("readable") != null && getAttribute("readable").getValue() != null && !getAttribute("readable").getValue().trim().equals("");
boolean foundReadable = (getItemDefinition().getReaddescription() != null && !getItemDefinition().getReaddescription().trim().equals(""));
return foundAttribute || foundReadable;
}
public DisplayInterface getRead()
{
itsLog.info("Item getRead");
if (!isReadable())
{
throw new ItemException("Item cannot be read.");
}
return new DisplayInterface()
{
@Override
public String getMainTitle() throws MudException
{
return getDescription();
}
@Override
public String getImage() throws MudException
{
return getItemDefinition().getImage();
}
@Override
public String getBody() throws MudException
{
String read
= getAttribute("readable") == null ? null : getAttribute("readable").getValue();
if (read == null || read.trim().equals(""))
{
read = getItemDefinition().getReaddescription();
}
if (read == null || read.trim().equals(""))
{
return null;
}
return read;
}
};
}
/**
* Returns whether or not the container has a lock. Meaning whether or not the
* container can be unlocked/locked.
*
* @return boolean true if the container can be locked/unlocked.
*/
public boolean hasLock()
{
return getItemDefinition().hasLock();
}
/**
* Verifies that the key provided can open this container.
*
* @param key the key to hopefully open this container.
* @return true, if it is possible to open this container with the key provided.
*/
public boolean isKey(Item key)
{
if (key == null)
{
return false;
}
return key.getItemDefinition().equals(getItemDefinition().getKey());
}
public void lock()
{
setAttribute(Attributes.LOCKED, "true");
}
public void unlock()
{
setAttribute(Attributes.LOCKED, "false");
}
public boolean isVisible()
{
return getItemDefinition().getId() >= 0
&& (getItemDefinition().getVisible() == null || getItemDefinition().getVisible());
}
/**
* Returns the category of the item.
*
* @return
*/
abstract public ItemCategory getCategory();
/**
* Items are sellable by default, unless attribute "notsellable" is set.
*
* @see Attributes#NOTSELLABLE
* @return
*/
public boolean isSellable()
{
if (isBound())
{
return false;
}
if (containsItems())
{
return false;
}
if (getCopper() == 0)
{
return false;
}
if (getItemDefinition().getId() < 0)
{
return false;
}
if (getAttribute(Attributes.NOTSELLABLE) != null)
{
return !verifyAttribute(Attributes.NOTSELLABLE, "true");
}
return true;
// TODO: getItemDefinition().isSellable();
}
/**
* Items are buyable by default, unless attribute "notbuyable" is set.
*
* @see Attributes#NOTBUYABLE
* @return
*/
public boolean isBuyable()
{
if (isBound())
{
return false;
}
if (getItemDefinition().getId() < 0)
{
return false;
}
if (getCopper() == 0)
{
return false;
}
if (getAttribute(Attributes.NOTBUYABLE) != null)
{
return !verifyAttribute(Attributes.NOTBUYABLE, "true");
}
return true;
// TODO: getItemDefinition().isBuyable();
}
/**
* Tells you if this item contains other items, for example if it is a bag.
*
* @return
*/
public boolean containsItems()
{
return items != null && !items.isEmpty();
}
/**
* Adds an {@link Item} to the bag (container).
*
* @param item the new item. May not be null.
* @return the new item, null if unable to add.
*/
@Override
public Item addItem(Item item)
{
if (item.getBelongsTo() != null || item.getRoom() != null || item.getContainer() != null)
{
throw new MudException("Item already assigned.");
}
if (!this.isContainer())
{
throw new MudException("You cannot drop a new item in another item, if the other item is not a container.");
}
if (!items.add(item))
{
return null;
}
item.setContainer(this);
return item;
}
public void assignTo(Room room)
{
if (getBelongsTo() != null || getRoom() != null || getContainer() != null)
{
throw new MudException("Item already assigned.");
}
this.room = room;
}
}