v2.3.0: implemented very rough early logic for geomancer fight stages

This commit is contained in:
Evan Debenham
2023-11-17 14:40:27 -05:00
parent cd2244f545
commit ba14fda19a
7 changed files with 214 additions and 8 deletions

View File

@@ -1209,6 +1209,8 @@ actors.mobs.gnoll.name=gnoll scout
actors.mobs.gnoll.desc=Gnolls are hyena-like humanoids. They dwell in sewers and dungeons, venturing up to raid the surface from time to time. Gnoll scouts are regular members of their pack, they are not as strong as brutes and not as intelligent as shamans.
actors.mobs.gnollgeomancer.name=gnoll geomancer
actors.mobs.gnollgeomancer.warning=The geomancer begins to stir as you start picking at its rock armor. Be ready for a fight if you continue!
actors.mobs.gnollgeomancer.alert=The geomancer awakens! The entire cave seems to shift around as it leaps away while laughing madly!
actors.mobs.gnollgeomancer.desc=TODO, fight info
actors.mobs.gnollgeomancer.desc_sleeping=This impressively tall gnoll shaman is surrounded by a layer of rock, and looks almost like a statue. Looking closely you can see the rock magically move in time with the senior gnoll's breathing. It can't be harmed while encased in rock like this, and it appears to be enjoying a literal dirt nap.\n\nYou can probably break through the layers of rock with your pickaxe, but _be sure you're ready for a fight when you do so._ The geomancer must be the source of the various earth-moving magic around here, and the organizer of all the gnoll activity. _Defeating the gnolls scattered around here before fighting it might be a good idea._

View File

