v2.2.0: loads more refinement on the crystal spire fight.

This commit is contained in:
Evan Debenham
2023-09-25 16:01:56 -04:00
parent 1ac19a002d
commit f9e6529949
4 changed files with 117 additions and 41 deletions

View File

@@ -1122,7 +1122,7 @@ actors.mobs.crystalmimic.desc=Mimics are magical creatures which can take any sh
actors.mobs.crystalspire.name=crystal spire
actors.mobs.crystalspire.warning=The crystal vibrates as you strike it. Keeping this up is bound to draw attention to you...
actors.mobs.crystalspire.alert=The vibration grows and rumbles through the whole cave!
actors.mobs.crystalspire.desc=This gigantic spire of ultra hard crystal is probably the source of all the strange crystalline creatures in this cave. Your regular weapons are ineffective against it, so you'll need to use your pickaxe if you want to destroy it.\n\nBreaking such a big crystal is likely to take a while though, and you probably won't be able to do it undisturbed. The spire itself may even have some means of defending itself as well. _Make sure you're prepared before you start breaking it._
actors.mobs.crystalspire.desc=This gigantic spire of ultra hard crystal is probably the source of all the strange crystalline creatures in this cave. Your regular weapons are ineffective against it, so you'll need to use your pickaxe if you want to destroy it.\n\n_Make sure you're prepared before you start breaking it._ Such a big crystal is likely to take a while to mine through, and you probably won't be able to do it undisturbed. The spire itself may even have some means of defending itself as well. _Perhaps the spire's own attacks would be used against its minions._
actors.mobs.crystalwisp.name=crystal wisp
actors.mobs.crystalwisp.beam_kill=The beam of light killed you...

View File

