bug-fix release
master
Alex Henry 2016-05-06 13:43:07 -03:00
parent 50de5a477d
commit 805114cba6
31 changed files with 324 additions and 106 deletions

View File

@ -1,3 +1,7 @@
Version 1.5 (2016-05-06)
* Bug-fix release
Version 1.4 (2016-04-30)
* Bug-fix release

View File

@ -26,4 +26,4 @@ Spell resistance: the more powerful your resistance, the less likely you'll be a
Spell immunity: cannot be the target of harmful spells.
Stunning shock: touch attack, deals 2d8 damage (reflex save DC 12 for half) .
Swimming: swims over water faster, ignores penalties for being on water.
Toughness: +3 hit points.
Toughness: +3 hit points.

View File

@ -13,6 +13,8 @@ import java.util.List;
import java.util.TreeMap;
import java.util.prefs.Preferences;
import javax.swing.JOptionPane;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
@ -70,7 +72,7 @@ public class Javelin {
public static final boolean DEBUG_SPAWNINCURSION = false;
public static final Float DEBUGSTARTINGCR = null;
public static final Item DEBUGSTARTINGITEM = null;
public static final String DEBUGALLOWMONSTER = null;
public static final String DEBUGFOE = null;
public static final String DEBUGPERIOD = null;
public static final String PERIOD_MORNING = "Morning";
@ -91,6 +93,7 @@ public class Javelin {
public static Combatant captured = null;
static {
checkjava();
try {
final DefaultHandler defaultHandler = new MonsterReader();
final XMLReader reader = XMLReaderFactory.createXMLReader();
@ -143,6 +146,25 @@ public class Javelin {
app.init();
}
private static void checkjava() {
String[] version = System.getProperty("java.version").split("\\.");
if (Integer.parseInt(version[0]) != 1) {
/* java 2.x? Don't even try to guess what to do... */
return;
}
int major = Integer.parseInt(version[1]);
if (major < 8) {
String error;
error = "Javelin needs Java 8 or newer to run properly.";
error += "\nYou currently have Java " + major + " installed.";
error += "\nPlease update Java in order play Javelin and to install the newest security updates.";
error += "\n\nThe following webpage has further information on updating Java on all major operating systems:";
error += "\nwww.techhelpkb.com/how-to-update-java-on-your-computer";
JOptionPane.showMessageDialog(null, error);
System.exit(1);
}
}
public static Combatant getCombatant(final Thing t) {
for (final Combatant c : BattleMap.combatants) {
if (c.visual == t) {

View File

@ -29,7 +29,6 @@ public class QualitiesFactor extends CrFactor {
@Override
public void listupgrades(UpgradeHandler handler) {
handler.water.add(new FastHealing());
handler.good.add(new javelin.controller.upgrade.DamageReduction());
handler.evil.add(new Vision("Low-light vision", 1));
handler.evil.add(new Vision("Darkvision", 2));
@ -37,6 +36,7 @@ public class QualitiesFactor extends CrFactor {
handler.magic.add(new javelin.controller.upgrade.SpellResistance());
handler.magic.add(new javelin.controller.upgrade.EnergyResistance());
handler.good.add(new javelin.controller.upgrade.DamageReduction());
handler.good.add(new javelin.controller.upgrade.SpellImmunity());
handler.good.add(new javelin.controller.upgrade.EnergyImmunity());
}

View File

@ -27,6 +27,7 @@ public class Properties {
static public Integer getInteger(String key, Integer fallback) {
String value = getString(key);
/* Don't inline. */
if (value == null) {
return fallback;
} else {

View File

@ -491,8 +491,6 @@ public class MonsterReader extends DefaultHandler {
SpecialtiesLog.log(" Breaths: " + monster.breaths);
}
SpecialtiesLog.log();
// Organization.TERRAINDATA.put(monster.toString().toLowerCase(),
// terrains);
}
SpecialtiesLog.clear();
}
@ -503,18 +501,13 @@ public class MonsterReader extends DefaultHandler {
}
public void registermonster() {
if (Javelin.DEBUGALLOWMONSTER != null
&& !monster.name.contains(Javelin.DEBUGALLOWMONSTER)) {
return;
}
final float cr;
try {
cr = ChallengeRatingCalculator.calculateCr(monster);
ChallengeRatingCalculator.calculateCr(monster);
Javelin.ALLMONSTERS.add(monster);
} catch (final Exception e) {
throw new RuntimeException("Challenge rating issue " + monster.name,
e);
}
Javelin.ALLMONSTERS.add(monster);
}
private static PrintWriter log = null;

View File

@ -31,41 +31,45 @@ public class Skills extends FieldReader {
String[] split = skill.split(" ");
int value =
Integer.parseInt(split[split.length - 1].replace("*", ""));
if (skill.contains("tumble")) {
m.skills.acrobatics = value - Monster.getbonus(m.dexterity);
} else if (skill.contains("concentration")) {
m.skills.concentration =
value - Monster.getbonus(m.constitution);
} else if (skill.contains("diplomacy")) {
m.skills.diplomacy = value - Monster.getbonus(m.charisma);
} else if (skill.contains("disable device")) {
m.skills.disabledevice =
value - Monster.getbonus(m.intelligence);
} else if (skill.contains("gather information")) {
m.skills.gatherinformation =
value - Monster.getbonus(m.charisma);
} else if (skill.contains("hide")) {
m.skills.hide = value - Monster.getbonus(m.dexterity);
} else if (skill.contains("knowledge")) {
int knowledge = value - Monster.getbonus(m.intelligence);
if (knowledge > m.skills.knowledge) {
m.skills.knowledge = knowledge;
}
} else if (skill.contains("listen")) {
m.skills.listen = value - Monster.getbonus(m.wisdom);
} else if (skill.contains("move silently")) {
m.skills.movesilently = value - Monster.getbonus(m.dexterity);
} else if (skill.contains("search")) {
m.skills.search = value - Monster.getbonus(m.intelligence);
} else if (skill.contains("spellcraft")) {
m.skills.spellcraft = value - Monster.getbonus(m.intelligence);
} else if (skill.contains("spot")) {
m.skills.spot = value - Monster.getbonus(m.wisdom);
} else if (skill.contains("survival")) {
m.skills.survival = value - Monster.getbonus(m.wisdom);
} else if (Javelin.DEBUG) {
UNKNOWN.add(skill.replace('-', '+').split("\\+")[0].trim());
}
apply(m, skill, value);
}
}
static void apply(Monster m, String skill, int value) {
final int dexbased = value - Monster.getbonus(m.dexterity);
final int conbased = value - Monster.getbonus(m.constitution);
final int chabased = value - Monster.getbonus(m.charisma);
final int intbased = value - Monster.getbonus(m.intelligence);
final int wisbased = value - Monster.getbonus(m.wisdom);
javelin.model.unit.Skills s = m.skills;
if (skill.contains("tumble")) {
s.acrobatics = Math.max(s.acrobatics, dexbased);
} else if (skill.contains("concentration")) {
s.concentration = Math.max(s.concentration, conbased);
} else if (skill.contains("diplomacy")) {
s.diplomacy = Math.max(s.diplomacy, chabased);
} else if (skill.contains("disable device")) {
s.disabledevice = Math.max(s.disabledevice, intbased);
} else if (skill.contains("gather information")) {
s.gatherinformation = Math.max(s.gatherinformation, chabased);
} else if (skill.contains("hide")) {
s.hide = Math.max(s.hide, dexbased);
} else if (skill.contains("knowledge")) {
s.knowledge = Math.max(s.knowledge, intbased);
} else if (skill.contains("listen")) {
s.listen = Math.max(s.listen, wisbased);
} else if (skill.contains("move silently")) {
s.movesilently = Math.max(s.movesilently, dexbased);
} else if (skill.contains("search")) {
s.search = Math.max(s.search, intbased);
} else if (skill.contains("spellcraft")) {
s.spellcraft = Math.max(s.spellcraft, intbased);
} else if (skill.contains("spot")) {
s.spot = Math.max(s.spot, wisbased);
} else if (skill.contains("survival")) {
s.survival = Math.max(s.survival, wisbased);
} else if (Javelin.DEBUG) {
UNKNOWN.add(skill.replace('-', '+').split("\\+")[0].trim());
}
}
}

View File

@ -7,6 +7,7 @@ import javelin.Javelin;
import javelin.controller.db.reader.factor.Organization;
import javelin.controller.exception.GaveUpException;
import javelin.model.unit.Combatant;
import javelin.model.unit.Monster;
import javelin.model.world.Squad;
import tyrant.mikera.engine.RPG;
@ -25,6 +26,9 @@ public class EncounterGenerator {
public static ArrayList<Combatant> generate(int el, int terrainp)
throws GaveUpException {
if (Javelin.DEBUGFOE != null) {
return debugmonster();
}
ArrayList<Combatant> encounter = null;
for (int i = 0; i < MAXTRIES; i++) {
encounter = select(el, terrainp);
@ -35,6 +39,23 @@ public class EncounterGenerator {
throw new GaveUpException();
}
private static ArrayList<Combatant> debugmonster() {
ArrayList<Combatant> opponents = new ArrayList<Combatant>();
for (Monster m : Javelin.ALLMONSTERS) {
if (m.name.equalsIgnoreCase(Javelin.DEBUGFOE)) {
Integer n = Javelin.DEBUGMINIMUMFOES;
if (n == null) {
n = 1;
}
for (int i = 0; i < n; i++) {
opponents.add(new Combatant(null, m.clone(), true));
}
break;
}
}
return opponents;
}
public static ArrayList<Combatant> select(int elp, int terrainp) {
ArrayList<Integer> popper = new ArrayList<Integer>();
popper.add(elp);

View File

@ -0,0 +1,18 @@
package javelin.controller.fight;
import javelin.model.world.place.WorldPlace;
public class TownSiege extends Siege {
public TownSiege(WorldPlace town) {
super(town);
}
@Override
public boolean canbribe() {
/*
* TODO doesn't make much sense but potentially could enable it. Needs
* to Town#capture after the battle is over though.
*/
return false;
}
}

View File

@ -15,7 +15,7 @@ import javelin.model.unit.Monster;
public abstract class Quality {
/**
* should always be lowercase
* Should always be lowercase.
*/
public final String name;
@ -27,25 +27,14 @@ public abstract class Quality {
static {
for (final Quality q : new Quality[] { new FastHealing("fast healing"),
new SpecialPerception("low-light vision",
Monster.VISION_LOWLIGHT),
new SpecialPerception("blindsight", Monster.VISION_LOWLIGHT),
new SpecialPerception("darkvision", Monster.VISION_DARK),
new SpecialPerception("keen vision", Monster.VISION_DARK),
new SpecialPerception("keen senses", Monster.VISION_DARK),
new Vision("low-light vision", Monster.VISION_LOWLIGHT),
new Vision("blindsight", Monster.VISION_LOWLIGHT),
new Vision("darkvision", Monster.VISION_DARK),
new Vision("keen vision", Monster.VISION_DARK),
new Vision("keen senses", Monster.VISION_DARK),
new DamageReduction(), new EnergyResistance(),
new SpellResistance(), new SpellImmunity(),
new EnergyImmunity(), new MindImmunity() }) {
qualities.add(q);
}
}
public static final ArrayList<Quality> attacks = new ArrayList<Quality>();
static {
for (final Quality q : new Quality[] {
}) {
new EnergyImmunity(), new MindImmunity(), new Tremorsense() }) {
qualities.add(q);
}
}
@ -54,5 +43,8 @@ public abstract class Quality {
abstract public boolean has(Monster m);
/**
* @return Challenge rating factor fot the given {@link Monster}.
*/
abstract public float rate(Monster m);
}

View File

@ -0,0 +1,39 @@
package javelin.controller.quality;
import javelin.controller.upgrade.skill.Listen;
import javelin.model.unit.Monster;
/**
* Since {@link Listen} allows you to "see" far away enemies makes sense that
* this would function like 20 ranks of listen.
*
* @author alex
*/
public class Tremorsense extends Quality {
public static final int LISTENRANKS = 20;
/** Constructor. */
public Tremorsense() {
super("Tremorsense");
}
@Override
public void add(String declaration, Monster m) {
if (m.skills.listen < LISTENRANKS) {
m.skills.listen = LISTENRANKS;
}
}
@Override
public boolean has(Monster m) {
return m.skills.listen >= LISTENRANKS;
}
@Override
public float rate(Monster m) {
/* rated as a skill */
return 0;
}
}

View File

@ -7,10 +7,10 @@ import javelin.model.unit.Monster;
*
* @author alex
*/
public class SpecialPerception extends Quality {
public class Vision extends Quality {
final int target;
public SpecialPerception(String name, int target) {
public Vision(String name, int target) {
super(name);
this.target = target;
}

View File

@ -8,7 +8,8 @@ import javelin.model.unit.Combatant;
* @author alex
*/
public class WingsOfFlying extends Artifact {
private int without;
int originalfly;
int originalwalk;
/** Constructor. */
public WingsOfFlying(int price) {
@ -17,13 +18,16 @@ public class WingsOfFlying extends Artifact {
@Override
protected void apply(Combatant c) {
without = c.source.fly;
originalfly = c.source.fly;
originalwalk = c.source.walk;
c.source.fly = 60;
c.source.walk = 0;
}
@Override
protected void negate(Combatant c) {
c.source.fly = without;
c.source.fly = originalfly;
c.source.walk = originalwalk;
}
}

View File

@ -73,9 +73,29 @@ public class Monster implements Cloneable, Serializable {
@Deprecated
private int will;
/**
* 5 units = 1 square. A unit is able to move this number of squares as a
* move-equivalent action (.5 action points).
*
* @see #fly
*/
public int walk = 0;
/** TODO also offer perfect flight */
/**
* Flying allows an unit to ignore water and obstacles. A flying unit should
* have {@link #walk} 0.
*
* TODO also offer perfect flight, which could at least charge through
* obstacles.
*
* @see #walk
*/
public int fly = 0;
/**
* A swimming creature is able to ignore water penalties and charge through
* flooded squares.
*
* @see #walk
*/
public int swim = 0;
/**

View File

@ -15,6 +15,10 @@ import javelin.controller.ai.BattleAi;
* See doc/skills.txt guide for more information.
*
* Synergy bonuses are not being used for now.
*
* TODO it would probably be better to have this as an Enum,Integer Map. This
* way would allow more programmatic freedom on stuff like
* {@link javelin.controller.db.reader.factor.Skills}.
*
* @author alex
*/

View File

@ -106,6 +106,7 @@ public class Merchant extends WorldActor {
}
remove();
} else if (here != null) {
ignoreturn = false;
turn(0, null);// jump over other Actors
}
}

View File

@ -14,6 +14,10 @@ import javelin.view.screen.town.option.RecruitOption;
* @author alex
*/
public class ResearchData implements Serializable {
public static final int NATIVEUPGRADE = 1;
public static final int MONSTERLAIR = 5;
/**
* Represents a few {@link Research} options that the player or
* {@link #automanage}r can use to advance a town. Grow should always be the

View File

@ -9,6 +9,8 @@ import javelin.controller.action.world.CastSpells;
import javelin.controller.action.world.UseItems;
import javelin.controller.challenge.ChallengeRatingCalculator;
import javelin.controller.db.StateManager;
import javelin.controller.fight.Siege;
import javelin.controller.fight.TownSiege;
import javelin.controller.tournament.Exhibition;
import javelin.controller.tournament.Match;
import javelin.controller.upgrade.Spell;
@ -387,4 +389,9 @@ public class Town extends WorldPlace {
return transport.equals(Transport.NONE) ? Transport.CARRIAGE
: Transport.AIRSHIP;
}
@Override
protected Siege fight() {
return new TownSiege(this);
}
}

View File

@ -28,7 +28,7 @@ public class AccommodationResearch extends Research {
}
@Override
protected boolean isrepeated(Town t) {
public boolean isrepeated(Town t) {
return false;
}
}

View File

@ -2,40 +2,52 @@ package javelin.model.world.place.town.research;
import java.util.List;
import javelin.model.world.place.town.ResearchData;
import javelin.model.world.place.town.Town;
import javelin.view.screen.IntroScreen;
import javelin.view.screen.Option;
import javelin.view.screen.town.ResearchScreen;
/**
* For 1 labor allows player to discard one option from
* {@link Town#research.researchhand}.
* For 1 labor allows player to discard one option from {@link Town#research
* .researchhand}.
*
* @author alex
*/
public class Discard extends SpecialResearchCard {
static final double COST = 1;
/** Constructor. */
public Discard() {
super("Discard choice", 1);
super("Discard choice", COST);
immediate = true;
}
@Override
public void apply(Town t, ResearchScreen s) {
if (!candiscard(t, s)) {
t.labor += COST;
return;
}
Integer mini = Integer.MAX_VALUE;
Integer maxi = Integer.MIN_VALUE;
List<Option> options = s.getoptions();
s.print(s.text + "Discard which research? Select from 2 to "
+ (options.size() - 1) + ".");
Option choice = null;
while (choice == null) {
try {
choice = options.get(Integer.parseInt(
Character.toString(IntroScreen.feedback())) - 1);
} catch (NumberFormatException e) {
continue;
} catch (IndexOutOfBoundsException e) {
continue;
for (int i =
ResearchData.NATIVEUPGRADE; i <= ResearchData.MONSTERLAIR; i++) {
if (t.research.hand[i] != null) {
if (i < mini) {
mini = i;
}
if (i > maxi) {
maxi = i;
}
}
}
s.print(s.text + "Discard which research? Select from "
+ (options.indexOf(t.research.hand[mini]) + 1) + " to "
+ (options.indexOf(t.research.hand[maxi]) + 1) + ".");
Option choice = choose(mini, maxi, options, t.research.hand);
for (int i = 0; i < t.research.hand.length; i++) {
if (t.research.hand[i] == choice) {
t.research.hand[i] = null;
@ -43,4 +55,36 @@ public class Discard extends SpecialResearchCard {
}
}
}
Option choose(Integer mini, Integer maxi, List<Option> options,
Research[] hand) {
Option choice = null;
while (choice == null) {
try {
int i = indexof(hand,
options.get(Integer
.parseInt(Character
.toString(IntroScreen.feedback()))
- 1));
if (!(mini <= i && i <= maxi)) {
continue;
}
choice = hand[i];
} catch (NumberFormatException e) {
continue;
} catch (IndexOutOfBoundsException e) {
continue;
}
}
return choice;
}
private int indexof(Research[] hand, Option option) {
for (int i = 0; i < hand.length; i++) {
if (hand[i] == option) {
return i;
}
}
throw new RuntimeException("no index for " + option + " #discard");
}
}

View File

@ -20,7 +20,7 @@ public class Grow extends Research {
}
@Override
protected boolean isrepeated(Town t) {
public boolean isrepeated(Town t) {
return false;
}
}

View File

@ -35,7 +35,7 @@ public class ItemResearch extends Research {
}
@Override
protected boolean isrepeated(Town t) {
public boolean isrepeated(Town t) {
return t.items.contains(i);
}

View File

@ -29,7 +29,7 @@ public class LairResearch extends Research {
}
@Override
protected boolean isrepeated(Town t) {
public boolean isrepeated(Town t) {
for (RecruitOption r : t.lairs) {
if (r.m.equals(m)) {
return true;

View File

@ -62,7 +62,7 @@ public class Recruit extends Research {
}
@Override
protected boolean isrepeated(Town t) {
public boolean isrepeated(Town t) {
return false;
}
}

View File

@ -4,20 +4,26 @@ import javelin.model.world.place.town.Town;
import javelin.view.screen.town.ResearchScreen;
/**
* For 2 labor allows player to redraw all options from
* {@link Town#research.researchhand}.
* For 2 labor allows player to redraw all options from {@link Town#research
* .researchhand}.
*
* @author alex
*/
public class Redraw extends SpecialResearchCard {
private static final int COST = 2;
public Redraw() {
super("Redraw all choices", 2);
super("Redraw all choices", COST);
immediate = true;
}
@Override
public void apply(Town t, ResearchScreen s) {
if (!candiscard(t, s)) {
t.labor += COST;
return;
}
for (int i = 0; i < t.research.hand.length; i++) {
if (t.research.hand[i] != this) {
t.research.hand[i] = null;

View File

@ -221,7 +221,7 @@ public abstract class Research extends Option {
return name.equals(r.name);
}
protected abstract boolean isrepeated(Town t);
public abstract boolean isrepeated(Town t);
public void finish(Town town, ResearchScreen s) {
town.labor -= price;

View File

@ -1,6 +1,9 @@
package javelin.model.world.place.town.research;
import javelin.model.world.place.town.ResearchData;
import javelin.model.world.place.town.Town;
import javelin.view.screen.IntroScreen;
import javelin.view.screen.town.ResearchScreen;
/**
* These are supposed to be a stack of research cards each with a unique effect.
@ -12,13 +15,35 @@ import javelin.model.world.place.town.Town;
*/
public abstract class SpecialResearchCard extends Research {
/**
* @param t
* Town to be validated.
* @param s
* Used to print an error message if <code>false</code> is
* returned.
* @return <code>true</code> if there is at least 1 card in the
* {@link ResearchData#hand} that can be discarded.
*/
protected static boolean candiscard(Town t, ResearchScreen s) {
for (int i =
ResearchData.NATIVEUPGRADE; i <= ResearchData.MONSTERLAIR; i++) {
if (t.research.hand[i] != null) {
return true;
}
}
s.print(s.text
+ "\nThere are no more options for you to discard.\nPress any key to continue...");
IntroScreen.feedback();
return false;
}
public SpecialResearchCard(String name, double price) {
super(name, price);
aiable = false;
}
@Override
protected boolean isrepeated(Town t) {
public boolean isrepeated(Town t) {
return false;
}
}

View File

@ -28,7 +28,7 @@ public class TransportResearch extends Research {
}
@Override
protected boolean isrepeated(Town t) {
public boolean isrepeated(Town t) {
return false;
}

View File

@ -25,7 +25,7 @@ public class UpgradeResearch extends Research {
}
@Override
protected boolean isrepeated(Town t) {
public boolean isrepeated(Town t) {
for (Upgrade o : t.upgrades) {
if (o.equals(u)) {
return true;

View File

@ -128,13 +128,13 @@ public class SquadScreen extends InfoScreen {
public static ArrayList<Monster> getcandidates() {
ArrayList<Monster> candidates = new ArrayList<Monster>();
if (Javelin.DEBUGSTARTINGCR == null) {
for (float cr : SELECTABLE) {
candidates.addAll(Javelin.MONSTERSBYCR.get(cr));
}
} else {
if (Javelin.DEBUGSTARTINGCR != null) {
candidates
.addAll(Javelin.MONSTERSBYCR.get(Javelin.DEBUGSTARTINGCR));
return candidates;
}
for (float cr : SELECTABLE) {
candidates.addAll(Javelin.MONSTERSBYCR.get(cr));
}
return candidates;
}

View File

@ -9,7 +9,7 @@ import javelin.view.screen.Option;
import tyrant.mikera.tyrant.Game;
/**
* Manually manages {@link Town#research.researching} and {@link Town#research.researchhand}.
* Manually manages {@link Town#research}.
*
* @see Town#automanage
* @author alex
@ -21,6 +21,18 @@ public class ResearchScreen extends PurchaseScreen {
public ResearchScreen(String name, Town t) {
super("Reseach new options:", t);
this.t = t;
/*
* TODO initial towns are drawing upgrades before stashing other options
* so some isrepeated options are being added nonetheless. This
* mitigates by checking again on Screen opening.
*/
for (int i = 0; i < t.research.hand.length; i++) {
Research o = t.research.hand[i];
if (o != null && o.isrepeated(t)) {
t.research.hand[i] = null;
}
}
Research.draw(t);
}
@Override
@ -51,9 +63,6 @@ public class ResearchScreen extends PurchaseScreen {
r.finish(town, null);
} else if (!town.research.queue.contains(r)) {
town.research.queue.add(r);
// print(text + "\nAdded to queue, press any key to
// continue...");
// Game.getInput();
}
return true;
}