@@ -33,7 +33,6 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.SacrificialFire;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AdrenalineSurge;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AnkhInvulnerability;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.ArtifactRecharge;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AscensionChallenge;
@@ -1692,7 +1691,7 @@ public class Hero extends Char {
} else if (fieldOfView[cell] && ch instanceof Mob) {
if (ch.alignment != Alignment.ENEMY && ch.buff(Amok.class) == null) {
if (((Mob) ch).heroShouldInteract()) {
curAction = new HeroAction.Interact( ch );
} else {
curAction = new HeroAction.Attack( ch );

View File

@@ -296,7 +296,7 @@ public class CrystalSpire extends Mob {
@Override
public boolean isInvulnerable(Class effect) {
return effect != Pickaxe.class;
return super.isInvulnerable(effect) || effect != Pickaxe.class;
}
@Override
@@ -312,7 +312,6 @@ public class CrystalSpire extends Mob {
final Pickaxe p = Dungeon.hero.belongings.getItem(Pickaxe.class);
if (p == null){
//maybe a game log entry here?
return true;
}

View File

@@ -21,15 +21,38 @@
package com.shatteredpixel.shatteredpixeldungeon.actors.mobs;
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.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.ShieldBuff;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Blacksmith;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.Pushing;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.items.quest.DarkGold;
import com.shatteredpixel.shatteredpixeldungeon.items.quest.Pickaxe;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.GnollGeomancerSprite;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Callback;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
import java.util.ArrayList;
public class GnollGeomancer extends Mob {
{
//TODO
HP = HT = 300;
HP = HT = 100;
spriteClass = GnollGeomancerSprite.class;
EXP = 20;
@@ -43,6 +66,174 @@ public class GnollGeomancer extends Mob {
properties.add(Property.BOSS);
}
@Override
public boolean isInvulnerable(Class effect) {
return super.isInvulnerable(effect) || (buff(RockArmor.class) != null && effect != Pickaxe.class);
}
@Override
public boolean heroShouldInteract() {
return super.heroShouldInteract() || buff(RockArmor.class) != null;
}
@Override
protected boolean getCloser(int target) {
return false;
}
@Override
protected boolean getFurther(int target) {
return false;
}
int hits = 0;
int phase = 0;
@Override
public boolean interact(Char c) {
if (c != Dungeon.hero || buff(RockArmor.class) == null) {
return super.interact(c);
} else {
final Pickaxe p = Dungeon.hero.belongings.getItem(Pickaxe.class);
if (p == null){
return true;
}
Dungeon.hero.sprite.attack(pos, new Callback() {
@Override
public void call() {
//does its own special damage calculation that's only influenced by pickaxe level and augment
//we pretend the geomancer is the owner here so that properties like hero str or or other equipment do not factor in
int dmg = p.damageRoll(GnollGeomancer.this);
boolean wasSleeping = state == SLEEPING;
//ensure we don't do enough damage to break the barrier at the start
if (wasSleeping) dmg = Math.min(dmg, 15);
dmg = Math.min(dmg, buff(RockArmor.class).shielding());
damage(dmg, p);
sprite.bloodBurstA(Dungeon.hero.sprite.center(), dmg);
sprite.flash();
hits++;
if (hits == 1){
GLog.n( Messages.get(GnollGeomancer.this, "warning"));
} if (hits == 3){
GLog.n( Messages.get(GnollGeomancer.this, "alert"));
wasSleeping = false;
spend(TICK);
sprite.idle();
//this is a start, but need a lot more fight logic
int target;
do {
target = Random.Int(Dungeon.level.length());
} while (!Dungeon.level.insideMap(target) || Dungeon.level.distance(pos, target) != 10);
carveRock(target);
}
if (wasSleeping) {
state = SLEEPING;
alerted = false;
}
if (buff(RockArmor.class) == null){
sprite.idle();
}
Sample.INSTANCE.play(Assets.Sounds.MINE, 1f, Random.Float(0.85f, 1.15f));
Invisibility.dispel(Dungeon.hero);
Dungeon.hero.spendAndNext(p.delayFactor(GnollGeomancer.this));
}
});
return false;
}
}
@Override
public void damage(int dmg, Object src) {
int hpBracket = HT / 3;
int beforeHitHP = HP;
super.damage(dmg, src);
dmg = beforeHitHP - HP;
//geomancer cannot be hit through multiple brackets at a time
if ((beforeHitHP/hpBracket - HP/hpBracket) >= 2){
HP = hpBracket * ((beforeHitHP/hpBracket)-1) + 1;
}
if (beforeHitHP / hpBracket != HP / hpBracket) {
//this is a start, but need a lot more fight logic
int target;
do {
target = Random.Int(Dungeon.level.length());
} while (!Dungeon.level.insideMap(target) || Dungeon.level.distance(pos, target) != 10);
carveRock(target);
Buff.affect(this, RockArmor.class).setShield(50);
}
}
private void carveRock(int target){
Ballistica path = new Ballistica(pos, target, Ballistica.STOP_TARGET);
ArrayList<Integer> cells = new ArrayList<>(path.subPath(0, path.dist));
cells.addAll(spreadDiamondAOE(cells));
cells.addAll(spreadDiamondAOE(cells));
cells.addAll(spreadDiamondAOE(cells));
ArrayList<Integer> exteriorCells = spreadDiamondAOE(cells);
for (int i : cells){
if (Dungeon.level.map[i] == Terrain.WALL_DECO){
Dungeon.level.drop(new DarkGold(), i).sprite.drop();
}
if (Dungeon.level.solid[i]){
//TODO boulders?
Dungeon.level.map[i] = Terrain.EMPTY_DECO;
}
CellEmitter.get( i - Dungeon.level.width() ).start(Speck.factory(Speck.ROCK), 0.07f, 10);
}
for (int i : exteriorCells){
if (!Dungeon.level.solid[i]
&& Dungeon.level.map[i] != Terrain.EMPTY_SP
&& Dungeon.level.traps.get(i) == null
&& Dungeon.level.plants.get(i) == null
&& Actor.findChar(i) == null){
Dungeon.level.map[i] = Terrain.MINE_BOULDER;
}
}
//we potentially update a lot of cells, so might as well just reset properties instead of incrementally updating
Dungeon.level.buildFlagMaps();
Dungeon.level.cleanWalls();
GameScene.updateMap();
GameScene.updateFog();
Dungeon.observe();
PixelScene.shake(3, 0.7f);
Sample.INSTANCE.play(Assets.Sounds.ROCKS);
int oldpos = pos;
pos = target;
Actor.add(new Pushing(this, oldpos, pos));
}
private ArrayList<Integer> spreadDiamondAOE(ArrayList<Integer> currentCells){
ArrayList<Integer> spreadCells = new ArrayList<>();
for (int i : currentCells){
for (int j : PathFinder.NEIGHBOURS4){
if (Dungeon.level.insideMap(i+j) && !spreadCells.contains(i+j) && !currentCells.contains(i+j)){
spreadCells.add(i+j);
}
}
}
return spreadCells;
}
@Override
public String description() {
if (state == SLEEPING){
@@ -62,7 +253,9 @@ public class GnollGeomancer extends Mob {
@Override
protected void awaken(boolean enemyInFOV) {
//TODO . do nothing for now
//do nothing, has special awakening rules
}
}
public static class RockArmor extends ShieldBuff { }
}

View File

@@ -723,6 +723,11 @@ public abstract class Mob extends Char {
&& (!attacking || enemy.canSurpriseAttack());
}
//whether the hero should interact with the mob (true) or attack it (false)
public boolean heroShouldInteract(){
return alignment != Alignment.ENEMY && buff(Amok.class) == null;
}
public void aggro( Char ch ) {
enemy = ch;
if (state != PASSIVE){

View File

@@ -21,6 +21,7 @@
package com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.CrystalSpire;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollGeomancer;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Blacksmith;
@@ -122,6 +123,7 @@ public class MineGiantRoom extends CaveRoom {
GnollGeomancer g = new GnollGeomancer();
g.pos = level.pointToCell(center);
Buff.affect(g, GnollGeomancer.RockArmor.class).setShield(50);
level.mobs.add(g);
} else if (Blacksmith.Quest.Type() == Blacksmith.Quest.FUNGI){

View File

@@ -22,8 +22,9 @@
package com.shatteredpixel.shatteredpixeldungeon.sprites;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollGeomancer;
import com.watabou.noosa.TextureFilm;
import com.watabou.utils.ColorMath;
public class GnollGeomancerSprite extends MobSprite {
@@ -61,8 +62,13 @@ public class GnollGeomancerSprite extends MobSprite {
@Override
public void idle() {
super.idle();
if (ch instanceof Mob && ((Mob) ch).state == ((Mob) ch).SLEEPING){
if (ch != null && ch.buff(GnollGeomancer.RockArmor.class) != null){
play( statue );
}
}
@Override
public int blood() {
return curAnim == statue ? ColorMath.random( 0x444444, 0x777766 ) : super.blood();
}
}