v0.7.0: reworked the alchemist's toolkit!

This commit is contained in:
Evan Debenham 2018-09-02 20:43:43 -04:00
parent 9d9cde3982
commit 2d13ff8389
11 changed files with 209 additions and 223 deletions

View File

@ -66,6 +66,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.KindOfWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.glyphs.AntiMagic;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.glyphs.Viscosity;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.AlchemistsToolkit;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.CapeOfThorns;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.EtherealChains;
@ -674,6 +675,12 @@ public class Hero extends Char {
ready();
AlchemistsToolkit.kitEnergy kit = buff(AlchemistsToolkit.kitEnergy.class);
if (kit != null && kit.isCursed()){
GLog.w( Messages.get(AlchemistsToolkit.class, "cursed"));
return false;
}
Alchemy alch = (Alchemy) Dungeon.level.blobs.get(Alchemy.class);
//TODO logic for a well having dried up?
if (alch != null) {
@ -1214,6 +1221,9 @@ public class Hero extends Char {
HornOfPlenty.hornRecharge horn = buff(HornOfPlenty.hornRecharge.class);
if (horn != null) horn.gainCharge(percent);
AlchemistsToolkit.kitEnergy kit = buff(AlchemistsToolkit.kitEnergy.class);
if (kit != null) kit.gainCharge(percent);
Berserk berserk = buff(Berserk.class);
if (berserk != null) berserk.recover(percent);

View File

@ -577,8 +577,8 @@ public abstract class Mob extends Char {
int exp = Dungeon.hero.lvl <= maxLvl ? EXP : 0;
if (exp > 0) {
Dungeon.hero.sprite.showStatus(CharSprite.POSITIVE, Messages.get(this, "exp", exp));
Dungeon.hero.earnExp(exp);
}
Dungeon.hero.earnExp(exp);
}
}
}

View File

@ -224,7 +224,7 @@ public class Generator {
return item instanceof Bag ? Integer.MAX_VALUE : Integer.MAX_VALUE - 1;
}
private static final float[] INITIAL_ARTIFACT_PROBS = new float[]{ 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1};
private static final float[] INITIAL_ARTIFACT_PROBS = new float[]{ 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1};
static {
GOLD.classes = new Class<?>[]{
@ -434,7 +434,7 @@ public class Generator {
TalismanOfForesight.class,
TimekeepersHourglass.class,
UnstableSpellbook.class,
AlchemistsToolkit.class, //currently removed from drop tables, pending rework.
AlchemistsToolkit.class,
DriedRose.class,
LloydsBeacon.class,
EtherealChains.class

View File

@ -22,6 +22,7 @@
package com.shatteredpixel.shatteredpixeldungeon.items;
import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.AlchemistsToolkit;
import com.shatteredpixel.shatteredpixeldungeon.items.bombs.Bomb;
import com.shatteredpixel.shatteredpixeldungeon.items.food.Blandfruit;
import com.shatteredpixel.shatteredpixeldungeon.items.food.Feast;
@ -141,6 +142,7 @@ public abstract class Recipe {
//*******
private static Recipe[] oneIngredientRecipes = new Recipe[]{
new AlchemistsToolkit.upgradeKit(),
new Scroll.ScrollToStone(),
new StewedMeat.oneMeat()
};
@ -201,7 +203,7 @@ public abstract class Recipe {
public static boolean usableInRecipe(Item item){
return item.isIdentified()
&& (!(item instanceof EquipableItem) || item instanceof Dart)
&& (!(item instanceof EquipableItem) || item instanceof Dart || item instanceof AlchemistsToolkit)
&& !(item instanceof Wand);
}
}

View File

@ -21,64 +21,44 @@
package com.shatteredpixel.shatteredpixeldungeon.items.artifacts;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.items.Generator;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.potions.Potion;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.items.Recipe;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.AlchemyScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndBag;
import com.watabou.noosa.audio.Sample;
import com.watabou.noosa.Game;
import com.watabou.utils.Bundle;
import com.watabou.utils.Random;
import com.watabou.utils.GameMath;
import java.util.ArrayList;
import java.util.Collections;
public class AlchemistsToolkit extends Artifact {
{
image = ItemSpriteSheet.ARTIFACT_TOOLKIT;
defaultAction = AC_BREW;
levelCap = 10;
charge = 0;
partialCharge = 0;
chargeCap = 100;
}
public static final String AC_BREW = "BREW";
//arrays used in containing potion collections for mix logic.
public final ArrayList<String> combination = new ArrayList<String>();
public ArrayList<String> curGuess = new ArrayList<String>();
public ArrayList<String> bstGuess = new ArrayList<String>();
public int numWrongPlace = 0;
public int numRight = 0;
private int seedsToPotion = 0;
protected String inventoryTitle = "Select a potion";
protected WndBag.Mode mode = WndBag.Mode.POTION;
public AlchemistsToolkit() {
super();
Generator.Category cat = Generator.Category.POTION;
for (int i = 1; i <= 3; i++){
String potion;
do{
potion = convertName(cat.classes[Random.chances(cat.probs)].getSimpleName());
//forcing the player to use experience potions would be completely unfair.
} while (combination.contains(potion) || potion.equals("Experience"));
combination.add(potion);
}
}
private boolean alchemyReady = false;
@Override
public ArrayList<String> actions( Hero hero ) {
ArrayList<String> actions = super.actions( hero );
if (isEquipped( hero ) && level() < levelCap && !cursed)
if (isEquipped( hero ) && !cursed)
actions.add(AC_BREW);
return actions;
}
@ -89,199 +69,169 @@ public class AlchemistsToolkit extends Artifact {
super.execute(hero, action);
if (action.equals(AC_BREW)){
GameScene.selectItem(itemSelector, mode, inventoryTitle);
}
}
if (!isEquipped(hero)) GLog.i( Messages.get(this, "need_to_equip") );
else if (cursed) GLog.w( Messages.get(this, "cursed") );
else if (!alchemyReady) GLog.i( Messages.get(this, "not_ready") );
else if (hero.visibleEnemies() > hero.mindVisionEnemies.size()) GLog.i( Messages.get(this, "enemy_near") );
else {
public void guessBrew() {
if (curGuess.size() != 3)
return;
int numWrongPlace = 0;
int numRight = 0;
for (String potion : curGuess) {
if (combination.contains(potion)) {
if (curGuess.indexOf(potion) == combination.indexOf(potion)) {
numRight++;
} else {
numWrongPlace++;
}
}
}
int score = (numRight *3) + numWrongPlace;
if (score == 9)
score ++;
if (score == 0){
GLog.i("Your mixture is complete, but none of the potions you used seem to react well. " +
"The brew is useless, you throw it away.");
} else if (score > level()) {
level(score);
seedsToPotion = 0;
bstGuess = curGuess;
this.numRight = numRight;
this.numWrongPlace = numWrongPlace;
if (level() == 10){
bstGuess = new ArrayList<String>();
GLog.p("The mixture you've created seems perfect, you don't think there is any way to improve it!");
} else {
GLog.w("you finish mixing potions, " + brewDesc(numWrongPlace, numRight) +
". This is your best brew yet!");
AlchemyScene.setProvider(hero.buff(kitEnergy.class));
Game.switchScene(AlchemyScene.class);
}
} else {
GLog.w("you finish mixing potions, " + brewDesc(numWrongPlace, numRight) +
". This brew isn't as good as the current one, you throw it away.");
}
curGuess = new ArrayList<String>();
}
private String brewDesc(int numWrongPlace, int numRight){
String result = "";
if (numWrongPlace > 0){
result += numWrongPlace + " reacted well, but in the wrong order";
if (numRight > 0)
result += " and ";
}
if (numRight > 0){
result += numRight + " reacted perfectly";
}
return result;
}
@Override
protected ArtifactBuff passiveBuff() {
return new alchemy();
return new kitEnergy();
}
public void absorbEnergy( int energy ){
exp += energy;
while (exp >= 10 && level() < levelCap){
upgrade();
exp -= 10;
}
if (level() == levelCap){
partialCharge += exp;
energy -= exp;
exp = 0;
}
partialCharge += energy/3f;
while (partialCharge >= 1){
partialCharge -= 1;
charge++;
if (charge == chargeCap){
partialCharge = 0;
break;
}
}
updateQuickslot();
}
@Override
public String desc() {
String result = "This toolkit contains a number of regents and herbs used to improve the process of " +
"cooking potions.\n\n";
String result = Messages.get(this, "desc");
if (isEquipped(Dungeon.hero))
if (cursed)
result += "The cursed toolkit has bound itself to your side, and refuses to let you use alchemy.\n\n";
else
result += "The toolkit rests on your hip, the various tools inside make a light jingling sound as you move.\n\n";
if (level() == 0){
result += "The toolkit seems to be missing a key tool, a catalyst mixture. You'll have to make your own " +
"out of three common potions to get the most out of the toolkit.";
} else if (level() == 10) {
result += "The mixture you have created seems perfect, and the toolkit is working at maximum efficiency.";
} else if (!bstGuess.isEmpty()) {
result += "Your current best mixture is made from: " + bstGuess.get(0) + ", " + bstGuess.get(1) + ", "
+ bstGuess.get(2) + ", in that order.\n\n";
result += "Of the potions in that mix, " + brewDesc(numWrongPlace, numRight) + ".";
//would only trigger if an upgraded toolkit was gained through transmutation or bones.
} else {
result += "The toolkit seems to have a catalyst mixture already in it, but it isn't ideal. Unfortunately " +
"you have no idea what's in the mixture.";
if (isEquipped(Dungeon.hero)) {
if (cursed) result += "\n\n" + Messages.get(this, "desc_cursed");
else if (!alchemyReady) result += "\n\n" + Messages.get(this, "desc_warming");
else result += "\n\n" + Messages.get(this, "desc_hint");
}
return result;
}
private static final String COMBINATION = "combination";
private static final String CURGUESS = "curguess";
private static final String BSTGUESS = "bstguess";
@Override
public boolean doEquip(Hero hero) {
if (super.doEquip(hero)){
alchemyReady = false;
return true;
} else {
return false;
}
}
private static final String NUMWRONGPLACE = "numwrongplace";
private static final String NUMRIGHT = "numright";
private static final String SEEDSTOPOTION = "seedstopotion";
private static final String READY = "ready";
@Override
public void storeInBundle(Bundle bundle){
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(NUMWRONGPLACE, numWrongPlace);
bundle.put(NUMRIGHT, numRight);
bundle.put(SEEDSTOPOTION, seedsToPotion);
bundle.put(COMBINATION, combination.toArray(new String[combination.size()]));
bundle.put(CURGUESS, curGuess.toArray(new String[curGuess.size()]));
bundle.put(BSTGUESS, bstGuess.toArray(new String[bstGuess.size()]));
bundle.put(READY, alchemyReady);
}
@Override
public void restoreFromBundle( Bundle bundle ) {
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
numWrongPlace = bundle.getInt( NUMWRONGPLACE );
numRight = bundle.getInt( NUMRIGHT );
seedsToPotion = bundle.getInt( SEEDSTOPOTION );
combination.clear();
Collections.addAll( combination, bundle.getStringArray( COMBINATION ));
Collections.addAll( curGuess, bundle.getStringArray( CURGUESS ));
Collections.addAll( bstGuess, bundle.getStringArray( BSTGUESS ));
alchemyReady = bundle.getBoolean(READY);
}
public class kitEnergy extends ArtifactBuff implements AlchemyScene.AlchemyProvider {
public class alchemy extends ArtifactBuff {
public void gainCharge(float levelPortion) {
alchemyReady = true;
public boolean tryCook(int count){
if (cursed) return;
//this logic is handled inside the class with a variable so that it may be stored.
//to prevent manipulation where a player could keep throwing in 1-2 seeds until they get lucky.
if (seedsToPotion == 0){
if (Random.Int(20) < 10+level()){
if (Random.Int(20) < level()){
seedsToPotion = 1;
} else
seedsToPotion = 2;
} else
seedsToPotion = 3;
}
if (charge < chargeCap) {
if (count >= seedsToPotion){
seedsToPotion = 0;
return true;
} else
return false;
//generates 2 energy every hero level, +0.1 energy per toolkit level
//to a max of 12 energy per hero level
//This means that energy absorbed into the kit is recovered in 6.67 hero levels (as 33% of input energy is kept)
//exp towards toolkit levels is included here
float effectiveLevel = GameMath.gate(0, level() + exp/10f, 10);
partialCharge += (2 + (1f * effectiveLevel)) * levelPortion;
}
//charge is in increments of 1/10 max hunger value.
while (partialCharge >= 1) {
charge++;
partialCharge -= 1;
}
protected WndBag.Listener itemSelector = new WndBag.Listener() {
@Override
public void onSelect(Item item) {
if (item != null && item instanceof Potion && item.isIdentified()){
if (!curGuess.contains(convertName(item.getClass().getSimpleName()))) {
Hero hero = Dungeon.hero;
hero.sprite.operate( hero.pos );
hero.busy();
hero.spend( 2f );
Sample.INSTANCE.play(Assets.SND_DRINK);
item.detach(hero.belongings.backpack);
curGuess.add(convertName(item.getClass().getSimpleName()));
if (curGuess.size() == 3){
guessBrew();
} else {
GLog.i("You mix the " + item.name() + " into your current brew.");
if (charge == chargeCap){
GLog.p( Messages.get(HornOfPlenty.class, "full") );
partialCharge = 0;
}
} else {
GLog.w("Your current brew already contains that potion.");
updateQuickslot();
}
} else if (item != null) {
GLog.w("You need to select an identified potion.");
}
} else
partialCharge = 0;
}
};
@Override
public int getEnergy() {
return charge;
}
@Override
public void spendEnergy(int reduction) {
charge = Math.max(0, charge - reduction);
}
}
public static class upgradeKit extends Recipe {
@Override
public boolean testIngredients(ArrayList<Item> ingredients) {
return ingredients.get(0) instanceof AlchemistsToolkit
&& !AlchemyScene.providerIsToolkit();
}
private static int lastCost;
@Override
public int cost(ArrayList<Item> ingredients) {
return lastCost = Math.max(1, AlchemyScene.availableEnergy());
}
@Override
public Item brew(ArrayList<Item> ingredients) {
AlchemistsToolkit existing = (AlchemistsToolkit) ingredients.get(0);
existing.absorbEnergy(lastCost);
return existing;
}
@Override
public Item sampleOutput(ArrayList<Item> ingredients) {
AlchemistsToolkit sample = new AlchemistsToolkit();
sample.identify();
AlchemistsToolkit existing = (AlchemistsToolkit) ingredients.get(0);
sample.charge = existing.charge;
sample.partialCharge = existing.partialCharge;
sample.level(existing.level());
sample.absorbEnergy(AlchemyScene.availableEnergy());
return sample;
}
}
}

View File

@ -278,7 +278,7 @@ public class EtherealChains extends Artifact {
}
public void gainExp( float levelPortion ) {
if (cursed) return;
if (cursed || levelPortion == 0) return;
exp += Math.round(levelPortion*100);

View File

@ -210,6 +210,8 @@ public class HornOfPlenty extends Artifact {
public class hornRecharge extends ArtifactBuff{
public void gainCharge(float levelPortion) {
if (cursed) return;
if (charge < chargeCap) {
//generates 0.2x max hunger value every hero level, +0.1x max value per horn level

View File

@ -234,6 +234,8 @@ public class WandOfCorruption extends Wand {
if (enemy.EXP > 0 && curUser.lvl <= enemy.maxLvl) {
curUser.sprite.showStatus(CharSprite.POSITIVE, Messages.get(enemy, "exp", enemy.EXP));
curUser.earnExp(enemy.EXP);
} else {
curUser.earnExp(0);
}
enemy.rollToDropLoot();
} else {

View File

@ -32,6 +32,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.armor.PlateArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.RogueArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.ScaleArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.WarriorArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.AlchemistsToolkit;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.CapeOfThorns;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.ChaliceOfBlood;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.CloakOfShadows;
@ -217,7 +218,7 @@ public enum Catalog {
RINGS.seen.put( RingOfTenacity.class, false);
RINGS.seen.put( RingOfWealth.class, false);
//ARTIFACTS.seen.put( AlchemistsToolkit.class, false);
ARTIFACTS.seen.put( AlchemistsToolkit.class, false);
ARTIFACTS.seen.put( CapeOfThorns.class, false);
ARTIFACTS.seen.put( ChaliceOfBlood.class, false);
ARTIFACTS.seen.put( CloakOfShadows.class, false);

View File

@ -29,6 +29,7 @@ import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.Recipe;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.AlchemistsToolkit;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.darts.Dart;
import com.shatteredpixel.shatteredpixeldungeon.journal.Document;
import com.shatteredpixel.shatteredpixeldungeon.journal.Journal;
@ -139,8 +140,10 @@ public class AlchemyScene extends PixelScene {
protected void onClick() {
super.onClick();
if (item != null) {
if (!item.collect()) {
Dungeon.level.drop(item, Dungeon.hero.pos);
if (!(item instanceof AlchemistsToolkit)) {
if (!item.collect()) {
Dungeon.level.drop(item, Dungeon.hero.pos);
}
}
item = null;
slot.item(new WndBag.Placeholder(ItemSpriteSheet.SOMETHING));
@ -283,6 +286,8 @@ public class AlchemyScene extends PixelScene {
if (inputs[i].item == null) {
if (item instanceof Dart) {
inputs[i].item(item.detachAll(Dungeon.hero.belongings.backpack));
} else if (item instanceof AlchemistsToolkit) {
inputs[i].item(item);
} else {
inputs[i].item(item.detach(Dungeon.hero.belongings.backpack));
}
@ -312,11 +317,11 @@ public class AlchemyScene extends PixelScene {
Recipe recipe = Recipe.findRecipe(ingredients);
if (recipe != null){
int cost = recipe.cost(ingredients);
output.item(recipe.sampleOutput(ingredients));
output.visible = true;
int cost = recipe.cost(ingredients);
energyCost.text( Messages.get(AlchemyScene.class, "cost", cost) );
energyCost.y = btnCombine.top() - energyCost.baseLine();
energyCost.x = btnCombine.left() + (btnCombine.width() - energyCost.width())/2;
@ -349,7 +354,7 @@ public class AlchemyScene extends PixelScene {
if (recipe != null){
provider.spendEnergy(recipe.cost(ingredients));
energyLeft.text(Messages.get(AlchemyScene.class, "energy", availableEnergy()));
energyLeft.y = Camera.main.height - energyLeft.baseLine();
energyLeft.y = Camera.main.height - 5 - energyLeft.baseLine();
energyLeft.x = (Camera.main.width - energyLeft.width())/2;
result = recipe.brew(ingredients);
@ -361,8 +366,10 @@ public class AlchemyScene extends PixelScene {
Sample.INSTANCE.play( Assets.SND_PUFF );
output.item(result);
if (!result.collect()){
Dungeon.level.drop(result, Dungeon.hero.pos);
if (!(result instanceof AlchemistsToolkit)) {
if (!result.collect()){
Dungeon.level.drop(result, Dungeon.hero.pos);
}
}
try {
@ -374,7 +381,7 @@ public class AlchemyScene extends PixelScene {
synchronized (inputs) {
for (int i = 0; i < inputs.length; i++) {
if (inputs[i] != null && inputs[i].item != null) {
if (inputs[i].item.quantity() <= 0) {
if (inputs[i].item.quantity() <= 0 || inputs[i].item instanceof AlchemistsToolkit) {
inputs[i].slot.item(new WndBag.Placeholder(ItemSpriteSheet.SOMETHING));
inputs[i].item = null;
} else {
@ -394,8 +401,10 @@ public class AlchemyScene extends PixelScene {
synchronized ( inputs ) {
for (int i = 0; i < inputs.length; i++) {
if (inputs[i] != null && inputs[i].item != null) {
if (!inputs[i].item.collect()) {
Dungeon.level.drop(inputs[i].item, Dungeon.hero.pos);
if (!(inputs[i].item instanceof AlchemistsToolkit)) {
if (!inputs[i].item.collect()) {
Dungeon.level.drop(inputs[i].item, Dungeon.hero.pos);
}
}
}
inputs[i] = null;
@ -469,10 +478,14 @@ public class AlchemyScene extends PixelScene {
provider = p;
}
private static int availableEnergy(){
public static int availableEnergy(){
return provider == null ? 0 : provider.getEnergy();
}
public static boolean providerIsToolkit(){
return provider instanceof AlchemistsToolkit.kitEnergy;
}
public interface AlchemyProvider {
int getEnergy();

View File

@ -136,7 +136,13 @@ items.armor.warriorarmor.desc=While this armor looks heavy, it allows a warrior
###artifacts
items.artifacts.alchemiststoolkit.name=alchemists toolkit
items.artifacts.alchemiststoolkit.ac_brew=BREW
##this one isn't dropped, so for now no i18n
items.artifacts.alchemiststoolkit.not_ready=Your toolkit has not finished warming up.
items.artifacts.alchemiststoolkit.cursed=Your cursed toolkit prevents you from using alchemy!
items.artifacts.alchemiststoolkit.enemy_near=You cannot do that with enemies nearby.
items.artifacts.alchemiststoolkit.desc=This toolkit contains a number of regents and herbs along with a small mixing vial, allowing for alchemy on-the-go.
items.artifacts.alchemiststoolkit.desc_cursed=The cursed toolkit has bound itself to your side, and refuses to let you use alchemy.
items.artifacts.alchemiststoolkit.desc_warming=The toolkit is currently warming up, and will be ready to use after you gain experience.
items.artifacts.alchemiststoolkit.desc_hint=The equipped toolkit is slowly generated alchemical energy as you gain experience. Perhaps it could be enhanced in an alchemy pot?
items.artifacts.artifact.cannot_wear_two=You cannot wear two of the same artifact.
items.artifacts.artifact.equip_cursed=The artifact painfully binds itself to you.