v3.3.0: initial impl of skeleton key, missing lots of polish
This commit is contained in:
@@ -573,6 +573,7 @@ actors.hero.abilities.cleric.trinity.etherealchains_use=Trinity will apply this
|
||||
actors.hero.abilities.cleric.trinity.hornofplenty_use=Trinity will apply this artifact's snacking effect, at the cost of _%2$s charge._
|
||||
actors.hero.abilities.cleric.trinity.masterthievesarmband_use=Trinity will apply this artifact's enemy robbing effect at _+%1$d_, at the cost of _%2$s charge._
|
||||
actors.hero.abilities.cleric.trinity.sandalsofnature_use=Trinity will apply this artifact's root effect with a random harmful seed, at the cost of _%2$s charge._
|
||||
actors.hero.abilities.cleric.trinity.skeletonkey=Trinity will apply this artifact's insert effect, at the cost of _%2$s charge._
|
||||
actors.hero.abilities.cleric.trinity.talismanofforesight_use=Trinity will apply this artifact's scry effect at _+%1$d_, at the cost of _%2$s charge._
|
||||
actors.hero.abilities.cleric.trinity.timekeepershourglass_use=Trinity will apply this artifact's time freeze effect with a _%1$d_ turn duration, at the cost of _%2$s charge._
|
||||
actors.hero.abilities.cleric.trinity.unstablespellbook_use=Trinity will apply this artifact's random scroll effect with a _%1$d/10_ chance to offer the exotic version at no additional cost, at the cost of _%2$s charge._
|
||||
@@ -795,6 +796,7 @@ actors.hero.hero.noticed_smth=You noticed something.
|
||||
actors.hero.hero.wait=...
|
||||
actors.hero.hero.search=search
|
||||
actors.hero.hero.search_distracted=It's hard to concentrate, searching is exhausting.
|
||||
actors.hero.hero.key_distracted=You fiddle with the lock but can't quite open it this time.
|
||||
actors.hero.hero.pain_resist=The pain helps you resist the urge to sleep.
|
||||
actors.hero.hero.revive=The ankh explodes with life-giving energy!
|
||||
|
||||
|
||||
@@ -424,6 +424,21 @@ items.artifacts.sandalsofnature.desc_cursed=The cursed sandals are blocking any
|
||||
items.artifacts.sandalsofnature.desc_ability=The footwear ripples with the color of the last seed you fed them. They can briefly root into the ground and produce that plant's effect anywhere in a 3 tile radius.\nSeed: _%s_\nCharge needed: _%d%%_
|
||||
items.artifacts.sandalsofnature.desc_seeds=You have fed the footwear %d seeds.
|
||||
|
||||
items.artifacts.skeletonkey.name=skeleton key
|
||||
items.artifacts.skeletonkey.ac_insert=INSERT
|
||||
items.artifacts.skeletonkey.no_charge=Your key does not have enough charge.
|
||||
items.artifacts.skeletonkey.cursed=You can't use a cursed skeleton key.
|
||||
items.artifacts.skeletonkey.prompt=Choose a target
|
||||
items.artifacts.skeletonkey.invalid_target=There's nothing to lock or unlock there.
|
||||
items.artifacts.skeletonkey.gold_charges=Opening a gold lock requires 2 charges.
|
||||
items.artifacts.skeletonkey.lock_charges=Locking a door requires 2 charges.
|
||||
items.artifacts.skeletonkey.wall_charges=Creating a temporary wall requires 2 charges.
|
||||
items.artifacts.skeletonkey.crystal_charges=Opening a crystal lock requires 4 charges.
|
||||
items.artifacts.skeletonkey.wont_open=The key refuses to fit into this lock for some reason.
|
||||
items.artifacts.skeletonkey.desc=An important-looking key with a head shaped like a skull. Its teeth seem to be different every time you look at them.
|
||||
items.artifacts.skeletonkey.desc_worn=This magical key seems to gain power as you explore the dungeon and open more locks.\n\nThe key can be inserted into any of the dungeon's locks to either open or close them. This includes most special locks and doors that were never locked previously. The key can even be used on empty space in a cardinal or diagonal direction to 'lock' the air in front of you, creating a temporary solid wall. Locking will push away enemies if there is space to do so.
|
||||
items.artifacts.skeletonkey.desc_cursed=The cursed key seems to be actively avoiding fitting into any lock, and is even making it difficult (but not impossible) for you to use your other keys.
|
||||
|
||||
items.artifacts.talismanofforesight.name=talisman of foresight
|
||||
items.artifacts.talismanofforesight.ac_scry=SCRY
|
||||
items.artifacts.talismanofforesight.low_charge=The talisman requires at least 5% charge to scry.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
@@ -105,6 +105,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.EtherealChains;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HornOfPlenty;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.MasterThievesArmband;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.SkeletonKey;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TimekeepersHourglass;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.bags.MagicalHolster;
|
||||
@@ -2331,22 +2332,41 @@ public class Hero extends Char {
|
||||
|
||||
int doorCell = ((HeroAction.Unlock)curAction).dst;
|
||||
int door = Dungeon.level.map[doorCell];
|
||||
|
||||
if (Dungeon.level.distance(pos, doorCell) <= 1) {
|
||||
|
||||
SkeletonKey.keyRecharge skele = buff(SkeletonKey.keyRecharge.class);
|
||||
|
||||
if (skele != null && skele.isCursed() && Random.Int(6) != 0){
|
||||
GLog.n(Messages.get(this, "key_distracted"));
|
||||
spendAndNext(2*Key.TIME_TO_UNLOCK);
|
||||
Buff.affect(this, Hunger.class).affectHunger(-4);
|
||||
} else if (Dungeon.level.distance(pos, doorCell) <= 1) {
|
||||
boolean hasKey = true;
|
||||
if (door == Terrain.LOCKED_DOOR) {
|
||||
hasKey = Notes.remove(new IronKey(Dungeon.depth));
|
||||
if (hasKey) Level.set(doorCell, Terrain.DOOR);
|
||||
if (hasKey) {
|
||||
Level.set(doorCell, Terrain.DOOR);
|
||||
if (skele != null && !skele.isCursed()){
|
||||
skele.keyUsed(new IronKey(Dungeon.depth));
|
||||
}
|
||||
}
|
||||
} else if (door == Terrain.CRYSTAL_DOOR) {
|
||||
hasKey = Notes.remove(new CrystalKey(Dungeon.depth));
|
||||
if (hasKey) {
|
||||
Level.set(doorCell, Terrain.EMPTY);
|
||||
Sample.INSTANCE.play(Assets.Sounds.TELEPORT);
|
||||
CellEmitter.get( doorCell ).start( Speck.factory( Speck.DISCOVER ), 0.025f, 20 );
|
||||
if (skele != null && !skele.isCursed()){
|
||||
skele.keyUsed(new CrystalKey(Dungeon.depth));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hasKey = Notes.remove(new WornKey(Dungeon.depth));
|
||||
if (hasKey) Level.set(doorCell, Terrain.UNLOCKED_EXIT);
|
||||
if (hasKey) {
|
||||
Level.set(doorCell, Terrain.UNLOCKED_EXIT);
|
||||
if (skele != null && !skele.isCursed()){
|
||||
skele.keyUsed(new WornKey(Dungeon.depth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasKey) {
|
||||
@@ -2359,17 +2379,30 @@ public class Hero extends Char {
|
||||
} else if (curAction instanceof HeroAction.OpenChest) {
|
||||
|
||||
Heap heap = Dungeon.level.heaps.get( ((HeroAction.OpenChest)curAction).dst );
|
||||
|
||||
if (Dungeon.level.distance(pos, heap.pos) <= 1){
|
||||
SkeletonKey.keyRecharge skele = buff(SkeletonKey.keyRecharge.class);
|
||||
|
||||
if (skele != null && skele.isCursed()
|
||||
&& (heap.type == Type.LOCKED_CHEST || heap.type == Type.CRYSTAL_CHEST)
|
||||
&& Random.Int(6) != 0){
|
||||
GLog.n(Messages.get(this, "key_distracted"));
|
||||
spend(2*Key.TIME_TO_UNLOCK);
|
||||
Buff.affect(this, Hunger.class).affectHunger(-4);
|
||||
} else if (Dungeon.level.distance(pos, heap.pos) <= 1){
|
||||
boolean hasKey = true;
|
||||
if (heap.type == Type.SKELETON || heap.type == Type.REMAINS) {
|
||||
Sample.INSTANCE.play( Assets.Sounds.BONES );
|
||||
} else if (heap.type == Type.LOCKED_CHEST){
|
||||
hasKey = Notes.remove(new GoldenKey(Dungeon.depth));
|
||||
if (hasKey && skele != null && !skele.isCursed()){
|
||||
skele.keyUsed(new GoldenKey(Dungeon.depth));
|
||||
}
|
||||
} else if (heap.type == Type.CRYSTAL_CHEST){
|
||||
hasKey = Notes.remove(new CrystalKey(Dungeon.depth));
|
||||
if (hasKey && skele != null && !skele.isCursed()){
|
||||
skele.keyUsed(new CrystalKey(Dungeon.depth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (hasKey) {
|
||||
GameScene.updateKeyDisplay();
|
||||
heap.open(this);
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.ChaliceOfBlood;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.EtherealChains;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.SkeletonKey;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TimekeepersHourglass;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.UnstableSpellbook;
|
||||
@@ -540,7 +541,7 @@ public class Trinity extends ArmorAbility {
|
||||
return 2*chargeUse;
|
||||
}
|
||||
if (Artifact.class.isAssignableFrom(cls)){
|
||||
if (cls.equals(DriedRose.class) || cls.equals(UnstableSpellbook.class)){
|
||||
if (cls.equals(DriedRose.class) || cls.equals(UnstableSpellbook.class) || cls.equals(SkeletonKey.class)){
|
||||
return 2*chargeUse; //50 charge
|
||||
}
|
||||
if (cls.equals(EtherealChains.class) || cls.equals(TalismanOfForesight.class) || cls.equals(TimekeepersHourglass.class)){
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HornOfPlenty;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.MasterThievesArmband;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.SandalsOfNature;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.SkeletonKey;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TimekeepersHourglass;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.UnstableSpellbook;
|
||||
@@ -248,6 +249,9 @@ public class SpiritForm extends ClericSpell {
|
||||
|
||||
} else if (effect instanceof UnstableSpellbook){
|
||||
((UnstableSpellbook) effect).doReadEffect(Dungeon.hero);
|
||||
|
||||
} else if (effect instanceof SkeletonKey){
|
||||
GameScene.selectCell(((SkeletonKey) effect).targeter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HornOfPlenty;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.MasterThievesArmband;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.SandalsOfNature;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.SkeletonKey;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TimekeepersHourglass;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.UnstableSpellbook;
|
||||
@@ -566,11 +567,12 @@ public class Generator {
|
||||
HornOfPlenty.class,
|
||||
MasterThievesArmband.class,
|
||||
SandalsOfNature.class,
|
||||
SkeletonKey.class,
|
||||
TalismanOfForesight.class,
|
||||
TimekeepersHourglass.class,
|
||||
UnstableSpellbook.class
|
||||
};
|
||||
ARTIFACT.defaultProbs = new float[]{ 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1 };
|
||||
ARTIFACT.defaultProbs = new float[]{ 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 };
|
||||
ARTIFACT.probs = ARTIFACT.defaultProbs.clone();
|
||||
|
||||
//Trinkets are unique like artifacts, but unlike them you can only have one at once
|
||||
@@ -950,14 +952,19 @@ public class Generator {
|
||||
cat.dropped = bundle.getInt(cat.name().toLowerCase() + CATEGORY_DROPPED);
|
||||
}
|
||||
|
||||
//pre-v3.0.0 conversion for artifacts specifically
|
||||
//pre-v3.0.0 and pre-v3.3.0 conversion for artifacts (addition of tome and key)
|
||||
if (cat == Category.ARTIFACT && probs.length != cat.defaultProbs.length){
|
||||
int tomeIDX = 5;
|
||||
int keyIDX = 9;
|
||||
int j = 0;
|
||||
for (int i = 0; i < probs.length; i++){
|
||||
if (i == tomeIDX){
|
||||
//we do a specific check here for holy tome pre-v3.0.0
|
||||
if (j == tomeIDX && probs.length == cat.defaultProbs.length-2){
|
||||
cat.probs[j] = 0;
|
||||
j++;
|
||||
} else if (j == keyIDX){
|
||||
cat.probs[j] = 1;
|
||||
j++;
|
||||
}
|
||||
cat.probs[j] = probs[i];
|
||||
j++;
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* Pixel Dungeon
|
||||
* Copyright (C) 2012-2015 Oleg Dolya
|
||||
*
|
||||
* Shattered Pixel Dungeon
|
||||
* Copyright (C) 2014-2025 Evan Debenham
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package com.shatteredpixel.shatteredpixeldungeon.items.artifacts;
|
||||
|
||||
import com.shatteredpixel.shatteredpixeldungeon.Assets;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicImmune;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Regeneration;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.effects.BlobEmitter;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.Heap;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.keys.IronKey;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.keys.Key;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.keys.WornKey;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfEnergy;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfBlastWave;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.scenes.CellSelector;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
||||
import com.watabou.noosa.audio.Sample;
|
||||
import com.watabou.utils.Callback;
|
||||
import com.watabou.utils.PathFinder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
//TODO needs a way to handle the excess keys it can general
|
||||
public class SkeletonKey extends Artifact {
|
||||
|
||||
{
|
||||
image = ItemSpriteSheet.ARTIFACT_KEY;
|
||||
|
||||
levelCap = 5;
|
||||
|
||||
charge = 3+level();
|
||||
partialCharge = 0;
|
||||
chargeCap = 3+level();
|
||||
|
||||
defaultAction = AC_INSERT;
|
||||
}
|
||||
|
||||
public static final String AC_INSERT = "INSERT";
|
||||
|
||||
@Override
|
||||
public ArrayList<String> actions(Hero hero) {
|
||||
ArrayList<String> actions = super.actions(hero);
|
||||
if (isEquipped(hero)
|
||||
&& charge > 0
|
||||
&& hero.buff(MagicImmune.class) == null
|
||||
&& !cursed) {
|
||||
actions.add(AC_INSERT);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Hero hero, String action) {
|
||||
super.execute(hero, action);
|
||||
|
||||
if (hero.buff(MagicImmune.class) != null) return;
|
||||
|
||||
if (action.equals(AC_INSERT)){
|
||||
|
||||
curUser = hero;
|
||||
|
||||
if (!isEquipped( hero )) {
|
||||
GLog.i( Messages.get(Artifact.class, "need_to_equip") );
|
||||
|
||||
} else if (charge < 1) {
|
||||
GLog.i( Messages.get(this, "no_charge") );
|
||||
|
||||
} else if (cursed) {
|
||||
GLog.w( Messages.get(this, "cursed") );
|
||||
|
||||
} else {
|
||||
GameScene.selectCell(targeter);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public CellSelector.Listener targeter = new CellSelector.Listener(){
|
||||
|
||||
@Override
|
||||
public void onSelect(Integer target) {
|
||||
|
||||
if (target != null && (Dungeon.level.visited[target] || Dungeon.level.mapped[target])){
|
||||
|
||||
if (target == curUser.pos){
|
||||
GLog.w(Messages.get(SkeletonKey.class, "invalid_target"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Dungeon.level.adjacent(target, curUser.pos)) {
|
||||
if (Dungeon.level.map[target] == Terrain.LOCKED_EXIT){
|
||||
GLog.w(Messages.get(SkeletonKey.class, "wont_open"));
|
||||
return;
|
||||
}
|
||||
if (Dungeon.level.map[target] == Terrain.LOCKED_DOOR){
|
||||
if (Dungeon.level.locked){
|
||||
GLog.w(Messages.get(SkeletonKey.class, "wont_open"));
|
||||
return;
|
||||
}
|
||||
Sample.INSTANCE.play(Assets.Sounds.UNLOCK);
|
||||
curUser.sprite.operate(target, new Callback() {
|
||||
@Override
|
||||
public void call() {
|
||||
Level.set(target, Terrain.DOOR);
|
||||
GameScene.updateMap(target);
|
||||
charge -= 1;
|
||||
curUser.spendAndNext(Actor.TICK);
|
||||
curUser.sprite.idle();
|
||||
}
|
||||
});
|
||||
curUser.busy();
|
||||
return;
|
||||
|
||||
} else if (Dungeon.level.map[target] == Terrain.CRYSTAL_DOOR) {
|
||||
|
||||
if (charge < 4) {
|
||||
GLog.w(Messages.get(SkeletonKey.class, "crystal_charges"));
|
||||
return;
|
||||
}
|
||||
Sample.INSTANCE.play(Assets.Sounds.UNLOCK);
|
||||
curUser.sprite.operate(target, new Callback() {
|
||||
@Override
|
||||
public void call() {
|
||||
Level.set(target, Terrain.EMPTY);
|
||||
GameScene.updateMap(target);
|
||||
charge -= 4;
|
||||
Sample.INSTANCE.play(Assets.Sounds.TELEPORT);
|
||||
CellEmitter.get( target ).start( Speck.factory( Speck.DISCOVER ), 0.025f, 20 );
|
||||
curUser.spendAndNext(Actor.TICK);
|
||||
curUser.sprite.idle();
|
||||
}
|
||||
});
|
||||
curUser.busy();
|
||||
return;
|
||||
} else if (Dungeon.level.map[target] == Terrain.DOOR || Dungeon.level.map[target] == Terrain.OPEN_DOOR){
|
||||
|
||||
if (charge < 2) {
|
||||
GLog.w(Messages.get(SkeletonKey.class, "lock_charges"));
|
||||
return;
|
||||
}
|
||||
|
||||
//attempt to knock back char
|
||||
if (Actor.findChar(target) != null){
|
||||
int pushDIR = target - curUser.pos;
|
||||
Ballistica push = new Ballistica(target, target + pushDIR, Ballistica.PROJECTILE);
|
||||
WandOfBlastWave.throwChar(Actor.findChar(target), push, 1, false, false, this);
|
||||
//TODO what about pushing to the side?
|
||||
//TODO fail if there's no push DIR?
|
||||
}
|
||||
|
||||
Sample.INSTANCE.play(Assets.Sounds.UNLOCK);
|
||||
curUser.sprite.operate(target, new Callback() {
|
||||
@Override
|
||||
public void call() {
|
||||
Level.set(target, Terrain.LOCKED_DOOR);
|
||||
GameScene.updateMap(target);
|
||||
charge -= 2;
|
||||
curUser.spendAndNext(Actor.TICK);
|
||||
curUser.sprite.idle();
|
||||
}
|
||||
});
|
||||
curUser.busy();
|
||||
return;
|
||||
|
||||
} else if (Dungeon.level.heaps.get(target) != null && Dungeon.level.heaps.get(target).type == Heap.Type.LOCKED_CHEST){
|
||||
if (charge < 2) {
|
||||
GLog.w(Messages.get(SkeletonKey.class, "gold_charges"));
|
||||
return;
|
||||
}
|
||||
Sample.INSTANCE.play(Assets.Sounds.UNLOCK);
|
||||
curUser.sprite.operate(target, new Callback() {
|
||||
@Override
|
||||
public void call() {
|
||||
Dungeon.level.heaps.get(target).open(curUser);
|
||||
charge -= 2;
|
||||
curUser.spendAndNext(Actor.TICK);
|
||||
curUser.sprite.idle();
|
||||
}
|
||||
});
|
||||
curUser.busy();
|
||||
return;
|
||||
|
||||
} else if (Dungeon.level.heaps.get(target) != null && Dungeon.level.heaps.get(target).type == Heap.Type.CRYSTAL_CHEST){
|
||||
if (charge < 4) {
|
||||
GLog.w(Messages.get(SkeletonKey.class, "crystal_charges"));
|
||||
return;
|
||||
}
|
||||
Sample.INSTANCE.play(Assets.Sounds.UNLOCK);
|
||||
curUser.sprite.operate(target, new Callback() {
|
||||
@Override
|
||||
public void call() {
|
||||
Dungeon.level.heaps.get(target).open(curUser);
|
||||
charge -= 4;
|
||||
curUser.spendAndNext(Actor.TICK);
|
||||
curUser.sprite.idle();
|
||||
}
|
||||
});
|
||||
curUser.busy();
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (charge < 2){
|
||||
GLog.w(Messages.get(SkeletonKey.class, "wall_charges"));
|
||||
return;
|
||||
}
|
||||
|
||||
int closest = curUser.pos;
|
||||
int closestIdx = -1;
|
||||
|
||||
for (int i = 0; i < PathFinder.CIRCLE8.length; i++){
|
||||
int ofs = PathFinder.CIRCLE8[i];
|
||||
if (Dungeon.level.trueDistance(target, curUser.pos+ofs) < Dungeon.level.trueDistance(target, closest)){
|
||||
closest = curUser.pos+ofs;
|
||||
closestIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
int knockBackDir = PathFinder.CIRCLE8[closestIdx];
|
||||
|
||||
if (Dungeon.level.solid[closest]){
|
||||
GLog.w(Messages.get(SkeletonKey.class, "invalid_target"));
|
||||
return;
|
||||
}
|
||||
|
||||
int finalClosestIdx = closestIdx;
|
||||
Sample.INSTANCE.play(Assets.Sounds.UNLOCK);
|
||||
curUser.sprite.operate(target, new Callback() {
|
||||
@Override
|
||||
public void call() {
|
||||
placeWall(curUser.pos+PathFinder.CIRCLE8[finalClosestIdx], knockBackDir);
|
||||
placeWall(curUser.pos+PathFinder.CIRCLE8[(finalClosestIdx +7)%8], knockBackDir);
|
||||
placeWall(curUser.pos+PathFinder.CIRCLE8[(finalClosestIdx +1)%8], knockBackDir);
|
||||
|
||||
//if we're in a diagonal direction
|
||||
if (finalClosestIdx % 2 == 0){
|
||||
placeWall(curUser.pos+2*PathFinder.CIRCLE8[(finalClosestIdx +7)%8], knockBackDir);
|
||||
placeWall(curUser.pos+2*PathFinder.CIRCLE8[(finalClosestIdx +1)%8], knockBackDir);
|
||||
}
|
||||
|
||||
charge -= 2;
|
||||
|
||||
Dungeon.observe();
|
||||
GameScene.updateFog();
|
||||
Sample.INSTANCE.play(Assets.Sounds.TELEPORT);
|
||||
|
||||
curUser.spendAndNext(Actor.TICK);
|
||||
curUser.sprite.idle();
|
||||
}
|
||||
});
|
||||
curUser.busy();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prompt() {
|
||||
return Messages.get(SkeletonKey.class, "prompt");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected ArtifactBuff passiveBuff() {
|
||||
return new keyRecharge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void charge(Hero target, float amount) {
|
||||
super.charge(target, amount); //TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public String desc() {
|
||||
String desc = super.desc();
|
||||
|
||||
if ( isEquipped (Dungeon.hero) ){
|
||||
if (cursed){
|
||||
desc += "\n\n" + Messages.get(this, "desc_cursed");
|
||||
} else {
|
||||
desc += "\n\n" + Messages.get(this, "desc_worn"); //TODO probably want more info on making walls
|
||||
}
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
public class keyRecharge extends ArtifactBuff {
|
||||
@Override
|
||||
public boolean act() {
|
||||
if (charge < chargeCap
|
||||
&& !cursed
|
||||
&& target.buff(MagicImmune.class) == null
|
||||
&& Regeneration.regenOn()) {
|
||||
//120 turns to charge at full, 60 turns to charge at 0/8
|
||||
float chargeGain = 1 / (120f - (chargeCap - charge)*7.5f);
|
||||
chargeGain *= RingOfEnergy.artifactChargeMultiplier(target);
|
||||
partialCharge += chargeGain;
|
||||
|
||||
while (partialCharge >= 1) {
|
||||
partialCharge --;
|
||||
charge ++;
|
||||
|
||||
if (charge == chargeCap){
|
||||
partialCharge = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateQuickslot();
|
||||
|
||||
spend( TICK );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO either finish this or think of a different levelling mechanic
|
||||
public void keyUsed(Key key ){
|
||||
if (level() == levelCap){
|
||||
return;
|
||||
}
|
||||
|
||||
if (key instanceof IronKey){
|
||||
exp += 3;
|
||||
} else if (key instanceof WornKey){
|
||||
exp += 5;
|
||||
} else {
|
||||
exp += 2;
|
||||
}
|
||||
|
||||
if (exp >= 5 + 5*level()){
|
||||
exp -= 5 + 5*level();
|
||||
upgrade();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void placeWall( int pos, int knockbackDIR ){
|
||||
if (!Dungeon.level.solid[pos]) { //TODO this prevents wall stacking
|
||||
//TODO 10 or 20 turns?
|
||||
GameScene.add(Blob.seed(pos, 20, KeyWall.class));
|
||||
|
||||
Char ch = Actor.findChar(pos);
|
||||
if (ch != null && ch.alignment == Char.Alignment.ENEMY){
|
||||
WandOfBlastWave.throwChar(ch, new Ballistica(pos, pos+knockbackDIR, Ballistica.PROJECTILE), 1, false, false, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyWall extends Blob {
|
||||
|
||||
{
|
||||
alwaysVisible = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void evolve() {
|
||||
|
||||
int cell;
|
||||
boolean cellEnded = false;
|
||||
|
||||
Level l = Dungeon.level;
|
||||
for (int i = area.left; i < area.right; i++){
|
||||
for (int j = area.top; j < area.bottom; j++){
|
||||
cell = i + j*l.width();
|
||||
off[cell] = cur[cell] > 0 ? cur[cell] - 1 : 0;
|
||||
|
||||
if (cur[cell] > 0 && off[cell] == 0){
|
||||
cellEnded = true;
|
||||
}
|
||||
|
||||
//caps at 20
|
||||
//TODO or just one wall at a time?
|
||||
off[cell] = Math.min(off[cell], 19);
|
||||
|
||||
volume += off[cell];
|
||||
|
||||
l.losBlocking[cell] = off[cell] > 0 || (Terrain.flags[l.map[cell]] & Terrain.LOS_BLOCKING) != 0;
|
||||
l.solid[cell] = off[cell] > 0 || (Terrain.flags[l.map[cell]] & Terrain.SOLID) != 0;
|
||||
l.passable[cell] = off[cell] == 0 && (Terrain.flags[l.map[cell]] & Terrain.PASSABLE) != 0;
|
||||
l.avoid[cell] = off[cell] == 0 && (Terrain.flags[l.map[cell]] & Terrain.AVOID) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cellEnded){
|
||||
Dungeon.observe();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seed(Level level, int cell, int amount) {
|
||||
super.seed(level, cell, amount);
|
||||
level.losBlocking[cell] = cur[cell] > 0 || (Terrain.flags[level.map[cell]] & Terrain.LOS_BLOCKING) != 0;
|
||||
level.solid[cell] = cur[cell] > 0 || (Terrain.flags[level.map[cell]] & Terrain.SOLID) != 0;
|
||||
level.passable[cell] = cur[cell] == 0 && (Terrain.flags[level.map[cell]] & Terrain.PASSABLE) != 0;
|
||||
level.avoid[cell] = cur[cell] == 0 && (Terrain.flags[level.map[cell]] & Terrain.AVOID) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int cell) {
|
||||
super.clear(cell);
|
||||
if (cur == null) return;
|
||||
Level l = Dungeon.level;
|
||||
l.losBlocking[cell] = cur[cell] > 0 || (Terrain.flags[l.map[cell]] & Terrain.LOS_BLOCKING) != 0;
|
||||
l.solid[cell] = cur[cell] > 0 || (Terrain.flags[l.map[cell]] & Terrain.SOLID) != 0;
|
||||
l.passable[cell] = cur[cell] == 0 && (Terrain.flags[l.map[cell]] & Terrain.PASSABLE) != 0;
|
||||
l.avoid[cell] = cur[cell] == 0 && (Terrain.flags[l.map[cell]] & Terrain.AVOID) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullyClear() {
|
||||
super.fullyClear();
|
||||
Dungeon.level.buildFlagMaps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildFlagMaps(Level l) {
|
||||
if (volume > 0){
|
||||
for (int i=0; i < l.length(); i++) {
|
||||
l.losBlocking[i] = l.losBlocking[i] || cur[i] > 0;
|
||||
l.solid[i] = l.solid[i] || cur[i] > 0;
|
||||
l.passable[i] = l.passable[i] && cur[i] == 0;
|
||||
l.avoid[i] = l.avoid[i] && cur[i] == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void use(BlobEmitter emitter) {
|
||||
super.use( emitter );
|
||||
emitter.pour( MagicMissile.WhiteParticle.WALL, 0.02f ); //TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public String tileDesc() {
|
||||
return Messages.get(this, "desc");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -459,6 +459,7 @@ public class ItemSpriteSheet {
|
||||
public static final int ARTIFACT_ROSE2 = ARTIFACTS+21;
|
||||
public static final int ARTIFACT_ROSE3 = ARTIFACTS+22;
|
||||
public static final int ARTIFACT_TOME = ARTIFACTS+23;
|
||||
public static final int ARTIFACT_KEY = ARTIFACTS+24;
|
||||
static{
|
||||
assignItemRect(ARTIFACT_CLOAK, 9, 15);
|
||||
assignItemRect(ARTIFACT_ARMBAND, 16, 13);
|
||||
@@ -484,6 +485,7 @@ public class ItemSpriteSheet {
|
||||
assignItemRect(ARTIFACT_ROSE2, 14, 14);
|
||||
assignItemRect(ARTIFACT_ROSE3, 14, 14);
|
||||
assignItemRect(ARTIFACT_TOME, 14, 16);
|
||||
assignItemRect(ARTIFACT_KEY, 8, 16);
|
||||
}
|
||||
|
||||
private static final int TRINKETS = xy(1, 18); //32 slots
|
||||
|
||||
Reference in New Issue
Block a user