@@ -113,15 +113,9 @@ public class CrystalGuardian extends Mob{
@Override
public float speed() {
//crystal guardians move at 1/4 speed when enclosed, but only if enclosed by walls
//TODO maybe we want crystals to count?
//crystal guardians take up to 4 turns when moving through an enclosed space
if (!Dungeon.level.openSpace[pos]) {
for (int i = 0; i < PathFinder.CIRCLE4.length / 2; i++) {
if (Dungeon.level.solid[pos + PathFinder.CIRCLE4[i]] && Dungeon.level.solid[pos + PathFinder.CIRCLE4[i + 2]]
&& Dungeon.level.map[pos + PathFinder.CIRCLE4[i]] != Terrain.MINE_CRYSTAL && Dungeon.level.map[pos + PathFinder.CIRCLE4[i + 2]] != Terrain.MINE_CRYSTAL) {
return super.speed() / 4f;
}
}
return Math.max(0.25f, super.speed() / 4f);
}
return super.speed();
}
@@ -136,18 +130,18 @@ public class CrystalGuardian extends Mob{
Splash.at(pos, 0xFFFFFF, 5);
Sample.INSTANCE.play( Assets.Sounds.SHATTER );
}
//breaking a crystal costs an extra turn
spend(TICK);
//breaking a crystal costs an extra move, not affected by enclosed spaces though
spend(1/super.speed());
}
}
@Override
public boolean[] modifyPassable(boolean[] passable) {
//if we are hunting, and our current target is not reachable otherwise, then we can step on crystals
//if we are hunting, we can stomp through crystals, but prefer not to
if (state == HUNTING){
PathFinder.buildDistanceMap(target, passable);
if (PathFinder.distance[pos] == Integer.MAX_VALUE) {
if (PathFinder.distance[pos] > 2*Dungeon.level.distance(pos, target)) {
for (int i = 0; i < Dungeon.level.length(); i++) {
passable[i] = passable[i] || Dungeon.level.map[i] == Terrain.MINE_CRYSTAL;
}

View File

@@ -28,7 +28,9 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Blindness;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Cripple;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Dread;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Haste;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Sleep;
@@ -64,6 +66,9 @@ public class CrystalSpire extends Mob {
HP = HT = 300;
spriteClass = CrystalSpireSprite.class;
//acts before other mobs, this is important for how it blocks crystal guardians
actPriority = MOB_PRIO+1;
state = PASSIVE;
alignment = Alignment.NEUTRAL;
@@ -76,7 +81,7 @@ public class CrystalSpire extends Mob {
//TODO this fight needs some mechanics and balance tuning now
private float abilityCooldown;
private static final int ABILITY_CD = 15;
private static final int ABILITY_CD = 12;
private ArrayList<ArrayList<Integer>> targetedCells = new ArrayList<>();
@@ -112,8 +117,8 @@ public class CrystalSpire extends Mob {
for (int i : cellsToAttack){
Char ch = Actor.findChar(i);
if (ch instanceof CrystalGuardian || ch instanceof CrystalSpire){
continue; //don't spawn crystals on these chars
if (ch instanceof CrystalSpire){
continue; //don't spawn crystals on itself
}
Level.set(i, Terrain.MINE_CRYSTAL);
@@ -125,21 +130,43 @@ public class CrystalSpire extends Mob {
for (int i : cellsToAttack){
Char ch = Actor.findChar(i);
if (ch != null && !(ch instanceof CrystalWisp || ch instanceof CrystalGuardian || ch instanceof CrystalSpire)){
ch.damage(10, CrystalSpire.this);
if (ch != null && !(ch instanceof CrystalWisp || ch instanceof CrystalSpire)){
int dmg = Random.NormalIntRange(15, 20);
if (!(ch instanceof CrystalGuardian)) {
dmg -= ch.drRoll();
} else {
Buff.prolong(ch, Cripple.class, 20f);
}
ch.damage(dmg, CrystalSpire.this);
int movePos = i;
for (int j : PathFinder.NEIGHBOURS8){
if (!Dungeon.level.solid[i+j] && Actor.findChar(i+j) == null &&
Dungeon.level.trueDistance(i+j, pos) > Dungeon.level.trueDistance(movePos, pos)){
movePos = i+j;
//crystal guardians get knocked away from the hero, others get knocked away from the spire
if (ch instanceof CrystalGuardian){
for (int j : PathFinder.NEIGHBOURS8){
if (!Dungeon.level.solid[i+j] && Actor.findChar(i+j) == null &&
Dungeon.level.trueDistance(i+j, Dungeon.hero.pos) > Dungeon.level.trueDistance(movePos, Dungeon.hero.pos)){
movePos = i+j;
}
}
} else {
for (int j : PathFinder.NEIGHBOURS8){
if (!Dungeon.level.solid[i+j] && Actor.findChar(i+j) == null &&
Dungeon.level.trueDistance(i+j, pos) > Dungeon.level.trueDistance(movePos, pos)){
movePos = i+j;
}
}
}
if (movePos != i){
Actor.add(new Pushing(ch, i, movePos));
ch.pos = movePos;
Dungeon.level.occupyCell(ch);
if (ch.isAlive()){
if (movePos != i){
Actor.add(new Pushing(ch, i, movePos));
ch.pos = movePos;
Dungeon.level.occupyCell(ch);
}
} else if (ch == Dungeon.hero){
GLog.n( Messages.capitalize(Messages.get(Char.class, "kill", name())) );
Dungeon.fail(this);
}
}
}
@@ -169,7 +196,7 @@ public class CrystalSpire extends Mob {
abilityCooldown += ABILITY_CD;
spend(GameMath.gate(TICK, Dungeon.hero.cooldown(), 3*TICK));
spend(GameMath.gate(TICK, (int)Math.ceil(Dungeon.hero.cooldown()), 3*TICK));
} else {
abilityCooldown -= 1;
spend(TICK);
@@ -184,7 +211,7 @@ public class CrystalSpire extends Mob {
targetedCells.clear();
ArrayList<Integer> aoeCells = new ArrayList<>();
aoeCells.add(pos);
aoeCells.add(Dungeon.hero.pos);
aoeCells.addAll(spreadDiamondAOE(aoeCells));
targetedCells.add(new ArrayList<>(aoeCells));
@@ -201,13 +228,6 @@ public class CrystalSpire extends Mob {
private ArrayList<Integer> spreadDiamondAOE(ArrayList<Integer> currentCells){
ArrayList<Integer> spreadCells = new ArrayList<>();
for (int i : currentCells){
for (int j : PathFinder.NEIGHBOURS4){
if (!Dungeon.level.solid[i+j] && !spreadCells.contains(i+j) && !currentCells.contains(i+j)){
spreadCells.add(i+j);
}
}
}
for (int i : spreadCells.toArray(new Integer[0])){
for (int j : PathFinder.NEIGHBOURS4){
if ((!Dungeon.level.solid[i+j] || Dungeon.level.map[i+j] == Terrain.MINE_CRYSTAL)
&& !spreadCells.contains(i+j) && !currentCells.contains(i+j)){
@@ -233,10 +253,10 @@ public class CrystalSpire extends Mob {
targetedCells.add(new ArrayList<>(lineCells));
if (HP < 2*HT/3f){
lineCells.addAll(spreadAOE(lineCells));
lineCells.addAll(spreadDiamondAOE(lineCells));
targetedCells.add(new ArrayList<>(lineCells));
if (HP < HT/3f) {;
lineCells.addAll(spreadAOE(lineCells));
lineCells.addAll(spreadDiamondAOE(lineCells));
targetedCells.add(lineCells);
}
}
@@ -323,6 +343,9 @@ public class CrystalSpire extends Mob {
if (ch instanceof CrystalGuardian){
ch.damage(ch.HT, this);
}
if (ch instanceof CrystalWisp && fieldOfView[ch.pos]){
Buff.affect(ch, Blindness.class, 5f);
}
}
}
@@ -342,13 +365,56 @@ public class CrystalSpire extends Mob {
BossHealthBar.assignBoss(CrystalSpire.this);
}
boolean affectingGuardians = false;
for (Char ch : Actor.chars()) {
if (ch instanceof CrystalWisp) {
((CrystalWisp) ch).beckon(pos);
if (((CrystalWisp) ch).state != ((CrystalWisp)ch).HUNTING && ((CrystalWisp) ch).target != pos) {
((CrystalWisp) ch).beckon(pos);
}
} else if (ch instanceof CrystalGuardian) {
((CrystalGuardian) ch).beckon(pos);
if (((CrystalGuardian) ch).state != HUNTING) {
((CrystalGuardian) ch).aggro(Dungeon.hero);
if (((CrystalGuardian) ch).state != ((CrystalGuardian)ch).HUNTING && ((CrystalGuardian) ch).target != pos) {
affectingGuardians = true;
}
}
}
//build a pathfind route to the guardians
// cripple close sleeping guardians to give more time
// haste far awake guardians to punish waking them
if (affectingGuardians){
boolean[] passable = Dungeon.level.passable.clone();
for (int i = 0; i < Dungeon.level.length(); i++){
if (Dungeon.level.map[i] == Terrain.MINE_CRYSTAL){
passable[i] = true;
}
}
PathFinder.buildDistanceMap(pos, passable);
for (Char ch : Actor.chars()) {
if (ch instanceof CrystalGuardian){
if (((CrystalGuardian) ch).state == ((CrystalGuardian) ch).SLEEPING) {
((CrystalGuardian) ch).beckon(pos);
if (((CrystalGuardian) ch).state != HUNTING) {
((CrystalGuardian) ch).aggro(Dungeon.hero);
}
//delays sleeping guardians that happen to be near to the crystal
if (PathFinder.distance[ch.pos] < 24){
Buff.affect(ch, Paralysis.class, 24-PathFinder.distance[ch.pos]);
}
} else if (((CrystalGuardian) ch).state != ((CrystalGuardian) ch).HUNTING && ((CrystalGuardian) ch).target != pos){
((CrystalGuardian) ch).beckon(pos);
if (((CrystalGuardian) ch).state != HUNTING) {
((CrystalGuardian) ch).aggro(Dungeon.hero);
}
//speeds up already woken guardians that aren't very close
if (PathFinder.distance[ch.pos] > 8){
Buff.affect(ch, Haste.class, Math.round((PathFinder.distance[ch.pos]-8)/2f));
}
}
}
}
}
@@ -412,6 +478,10 @@ public class CrystalSpire extends Mob {
spriteClass = bundle.getClass(SPRITE);
hits = bundle.getInt(HITS);
if (hits >= 3){
BossHealthBar.assignBoss(this);
}
abilityCooldown = bundle.getFloat(ABILITY_COOLDOWN);
targetedCells.clear();
int i = 0;

View File

@@ -141,6 +141,12 @@ public class MiningLevel extends CavesLevel {
}
}
@Override
public float respawnCooldown() {
//normal enemies respawn more slowly here
return 2*TIME_TO_RESPAWN;
}
@Override
protected void createItems() {
Item item = Bones.get();
@@ -161,6 +167,12 @@ public class MiningLevel extends CavesLevel {
drop( Generator.randomUsingDefaults(Generator.Category.FOOD), cell );
}
@Override
protected int randomDropCell() {
//avoid placing random items next to hazards
return randomDropCell(MineSmallRoom.class);
}
@Override
public String tileName( int tile ) {
switch (tile) {