code:
package com.lilithsthrone.game.inventory.weapon;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.lilithsthrone.game.Game;
import com.lilithsthrone.game.character.CharacterUtils;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.attributes.Attribute;
import com.lilithsthrone.game.combat.Attack;
import com.lilithsthrone.game.combat.DamageType;
import com.lilithsthrone.game.combat.Spell;
import com.lilithsthrone.game.combat.SpellSchool;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.AbstractCoreItem;
import com.lilithsthrone.game.inventory.AbstractCoreType;
import com.lilithsthrone.game.inventory.Rarity;
import com.lilithsthrone.game.inventory.enchanting.AbstractItemEffectType;
import com.lilithsthrone.game.inventory.enchanting.ItemEffect;
import com.lilithsthrone.game.inventory.enchanting.ItemEffectType;
import com.lilithsthrone.game.inventory.enchanting.TFEssence;
import com.lilithsthrone.game.inventory.enchanting.TFModifier;
import com.lilithsthrone.game.inventory.enchanting.TFPotency;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.1.0
 * @version 0.3.3.11
 * @author Innoxia
 */
public abstract class AbstractWeapon extends AbstractCoreItem implements XMLSaving {

	private AbstractWeaponType weaponType;
	protected List<ItemEffect> effects;

	private DamageType damageType;
	private Attribute coreEnchantment;
	private List<Spell> spells;
	private Colour primaryColour;
	private Colour secondaryColour;

