v2.3.0: lots of coding and ability implementation for geomancer/sappers

This commit is contained in:
Evan Debenham
2023-11-24 13:32:01 -05:00
parent c6794ccb09
commit a7440afe2f
4 changed files with 312 additions and 197 deletions

View File

@@ -1211,6 +1211,7 @@ actors.mobs.gnoll.desc=Gnolls are hyena-like humanoids. They dwell in sewers and
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.rock_kill=The flying boulder killed you...
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._
@@ -1219,7 +1220,6 @@ actors.mobs.gnollguard.desc=A large and tough looking gnoll wielding a spear and
actors.mobs.gnollguard.desc_armor=_A nearby gnoll sapper is holding a device that is granting this guard earthen armor, heavily reducing the damage it takes._
actors.mobs.gnollsapper.name=gnoll sapper
actors.mobs.gnollsapper.rock_kill=The flying boulder killed you...
actors.mobs.gnollsapper.desc=A small and weak but intelligent gnoll with pale fur and a satchel full of various gadgets. They are likely here to assist with prospecting and mining dark gold, and have a nearby guard for protection. They are capable of fighting, but their attacks are pitifully weak, no stronger than a gnoll scout.\n\nInstead of fighting directly, sappers prefer to use the various gadgets they have to make the rocky environment attack for them. They don't know earth-moving magic themselves though, and so are pretty likely to use their gadgets recklessly. _There should be lots of ways to use their attacks against them, or their guard._
actors.mobs.gnolltrickster.name=gnoll trickster

View File

