/********************************************************************
* CreatureFactory
*
* Defines creature types and provides methods to generate creatures
* with given constraints (level, rarity, etc)
*******************************************************************/
package creid.mythos.util;
import java.io.BufferedReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import creid.mythos.*;
public class CreatureFactory
{
//--------------------------------------------------------
// Constants
//--------------------------------------------------------
//Game file columns
//KEY NAME SYMBOL COLOR LEVEL RARITY
//HEALTH BODY COORDINATION MIND SPEED PERCEPTION
//FACTION NATURAL_ARMOR AI_TYPE PROPERTIES
public static final int CREATURE_COLUMNS = 17;
public static final int COL_KEY = 0;
public static final int COL_NAME = 1;
public static final int COL_SYMBOL = 2;
public static final int COL_COLOR = 3;
public static final int COL_LEVEL = 4;
public static final int COL_RARITY = 5;
public static final int COL_HEALTH = 6;
public static final int COL_BODY = 7;
public static final int COL_COORD = 8;
public static final int COL_MIND = 9;
public static final int COL_SPEED = 10;
public static final int COL_PERCEPTION = 11;
public static final int COL_FACTION = 12;
public static final int COL_AI = 13;
public static final int COL_MOVE_MASK = 14;
public static final int COL_PROPS = 15;
public static final char ANY_SYMBOL = ' ';
//--------------------------------------------------------
// Attributes
//--------------------------------------------------------
//Creature Master List
public HashMap<String, Creature> bestiary;
//--------------------------------------------------------
// Constructor
//--------------------------------------------------------
//populates the geography and catalogue
public CreatureFactory()
{
//Load creature data from file
LinkedList<Creature> creatures = loadCreatures();
//Populate the geography and count the occurences of the
//different rarities
bestiary = new HashMap<String, Creature>();
for (Creature c : creatures)
{
//add to bestiary
bestiary.put(c.id, c);
}
}
//--------------------------------------------------------
// genCreature
// Return a randomly-chosen creature adhering to the given
// constraints.
//
// level: maximum allowed level. If ANY, accept all levels
// symbol: specific type. If ANY, accept all types.
// rarity: specific rarity. If ANY, a random rarity will be chosen
// requiredProps: if null, accept all. Otherwise only accept items
// with that have one or all of the properties
// matchAllProps: if false, allow items with one or more required props
// if true, match all of the listed properties
// matchValues: if true, properties must have same values as requiredProps,
// otherwise just require the properties to exist.
//--------------------------------------------------------
public Creature genCreature(int level, char symbol, int rarity)
{
char[] typeArr = { ANY_SYMBOL };
return genCreature(level, typeArr, rarity, new Property[0], new char[0],
new Property[0], false, false);
}
public static final int ANY = -99;
public Creature genCreature(int level, char[] types, int rarity, Property[] requiredProps,
char[] forbidTypes, Property[] forbidProps,
boolean matchAllProps, boolean matchValues)
{
if (rarity == ANY)
{
rarity = Mythos.callRNG(1, 31);
switch (rarity)
{
case 1:
rarity = Entity.UNHEARD_OF;
break;
case 2:
case 3:
rarity = Entity.VERY_RARE;
break;
case 4:
case 5:
case 6:
case 7:
rarity = Entity.RARE;
break;
case 8:
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
rarity = Entity.UNCOMMON;
break;
default:
rarity = Entity.COMMON;
}
}
LinkedList<Creature> list = new LinkedList<Creature>();
for (String id : bestiary.keySet())
{
Creature i = bestiary.get(id);
//Skip items that do not match our criteria
if (level != ANY && i.level > level)
continue;
if (types[0] != ANY_SYMBOL)
{
boolean match = false;
for (char t : types)
if (i.symbol == t)
match = true;
if (!match) continue;
}
//Check for forbidden types
if (forbidTypes != null && forbidTypes.length > 0)
{
for (char t: forbidTypes)
if (t == i.symbol)
continue;
}
if(rarity < i.rarity || i.rarity == Entity.NEVER_SPAWN)
continue;
//Check properties
if (requiredProps != null && requiredProps.length > 0)
{
boolean hasAny = false;
boolean missing = false;
boolean mismatch = false;
for (Property prop : requiredProps)
{
if (i.hasProperty(prop.name))
hasAny = true;
else
missing = true;
if (prop instanceof SProperty)
{
if (matchValues && !i.getSProperty(prop.name).value.equals(((SProperty)prop).value))
mismatch = true;
}
else if (prop instanceof NProperty)
{
if (matchValues && i.getNProperty(prop.name).value != ((NProperty)prop).value)
mismatch = true;
}
}
//Did we pass?
if (matchAllProps && missing)
continue;
if (matchValues && mismatch)
continue;
if (!hasAny)
continue;
}
//Check for forbidden props
if (forbidProps != null && forbidProps.length > 0)
{
for (Property p : forbidProps)
if (i.hasProperty(p.name))
continue;
}
//This item is acceptable to us
list.add(i);
}
//If there are any items, choose one to return
if (list.size() > 0)
{
int choice = Mythos.callRNG(0, list.size() - 1);
return new Creature(list.get(choice));
}
else
//No items exist that match the specifications
return null;
}
//--------------------------------------------------------
// randomCreature(int min, int max, bool ood)
//
// Spawn a random creature within the given level range
// and toggle allowing (rarely) something unusually deeper
// to be spawned.
//--------------------------------------------------------
public Creature randomCreature(int minLvl, int maxLvl, boolean allowOOD)
{
//Select how unusual of a creature we will create; each successive rarity
//is twice as unlikely to be included.
//Note that we do not pick a rarity and choose something with that rarity,
//rather we pick a rarity and disallow anything more rare than that, so
//with unheardOf rarity selected, we allow common, uncommon, rare, very rare
//and unheardOf creatures, so unheardOf is very rare indeed!
int rarity = Mythos.callRNG(1, 31);
switch (rarity)
{
case 1:
rarity = Entity.UNHEARD_OF;
break;
case 2:
case 3:
rarity = Entity.VERY_RARE;
break;
case 4:
case 5:
case 6:
case 7:
rarity = Entity.RARE;
break;
case 8:
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
rarity = Entity.UNCOMMON;
break;
default:
rarity = Entity.COMMON;
}
//Small chance for out-of-depth generation
while (allowOOD && Mythos.callRNG(0, Creature.ODDS_OUT_OF_DEPTH) == 0)
maxLvl += 5;
//Now use the rarity to build our pool of creatures
LinkedList<Creature> pool = new LinkedList<Creature>();
for (Iterator<Creature> it = bestiary.values().iterator(); it.hasNext(); )
{
Creature candidate = it.next();
if (candidate.level <= maxLvl && candidate.level >= minLvl &&
candidate.rarity <= rarity)
pool.add(candidate);
}
//Create a clone of the selected creature in the geography
Creature c = new Creature(pool.get(Mythos.callRNG(0, pool.size() - 1)));
//Any individualization will be done here
//Return the finished product
return c;
}
//--------------------------------------------------------------------------
// loadCreatures
//
// Extract creature data from creature.game file
//--------------------------------------------------------------------------
public LinkedList<Creature> loadCreatures()
{
//Extract the monsters from the saved file
LinkedList<Creature> monsterList = new LinkedList<Creature>();
try
{
BufferedReader fin = Mythos.readDataFile(Mythos.CREATURE_GAME_FILE);
//read in creatures from the file
while (fin.ready())
{
String line = fin.readLine().trim();
//skip blank lines
if (line.length() < 1)
continue;
//skip comments
if (line.charAt(0) == '#')
continue;
String[] input = line.split("\t");
//skip badly formatted lines
if (input.length != CREATURE_COLUMNS)
{
Mythos.logger.debugLog("Invalid input line:" + line);
}
//KEY NAME SYMBOL COLOR LEVEL RARITY
//HEALTH BODY COORDINATION MIND SPEED PERCEPTION
//FACTION NATURAL_ARMOR AI_TYPE PROPERTIES
String id = input[COL_KEY];
String name = input[COL_NAME];
char symbol = input[COL_SYMBOL].charAt(0);
int color = Entity.parseColor(input[COL_COLOR]);
int level = Integer.parseInt(input[COL_LEVEL]);
int rarity = Entity.parseRarity(input[COL_RARITY]);
int health = Integer.parseInt(input[COL_HEALTH]);
int body = Integer.parseInt(input[COL_BODY]);
int coordination = Integer.parseInt(input[COL_COORD]);
int mind = Integer.parseInt(input[COL_MIND]);
int speed = Integer.parseInt(input[COL_SPEED]);
int perception = Integer.parseInt(input[COL_PERCEPTION]);
int faction = Entity.parseFaction(input[COL_FACTION]);
//int ai = Integer.parseInt(input[COL_AI]);
int moveMask = Entity.parseMoveMask(input[COL_MOVE_MASK]);
String[] properties = null;
if (!input[COL_PROPS].trim().equals("-"))
properties = input[COL_PROPS].split(":");
Creature c = new Creature(id, name, symbol, color, health, body, coordination,
mind, speed, moveMask, 0, perception, faction, -99, level,
rarity, false, false);
if (properties != null)
for (String prop : properties)
{
String[] part = prop.split("=");
//Natural Armor
if(part[0].equals("naturalArmor"))
{
StatEffect narmor = new StatEffect("naturalArmor", StatEffect.STAT_ARMOR, Integer.parseInt(part[1]), Effect.FOR_DURATION, Effect.UNLIMITED, Effect.UNLIMITED);
narmor.apply(c);
}
else
{
//Is value a number?
try
{
c.setProperty(part[0], Integer.parseInt(part[1]));
}
catch(NumberFormatException ex)
{
c.setProperty(part[0], part[1]);
}
}
}
monsterList.add(c);
}
}
catch (Exception ex)
{
Mythos.logger.debugLog(ex.toString());
System.err.println(ex);
System.exit(1);
}
//return as array
return monsterList;
}
//--------------------------------------------------------------------------
// spawnedUnique:
// some monsters have limited generation. Decrement their unique value each
// time one is spawned, and remove them from the bestiary when unique=0
//--------------------------------------------------------------------------
public void spawnedUnique(Creature unique)
{
//Paranoia: don't do anything to a non-unique critter
if (!unique.hasProperty("unique"))
return;
NProperty uniqueness = unique.getNProperty("unique");
uniqueness.value--;
if (uniqueness.value < 1)
{
Mythos.logger.debugLog(unique.name + " will no longer be spawned");
//Remove them from our choice of creatures
bestiary.remove(unique.id);
}
}
}
|