	public AbstractWeapon(AbstractWeaponType weaponType, DamageType dt, Colour primaryColour, Colour secondaryColour) {
		super(weaponType.getName(), weaponType.getNamePlural(), weaponType.getPathName(), dt.getMultiplierAttribute().getColour(), weaponType.getRarity(), null);

		this.weaponType = weaponType;
		damageType = dt;

		coreEnchantment = null;

		spells = new ArrayList<>();
		if (weaponType.getSpells() != null) {
			this.spells.addAll(weaponType.getSpells());
		}
		if (weaponType.getGenerationSpells(damageType) != null) {
			this.spells.addAll(weaponType.getGenerationSpells(damageType));
		}

		this.effects = new ArrayList<>();
		if(weaponType.getEffects()!=null) {
			for(ItemEffect effect : weaponType.getEffects()) {
				if(effect.getSecondaryModifier()==TFModifier.DAMAGE_WEAPON) {
					switch(damageType) {
						case FIRE:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_FIRE, TFPotency.MAJOR_BOOST, 0));
							break;
						case ICE:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_ICE, TFPotency.MAJOR_BOOST, 0));
							break;
						case LUST:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_LUST, TFPotency.MAJOR_BOOST, 0));
							break;
						case MISC:
						case UNARMED:
						case HEALTH:
							break;
						case PHYSICAL:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_PHYSICAL, TFPotency.MAJOR_BOOST, 0));
							break;
						case POISON:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_POISON, TFPotency.MAJOR_BOOST, 0));
							break;
					}

				} else if(effect.getSecondaryModifier()==TFModifier.RESISTANCE_WEAPON) {
					switch(damageType) {
						case FIRE:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_FIRE, TFPotency.MAJOR_BOOST, 0));
							break;
						case ICE:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_ICE, TFPotency.MAJOR_BOOST, 0));
							break;
						case LUST:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_LUST, TFPotency.MAJOR_BOOST, 0));
							break;
						case MISC:
						case UNARMED:
						case HEALTH:
							break;
						case PHYSICAL:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_PHYSICAL, TFPotency.MAJOR_BOOST, 0));
							break;
						case POISON:
							this.effects.add(new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_POISON, TFPotency.MAJOR_BOOST, 0));
							break;
					}

				} else {
					this.effects.add(effect);
				}
			}
		}

		int highestEnchantment = 0;
		for (Attribute a : getAttributeModifiers().keySet()) {
			if (getAttributeModifiers().get(a) > highestEnchantment) {
				coreEnchantment = a;
				highestEnchantment = getAttributeModifiers().get(a);
			}
		}

		this.primaryColour = primaryColour;
		this.secondaryColour = secondaryColour;
	}

	public AbstractWeapon(AbstractWeapon weapon) {
		this(weapon.getWeaponType(), weapon.getDamageType(), weapon.getPrimaryColour(), weapon.getSecondaryColour());

		this.setEffects(new ArrayList<>(weapon.getEffects()));

		int highestEnchantment = 0;
		for (Attribute a : getAttributeModifiers().keySet()) {
			if (getAttributeModifiers().get(a) > highestEnchantment) {
				coreEnchantment = a;
				highestEnchantment = getAttributeModifiers().get(a);
			}
		}

		if(!weapon.name.isEmpty()) {
			this.setName(weapon.name);
		}
	}

	public String getId() {
		StringBuilder sb = new StringBuilder();

		sb.append(WeaponType.getIdFromWeaponType(this.getWeaponType()));
		sb.append(this.getColour().toString());
		sb.append(this.getSecondaryColour()!=null?this.getSecondaryColour().toString():"n");
		sb.append(this.getDamageType().toString());
		sb.append(this.getCoreEnchantment()==null?"n":this.getCoreEnchantment().toString());

		for(Spell s : this.getSpells()) {
			sb.append(s.toString());
		}

		for(ItemEffect ie : this.getEffects()) {
			sb.append(ie.getId());
		}

		return sb.toString();
	}

	@Override
	public boolean equals(Object o) {
		if(super.equals(o)){
			if(o instanceof AbstractWeapon){
				if(((AbstractWeapon)o).getWeaponType().equals(getWeaponType())
						&& ((AbstractWeapon)o).getPrimaryColour()==this.getPrimaryColour()
						&& ((AbstractWeapon)o).getSecondaryColour()==this.getSecondaryColour()
						&& ((AbstractWeapon)o).getDamageType()==this.getDamageType()
						&& ((AbstractWeapon)o).getCoreEnchantment()==this.getCoreEnchantment()
						&& ((AbstractWeapon)o).getSpells().equals(this.getSpells())
						&& ((AbstractWeapon)o).getEffects().equals(this.getEffects())
						){
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public int hashCode() {
		int result = super.hashCode();
		result = 31 * result + getWeaponType().hashCode();
		result = 31 * result + getDamageType().hashCode();
		if(getPrimaryColour()!=null) {
			result = 31 * result + getPrimaryColour().hashCode();
		}
		if(getSecondaryColour()!=null) {
			result = 31 * result + getSecondaryColour().hashCode();
		}
		if(getCoreEnchantment()!=null) {
			result = 31 * result + getCoreEnchantment().hashCode();
		}
		result = 31 * result + getSpells().hashCode();
		result = 31 * result + this.getEffects().hashCode();
		return result;
	}

	public Element saveAsXML(Element parentElement, Document doc) {
		Element element = doc.createElement("weapon");
		parentElement.appendChild(element);

		CharacterUtils.addAttribute(doc, element, "id", this.getWeaponType().getId());
		CharacterUtils.addAttribute(doc, element, "name", name);
		CharacterUtils.addAttribute(doc, element, "damageType", this.getDamageType().toString());
		CharacterUtils.addAttribute(doc, element, "coreEnchantment", (this.getCoreEnchantment()==null?"null":this.getCoreEnchantment().toString()));
		CharacterUtils.addAttribute(doc, element, "colourPrimary", this.getPrimaryColour().toString());
		CharacterUtils.addAttribute(doc, element, "colourSecondary", this.getSecondaryColour().toString());

		Element innerElement = doc.createElement("effects");
		element.appendChild(innerElement);
		for(ItemEffect ie : this.getEffects()) {
			ie.saveAsXML(innerElement, doc);
		}

		innerElement = doc.createElement("spells");
		element.appendChild(innerElement);
		for(Spell s : this.getSpells()) {
			Element spell = doc.createElement("spell");
			innerElement.appendChild(spell);
			CharacterUtils.addAttribute(doc, spell, "value", s.toString());
		}

		return element;
	}

	public static AbstractWeapon loadFromXML(Element parentElement, Document doc) {
		AbstractWeapon weapon = null;

		try {
			String id = parentElement.getAttribute("id");
			weapon = AbstractWeaponType.generateWeapon(WeaponType.getWeaponTypeFromId(id), DamageType.valueOf(parentElement.getAttribute("damageType")));
		} catch(Exception ex) {
			ex.printStackTrace();
			System.err.println("Warning: An instance of AbstractWeapon was unable to be imported. ("+parentElement.getAttribute("id")+")");
			return null;
		}

		if(weapon==null) {
			System.err.println("Warning: An instance of AbstractWeapon was unable to be imported. ("+parentElement.getAttribute("id")+")");
			return null;
		}

		if(!parentElement.getAttribute("name").isEmpty()) {
			weapon.setName(parentElement.getAttribute("name"));
		}

		if(!parentElement.getAttribute("coreEnchantment").equals("null")) {
			try {
				weapon.coreEnchantment = Attribute.getAttributeFromId(parentElement.getAttribute("coreEnchantment"));
			} catch(Exception ex) {
			}
		}

		// Try to load colour:
		try {
			weapon.setPrimaryColour(Colour.valueOf(parentElement.getAttribute("colourPrimary")));
			weapon.setSecondaryColour(Colour.valueOf(parentElement.getAttribute("colourSecondary")));
		} catch(Exception ex) {
		}

		if(!Main.isVersionOlderThan(Game.loadingVersion, "0.2.10.5")) {
			try {
				weapon.getEffects().clear();

				Element element = (Element)parentElement.getElementsByTagName("effects").item(0);
				NodeList effectElements = element.getElementsByTagName("effect");
				for(int i=0; i<effectElements.getLength(); i++){
					Element e = ((Element)effectElements.item(i));
					ItemEffect ie = ItemEffect.loadFromXML(e, doc);
					if(ie!=null) {
						weapon.addEffect(ie);
					}
				}
			} catch(Exception ex) {
			}
		}

		weapon.spells = new ArrayList<>();
		Element element = (Element)parentElement.getElementsByTagName("spells").item(0);
		NodeList spellElements = element.getElementsByTagName("spell");
		for(int i=0; i<spellElements.getLength(); i++){
			Element e = ((Element)spellElements.item(i));
			try {
				String weaponId = e.getAttribute("value");
				if(weaponId.equals("DARK_SIREN_BANEFUL_FISSURE")) {
					weaponId = "DARK_SIREN_SIRENS_CALL";
				}
				weapon.spells.add(Spell.valueOf(weaponId));
			} catch(Exception ex) {
			}
		}

		return weapon;
	}

	public abstract String onEquip(GameCharacter character);

	public abstract String onUnequip(GameCharacter character);

	private StringBuilder descriptionSB = new StringBuilder("");

	public Colour getPrimaryColour() {
		return primaryColour;
	}

	public void setPrimaryColour(Colour primaryColour) {
		this.primaryColour = primaryColour;
	}

	public Colour getSecondaryColour() {
		return secondaryColour;
	}

	public void setSecondaryColour(Colour secondaryColour) {
		this.secondaryColour = secondaryColour;
	}

	public String getDescription() {
		descriptionSB = new StringBuilder();

		int essenceCost = this.getWeaponType().getArcaneCost();
		descriptionSB.append(
					"
"
						+ ""+Attack.getMinimumDamage(Main.game.getPlayer(), null, Attack.MAIN, this) + "-" + Attack.getMaximumDamage(Main.game.getPlayer(), null, Attack.MAIN, this)+""
						+ " "+ damageType.getName()+ " damage"
						+ "
"
						+ ""+(this.getWeaponType().isMelee()?"Melee":"Ranged")+" | "+(this.getWeaponType().isTwoHanded()?"Two-handed":"One-handed")+""
						+ (essenceCost==0
							?""
							:"
Costs [style.colourArcane("+essenceCost+" arcane essence"+(essenceCost==1?"":"s")+")] "+(this.getWeaponType().isMelee()?"per attack":"to fire")+"")
					+ "
"
					+ "
"
						+ weaponType.getDescription()
					+ "
");

		// Physical resistance
		if(getWeaponType().getPhysicalResistance()>0) {
			descriptionSB.append("
"
							+ (getWeaponType().isPlural()
									? "They are armoured, and provide "
									: "It is armoured, and provides ")
								+ " " + getWeaponType().getPhysicalResistance() + " [style.colourResPhysical(" + Attribute.RESISTANCE_PHYSICAL.getName() + ")]."
							+ "
");
		}

		if (!attributeModifiers.isEmpty()) {
			descriptionSB.append("
It provides ");
			int i = 0;
			for (Entry<Attribute, Integer> e : attributeModifiers.entrySet()) {
				if (i != 0) {
					if (i + 1 == attributeModifiers.size())
						descriptionSB.append(" and ");
					else
						descriptionSB.append(", ");
				}

				descriptionSB.append(" " + e.getValue() + "  " + e.getKey().getName() + "");
				i++;
			}
			descriptionSB.append(".
");
		}

		if (!spells.isEmpty()) {
			descriptionSB.append("
Its arcane power grants you the ability to cast ");
			int i = 0;
			for (Spell s : spells) {
				if (i != 0) {
					if (i + 1 == spells.size())
						descriptionSB.append(" and ");
					else
						descriptionSB.append(", ");
				}

				descriptionSB.append("" + Util.capitaliseSentence(s.getName()) + "");
				i++;
			}
			descriptionSB.append(".
");
		}

		descriptionSB.append("
It has a value of " + UtilText.formatAsMoney(getValue()) + ".
");

		if (getWeaponType().getClothingSet() != null) {
			descriptionSB.append("
" + (getWeaponType().isPlural() ? "They are" : "It is") + " part of the "
					+ getWeaponType().getClothingSet().getName() + " set." + "
");
		}

		return descriptionSB.toString();
	}

	@Override
	public int getValue() {
		float runningTotal = this.getWeaponType().getBaseValue();

		if (colourShade == Colour.CLOTHING_PLATINUM) {
			runningTotal *= 2f;

		} else if (colourShade == Colour.CLOTHING_GOLD) {
			runningTotal *= 1.75f;

		} else if (colourShade == Colour.CLOTHING_ROSE_GOLD) {
			runningTotal *= 1.5f;

		} else if (colourShade == Colour.CLOTHING_SILVER) {
			runningTotal *= 1.25f;
		}

		if(rarity==Rarity.JINXED) {
			runningTotal *= 0.5;
		}

		float attributeBonuses = 0;//getModifiedDropoffValue
		if (attributeModifiers != null) {
			for (Integer i : attributeModifiers.values()) {
				attributeBonuses += i * 5;
			}
		}

		if (getWeaponType().getClothingSet() != null) {
			if (getWeaponType().getClothingSet().getAssociatedStatusEffect().getAttributeModifiers(Main.game.getPlayer()) != null) {
				for (Float f : getWeaponType().getClothingSet().getAssociatedStatusEffect().getAttributeModifiers(Main.game.getPlayer()).values()) {
					attributeBonuses += f * 15;
				}
			}
		}

		attributeBonuses = Util.getModifiedDropoffValue(attributeBonuses * 25, 500);

		runningTotal += attributeBonuses;

		if (runningTotal < 1) {
			runningTotal = 1;
		}

		return (int) runningTotal;
	}

	public DamageType getDamageType() {
		return damageType;
	}

	public void setDamageType(DamageType damageType) {
		this.damageType = damageType;
	}

	public AbstractWeaponType getWeaponType() {
		return weaponType;
	}

	public String getName(boolean withDeterminer, boolean withRarityColour) {
		return (withDeterminer
				? (!weaponType.getDeterminer().equalsIgnoreCase("a") && !weaponType.getDeterminer().equalsIgnoreCase("an")
					? weaponType.getDeterminer() + " "
					: (Util.isVowel(damageType.getWeaponDescriptor().charAt(0)) ? "an " : "a "))
				: " ")
				+ damageType.getWeaponDescriptor() + (withRarityColour ? (" " + name + "") : " "+name);
	}

	public String getDisplayName(boolean withRarityColour) {
		return "" + Util.capitaliseSentence(damageType.getWeaponDescriptor()) + " "
				+ (withRarityColour ? (" " + name + "") : name);
	}

	@Override
	public String getSVGString() {
		return weaponType.getSVGImage(damageType, this.getPrimaryColour(), this.getSecondaryColour());
	}

	public String getSVGEquippedString(GameCharacter owner) {
		return weaponType.getSVGEquippedImage(damageType, this.getPrimaryColour(), this.getSecondaryColour());
	}

	public List<Spell> getSpells() {
		return spells;
	}

	public Attribute getCoreEnchantment() {
		return coreEnchantment;
	}

	public SpellSchool getSpellSchool() {
		return this.getDamageType().getSpellSchool();
	}

	public boolean isAbleToBeUsed(GameCharacter user, GameCharacter target) {
		return this.getWeaponType().isAbleToBeUsed(user, target);
	}

	public String getUnableToBeUsedDescription() {
		return this.getWeaponType().getUnableToBeUsedDescription();
	}

	public String applyExtraEffects(GameCharacter user, GameCharacter target, boolean isHit) {
		return this.getWeaponType().applyExtraEffects(user, target, isHit);
	}

	@Override
	public List<ItemEffect> getEffects() {
		return effects;
	}

	public void setEffects(List<ItemEffect> effects) {
		this.effects = effects;
	}

	public void addEffect(ItemEffect effect) {
		effects.add(effect);
	}

	@Override
	public Map<Attribute, Integer> getAttributeModifiers() {
		attributeModifiers.clear();

		for(ItemEffect ie : getEffects()) {
			if(ie.getPrimaryModifier() == TFModifier.CLOTHING_ATTRIBUTE || ie.getPrimaryModifier() == TFModifier.CLOTHING_MAJOR_ATTRIBUTE) {
				if(attributeModifiers.containsKey(ie.getSecondaryModifier().getAssociatedAttribute())) {
					attributeModifiers.put(ie.getSecondaryModifier().getAssociatedAttribute(), attributeModifiers.get(ie.getSecondaryModifier().getAssociatedAttribute()) + ie.getPotency().getClothingBonusValue());
				} else {
					attributeModifiers.put(ie.getSecondaryModifier().getAssociatedAttribute(), ie.getPotency().getClothingBonusValue());
				}
			}
		}

		return attributeModifiers;
	}

	/**
	 * @return An integer value of the 'enchantment capacity cost' for this particular weapon. Does not count negative attribute values, and values of Corruption are reversed (so reducing corruption costs enchantment stability).
	 */
	public int getEnchantmentCapacityCost() {
		Map<Attribute, Integer> noCorruption = new HashMap<>();
		attributeModifiers.entrySet().stream().filter(ent -> ent.getKey()!=Attribute.FERTILITY && ent.getKey()!=Attribute.VIRILITY).forEach(ent -> noCorruption.put(ent.getKey(), ent.getValue()*(ent.getKey()==Attribute.MAJOR_CORRUPTION?-1:1)));
		return noCorruption.values().stream().reduce(0, (a, b) -> a + Math.max(0, b));
	}

	@Override
	public int getEnchantmentLimit() {
		return weaponType.getEnchantmentLimit();
	}

	@Override
	public AbstractItemEffectType getEnchantmentEffect() {
		return weaponType.getEnchantmentEffect();
	}

	@Override
	public AbstractCoreType getEnchantmentItemType(List<ItemEffect> effects) {
		return weaponType.getEnchantmentItemType(effects);
	}

	@Override
	public TFEssence getRelatedEssence() {
		return weaponType.getRelatedEssence();
	}

}

repo_name: klorpa/Pedo

license: unknown


code:
package com.lilithsthrone.game.inventory.weapon;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;

import com.lilithsthrone.controller.xmlParsing.Element;
import com.lilithsthrone.controller.xmlParsing.XMLMissingTagException;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.combat.DamageType;
import com.lilithsthrone.game.combat.DamageVariance;
import com.lilithsthrone.game.combat.Spell;
import com.lilithsthrone.game.dialogue.eventLog.EventLogEntryEncyclopediaUnlock;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.AbstractCoreType;
import com.lilithsthrone.game.inventory.ItemTag;
import com.lilithsthrone.game.inventory.Rarity;
import com.lilithsthrone.game.inventory.clothing.ClothingSet;
import com.lilithsthrone.game.inventory.enchanting.AbstractItemEffectType;
import com.lilithsthrone.game.inventory.enchanting.ItemEffect;
import com.lilithsthrone.game.inventory.enchanting.ItemEffectType;
import com.lilithsthrone.game.inventory.enchanting.TFEssence;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.ColourListPresets;
import com.lilithsthrone.utils.SvgUtil;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.84
 * @version 0.2.11
 * @author Innoxia
 */
public abstract class AbstractWeaponType extends AbstractCoreType {

	private int baseValue;
	private boolean isMod;

	private boolean melee;
	private boolean twoHanded;

	private String determiner;
	boolean plural;
	private String name;
	private String namePlural;
	private String attackDescriptor;
	private String attackTooltipDescription;
	private String description;

	public String getAuthorDescription() {
		return authorDescription;
	}

	private ClothingSet clothingSet;
	private Rarity rarity;
	private float physicalResistance;

	private String equipText;
	private String unequipText;
	private List<String> hitDescriptions;
	private List<String> missDescriptions;

	private String pathName;
	private String pathNameEquipped;

	private String authorDescription;

	protected int damage;
	protected int arcaneCost;
	protected DamageVariance damageVariance;
	private List<DamageType> availableDamageTypes;

	private List<Spell> spells;

	// Enchantments:
	@SuppressWarnings("unused")
	private int enchantmentLimit; // Removed as part of 0.3.3.7's update to add enchantment capacity mechanics.
	protected List<ItemEffect> effects;

	private Map<DamageType, Map<Colour, Map<Colour, String>>> SVGStringMap;
	private Map<DamageType, Map<Colour, Map<Colour, String>>> SVGStringEquippedMap;

	private List<Colour> availablePrimaryColours;
	private List<Colour> availablePrimaryDyeColours;
	private List<Colour> allAvailablePrimaryColours;

	private List<Colour> availableSecondaryColours;
	private List<Colour> availableSecondaryDyeColours;
	private List<Colour> allAvailableSecondaryColours;

	private List<ItemTag> itemTags;

	public AbstractWeaponType(int baseValue,
			boolean melee,
			boolean twoHanded,
			String pronoun,
			boolean plural,
			String name,
			String namePlural,
			String attackDescriptor,
			String description,
			String pathName,
			String pathNameEquipped,
			Rarity rarity,
			float physicalResistance,
			ClothingSet clothingSet,
			List<DamageType> availableDamageTypes,
			int damage,
			int arcaneCost,
			DamageVariance damageVariance,
			int enchantmentLimit,
			List<ItemEffect> effects,
			List<Spell> spells,
			List<Colour> availablePrimaryColours,
			List<Colour> availablePrimaryDyeColours,
			List<Colour> availableSecondaryColours,
			List<Colour> availableSecondaryDyeColours,
			List<ItemTag> itemTags) {
		this(baseValue,
				melee,
				twoHanded,
				pronoun,
				plural,
				name,
				namePlural,
				attackDescriptor,
				null,
				description,
				pathName,
				pathNameEquipped,
				rarity,
				physicalResistance,
				clothingSet,
				availableDamageTypes,
				damage,
				arcaneCost,
				damageVariance,
				enchantmentLimit,
				effects,
				spells,
				availablePrimaryColours,
				availablePrimaryDyeColours,
				availableSecondaryColours,
				availableSecondaryDyeColours,
				itemTags,
				null,
				null,
				null,
				null);
	}

	public AbstractWeaponType(int baseValue,
			boolean melee,
			boolean twoHanded,
			String determiner,
			boolean plural,
			String name,
			String namePlural,
			String attackDescriptor,
			String attackTooltipDescription,
			String description,
			String pathName,
			String pathNameEquipped,
			Rarity rarity,
			float physicalResistance,
			ClothingSet clothingSet,
			List<DamageType> availableDamageTypes,
			int damage,
			int arcaneCost,
			DamageVariance damageVariance,
			int enchantmentLimit,
			List<ItemEffect> effects,
			List<Spell> spells,
			List<Colour> availablePrimaryColours,
			List<Colour> availablePrimaryDyeColours,
			List<Colour> availableSecondaryColours,
			List<Colour> availableSecondaryDyeColours,
			List<ItemTag> itemTags,
			String equipText,
			String unequipText,
			List<String> hitDescriptions,
			List<String> missDescriptions) {

		this.baseValue = baseValue;
		this.isMod = false;

		this.melee = melee;
		this.twoHanded = twoHanded;

		this.determiner = determiner;
		this.plural = plural;
		this.name = name;
		this.namePlural = namePlural;
		this.attackDescriptor = attackDescriptor;
		if(attackTooltipDescription!=null) {
			this.attackTooltipDescription = attackTooltipDescription;
		} else {
			this.attackTooltipDescription = "Use your "+this.getName()+" to strike at [npc2.name].";
		}
		this.description = description;

		this.rarity = rarity;
		this.physicalResistance = physicalResistance;
		this.clothingSet = clothingSet;

		this.availableDamageTypes = availableDamageTypes;

		if(spells==null) {
			this.spells = new ArrayList<>();
		} else {
			this.spells = spells;
		}

		this.damage = damage;
		this.damageVariance = damageVariance;

		this.arcaneCost = arcaneCost;

		this.pathName = pathName;
		this.pathNameEquipped = pathNameEquipped;

		this.enchantmentLimit = enchantmentLimit;

		if (effects != null) {
			this.effects = new ArrayList<>(effects);
		} else {
			this.effects = new ArrayList<>();
		}

		if(itemTags==null) {
			this.itemTags = new ArrayList<>();
		} else {
			this.itemTags = itemTags;
		}

		setUpColours(availablePrimaryColours,
				availablePrimaryDyeColours,
				availableSecondaryColours,
				availableSecondaryDyeColours);

		SVGStringMap = new HashMap<>();
		SVGStringEquippedMap = new HashMap<>();

		if(equipText!=null) {
			this.equipText = equipText;
		} else {
			this.equipText = "[npc.Name] [npc.verb(equip)] [npc.her] "+this.getName()+".";
		}

		if(unequipText!=null) {
			this.unequipText = unequipText;
		} else {
			this.unequipText = "[npc.Name] [npc.verb(unequip)] [npc.her] "+this.getName()+".";
		}

		if(hitDescriptions!=null) {
			this.hitDescriptions = hitDescriptions;
		} else {
			this.hitDescriptions = Util.newArrayListOfValues("[npc.Name] hits [npc2.name] with [npc.her] "+this.getName()+"!");
		}

		if(missDescriptions!=null) {
			this.missDescriptions = missDescriptions;
		} else {
			this.missDescriptions = Util.newArrayListOfValues("[npc.Name] misses [npc2.name] with [npc.her] "+this.getName()+"!");
		}

		this.authorDescription = ""; // Do not give attribution to Innoxia's items.

	}

	@SuppressWarnings("deprecation")
	public AbstractWeaponType(File weaponXMLFile, String author) {
		this.itemTags = new ArrayList<>();

		if (weaponXMLFile.exists()) {
			try {
				DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
				Document doc = dBuilder.parse(weaponXMLFile);

				// Cast magic:
				doc.getDocumentElement().normalize();

				Element weaponElement = Element.getDocumentRootElement(weaponXMLFile); // Loads the document and returns the root element - in clothing mods it's <clothing>
				Element coreAttributes = null;
				try {
					coreAttributes = weaponElement.getMandatoryFirstOf("coreAtributes");
				} catch (XMLMissingTagException ex) {
					coreAttributes = weaponElement.getMandatoryFirstOf("coreAttributes");
				}

				this.itemTags = coreAttributes
					.getMandatoryFirstOf("itemTags")
					.getAllOf("tag").stream()
					.map(Element::getTextContent).map(ItemTag::valueOf)
					.collect(Collectors.toList());

				this.isMod = true;

				this.baseValue = Integer.valueOf(coreAttributes.getMandatoryFirstOf("value").getTextContent());
				this.melee = Boolean.valueOf(coreAttributes.getMandatoryFirstOf("melee").getTextContent());
				this.twoHanded = Boolean.valueOf(coreAttributes.getMandatoryFirstOf("twoHanded").getTextContent());

				this.determiner = coreAttributes.getMandatoryFirstOf("determiner").getTextContent();
				this.plural = Boolean.valueOf(((Element)coreAttributes.getMandatoryFirstOf("namePlural")).getAttribute("pluralByDefault"));
				this.name = coreAttributes.getMandatoryFirstOf("name").getTextContent();
				this.namePlural = coreAttributes.getMandatoryFirstOf("namePlural").getTextContent();
				this.description = coreAttributes.getMandatoryFirstOf("description").getTextContent();
				this.attackDescriptor = coreAttributes.getMandatoryFirstOf("attackDescriptor").getTextContent();
				this.attackTooltipDescription = coreAttributes.getMandatoryFirstOf("attackTooltipDescription").getTextContent();

				if(coreAttributes.getOptionalFirstOf("weaponAuthorTag").isPresent()) {
					this.authorDescription = coreAttributes.getMandatoryFirstOf("weaponAuthorTag").getTextContent();
				} else if(coreAttributes.getOptionalFirstOf("authorTag").isPresent()) {
					this.authorDescription = coreAttributes.getMandatoryFirstOf("authorTag").getTextContent();
				} else if(!author.equalsIgnoreCase("innoxia")){
					this.authorDescription = "A discreet inscription on the surface of the "+(plural?namePlural:name)+" informs you that "+(plural?"they were":"it was")+" made by a certain '"+Util.capitaliseSentence(author)+"'.";
				} else {
					this.authorDescription = "";
				}

				this.equipText = coreAttributes.getMandatoryFirstOf("equipText").getTextContent();
				this.unequipText = coreAttributes.getMandatoryFirstOf("unequipText").getTextContent();

				this.pathName = weaponXMLFile.getParentFile().getAbsolutePath() + "/" + coreAttributes.getMandatoryFirstOf("imageName").getTextContent();

				Predicate<Element> filterEmptyElements = element -> !element.getTextContent().isEmpty(); //helper function to filter out empty elements.

				this.pathNameEquipped = coreAttributes.getOptionalFirstOf("imageEquippedName")
					.filter(filterEmptyElements)
					.map(o -> weaponXMLFile.getParentFile().getAbsolutePath() + "/" + o.getTextContent())
					.orElse(pathName);

				this.damage = Integer.valueOf(coreAttributes.getMandatoryFirstOf("damage").getTextContent());
				this.arcaneCost = Integer.valueOf(coreAttributes.getMandatoryFirstOf("arcaneCost").getTextContent());
				this.damageVariance = DamageVariance.valueOf(coreAttributes.getMandatoryFirstOf("damageVariance").getTextContent());

				if(coreAttributes.getOptionalFirstOf("availableDamageTypes").isPresent()) {
					this.availableDamageTypes = coreAttributes
							.getMandatoryFirstOf("availableDamageTypes")
							.getAllOf("damageType").stream()
							.map(Element::getTextContent).map(DamageType::valueOf)
							.collect(Collectors.toList());
				} else {
					this.availableDamageTypes = new ArrayList<>();
				}

				if(coreAttributes.getOptionalFirstOf("spells").isPresent()) {
					this.spells = coreAttributes
							.getMandatoryFirstOf("spells")
							.getAllOf("spell").stream()
							.map(o -> o.getTextContent().replaceAll("DARK_SIREN_BANEFUL_FISSURE", "DARK_SIREN_SIRENS_CALL"))
							.map(Spell::valueOf)
							.collect(Collectors.toList());
				} else {
					this.spells = new ArrayList<>();
				}

				enchantmentLimit = Integer.valueOf(coreAttributes.getMandatoryFirstOf("enchantmentLimit").getTextContent());

				this.clothingSet = coreAttributes.getOptionalFirstOf("weaponSet")
					.filter(filterEmptyElements)
					.map(Element::getTextContent).map(ClothingSet::valueOf)
					.orElse(null);

				this.effects = coreAttributes
					.getMandatoryFirstOf("effects")
					.getAllOf("effect") // Get all child elements with this tag (checking only contents of parent element) and return them as List<Element>
					.stream() // Convert this list to Stream<Element>, which lets us do some nifty operations on every element at once
					.map( e -> ItemEffect.loadFromXML(e.getInnerElement(), e.getDocument())) // Take every element and do something with them, return a Stream of results after this action. Here we load item effects and get Stream<ItemEffect>
					.filter(Objects::nonNull) // Ensure that we only add non-null effects
					.collect(Collectors.toList()); // Collect stream back into a list, but this time we get List<ItemEffect> we need!

				this.rarity = Rarity.valueOf(coreAttributes.getMandatoryFirstOf("rarity").getTextContent());

				if(coreAttributes.getOptionalFirstOf("physicalResistance").isPresent()) {
					this.physicalResistance = Float.valueOf(coreAttributes.getMandatoryFirstOf("physicalResistance").getTextContent());
				}

				// Hit/miss descriptions:

				if(weaponElement.getOptionalFirstOf("hitDescriptions").isPresent()) {
					this.hitDescriptions = weaponElement
							.getMandatoryFirstOf("hitDescriptions")
							.getAllOf("hitText").stream()
							.map(o -> o.getTextContent())
							.collect(Collectors.toList());
				} else {
					this.hitDescriptions = new ArrayList<>();
				}

				if(weaponElement.getOptionalFirstOf("missDescriptions").isPresent()) {
					this.missDescriptions = weaponElement
							.getMandatoryFirstOf("missDescriptions")
							.getAllOf("missText").stream()
							.map(o -> o.getTextContent())
							.collect(Collectors.toList());
				} else {
					this.missDescriptions = new ArrayList<>();
				}

				Function< Element, List<Colour> > getColoursFromElement = (colorsElement) -> { //Helper function to get the colors depending on if it's a specified group or a list of individual colors
					String values = colorsElement.getAttribute("values");
					try {
						if(values.isEmpty()) {
							return colorsElement.getAllOf("colour").stream()
									.map(Element::getTextContent).map(Colour::valueOf)
									.collect(Collectors.toList());
						} else {
							return ColourListPresets.getColourListFromId(values);
						}
					} catch (Exception e) {
						printHelpfulErrorForEnumValueMismatches(e);
						throw new IllegalStateException("Colour tag reading failure: "+colorsElement.getTagName()+" " + e.getMessage(), e);
					}
				};

				List<Colour> importedPrimaryColours = getColoursFromElement
					.apply(coreAttributes.getMandatoryFirstOf("primaryColours"));
				List<Colour> importedPrimaryColoursDye = getColoursFromElement
					.apply(coreAttributes.getMandatoryFirstOf("primaryColoursDye"));

				List<Colour> importedSecondaryColours = coreAttributes.getOptionalFirstOf("secondaryColours")
					.map(getColoursFromElement::apply)
					.orElseGet(ArrayList::new);
				List<Colour> importedSecondaryColoursDye = coreAttributes.getOptionalFirstOf("secondaryColoursDye")
					.map(getColoursFromElement::apply)
					.orElseGet(ArrayList::new);

				setUpColours(
						importedPrimaryColours,
						importedPrimaryColoursDye,
						importedSecondaryColours,
						importedSecondaryColoursDye);

				SVGStringMap = new HashMap<>();
				SVGStringEquippedMap = new HashMap<>();

			} catch(Exception ex) {
				ex.printStackTrace();
				System.err.println("WeaponType was unable to be loaded from file! (" + weaponXMLFile.getName() + ")\n" + ex);
			}
		}
	}

	@Override
	public boolean equals(Object o) { // I know it doesn't include everything, but this should be enough to check for equality.
		if(super.equals(o)){
			if(o instanceof AbstractWeaponType){
				if(((AbstractWeaponType)o).getName().equals(getName())
						&& ((AbstractWeaponType)o).isMelee() == isMelee()
						&& ((AbstractWeaponType)o).isTwoHanded() == isTwoHanded()
						&& ((AbstractWeaponType)o).getPathName().equals(getPathName())
						&& ((AbstractWeaponType)o).getPhysicalResistance() == getPhysicalResistance()
						&& ((AbstractWeaponType)o).getDamage() == getDamage()
						&& ((AbstractWeaponType)o).getDamageVariance() == getDamageVariance()
						&& ((AbstractWeaponType)o).getRarity() == getRarity()
						&& ((AbstractWeaponType)o).getAvailableDamageTypes().equals(getAvailableDamageTypes())
						&& ((AbstractWeaponType)o).getSpells().equals(getSpells())
						&& ((AbstractWeaponType)o).getEffects().equals(getEffects())
						&& ((AbstractWeaponType)o).getClothingSet() == getClothingSet()
						){
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public int hashCode() { // I know it doesn't include everything, but this should be enough to check for equality.
		int result = super.hashCode();
		result = 31 * result + getName().hashCode();
		result = 31 * result + getPathName().hashCode();
		result = 31 * result + Float.floatToIntBits(getPhysicalResistance());
		result = 31 * result + getDamage();
		result = 31 * result + getDamageVariance().hashCode();
		result = 31 * result + (melee ? 1 : 0);
		result = 31 * result + (twoHanded ? 1 : 0);
		result = 31 * result + getRarity().hashCode();
		result = 31 * result + getAvailableDamageTypes().hashCode();
		result = 31 * result + getSpells().hashCode();
		result = 31 * result + getEffects().hashCode();
		if(getClothingSet()!=null) {
			result = 31 * result + getClothingSet().hashCode();
		}
		return result;
	}

	public static AbstractWeapon generateWeapon(AbstractWeaponType wt, DamageType dt) {
		return generateWeapon(wt, dt, null, null);
	}

	public static AbstractWeapon generateWeapon(String id, DamageType dt) {
		return generateWeapon(id, dt, null, null);
	}

	public static AbstractWeapon generateWeapon(String id, DamageType dt, Colour primaryColour, Colour secondaryColour) {
		return generateWeapon(WeaponType.getWeaponTypeFromId(id), dt, primaryColour, secondaryColour);
	}

	public static AbstractWeapon generateWeapon(AbstractWeaponType wt, DamageType dt, Colour primaryColour, Colour secondaryColour) {

		if (wt.getAvailableDamageTypes() != null) {
			if (!wt.getAvailableDamageTypes().contains(dt)) {
				dt = wt.getAvailableDamageTypes().get(Util.random.nextInt(wt.getAvailableDamageTypes().size()));
			}
		}

		Colour c1 = primaryColour;
		Colour c2 = secondaryColour;

		if (primaryColour == null || !wt.getAllAvailablePrimaryColours().contains(primaryColour)) {
			if(wt.getAvailablePrimaryColours().isEmpty()) {
				c1 = Colour.CLOTHING_BLACK;
			} else {
				c1 = wt.getAvailablePrimaryColours().get(Util.random.nextInt(wt.getAvailablePrimaryColours().size()));
			}
		}

		if (secondaryColour == null || !wt.getAllAvailableSecondaryColours().contains(secondaryColour)) {
			if(wt.getAvailableSecondaryColours().isEmpty()) {
				c2 = Colour.CLOTHING_BLACK;
			} else {
				c2 = wt.getAvailableSecondaryColours().get(Util.random.nextInt(wt.getAvailableSecondaryColours().size()));
			}
		}

		return new AbstractWeapon(wt, dt, c1, c2) {
			@Override
			public String onEquip(GameCharacter character) {
				if (character.isPlayer()) {
					if (Main.getProperties().addWeaponDiscovered(wt)) {
						Main.game.addEvent(new EventLogEntryEncyclopediaUnlock(wt.getName(), wt.getRarity().getColour()), true);
					}
				}
				return wt.equipText(character);
			}

			@Override
			public String onUnequip(GameCharacter character) {
				return wt.unequipText(character);
			}
		};
	}

	public static AbstractWeapon generateWeapon(AbstractWeapon weapon) {
		return new AbstractWeapon(weapon) {
			@Override
			public String onEquip(GameCharacter character) {
				if (character.isPlayer()) {
					if (Main.getProperties().addWeaponDiscovered(weapon.getWeaponType())) {
						Main.game.addEvent(new EventLogEntryEncyclopediaUnlock(weapon.getWeaponType().getName(), weapon.getWeaponType().getRarity().getColour()), true);
					}
				}
				return weapon.getWeaponType().equipText(character);
			}

			@Override
			public String onUnequip(GameCharacter character) {
				return weapon.getWeaponType().unequipText(character);
			}
		};
	}

	private void setUpColours(List<Colour> availablePrimaryColours,
			List<Colour> availablePrimaryDyeColours,
			List<Colour> availableSecondaryColours,
			List<Colour> availableSecondaryDyeColours) {

		this.availablePrimaryColours = new ArrayList<>();
		if (availablePrimaryColours != null) {
			this.availablePrimaryColours.addAll(availablePrimaryColours);
		}

		Set<Colour> colourSet = new HashSet<>();

		this.availablePrimaryDyeColours = new ArrayList<>();
		if (availablePrimaryDyeColours != null) {
			this.availablePrimaryDyeColours.addAll(availablePrimaryDyeColours);
		}

		this.allAvailablePrimaryColours = new ArrayList<>();
		colourSet.addAll(this.availablePrimaryColours);
		if(availablePrimaryDyeColours!=null) {
			colourSet.addAll(availablePrimaryDyeColours);
		}
		this.allAvailablePrimaryColours.addAll(colourSet);
		this.allAvailablePrimaryColours.sort((c1, c2) -> c1.compareTo(c2));

		this.availableSecondaryColours = new ArrayList<>();
		if (availableSecondaryColours != null) {
			this.availableSecondaryColours.addAll(availableSecondaryColours);
		}

		this.availableSecondaryDyeColours = new ArrayList<>();
		if (availableSecondaryDyeColours != null) {
			this.availableSecondaryDyeColours.addAll(availableSecondaryDyeColours);
		}

		colourSet.clear();
		this.allAvailableSecondaryColours = new ArrayList<>();
		if(availableSecondaryColours!=null) {
			colourSet.addAll(availableSecondaryColours);
		}
		if(availableSecondaryDyeColours!=null) {
			colourSet.addAll(availableSecondaryDyeColours);
		}
		this.allAvailableSecondaryColours.addAll(colourSet);
		this.allAvailableSecondaryColours.sort((c1, c2) -> c1.compareTo(c2));
	}

	@SuppressWarnings("rawtypes")
	private static void printHelpfulErrorForEnumValueMismatches(Exception ex) {
		Map<Class, Set<String>> possibleEnumValues = new HashMap<>();
		possibleEnumValues.put(ColourListPresets.class, ColourListPresets.getIdToColourListMap().keySet());
		String exMessage = ex.getMessage();
		if (exMessage.startsWith("No ColourListPreset constant")){
			for (Entry<Class, Set<String>> possibleMatch : possibleEnumValues.entrySet()) {
				if (exMessage.contains(possibleMatch.getKey().getCanonicalName())) {
					StringJoiner valueLister = new StringJoiner(",");
					Arrays.asList(possibleMatch.getValue()).forEach(enumValue -> valueLister.add(enumValue.toString()));
					System.err.println("Possible values for "+possibleMatch.getKey().getSimpleName()+" are " + valueLister.toString());
				}
			}
		}
	}

	/**
	 * Generates a weapon with random damage type
	 *
	 * @param wt
	 * @param level
	 * @return
	 */
	public static AbstractWeapon generateWeapon(AbstractWeaponType wt) {
		return AbstractWeaponType.generateWeapon(wt, wt.getAvailableDamageTypes().get(Util.random.nextInt(wt.getAvailableDamageTypes().size())));
	}

	public String getId() {
		return WeaponType.weaponToIdMap.get(this);
	}

	public String equipText(GameCharacter character) {
		return UtilText.parse(character, equipText);
	}

	public String unequipText(GameCharacter character) {
		return UtilText.parse(character, unequipText);
	}

	public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
		if(isHit) {
			return UtilText.parse(character, target, getHitText(character, target));
		} else {
			return UtilText.parse(character, target, getMissText(character, target));
		}
	}

	public String getHitText(GameCharacter character, GameCharacter target) {
		return UtilText.parse(character, target, Util.randomItemFrom(hitDescriptions));
	}

	public String getMissText(GameCharacter character, GameCharacter target) {
		return UtilText.parse(character, target, Util.randomItemFrom(missDescriptions));
	}

	protected static String getDescriptions(GameCharacter character, GameCharacter target, boolean isHit,
			String playerStrikingNPC,
			String NPCStrikingPlayer,
			String NPCStrikingNPC,
			String playerMissingNPC,
			String NPCMissingPlayer,
			String NPCMissingNPC) {
		if(isHit) {
			if(character.isPlayer()) {
				return UtilText.parse(target, playerStrikingNPC);

			} else {
				if(target.isPlayer()) {
					return UtilText.parse(character, NPCStrikingPlayer);
				} else {
					return UtilText.parse(character, target, NPCStrikingNPC);
				}
			}

		} else {
			if(character.isPlayer()) {
				return UtilText.parse(target, playerMissingNPC);

			} else {
				if(target.isPlayer()) {
					return UtilText.parse(character, NPCMissingPlayer);
				} else {
					return UtilText.parse(character, target, NPCMissingNPC);
				}
			}
		}
	}

	public static String genericMeleeAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
		if(isHit) {
			if(character.isPlayer()) {
				return UtilText.parse(target,
						UtilText.returnStringAtRandom(
							"Darting forwards, you deliver a solid punch to [npc.namePos] [npc.arm].",
							"You throw a punch at [npc.name], grinning as you feel it connect with [npc.her] [npc.arm].",
							"You kick out at [npc.name], smiling to yourself as you feel your [pc.foot] connect with [npc.her] [npc.leg]."));

			} else {
				if(target.isPlayer()) {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"Darting forwards, [npc.name] delivers a solid punch to your [pc.arm].",
								"[npc.Name] throws a punch at you, grinning as [npc.her] attack connects with your [pc.arm].",
								"[npc.Name] kicks out at you, smiling to [npc.herself] as [npc.her] [npc.foot] connects with your [pc.leg]."));
				} else {
					return UtilText.parse(character, target,
							UtilText.returnStringAtRandom(
								"Darting forwards, [npc1.name] delivers a solid punch to [npc2.namePos] [npc2.arm].",
								"[npc1.Name] throws a punch at [npc2.name], grinning as [npc1.her] attack connects with [npc2.her] [npc2.arm].",
								"[npc1.Name] kicks out at [npc2.name], smiling to [npc1.herself] as [npc1.her] [npc1.foot] connects with [npc2.namePos] [npc2.leg]."));
				}
			}

		} else {
			if(character.isPlayer()) {
				return UtilText.parse(target,
						UtilText.returnStringAtRandom(
							"Darting forwards, you try to deliver a punch to [npc.namePos] [npc.arm], but [npc.she] manages to step out of the way in time.",
							"You try to throw a punch at [npc.name], but fail to make contact with any part of [npc.her] body.",
							"You kick out at [npc.name], but your [pc.foot] sails harmlessly through the air."));

			} else {
				if(target.isPlayer()) {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"Darting forwards, [npc.name] tries to deliver a punch to your [pc.arm], but you manage to step out of the way in time.",
								"[npc.Name] throws a punch at you, but fails to make contact with any part of your body.",
								"[npc.Name] kicks out at you, but [npc.her] [npc.foot] sails harmlessly through the air."));
				} else {
					return UtilText.parse(character, target,
							UtilText.returnStringAtRandom(
								"Darting forwards, [npc1.name] tries to deliver a punch to [npc2.namePos] [npc2.arm], but [npc2.she] manages to step out of the way in time.",
								"[npc1.Name] throws a punch at [npc2.name], but fails to make contact with any part of [npc2.her] body.",
								"[npc1.Name] kicks out at [npc2.name], but [npc1.her] [npc1.foot] sails harmlessly through the air."));
				}
			}
		}
	}

	public static String genericRangedAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
		if(isHit) {
			if(character.isPlayer()) {
				return UtilText.parse(target,
						UtilText.returnStringAtRandom(
							"Punching your fist out towards [npc.name], a bolt of arcane energy shoots out to strike [npc.her] [npc.arm].",
							"Striking out towards [npc.name], a bolt of arcane energy shoots out of your fist to connect with [npc.her] [npc.arm].",
							"You kick out at [npc.name], and as you do, a bolt of arcane energy shoots out of your [pc.foot] to connect with [npc.her] [npc.leg]."));

			} else {
				if(target.isPlayer()) {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"Punching [npc.her] fist out towards you, a bolt of arcane energy shoots out to strike your [pc.arm].",
								"Striking out towards you, a bolt of arcane energy shoots out of [npc.namePos] fist to connect with your [pc.arm].",
								"[npc.Name] kicks out at you, and as [npc.she] does so, a bolt of arcane energy shoots out of [npc.her] [npc.foot] to connect with your [pc.leg]."));
				} else {
					return UtilText.parse(character, target,
							UtilText.returnStringAtRandom(
								"Punching [npc1.her] fist out towards [npc2.name], a bolt of arcane energy shoots out to strike [npc2.her] [npc2.arm].",
								"Striking out towards [npc2.name], a bolt of arcane energy shoots out of [npc1.namePos] fist to connect with [npc2.her] [npc2.arm].",
								"[npc1.Name] kicks out at [npc2.name], and as [npc1.she] does so, a bolt of arcane energy shoots out of [npc1.her] [npc1.foot] to connect with [npc2.namePos] [npc2.leg]."));
				}
			}

		} else {
			if(character.isPlayer()) {
				return UtilText.parse(target,
						UtilText.returnStringAtRandom(
							"Punching your fist out towards [npc.name], a bolt of arcane energy shoots out of your fist, sailing harmlessly through the air as [npc.she] dodges your attack.",
							"Striking out towards [npc.name], a bolt of arcane energy shoots out of your fist to sail harmlessly through the air as [npc.she] dodges your attack.",
							"You kick out at [npc.name], and as you do, a bolt of arcane energy shoots out of your [pc.foot] to sail harmlessly through the air as [npc.she] dodges your attack."));

			} else {
				if(target.isPlayer()) {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"Punching [npc.her] fist out towards you, a bolt of arcane energy shoots out to sail harmlessly through the air as you dodge [npc.her] attack.",
								"Striking out towards you, a bolt of arcane energy shoots out of [npc.namePos] fist to sail harmlessly through the air as you dodge [npc.her] attack.",
								"[npc.Name] kicks out at you, and as [npc.she] does so, a bolt of arcane energy shoots out of [npc.her] [npc.foot] to sail harmlessly through the air as you dodge [npc.her] attack."));
				} else {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"Punching [npc1.her] fist out towards [npc2.name], a bolt of arcane energy shoots out to sail harmlessly through the air as [npc2.name] dodges [npc1.her] attack.",
								"Striking out towards [npc2.name], a bolt of arcane energy shoots out of [npc1.namePos] fist to sail harmlessly through the air as [npc2.name] dodges [npc1.her] attack.",
								"[npc1.Name] kicks out at [npc2.name], and as [npc1.she] does so, a bolt of arcane energy shoots out of [npc1.her] [npc1.foot] to sail harmlessly through the air as [npc2.name] dodges [npc1.her] attack."));
				}
			}
		}
	}

	public boolean isAbleToBeUsed(GameCharacter user, GameCharacter target) {
		if(this.getArcaneCost()>0) {
			return user.getEssenceCount(TFEssence.ARCANE) > 0;
		} else {
			return true;
		}
	}

	public String getUnableToBeUsedDescription() {
		if(this.getArcaneCost()>0) {
			return "You need at least [style.boldBad(one)] [style.boldArcane(arcane essence)] in order to use this weapon!";
		} else {
			return "";
		}
	}

	public String applyExtraEffects(GameCharacter user, GameCharacter target, boolean isHit) {
		if(this.getArcaneCost()>0) {
			user.incrementEssenceCount(TFEssence.ARCANE, -this.getArcaneCost(), false);
			if(user.isPlayer()) {
				return (this.isMelee()?"Using":"Firing")+" the "+this.getName()+" drains [style.boldBad("+Util.intToString(this.getArcaneCost())+")] [style.boldArcane(arcane essence)] from your aura!";
			} else {
				return UtilText.parse(user, (this.isMelee()?"Using":"Firing")+" the "+this.getName()+" drains [style.boldBad("+Util.intToString(this.getArcaneCost())+")] [style.boldArcane(arcane essence)] from [npc.namePos] aura!");
			}
		} else {
			return "";
		}
	}

	public int getBaseValue() {
		return baseValue;
	}

	public boolean isMelee() {
		return melee;
	}

	public boolean isTwoHanded() {
		return twoHanded;
	}

	public String getDeterminer() {
		return determiner;
	}

	public boolean isPlural() {
		return plural;
	}

	public String getName() {
		if(isPlural()) {
			return namePlural;
		}
		return name;
	}

	public String getNamePlural() {
		return namePlural;
	}

	public String getAttackDescriptor() {
		return attackDescriptor;
	}

	public String getAttackDescription(GameCharacter user, GameCharacter target) {
		return UtilText.parse(user, target, attackTooltipDescription);
	}

	public String getDescription() {
		return description;
	}

	public Rarity getRarity() {
		return rarity;
	}

	public float getPhysicalResistance() {
		return physicalResistance;
	}

	public ClothingSet getClothingSet() {
		return clothingSet;
	}

	public String getPathName() {
		return pathName;
	}

	public int getDamage() {
		return damage;
	}

	public DamageVariance getDamageVariance() {
		return damageVariance;
	}

	public int getArcaneCost() {
		return arcaneCost;
	}

	public List<DamageType> getAvailableDamageTypes() {
		return availableDamageTypes;
	}

	public List<Spell> getGenerationSpells(DamageType dt) {
		return null;
	}

	public List<Spell> getSpells() {
		return spells;
	}

	public List<Colour> getAvailablePrimaryColours() {
		return availablePrimaryColours;
	}

	public List<Colour> getAvailablePrimaryDyeColours() {
		return availablePrimaryDyeColours;
	}

	public List<Colour> getAllAvailablePrimaryColours() {
		return allAvailablePrimaryColours;
	}

	public List<Colour> getAvailableSecondaryColours() {
		return availableSecondaryColours;
	}

	public List<Colour> getAvailableSecondaryDyeColours() {
		return availableSecondaryDyeColours;
	}

	public List<Colour> getAllAvailableSecondaryColours() {
		return allAvailableSecondaryColours;
	}

	private void addSVGStringMapping(DamageType dt, Colour colourSecondary, Colour colourTertiary, String s) {
		if(SVGStringMap.get(dt)==null) {
			SVGStringMap.put(dt, new HashMap<>());
			SVGStringMap.get(dt).put(colourSecondary, new HashMap<>());

		} else if(SVGStringMap.get(dt).get(colourSecondary)==null) {
			SVGStringMap.get(dt).put(colourSecondary, new HashMap<>());
		}

		SVGStringMap.get(dt).get(colourSecondary).put(colourTertiary, s);
	}

	private String getSVGStringFromMap(DamageType dt, Colour colourSecondary, Colour colourTertiary) {
		if(SVGStringMap.get(dt)==null) {
			return null;
		} else {
			if(SVGStringMap.get(dt).get(colourSecondary)==null) {
				return null;
			} else {
				return SVGStringMap.get(dt).get(colourSecondary).get(colourTertiary);
			}
		}
	}

	public String getSVGImage() {
		DamageType dt = DamageType.PHYSICAL;
		if (this.getAvailableDamageTypes() != null) {
			if (!this.getAvailableDamageTypes().contains(dt)) {
				dt = this.getAvailableDamageTypes().get(0);
			}
		}

		Colour pColour = Colour.CLOTHING_BLACK;
		if(this.getAllAvailablePrimaryColours()!=null && !this.getAllAvailablePrimaryColours().isEmpty()) {
			pColour = this.getAllAvailablePrimaryColours().get(0);
		}
		Colour sColour = Colour.CLOTHING_BLACK;
		if(this.getAllAvailableSecondaryColours()!=null && !this.getAllAvailableSecondaryColours().isEmpty()) {
			sColour = this.getAllAvailableSecondaryColours().get(0);
		}

		return getSVGImage(dt, pColour, sColour);
	}

	public String getSVGImage(DamageType dt, Colour colourPrimary, Colour colourSecondary) {
		if (!this.getAvailableDamageTypes().contains(dt)) {
			return "";
		}

		String stringFromMap = getSVGStringFromMap(dt, colourPrimary, colourSecondary);
		if(stringFromMap!=null) {
			return stringFromMap;
		}

		try {
			InputStream is;
			String s;
			if(isMod) {
				List<String> lines = Files.readAllLines(Paths.get(pathName));
				StringBuilder sb = new StringBuilder();
				for(String line : lines) {
					sb.append(line);
				}
				s = sb.toString();

			} else {
				is = this.getClass().getResourceAsStream("/com/lilithsthrone/res/weapons/" + pathName + ".svg");
				s = Util.inputStreamToString(is);
				is.close();
			}

			s = SvgUtil.colourReplacement(this.getId(), dt.getColour(), colourPrimary, colourSecondary, s);

			addSVGStringMapping(dt, colourPrimary, colourSecondary, s);

			return s;

		} catch (IOException e) {
			e.printStackTrace();
		}

		return "";
	}

	private void addSVGStringEquippedMapping(DamageType dt, Colour colourSecondary, Colour colourTertiary, String s) {
		if(SVGStringEquippedMap.get(dt)==null) {
			SVGStringEquippedMap.put(dt, new HashMap<>());
			SVGStringEquippedMap.get(dt).put(colourSecondary, new HashMap<>());

		} else if(SVGStringEquippedMap.get(dt).get(colourSecondary)==null) {
			SVGStringEquippedMap.get(dt).put(colourSecondary, new HashMap<>());
		}

		SVGStringEquippedMap.get(dt).get(colourSecondary).put(colourTertiary, s);
	}

	private String getSVGStringEquippedFromMap(DamageType dt, Colour colourSecondary, Colour colourTertiary) {
		if(SVGStringEquippedMap.get(dt)==null) {
			return null;
		} else {
			if(SVGStringEquippedMap.get(dt).get(colourSecondary)==null) {
				return null;
			} else {
				return SVGStringEquippedMap.get(dt).get(colourSecondary).get(colourTertiary);
			}
		}
	}

	public String getSVGEquippedImage() {
		DamageType dt = DamageType.PHYSICAL;
		if (this.getAvailableDamageTypes() != null) {
			if (!this.getAvailableDamageTypes().contains(dt)) {
				dt = this.getAvailableDamageTypes().get(0);
			}
		}

		Colour pColour = Colour.CLOTHING_BLACK;
		if(this.getAllAvailablePrimaryColours()!=null && !this.getAllAvailablePrimaryColours().isEmpty()) {
			pColour = this.getAllAvailablePrimaryColours().get(0);
		}
		Colour sColour = Colour.CLOTHING_BLACK;
		if(this.getAllAvailableSecondaryColours()!=null && !this.getAllAvailableSecondaryColours().isEmpty()) {
			sColour = this.getAllAvailableSecondaryColours().get(0);
		}

		return getSVGEquippedImage(dt, pColour, sColour);
	}

	public String getSVGEquippedImage(DamageType dt, Colour colourPrimary, Colour colourSecondary) {
		if (!this.getAvailableDamageTypes().contains(dt)) {
			return "";
		}

		String stringFromMap = getSVGStringEquippedFromMap(dt, colourPrimary, colourSecondary);
		if(stringFromMap!=null) {
			return stringFromMap;
		}

		try {
			InputStream is;
			String s;
			if(isMod) {
				List<String> lines = Files.readAllLines(Paths.get(pathNameEquipped));
				StringBuilder sb = new StringBuilder();
				for(String line : lines) {
					sb.append(line);
				}
				s = sb.toString();

			} else {
				is = this.getClass().getResourceAsStream("/com/lilithsthrone/res/weapons/" + pathNameEquipped + ".svg");
				s = Util.inputStreamToString(is);
				is.close();
			}

			s = SvgUtil.colourReplacement(this.getId()+"equipped", dt.getColour(), colourPrimary, colourSecondary, s);

			addSVGStringEquippedMapping(dt, colourPrimary, colourSecondary, s);

			return s;

		} catch (IOException e) {
			e.printStackTrace();
		}

		return "";
	}

	// Enchantments:

	public List<ItemEffect> getEffects() {
		return effects;
	}

	public boolean isAbleToBeSold() {
		return getRarity()!=Rarity.QUEST;
	}

	public boolean isAbleToBeDropped() {
		return getRarity()!=Rarity.QUEST;
	}

	public int getEnchantmentLimit() {
		return 100;
//		if(enchantmentLimit==-1) {
//			return (getClothingSet()==null?5:10);
//		} else {
//			return enchantmentLimit;
//		}
	}

	public AbstractItemEffectType getEnchantmentEffect() {
		return ItemEffectType.WEAPON;
	}

	public TFEssence getRelatedEssence() {
		return TFEssence.ARCANE;
	}

	public AbstractWeaponType getEnchantmentItemType(List<ItemEffect> effects) {
		return this;
	}

	public List<ItemTag> getItemTags() {
		return itemTags;
	}
}


code:
package com.lilithsthrone.game.inventory.weapon;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.combat.DamageType;
import com.lilithsthrone.game.combat.DamageVariance;
import com.lilithsthrone.game.combat.Spell;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.ItemTag;
import com.lilithsthrone.game.inventory.Rarity;
import com.lilithsthrone.game.inventory.enchanting.ItemEffect;
import com.lilithsthrone.game.inventory.enchanting.ItemEffectType;
import com.lilithsthrone.game.inventory.enchanting.TFModifier;
import com.lilithsthrone.game.inventory.enchanting.TFPotency;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.ColourListPresets;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.world.places.PlaceType;

/**
 * @since 0.1.84
 * @version 0.3.4
 * @author Innoxia
 */
public class WeaponType {

	public static AbstractWeaponType MELEE_CHAOS_RARE = new AbstractWeaponType(1000,
			true,
			false,
			"an",
			false,
			"opaque demonstone",
			"opaque demonstones",
			"Crystal Strike",
			"A common type of demonstone, the power of which can be harnessed as a weapon."
					+ " Demonstones are rumoured to be crystallised essences of a Lilin's orgasm.",
			"meleeCrystal1",
			"meleeCrystal1",
			Rarity.RARE,
			0,
			null,
			Util.newArrayListOfValues(
					DamageType.PHYSICAL,
					DamageType.FIRE,
					DamageType.ICE,
					DamageType.POISON),
			8,
			0,
			DamageVariance.MEDIUM,
			5,
			Util.newArrayListOfValues(
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_WEAPON, TFPotency.MAJOR_BOOST, 0)),
			null,
			null,
			null,
			null,
			null,
			Util.newArrayListOfValues(ItemTag.SOLD_BY_VICKY)) {

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy in the crystal."
					+ " As [npc.she] [npc.does] so, it dissolves and flows into [npc.her] body, granting [npc.herHim] the ability to perform arcane-imbued attacks.");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy inside of [npc.herHim], forcing it out from [npc.her] body."
					+ " As [npc.she] [npc.does] so, it reforms back into a demonstone.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			return genericMeleeAttackDescription(character, target, isHit);
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Strike at [npc.name] in melee, using your crystal's power to inflict extra damage!");
		}

		@Override
		public List<Spell> getGenerationSpells(DamageType dt) {
			switch(dt) {
				case FIRE:
					return Util.newArrayListOfValues(Spell.FIREBALL);
				case ICE:
					return Util.newArrayListOfValues(Spell.ICE_SHARD);
				case LUST:
					return Util.newArrayListOfValues(Spell.ARCANE_AROUSAL);
				case PHYSICAL:
					return Util.newArrayListOfValues(Spell.SLAM);
				case POISON:
					return Util.newArrayListOfValues(Spell.POISON_VAPOURS);
				default:
					break;
			}
			return null;
		}
	};

	public static AbstractWeaponType MELEE_CHAOS_EPIC = new AbstractWeaponType(1500,
			true,
			false,
			"a",
			false,
			"misty demonstone",
			"misty demonstones",
			"Crystal Strike",
			"A powerful demonstone, the power of which can be harnessed as a weapon."
					+ " Demonstones are rumoured to be crystallised essences of a Lilin's orgasm.",
			"meleeCrystal2",
			"meleeCrystal2",
			Rarity.EPIC,
			0,
			null,
			Util.newArrayListOfValues(
					DamageType.PHYSICAL,
					DamageType.FIRE,
					DamageType.ICE,
					DamageType.POISON),
			12,
			0,
			DamageVariance.MEDIUM,
			5,
			Util.newArrayListOfValues(
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_WEAPON, TFPotency.MAJOR_BOOST, 0),
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_WEAPON, TFPotency.MAJOR_BOOST, 0)),
			null,
			null,
			null,
			null,
			null,
			Util.newArrayListOfValues(ItemTag.SOLD_BY_VICKY)) {

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy in the crystal."
					+ " As [npc.she] [npc.does] so, it dissolves and flows into [npc.her] body, granting [npc.herHim] the ability to perform arcane-imbued attacks.");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy inside of [npc.herHim], forcing it out from [npc.her] body."
					+ " As [npc.she] [npc.does] so, it reforms back into a demonstone.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			return genericMeleeAttackDescription(character, target, isHit);
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Strike at [npc.name] in melee, using your crystal's power to inflict extra damage!");
		}

		@Override
		public List<Spell> getGenerationSpells(DamageType dt) {
			switch(dt) {
				case FIRE:
					return Util.newArrayListOfValues(Spell.FIREBALL);
				case ICE:
					return Util.newArrayListOfValues(Spell.ICE_SHARD);
				case LUST:
					return Util.newArrayListOfValues(Spell.ARCANE_AROUSAL);
				case PHYSICAL:
					return Util.newArrayListOfValues(Spell.SLAM);
				case POISON:
					return Util.newArrayListOfValues(Spell.POISON_VAPOURS);
				default:
					break;
			}
			return null;
		}
	};

	public static AbstractWeaponType MELEE_CHAOS_LEGENDARY = new AbstractWeaponType(2500,
			true,
			false,
			"a",
			false,
			"clear demonstone",
			"clear demonstones",
			"Crystal Strike",
			"An extremely powerful demonstone, the power of which can be harnessed as a weapon."
					+ " Demonstones are rumoured to be crystallised essences of a Lilin's orgasm.",
			"meleeCrystal3",
			"meleeCrystal3",
			Rarity.LEGENDARY,
			0,
			null,
			Util.newArrayListOfValues(
					DamageType.PHYSICAL,
					DamageType.FIRE,
					DamageType.ICE,
					DamageType.POISON),
			16,
			0,
			DamageVariance.LOW,
			10,
			Util.newArrayListOfValues(
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_WEAPON, TFPotency.MAJOR_BOOST, 0),
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_WEAPON, TFPotency.MAJOR_BOOST, 0),
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_WEAPON, TFPotency.MAJOR_BOOST, 0)),
			null,
			null,
			null,
			null,
			null,
			Util.newArrayListOfValues(ItemTag.SOLD_BY_VICKY)){

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy in the crystal."
					+ " As [npc.she] [npc.does] so, it dissolves and flows into [npc.her] body, granting [npc.herHim] the ability to perform arcane-imbued attacks.");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy inside of [npc.herHim], forcing it out from [npc.her] body."
					+ " As [npc.she] [npc.does] so, it reforms back into a demonstone.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			return genericMeleeAttackDescription(character, target, isHit);
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Strike at [npc.name] in melee, using your crystal's power to inflict extra damage!");
		}

		@Override
		public List<Spell> getGenerationSpells(DamageType dt) {
			switch(dt) {
				case FIRE:
					return Util.newArrayListOfValues(Spell.FIREBALL);
				case ICE:
					return Util.newArrayListOfValues(Spell.ICE_SHARD);
				case LUST:
					return Util.newArrayListOfValues(Spell.ARCANE_AROUSAL);
				case PHYSICAL:
					return Util.newArrayListOfValues(Spell.SLAM);
				case POISON:
					return Util.newArrayListOfValues(Spell.POISON_VAPOURS);
				default:
					break;
			}
			return null;
		}
	};

	public static AbstractWeaponType MELEE_ZWEIHANDER = new AbstractWeaponType(5000,
			true,
			true,
			"a",
			false,
			"Zweihänder",
			"Zweihänders",
			"Slash",
			"As its name of 'Zweihänder' (two-hander) suggests, this sword is so large that it requires two hands to wield correctly."
					+ " The blade is made out of ethereal arcane energy, which, instead of cutting someone, drains their energy as it passes through them.",
			"zweihander",
			"zweihander",
			Rarity.EPIC,
			0,
			null,
			Util.newArrayListOfValues(
					DamageType.PHYSICAL,
					DamageType.FIRE,
					DamageType.ICE,
					DamageType.POISON),
			20,
			0,
			DamageVariance.MEDIUM,
			5,
			Util.newArrayListOfValues(
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_WEAPON, TFPotency.MAJOR_BOOST, 0),
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_MELEE_WEAPON, TFPotency.MAJOR_BOOST, 0),
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_MELEE_WEAPON, TFPotency.MAJOR_BOOST, 0),
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_MELEE_WEAPON, TFPotency.MAJOR_BOOST, 0)),
			null,
			ColourListPresets.JUST_BLACK,
			ColourListPresets.ALL,
			ColourListPresets.JUST_STEEL,
			ColourListPresets.ALL_METAL,
			Util.newArrayListOfValues(
					ItemTag.SOLD_BY_VICKY,
					ItemTag.WEAPON_BLADE)) {

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(ready)] [npc.her] "+this.getName()+".");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(put)] [npc.her] "+this.getName()+" away.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			return getDescriptions(character, target, isHit,
					UtilText.returnStringAtRandom(
							"You slash your "+this.getName()+" at [npc.name], grinning as the ethereal arcane blade travels through [npc.her] body!",
							"Slashing out at [npc.name] with your "+this.getName()+", you grin as you see the ethereal blade travel straight through [npc.her] body!",
							"You slash at [npc.name] with your "+this.getName()+", causing [npc.herHim] to stagger back as the ethereal blade passes right through [npc.her] chest!"),
					UtilText.returnStringAtRandom(
							"[npc.Name] slashes [npc.her] "+this.getName()+" at you, grinning as the ethereal arcane blade travels through your body!",
							"Slashing out at you with [npc.her] "+this.getName()+", [npc.name] grins as [npc.she] sees the ethereal blade travel straight through your body!",
							"[npc.Name] slashes at you with [npc.her] "+this.getName()+", causing you to stagger back as the ethereal blade passes right through your chest!"),
					UtilText.returnStringAtRandom(
							"[npc.Name] slashes [npc.her] "+this.getName()+" at [npc2.name], grinning as the ethereal arcane blade travels through [npc2.her] body!",
							"Slashing out at [npc2.name] with [npc.her] "+this.getName()+", [npc.name] grins as [npc.she] sees the ethereal blade travel straight through [npc2.her] body!",
							"[npc.Name] slashes at [npc2.name] with [npc.her] "+this.getName()+", causing [npc2.herHim] to stagger back as the ethereal blade passes right through [npc2.her] chest!"),
					UtilText.returnStringAtRandom(
							"You slash your "+this.getName()+" at [npc.name], but the blade sails harmlessly through mid-air as you miss your target!",
							"Slashing out at [npc.name] with your "+this.getName()+", you sigh in frustration as you end up missing your target!",
							"You slash at [npc.name] with your "+this.getName()+", but fail to land a hit!"),
					UtilText.returnStringAtRandom(
							"[npc.Name] slashes [npc.her] "+this.getName()+" at you, but the blade sails harmlessly through mid-air as [npc.she] misses you!",
							"Slashing out at you with [npc.her] "+this.getName()+", [npc.name] sighs in frustration as [npc.she] ends up missing you!",
							"[npc.Name] slashes at you with [npc.her] "+this.getName()+", but fails to land a hit!"),
					UtilText.returnStringAtRandom(
							"[npc.Name] slashes [npc.her] "+this.getName()+" at [npc2.name], but the blade sails harmlessly through mid-air as [npc.she] misses [npc2.herHim]!",
							"Slashing out at [npc2.name] with [npc.her] "+this.getName()+", [npc.name] sighs in frustration as [npc.she] ends up missing [npc2.herHim]!",
							"[npc.Name] slashes at [npc2.name] with [npc.her] "+this.getName()+", but fails to land a hit!"));
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Strike at [npc.name] with your zweihander!");
		}
	};

	// OFFHAND
	public static AbstractWeaponType OFFHAND_CHAOS_RARE = new AbstractWeaponType(1000,
			false,
			false,
			"a",
			false,
			"chaos feather",
			"chaos feathers",
			"Feather Bolt",
			"An arcane feather, the power of which can be harnessed as a weapon."
					+ " Feathers like this are rumoured to have been plucked from a Lilin's wings.",
			"rangedFeather1",
			"rangedFeather1",
			Rarity.RARE,
			0,
			null,
			Util.newArrayListOfValues(DamageType.PHYSICAL, DamageType.FIRE, DamageType.ICE, DamageType.POISON),
			8,
			0,
			DamageVariance.HIGH,
			5,
			Util.newArrayListOfValues(
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_WEAPON, TFPotency.MAJOR_BOOST, 0)),
			null,
			null,
			null,
			null,
			null,
			Util.newArrayListOfValues(ItemTag.SOLD_BY_VICKY)) {

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy in the feather."
					+ " As [npc.she] [npc.does] so, it dissolves and flows into [npc.her] body, granting [npc.herHim] the ability to perform arcane-imbued attacks at range.");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy inside of [npc.herHim], forcing it out from [npc.her] body."
					+ " As [npc.she] [npc.does] so, it reforms back into a chaos feather.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			return genericRangedAttackDescription(character, target, isHit);
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Use your feather's power to shoot a bolt of energy at [npc.name]!");
		}

		@Override
		public List<Spell> getGenerationSpells(DamageType dt) {
			switch(dt) {
				case FIRE:
					return Util.newArrayListOfValues(Spell.FIREBALL);
				case ICE:
					return Util.newArrayListOfValues(Spell.ICE_SHARD);
				case LUST:
					return Util.newArrayListOfValues(Spell.ARCANE_AROUSAL);
				case PHYSICAL:
					return Util.newArrayListOfValues(Spell.SLAM);
				case POISON:
					return Util.newArrayListOfValues(Spell.POISON_VAPOURS);
				default:
					break;
			}
			return null;
		}
	};

	public static AbstractWeaponType OFFHAND_CHAOS_EPIC = new AbstractWeaponType(1500,
			false,
			false,
			"a",
			false,
			"chaos feather",
			"chaos feathers",
			"Feather Bolt",
			"A well-preserved arcane feather, the power of which can be harnessed as a weapon."
					+ " Feathers like this are rumoured to have been plucked from a Lilin's wings.",
			"rangedFeather2",
			"rangedFeather2",
			Rarity.EPIC,
			0,
			null,
			Util.newArrayListOfValues(DamageType.PHYSICAL, DamageType.FIRE, DamageType.ICE, DamageType.POISON),
			14,
			0,
			DamageVariance.HIGH,
			5,
			Util.newArrayListOfValues(
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_WEAPON, TFPotency.MAJOR_BOOST, 0),
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.RESISTANCE_WEAPON, TFPotency.MAJOR_BOOST, 0)),
			null,
			null,
			null,
			null,
			null,
			Util.newArrayListOfValues(ItemTag.SOLD_BY_VICKY)) {

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy in the feather."
					+ " As [npc.she] [npc.does] so, it dissolves and flows into [npc.her] body, granting [npc.herHim] the ability to perform arcane-imbued attacks at range.");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(focus)] on the energy inside of [npc.herHim], forcing it out from [npc.her] body."
					+ " As [npc.she] [npc.does] so, it reforms back into a chaos feather.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			return genericRangedAttackDescription(character, target,isHit);
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Use your feather's power to shoot a bolt of energy at [npc.name]!");
		}

		@Override
		public List<Spell> getGenerationSpells(DamageType dt) {
			switch(dt) {
				case FIRE:
					return Util.newArrayListOfValues(Spell.FIREBALL);
				case ICE:
					return Util.newArrayListOfValues(Spell.ICE_SHARD);
				case LUST:
					return Util.newArrayListOfValues(Spell.ARCANE_AROUSAL);
				case PHYSICAL:
					return Util.newArrayListOfValues(Spell.SLAM);
				case POISON:
					return Util.newArrayListOfValues(Spell.POISON_VAPOURS);
				default:
					break;
			}
			return null;
		}
	};

	public static AbstractWeaponType RANGED_MUSKET = new AbstractWeaponType(15000,
			false,
			true,
			"an",
			false,
			"Arcane Musket",
			"Arcane Muskets",
			"Fire Musket",
			"Carried by Lyssieth's demonic guard, these smoothbore long guns fire bolts of arcane energy instead of bullets."
					+ " Each discharge drains one arcane essence from the user, meaning that only those who have the ability to absorb arcane essences can fire it.",
			"arcaneMusket",
			"arcaneMusket",
			Rarity.LEGENDARY,
			0,
			null,
			Util.newArrayListOfValues(
					DamageType.PHYSICAL),
			25,
			1,
			DamageVariance.LOW,
			5,
			null,
			null,
			null,
			null,
			null,
			null,
			null) {

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(ready)] [npc.her] musket.");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(put)] [npc.her] musket away.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			if(isHit) {
				if(character.isPlayer()) {
					return UtilText.parse(target,
							UtilText.returnStringAtRandom(
								"You point your arcane musket at [npc.name] and pull the trigger, unleashing a blast of arcane power that strikes [npc.herHim] right in the chest!",
								"Taking aim at [npc.name] with your musket, you pull the trigger, unleashing a powerful blast of arcane energy that slams into [npc.her] torso!",
								"You fire your musket at [npc.name], blasting forth a powerful jolt of arcane energy that strikes [npc.herHim] straight in the chest!"));

				} else {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"[npc.Name] points [npc.her] arcane musket at you and pulls the trigger, unleashing a blast of arcane power that strikes you right in the chest!",
								"Taking aim at you with [npc.her] musket, [npc.name] pulls the trigger, unleashing a powerful blast of arcane energy that slams into your torso!",
								"[npc.Name] fires [npc.her] musket at you, blasting forth a powerful jolt of arcane energy that strikes you straight in the chest!"));
				}

			} else {
				if(character.isPlayer()) {
					return UtilText.parse(target,
							UtilText.returnStringAtRandom(
								"You point you arcane musket at [npc.name] and pull the trigger, but, much to your dismay, the blast of arcane power misses its target!",
								"Taking aim at [npc.name] with your musket, you pull the trigger, but unfortunately the blast of arcane power that you unleash misses your target!",
								"You fire your musket at [npc.name], blasting forth a powerful jolt of arcane energy that unfortunately fails to hit your target!"));

				} else {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
									"[npc.Name] points [npc.her] arcane musket at you and pulls the trigger, but, much to [npc.her] dismay, the blast of arcane power misses you!",
									"Taking aim at you with [npc.her] musket, [npc.name] pulls the trigger, but thankfully the blast of arcane power that [npc.she] unleashes misses you!",
									"[npc.Name] fires [npc.her] musket at you, blasting forth a powerful jolt of arcane energy that thankfully fails to hit you!"));
				}
			}
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Point your musket at [npc.name] and fire!");
		}
	};

	public static AbstractWeaponType MAIN_WITCH_BROOM = new AbstractWeaponType(5000,
			true,
			false,
			"a",
			false,
			"Witch's Broom",
			"Witch's Brooms",
			"Broom Swipe",
			"An old-fashioned wooden broom, consisting of a long pole with a bundle of flexible twigs attached to one end."
					+ " The opposite end of the pole widens out a little, where there's a curious engraving of a pentagram etched into the wood.",
			"primary_witch_broom",
			"primary_witch_broom",
			Rarity.EPIC,
			0,
			null,
			Util.newArrayListOfValues(
					DamageType.PHYSICAL,
					DamageType.FIRE,
					DamageType.ICE,
					DamageType.POISON),
			5,
			0,
			DamageVariance.LOW,
			5,
			Util.newArrayListOfValues(
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.INTELLIGENCE, TFPotency.MAJOR_BOOST, 0)),
			Util.newArrayListOfValues(
					Spell.WITCH_SEAL,
					Spell.WITCH_CHARM),
			null,
			null,
			null,
			null,
			null) {

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(ready)] [npc.her] broom.");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(put)] [npc.her] broom away.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			if(isHit) {
				if(character.isPlayer()) {
					return UtilText.parse(target,
							UtilText.returnStringAtRandom(
								"You whack [npc.namePos] [npc.arm] with your broom.",
								"You swipe your broom at [npc.namePos] [npc.legs], and manage to hit [npc.herHim] in the shins.",
								"You swing your broom at [npc.name], grinning as the end makes contact with [npc.her] torso."));

				} else {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"[npc.Name] whacks your [pc.arm] with [npc.her] broom.",
								"[npc.Name] swipes [npc.her] broom at your [pc.legs], and manages to hit you in the shins.",
								"[npc.Name] swings [npc.her] broom at you, grinning as the end makes contact with your torso."));
				}

			} else {
				if(character.isPlayer()) {
					return UtilText.parse(target,
							UtilText.returnStringAtRandom(
								"You try to whack [npc.name] with your broom, but [npc.she] manages to grab the shaft and push you away.",
								"You attempt to swipe your broom at [npc.namePos] [npc.legs], but [npc.she] jumps back and manages to avoid the blow.",
								"You swing your broom at [npc.name], but [npc.she] manages to duck at the last moment and avoid the blow."));

				} else {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"[npc.Name] tries to whack you with [npc.her] broom, but you manage to grab the shaft and push [npc.herHim] away.",
								"[npc.Name] attempts to swipe [npc.her] broom at your [pc.legs], but you jump back and manage to avoid the blow.",
								"[npc.Name] swings [npc.her] broom at you, but you manage to duck at the last moment and avoid the blow."));
				}
			}
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Swipe the broom at [npc.name]!");
		}
	};

	public static AbstractWeaponType MAIN_FEATHER_DUSTER = new AbstractWeaponType(250,
			true,
			false,
			"a",
			false,
			"feather duster",
			"feather dusters",
			"Duster Tickle",
			"A short-handled feather duster, ideal for keeping a house clean, but not much use in combat...",
			"feather_duster",
			"feather_duster",
			Rarity.EPIC,
			0,
			null,
			Util.newArrayListOfValues(DamageType.PHYSICAL),
			2,
			0,
			DamageVariance.LOW,
			5,
			null,
			null,
			null,
			null,
			null,
			null,
			Util.newArrayListOfValues(ItemTag.SOLD_BY_VICKY)) {

		@Override

		public String getDescription() {
			if(Main.game.getPlayer().getLocationPlace().getPlaceType().equals(PlaceType.SHOPPING_ARCADE_ASHLEYS_SHOP)) {
				return "A short-handled feather duster, ideal for keeping a house clean, but not much use in combat..."
						+ " [Ashley.speech(A feather duster: the epitome of romance, at least for those who don't know anything about their lover, other than that they're the person who keeps the house clean.)]";
//						+ " Surely, that's all that's going on with their lives, right?)]";
			} else {
				return "A short-handled feather duster, ideal for keeping a house clean, but not much use in combat...";
			}
		}

		@Override
		public String equipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(ready)] [npc.her] feather duster.");
		}

		@Override
		public String unequipText(GameCharacter character) {
			return UtilText.parse(character, "[npc.Name] [npc.verb(put)] [npc.her] feather duster away.");
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			if(isHit) {
				if(character.isPlayer()) {
					return UtilText.parse(target,
							UtilText.returnStringAtRandom(
								"You tickle [npc.namePos] [npc.arm] with your feather duster.",
								"You swipe your feather duster at [npc.namePos] [npc.legs], and manage to tickle [npc.her] shins.",
								"You swing your feather duster at [npc.name], grinning as you tickle [npc.her] torso."));

				} else {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"[npc.Name] tickles your [pc.arm] with [npc.her] feather duster.",
								"[npc.Name] swipes [npc.her] feather duster at your [pc.legs], and manages to tickle your shins.",
								"[npc.Name] swings [npc.her] feather duster at you, grinning as [npc.she] tickles your torso."));
				}

			} else {
				if(character.isPlayer()) {
					return UtilText.parse(target,
							UtilText.returnStringAtRandom(
								"You try to tickle [npc.name] with your feather duster, but [npc.she] manages to push you away.",
								"You attempt to swipe your feather duster at [npc.namePos] [npc.legs], but [npc.she] jumps back and manages to avoid the tickling.",
								"You swing your feather duster at [npc.name], but [npc.she] manages to duck at the last moment and avoid the tickling."));

				} else {
					return UtilText.parse(character,
							UtilText.returnStringAtRandom(
								"[npc.Name] tries to tickle you with [npc.her] feather duster, but you manage to grab the shaft and push [npc.herHim] away.",
								"[npc.Name] attempts to swipe [npc.her] feather duster at your [pc.legs], but you jump back and manage to avoid the tickling.",
								"[npc.Name] swings [npc.her] feather duster at you, but you manage to duck at the last moment and avoid the tickling."));
				}
			}
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Tickle [npc.name] with your feather duster!");
		}
	};

	public static AbstractWeaponType OFFHAND_BOW_AND_ARROW = new AbstractWeaponType(2500,
			false,
			true,
			"an",
			false,
			"Arcane Short Bow",
			"Arcane Short Bows",
			"Fire Bow",
			"This short bow is infused with a potent arcane enchantment, which, once the string is drawn back, extracts an arcane essence out of the user's aura, before converting it into a bolt of energy to be fired at the target.",
			"bowandarrow",
			"bowandarrow",
			Rarity.EPIC,
			0,
			null,
			Util.newArrayListOfValues(
					DamageType.PHYSICAL,
					DamageType.FIRE,
					DamageType.ICE,
					DamageType.POISON),
			18,
			1,
			DamageVariance.LOW,
			5,
			Util.newArrayListOfValues(
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.DAMAGE_WEAPON, TFPotency.MAJOR_BOOST, 0),
					new ItemEffect(ItemEffectType.WEAPON, TFModifier.CLOTHING_ATTRIBUTE, TFModifier.CRITICAL_DAMAGE, TFPotency.MAJOR_BOOST, 0)),
			null,
			null,
			null,
			null,
			null,
			Util.newArrayListOfValues(ItemTag.SOLD_BY_VICKY)) {

		@Override
		public String equipText(GameCharacter character) {
			return "You ready your "+this.getName()+".";
		}

		@Override
		public String unequipText(GameCharacter character) {
			return "You put your "+this.getName()+" away.";
		}

		@Override
		public String getAttackDescription(GameCharacter character, GameCharacter target, boolean isHit) {
			StringBuilder sb = new StringBuilder();

			sb.append(UtilText.returnStringAtRandom(
							"Pulling back the arcane bow's string, an essence is drawn out of [npc.namePos] arcane aura, before [npc.she] [npc.verb(let)] loose and fires it at [npc2.name].",
							"[npc.Name] pulls the bow's string back, grinning as an arcane essence is drawn out of [npc.her] arcane aura, before letting loose and firing the newly-formed bolt straight at [npc2.name]."));

			if(isHit) {
				sb.append(UtilText.returnStringAtRandom(
								" The ethereal arrow hits its mark, and passes right through [npc2.namePos] chest, causing [npc2.herHim] to gasp and stagger back from the blow.",
								" The arcane arrow sails straight through the centre of [npc2.namePos] chest, causing [npc2.herHim] to let out a shocked gasp as [npc2.she] staggers back."));
			} else {
				sb.append(UtilText.returnStringAtRandom(
								" The ethereal arrow sails harmlessly off to one side of its target, causing [npc.name] to let out a frustrated sigh.",
								" The arcane arrow sails harmlessly over [npc2.namePos] shoulder, causing [npc2.herHim] to let out a mocking laugh."));
			}
			return UtilText.parse(character, target,sb.toString());
		}

		@Override
		public String getAttackDescription(GameCharacter user, GameCharacter target) {
			return UtilText.parse(target,
					"Shoot at [npc.name] with your "+this.getName()+"!");
		}
	};

	public static List<AbstractWeaponType> rareWeapons = new ArrayList<>();
	private static List<AbstractWeaponType> allWeapons = new ArrayList<>();
	public static List<AbstractWeaponType> moddedWeapons = new ArrayList<>();

	public static Map<AbstractWeaponType, String> weaponToIdMap = new HashMap<>();
	public static Map<String, AbstractWeaponType> idToWeaponMap = new HashMap<>();

	public static AbstractWeaponType getWeaponTypeFromId(String id) {
//		System.out.print("ID: "+id);
		if(id.equals("OFFHAND_BUCKLER")) {
			id = "innoxia_buckler_buckler";
		}
		if(id.equals("MAIN_WESTERN_KKP")) {
			id = "innoxia_western_kkp_western_kkp";
		}
		if(id.equals("MELEE_KNIGHTLY_SWORD")) {
			id = "innoxia_european_swords_arming_sword";
		}
		id = Util.getClosestStringMatch(id, idToWeaponMap.keySet());
//		System.out.println("  set to: "+id);
		return idToWeaponMap.get(id);
	}

	public static String getIdFromWeaponType(AbstractWeaponType weaponType) {
		return weaponToIdMap.get(weaponType);
	}

	static {
		// Load in modded weapons:
		moddedWeapons = new ArrayList<>();
		File dir = new File("res/mods");

		if (dir.exists() && dir.isDirectory()) {
			File[] modDirectoryListing = dir.listFiles();
			if (modDirectoryListing != null) {
				for (File modAuthorDirectory : modDirectoryListing) {
					File modAuthorClothingDirectory = new File(modAuthorDirectory.getAbsolutePath()+"/items/weapons");

					File[] clothingDirectoriesListing = modAuthorClothingDirectory.listFiles();
					if (clothingDirectoriesListing != null) {
						for (File clothingDirectory : clothingDirectoriesListing) {
							if (clothingDirectory.isDirectory()){
								File[] innerDirectoryListing = clothingDirectory.listFiles((path, filename) -> filename.endsWith(".xml"));
								if (innerDirectoryListing != null) {
									for (File innerChild : innerDirectoryListing) {
										try {
											String id = modAuthorDirectory.getName()+"_"+innerChild.getParentFile().getName()+"_"+innerChild.getName().split("\\.")[0];
											AbstractWeaponType ct = new AbstractWeaponType(innerChild, modAuthorDirectory.getName()) {};
											moddedWeapons.add(ct);
											weaponToIdMap.put(ct, id);
											idToWeaponMap.put(id, ct);
										} catch(Exception ex) {
											System.err.println("Loading modded weapon failed at 'WeaponType' Code 1. File path: "+innerChild.getAbsolutePath());
										}
									}
								}
							}
						}
					}
				}
			}
		}

		allWeapons.addAll(moddedWeapons);

		// Add in external res clothing:

		dir = new File("res/weapons");

		if (dir.exists() && dir.isDirectory()) {
			File[] authorDirectoriesListing = dir.listFiles();
			if (authorDirectoriesListing != null) {
				for (File authorDirectory : authorDirectoriesListing) {
					if (authorDirectory.isDirectory()){
						for (File clothingDirectory : authorDirectory.listFiles()) {
							if (clothingDirectory.isDirectory()){
								File[] innerDirectoryListing = clothingDirectory.listFiles((path, filename) -> filename.endsWith(".xml"));
								if (innerDirectoryListing != null) {
									for (File innerChild : innerDirectoryListing) {
										try {
											String id = authorDirectory.getName()+"_"+innerChild.getParentFile().getName()+"_"+innerChild.getName().split("\\.")[0];
											AbstractWeaponType ct = new AbstractWeaponType(innerChild, authorDirectory.getName()) {};
											allWeapons.add(ct);
											weaponToIdMap.put(ct, id);
											idToWeaponMap.put(id, ct);
										} catch(Exception ex) {
											System.err.println("Loading modded weapon failed at 'WeaponType' Code 2. File path: "+innerChild.getAbsolutePath());
										}
									}
								}
							}
						}
					}
				}
			}
		}

		Field[] fields = WeaponType.class.getFields();

		for(Field f : fields){

			if (AbstractWeaponType.class.isAssignableFrom(f.getType())) {

				AbstractWeaponType weapon;

				try {
					weapon = ((AbstractWeaponType) f.get(null));

					// I feel like this is stupid :thinking:
					weaponToIdMap.put(weapon, f.getName());
					idToWeaponMap.put(f.getName(), weapon);

					allWeapons.add(weapon);

					if (weapon.getRarity() == Rarity.RARE) {
						rareWeapons.add(weapon);
					}

				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public static List<AbstractWeaponType> getAllWeapons() {
		return allWeapons;
	}
}