@@ -22,27 +22,39 @@
package com.shatteredpixel.shatteredpixeldungeon.actors.mobs;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Badges;
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.Paralysis;
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.effects.Splash;
import com.shatteredpixel.shatteredpixeldungeon.effects.TargetedCell;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.quest.DarkGold;
import com.shatteredpixel.shatteredpixeldungeon.items.quest.Pickaxe;
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.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.GnollGeomancerSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.shatteredpixel.shatteredpixeldungeon.sprites.MissileSprite;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.Callback;
import com.watabou.utils.ColorMath;
import com.watabou.utils.GameMath;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
@@ -52,20 +64,46 @@ public class GnollGeomancer extends Mob {
{
//TODO
HP = HT = 100;
HP = HT = 150;
spriteClass = GnollGeomancerSprite.class;
EXP = 20;
//acts after other mobs?
//acts after other mobs, just like sappers
actPriority = MOB_PRIO-1;
SLEEPING = new Sleeping();
HUNTING = new Hunting();
state = SLEEPING;
//can see the hero from a distance
viewDistance = 12;
properties.add(Property.BOSS);
}
private int abilityCooldown = Random.NormalIntRange(3, 5);
//TODO do we want to allow for multple rock throws at once here?
private int throwingRockFromPos = -1;
private int throwingRockToPos = -1;
@Override
protected boolean act() {
if (throwingRockFromPos != -1){
GnollGeomancer.doRockThrowAttack(this, throwingRockFromPos, throwingRockToPos);
throwingRockFromPos = -1;
throwingRockToPos = -1;
spend(TICK);
return false;
} else {
return super.act();
}
}
@Override
public boolean isInvulnerable(Class effect) {
return super.isInvulnerable(effect) || (buff(RockArmor.class) != null && effect != Pickaxe.class);
@@ -109,7 +147,7 @@ public class GnollGeomancer extends Mob {
boolean wasSleeping = state == SLEEPING;
//ensure we don't do enough damage to break the barrier at the start
//ensure we don't do enough damage to break the armor at the start
if (wasSleeping) dmg = Math.min(dmg, 15);
dmg = Math.min(dmg, buff(RockArmor.class).shielding());
@@ -120,7 +158,7 @@ public class GnollGeomancer extends Mob {
hits++;
if (hits == 1){
GLog.n( Messages.get(GnollGeomancer.this, "warning"));
GLog.w( Messages.get(GnollGeomancer.this, "warning"));
} if (hits == 3){
GLog.n( Messages.get(GnollGeomancer.this, "alert"));
wasSleeping = false;
@@ -133,6 +171,9 @@ public class GnollGeomancer extends Mob {
target = Random.Int(Dungeon.level.length());
} while (!Dungeon.level.insideMap(target) || Dungeon.level.distance(pos, target) != 10);
carveRock(target);
state = HUNTING;
enemy = Dungeon.hero;
}
if (wasSleeping) {
@@ -160,21 +201,21 @@ public class GnollGeomancer extends Mob {
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) {
//taking damage from full HP does not trigger a jump
if (beforeHitHP != HT && 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);
Buff.affect(this, RockArmor.class).setShield(30);
}
}
@@ -257,5 +298,252 @@ public class GnollGeomancer extends Mob {
}
}
private class Hunting extends Mob.Hunting {
@Override
public boolean act(boolean enemyInFOV, boolean justAlerted) {
if (!enemyInFOV){
spend(TICK);
return true;
} else {
enemySeen = true;
//sprite.showStatus(CharSprite.DEFAULT, "seen");
//TODO cooldown
if (abilityCooldown-- <= 0){
//do we care?
boolean targetNextToBarricade = false;
for (int i : PathFinder.NEIGHBOURS8){
if (Dungeon.level.map[enemy.pos+i] == Terrain.BARRICADE
|| Dungeon.level.map[enemy.pos+i] == Terrain.ENTRANCE){
targetNextToBarricade = true;
break;
}
}
// 50/50 to either throw a rock or do rockfall
// unless target is next to a barricade, then always try to throw
// unless nothing to throw, then always rockfall
Ballistica aim = GnollGeomancer.prepRockThrowAttack(enemy, GnollGeomancer.this);
if (aim != null && (targetNextToBarricade || Random.Int(2) == 0)) {
throwingRockFromPos = aim.sourcePos;
throwingRockToPos = aim.collisionPos;
Ballistica warnPath = new Ballistica(aim.sourcePos, aim.collisionPos, Ballistica.STOP_SOLID);
for (int i : warnPath.subPath(0, warnPath.dist)){
sprite.parent.add(new TargetedCell(i, 0xFF0000));
}
Dungeon.hero.interrupt();
abilityCooldown = Random.NormalIntRange(3, 5);
spend(GameMath.gate(TICK, (int)Math.ceil(enemy.cooldown()), 3*TICK));
return true;
} else if (GnollGeomancer.prepRockFallAttack(enemy, GnollGeomancer.this, 3, true)) {
Dungeon.hero.interrupt();
spend(GameMath.gate(TICK, (int)Math.ceil(enemy.cooldown()), 3*TICK));
abilityCooldown = Random.NormalIntRange(3, 5);
return true;
}
}
spend(TICK);
return true;
}
}
}
//*** These methods are public static as their logic is also accessed by gnoll sappers ***
public static Ballistica prepRockThrowAttack( Char target, Char source ){
ArrayList<Integer> candidateRocks = new ArrayList<>();
for (int i = 0; i < Dungeon.level.length(); i++){
if (source.fieldOfView[i] && Dungeon.level.map[i] == Terrain.MINE_BOULDER){
if (new Ballistica(i, target.pos, Ballistica.PROJECTILE).collisionPos == target.pos){
candidateRocks.add(i);
}
}
}
if (candidateRocks.isEmpty()){
return null;
} else {
//throw closest rock to enemy
int throwingFromPos = candidateRocks.get(0);
for (int i : candidateRocks){
if (Dungeon.level.trueDistance(i, target.pos) < Dungeon.level.trueDistance(throwingFromPos, target.pos)){
throwingFromPos = i;
}
}
int throwingToPos = target.pos;
return new Ballistica(throwingFromPos, throwingToPos, Ballistica.PROJECTILE);
}
}
public static void doRockThrowAttack( Char source, int from, int to ){
Level.set(from, Terrain.EMPTY);
GameScene.updateMap(from);
source.sprite.attack(from, new Callback() {
@Override
public void call() {
//do nothing
}
});
Ballistica rockPath = new Ballistica(from, to, Ballistica.MAGIC_BOLT);
Sample.INSTANCE.play(Assets.Sounds.MISS);
((MissileSprite)source.sprite.parent.recycle( MissileSprite.class )).
reset( from, rockPath.collisionPos, new GnollGeomancer.Boulder(), new Callback() {
@Override
public void call() {
Splash.at(rockPath.collisionPos, ColorMath.random( 0x444444, 0x777766 ), 15);
Sample.INSTANCE.play(Assets.Sounds.ROCKS);
Char ch = Actor.findChar(rockPath.collisionPos);
if (ch == Dungeon.hero){
PixelScene.shake( 3, 0.7f );
} else {
PixelScene.shake(0.5f, 0.5f);
}
if (ch != null && !(ch instanceof GnollGeomancer)){
ch.damage(Random.NormalIntRange(5, 10), this);
if (ch.isAlive()){
Buff.prolong( ch, Paralysis.class, ch instanceof GnollGuard ? 10 : 3 );
} else if (!ch.isAlive() && ch == Dungeon.hero) {
Badges.validateDeathFromEnemyMagic();
Dungeon.fail( source.getClass() );
GLog.n( Messages.get( GnollGeomancer.class, "rock_kill") );
}
if (rockPath.path.size() > rockPath.dist+1) {
Ballistica trajectory = new Ballistica(ch.pos, rockPath.path.get(rockPath.dist + 1), Ballistica.MAGIC_BOLT);
WandOfBlastWave.throwChar(ch, trajectory, 1, false, false, source);
}
} else if (ch == null) {
Dungeon.level.pressCell(rockPath.collisionPos);
}
source.next();
}
} );
}
public static class Boulder extends Item {
{
image = ItemSpriteSheet.GEO_BOULDER;
}
}
//similar overall logic as DM-300's rock fall attack, but with more parameters
public static boolean prepRockFallAttack( Char target, Char source, int range, boolean avoidBarricades ){
final int rockCenter = target.pos;
int safeCell;
do {
safeCell = rockCenter + PathFinder.NEIGHBOURS8[Random.Int(8)];
} while (safeCell == source.pos
|| (Dungeon.level.solid[safeCell] && Random.Int(2) == 0)
|| (Dungeon.level.traps.containsKey(safeCell) && Random.Int(2) == 0));
ArrayList<Integer> rockCells = new ArrayList<>();
int start = rockCenter - Dungeon.level.width() * range - range;
int pos;
for (int y = 0; y < 1+2*range; y++) {
pos = start + Dungeon.level.width() * y;
for (int x = 0; x < 1+2*range; x++) {
if (!Dungeon.level.insideMap(pos)) {
pos++;
continue;
}
if (avoidBarricades){
boolean barricade = false;
for (int j : PathFinder.NEIGHBOURS9){
if (Dungeon.level.map[pos+j] == Terrain.BARRICADE
|| Dungeon.level.map[pos+j] == Terrain.ENTRANCE){
barricade = true;
}
}
if (barricade){
pos++;
continue;
}
}
//add rock cell to pos, if it is not solid, isn't the safecell, and isn't where geomancer is standing
if (!Dungeon.level.solid[pos]
&& pos != safeCell
&& !(Actor.findChar(pos) instanceof GnollGeomancer)
&& Random.Int(Dungeon.level.distance(rockCenter, pos)) == 0) {
rockCells.add(pos);
}
pos++;
}
}
for (int i : rockCells){
source.sprite.parent.add(new TargetedCell(i, 0xFF0000));
}
//don't want to overly punish players with slow move or attack speed
Buff.append(source, GnollRockFall.class, GameMath.gate(TICK, (int)Math.ceil(target.cooldown()), 3*TICK)).setRockPositions(rockCells);
source.sprite.attack(target.pos, new Callback() {
@Override
public void call() {
//do nothing
}
});
return true;
}
public static class GnollRockFall extends DelayedRockFall{
@Override
public void affectChar(Char ch) {
Buff.prolong(ch, Paralysis.class, ch instanceof GnollGuard ? 10 : 3);
}
@Override
public void affectCell(int cell) {
if (Random.Int(3) == 0) {
Level.set(cell, Terrain.MINE_BOULDER);
GameScene.updateMap(cell);
}
}
}
public static class RockArmor extends ShieldBuff { }
public static final String HITS = "hits";
private static final String ABILITY_COOLDOWN = "ability_cooldown";
private static final String ROCK_FROM_POS = "rock_from_pos";
private static final String ROCK_TO_POS = "rock_to_pos";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(HITS, hits);
bundle.put(ABILITY_COOLDOWN, abilityCooldown);
bundle.put(ROCK_FROM_POS, throwingRockFromPos);
bundle.put(ROCK_TO_POS, throwingRockToPos);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
hits = bundle.getInt(HITS);
abilityCooldown = bundle.getInt(ABILITY_COOLDOWN);
throwingRockFromPos = bundle.getInt(ROCK_FROM_POS);
throwingRockToPos = bundle.getInt(ROCK_TO_POS);
}
}

View File

@@ -21,38 +21,18 @@
package com.shatteredpixel.shatteredpixeldungeon.actors.mobs;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Badges;
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.Paralysis;
import com.shatteredpixel.shatteredpixeldungeon.effects.Splash;
import com.shatteredpixel.shatteredpixeldungeon.effects.TargetedCell;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfBlastWave;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.MiningLevel;
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.GnollSapperSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.shatteredpixel.shatteredpixeldungeon.sprites.MissileSprite;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.Callback;
import com.watabou.utils.ColorMath;
import com.watabou.utils.GameMath;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
import java.util.ArrayList;
public class GnollSapper extends Mob {
{
@@ -119,54 +99,7 @@ public class GnollSapper extends Mob {
@Override
protected boolean act() {
if (throwingRockFromPos != -1){
Level.set(throwingRockFromPos, Terrain.EMPTY);
GameScene.updateMap(throwingRockFromPos);
sprite.attack(throwingRockToPos, new Callback() {
@Override
public void call() {
//do nothing
}
});
Ballistica rockPath = new Ballistica(throwingRockFromPos, throwingRockToPos, Ballistica.MAGIC_BOLT);
Sample.INSTANCE.play(Assets.Sounds.MISS);
((MissileSprite)sprite.parent.recycle( MissileSprite.class )).
reset( throwingRockFromPos, rockPath.collisionPos, new Boulder(), new Callback() {
@Override
public void call() {
Splash.at(rockPath.collisionPos, ColorMath.random( 0x444444, 0x777766 ), 15);
Sample.INSTANCE.play(Assets.Sounds.ROCKS);
Char ch = Actor.findChar(rockPath.collisionPos);
if (ch == Dungeon.hero){
PixelScene.shake( 3, 0.7f );
} else {
PixelScene.shake(0.5f, 0.5f);
}
if (ch != null){
ch.damage(Random.NormalIntRange(5, 10), this);
if (ch.isAlive()){
Buff.prolong( ch, Paralysis.class, ch instanceof GnollGuard ? 10 : 3 );
} else if (!ch.isAlive() && ch == Dungeon.hero) {
Badges.validateDeathFromEnemyMagic();
Dungeon.fail( GnollSapper.this );
GLog.n( Messages.get(this, "rock_kill") );
}
if (rockPath.path.size() > rockPath.dist+1) {
Ballistica trajectory = new Ballistica(ch.pos, rockPath.path.get(rockPath.dist + 1), Ballistica.MAGIC_BOLT);
WandOfBlastWave.throwChar(ch, trajectory, 1, false, false, GnollSapper.this);
}
} else {
Dungeon.level.pressCell(rockPath.collisionPos);
}
next();
}
} );
GnollGeomancer.doRockThrowAttack(this, throwingRockFromPos, throwingRockToPos);
throwingRockFromPos = -1;
throwingRockToPos = -1;
@@ -210,12 +143,22 @@ public class GnollSapper extends Mob {
// 50/50 to either throw a rock or do rockfall
// unless target is next to a barricade, then always try to throw
// unless nothing to throw, then always rockfall
if ((targetNextToBarricade || Random.Int(2) == 0) && prepRockAttack(enemy)) {
Ballistica aim = GnollGeomancer.prepRockThrowAttack(enemy, GnollSapper.this);
if (aim != null && (targetNextToBarricade || Random.Int(2) == 0)) {
throwingRockFromPos = aim.sourcePos;
throwingRockToPos = aim.collisionPos;
Ballistica warnPath = new Ballistica(aim.sourcePos, aim.collisionPos, Ballistica.STOP_SOLID);
for (int i : warnPath.subPath(0, warnPath.dist)){
sprite.parent.add(new TargetedCell(i, 0xFF0000));
}
Dungeon.hero.interrupt();
abilityCooldown = Random.NormalIntRange(4, 6);
spend(GameMath.gate(TICK, (int)Math.ceil(enemy.cooldown()), 3*TICK));
return true;
} else if (prepRockFallAttack(enemy)) {
} else if (GnollGeomancer.prepRockFallAttack(enemy, GnollSapper.this, 2, true)) {
Dungeon.hero.interrupt();
spend(GameMath.gate(TICK, (int)Math.ceil(enemy.cooldown()), 3*TICK));
abilityCooldown = Random.NormalIntRange(4, 6);
@@ -234,122 +177,6 @@ public class GnollSapper extends Mob {
}
}
private boolean prepRockAttack( Char target ){
ArrayList<Integer> candidateRocks = new ArrayList<>();
for (int i = 0; i < Dungeon.level.length(); i++){
if (fieldOfView[i] && Dungeon.level.map[i] == Terrain.MINE_BOULDER){
if (new Ballistica(i, target.pos, Ballistica.PROJECTILE).collisionPos == target.pos){
candidateRocks.add(i);
}
}
}
if (candidateRocks.isEmpty()){
return false;
} else {
//throw closest rock to enemy
throwingRockFromPos = candidateRocks.get(0);
for (int i : candidateRocks){
if (Dungeon.level.trueDistance(i, target.pos) < Dungeon.level.trueDistance(throwingRockFromPos, target.pos)){
throwingRockFromPos = i;
}
}
throwingRockToPos = enemy.pos;
Ballistica warnPath = new Ballistica(throwingRockFromPos, throwingRockToPos, Ballistica.STOP_SOLID);
for (int i : warnPath.subPath(0, warnPath.dist)){
sprite.parent.add(new TargetedCell(i, 0xFF0000));
}
}
return true;
}
//similar overall logic as DM-300's rock fall attack, but 5x5 and can't hit barricades
private boolean prepRockFallAttack( Char target ){
final int rockCenter = target.pos;
int safeCell;
do {
safeCell = rockCenter + PathFinder.NEIGHBOURS8[Random.Int(8)];
} while (safeCell == pos
|| (Dungeon.level.solid[safeCell] && Random.Int(2) == 0)
|| (Dungeon.level.traps.containsKey(safeCell) && Random.Int(2) == 0));
ArrayList<Integer> rockCells = new ArrayList<>();
int start = rockCenter - Dungeon.level.width() * 2 - 2;
int pos;
for (int y = 0; y < 5; y++) {
pos = start + Dungeon.level.width() * y;
for (int x = 0; x < 5; x++) {
if (!Dungeon.level.insideMap(pos)) {
pos++;
continue;
}
if (Dungeon.level instanceof MiningLevel){
boolean barricade = false;
for (int j : PathFinder.NEIGHBOURS9){
if (Dungeon.level.map[pos+j] == Terrain.BARRICADE
|| Dungeon.level.map[pos+j] == Terrain.ENTRANCE){
barricade = true;
}
}
if (barricade){
pos++;
continue;
}
}
//add rock cell to pos, if it is not solid, and isn't the safecell
if (!Dungeon.level.solid[pos] && pos != safeCell && Random.Int(Dungeon.level.distance(rockCenter, pos)) == 0) {
rockCells.add(pos);
}
pos++;
}
}
for (int i : rockCells){
sprite.parent.add(new TargetedCell(i, 0xFF0000));
}
//don't want to overly punish players with slow move or attack speed
Buff.append(this, SapperRockFall.class, GameMath.gate(TICK, (int)Math.ceil(target.cooldown()), 3*TICK)).setRockPositions(rockCells);
sprite.attack(target.pos, new Callback() {
@Override
public void call() {
//do nothing
}
});
return true;
}
public static class SapperRockFall extends DelayedRockFall {
@Override
public void affectChar(Char ch) {
Buff.prolong(ch, Paralysis.class, ch instanceof GnollGuard ? 10 : 3);
}
@Override
public void affectCell(int cell) {
if (Random.Int(3) == 0) {
Level.set(cell, Terrain.MINE_BOULDER);
GameScene.updateMap(cell);
}
}
}
public class Boulder extends Item {
{
image = ItemSpriteSheet.GEO_BOULDER;
}
}
public class Wandering extends Mob.Wandering {
@Override
protected int randomDestination() {

View File

@@ -22,7 +22,7 @@
package com.shatteredpixel.shatteredpixeldungeon.sprites;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollSapper;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollGeomancer;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.SpiritBow;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Crossbow;
@@ -104,7 +104,7 @@ public class MissileSprite extends ItemSprite implements Tweener.Listener {
//720 is default
ANGULAR_SPEEDS.put(GnollSapper.Boulder.class, 90);
ANGULAR_SPEEDS.put(GnollGeomancer.Boulder.class, 90);
ANGULAR_SPEEDS.put(HeavyBoomerang.class,1440);
ANGULAR_SPEEDS.put(Bolas.class, 1440);
@@ -152,7 +152,7 @@ public class MissileSprite extends ItemSprite implements Tweener.Listener {
updateFrame();
}
if (item instanceof GnollSapper.Boulder){
if (item instanceof GnollGeomancer.Boulder){
angle = 0;
flipHorizontal = false;
updateFrame();