v2.3.0: lots more impl and refinement for gnoll sappers and quest
This commit is contained in:
@@ -448,7 +448,6 @@ public class DM300 extends Mob {
|
|||||||
}
|
}
|
||||||
//add rock cell to pos, if it is not solid, and isn't the safecell
|
//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) {
|
if (!Dungeon.level.solid[pos] && pos != safeCell && Random.Int(Dungeon.level.distance(rockCenter, pos)) == 0) {
|
||||||
//don't want to overly punish players with slow move or attack speed
|
|
||||||
rockCells.add(pos);
|
rockCells.add(pos);
|
||||||
}
|
}
|
||||||
pos++;
|
pos++;
|
||||||
@@ -457,6 +456,7 @@ public class DM300 extends Mob {
|
|||||||
for (int i : rockCells){
|
for (int i : rockCells){
|
||||||
sprite.parent.add(new TargetedCell(i, 0xFF0000));
|
sprite.parent.add(new TargetedCell(i, 0xFF0000));
|
||||||
}
|
}
|
||||||
|
//don't want to overly punish players with slow move or attack speed
|
||||||
Buff.append(this, FallingRockBuff.class, GameMath.gate(TICK, (int)Math.ceil(target.cooldown()), 3*TICK)).setRockPositions(rockCells);
|
Buff.append(this, FallingRockBuff.class, GameMath.gate(TICK, (int)Math.ceil(target.cooldown()), 3*TICK)).setRockPositions(rockCells);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -81,9 +81,9 @@ public class GnollGuard extends Mob {
|
|||||||
@Override
|
@Override
|
||||||
public int damageRoll() {
|
public int damageRoll() {
|
||||||
if (enemy != null && !Dungeon.level.adjacent(pos, enemy.pos)){
|
if (enemy != null && !Dungeon.level.adjacent(pos, enemy.pos)){
|
||||||
return Random.NormalIntRange( 15, 25 );
|
return Random.NormalIntRange( 10, 25 );
|
||||||
} else {
|
} else {
|
||||||
return Random.NormalIntRange( 5, 15 );
|
return Random.NormalIntRange( 3, 12 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+87
-38
@@ -54,10 +54,12 @@ import java.util.ArrayList;
|
|||||||
public class GnollSapper extends Mob {
|
public class GnollSapper extends Mob {
|
||||||
|
|
||||||
{
|
{
|
||||||
//TODO act after gnoll guards? makes it easier to kite them
|
//always acts after guards, makes it easier to kite them into attacks
|
||||||
|
actPriority = Actor.MOB_PRIO-1;
|
||||||
|
|
||||||
spriteClass = GnollSapperSprite.class;
|
spriteClass = GnollSapperSprite.class;
|
||||||
|
|
||||||
HP = HT = 35;
|
HP = HT = 50;
|
||||||
defenseSkill = 15;
|
defenseSkill = 15;
|
||||||
|
|
||||||
EXP = 10;
|
EXP = 10;
|
||||||
@@ -71,48 +73,45 @@ public class GnollSapper extends Mob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int spawnPos;
|
public int spawnPos;
|
||||||
private ArrayList<Integer> guardIDs = new ArrayList<>();
|
private int guardID = -1;
|
||||||
|
|
||||||
|
private int abilityCooldown = Random.NormalIntRange(4, 6);
|
||||||
|
|
||||||
private int throwingRockFromPos = -1;
|
private int throwingRockFromPos = -1;
|
||||||
private int throwingRockToPos = -1;
|
private int throwingRockToPos = -1;
|
||||||
|
|
||||||
public void linkGuard(GnollGuard g){
|
public void linkGuard(GnollGuard g){
|
||||||
guardIDs.add(g.id());
|
guardID = g.id();
|
||||||
g.linkSapper(this);
|
g.linkSapper(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void die(Object cause) {
|
public void die(Object cause) {
|
||||||
super.die(cause);
|
super.die(cause);
|
||||||
for (Integer g : guardIDs){
|
if (guardID != -1 && Actor.findById(guardID) instanceof GnollGuard){
|
||||||
if (Actor.findById(g) instanceof GnollGuard){
|
((GnollGuard) Actor.findById(guardID)).loseSapper();
|
||||||
((GnollGuard) Actor.findById(g)).loseSapper();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String SPAWN_POS = "spawn_pos";
|
|
||||||
private static final String GUARD_IDS = "guard_ids";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storeInBundle(Bundle bundle) {
|
public int damageRoll() {
|
||||||
super.storeInBundle(bundle);
|
return Random.NormalIntRange( 3, 6 );
|
||||||
int[] toStore = new int[guardIDs.size()];
|
|
||||||
for (int i = 0; i < toStore.length; i++){
|
|
||||||
toStore[i] = guardIDs.get(i);
|
|
||||||
}
|
|
||||||
bundle.put(GUARD_IDS, toStore);
|
|
||||||
bundle.put(SPAWN_POS, spawnPos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void restoreFromBundle(Bundle bundle) {
|
public int attackSkill( Char target ) {
|
||||||
super.restoreFromBundle(bundle);
|
return 18;
|
||||||
guardIDs = new ArrayList<>();
|
}
|
||||||
for (int g : bundle.getIntArray(GUARD_IDS)){
|
|
||||||
guardIDs.add(g);
|
@Override
|
||||||
}
|
public void damage(int dmg, Object src) {
|
||||||
spawnPos = bundle.getInt(SPAWN_POS);
|
super.damage(dmg, src);
|
||||||
|
abilityCooldown -= dmg/10f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int drRoll() {
|
||||||
|
return super.drRoll() + Random.NormalIntRange(0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -160,6 +159,8 @@ public class GnollSapper extends Mob {
|
|||||||
Ballistica trajectory = new Ballistica(ch.pos, rockPath.path.get(rockPath.dist + 1), Ballistica.MAGIC_BOLT);
|
Ballistica trajectory = new Ballistica(ch.pos, rockPath.path.get(rockPath.dist + 1), Ballistica.MAGIC_BOLT);
|
||||||
WandOfBlastWave.throwChar(ch, trajectory, 1, false, false, GnollSapper.this);
|
WandOfBlastWave.throwChar(ch, trajectory, 1, false, false, GnollSapper.this);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Dungeon.level.pressCell(rockPath.collisionPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
@@ -183,22 +184,40 @@ public class GnollSapper extends Mob {
|
|||||||
if (!enemyInFOV) {
|
if (!enemyInFOV) {
|
||||||
return super.act(enemyInFOV, justAlerted);
|
return super.act(enemyInFOV, justAlerted);
|
||||||
} else {
|
} else {
|
||||||
|
enemySeen = true;
|
||||||
|
|
||||||
//TODO we need a cooldown mechanic
|
if (abilityCooldown-- <= 0){
|
||||||
if (Random.Int(4) == 0){
|
boolean targetNextToBarricade = false;
|
||||||
if (Random.Int(3) != 0 && prepRockAttack(enemy)) {
|
for (int i : PathFinder.NEIGHBOURS8){
|
||||||
|
if (Dungeon.level.map[enemy.pos+i] == Terrain.BARRICADE){
|
||||||
|
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
|
||||||
|
if ((targetNextToBarricade || Random.Int(2) == 0) && prepRockAttack(enemy)) {
|
||||||
Dungeon.hero.interrupt();
|
Dungeon.hero.interrupt();
|
||||||
|
abilityCooldown = Random.NormalIntRange(4, 6);
|
||||||
spend(GameMath.gate(TICK, (int)Math.ceil(enemy.cooldown()), 3*TICK));
|
spend(GameMath.gate(TICK, (int)Math.ceil(enemy.cooldown()), 3*TICK));
|
||||||
return true;
|
return true;
|
||||||
} else if (prepRockFallAttack(enemy)) {
|
} else if (prepRockFallAttack(enemy)) {
|
||||||
|
Dungeon.hero.interrupt();
|
||||||
spend(GameMath.gate(TICK, (int)Math.ceil(enemy.cooldown()), 3*TICK));
|
spend(GameMath.gate(TICK, (int)Math.ceil(enemy.cooldown()), 3*TICK));
|
||||||
|
abilityCooldown = Random.NormalIntRange(4, 6);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spend(TICK);
|
//does not approach an enemy it can see, but does melee if in range
|
||||||
return true;
|
if (canAttack(enemy)){
|
||||||
|
return super.act(enemyInFOV, justAlerted);
|
||||||
|
} else {
|
||||||
|
spend(TICK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,8 +237,13 @@ public class GnollSapper extends Mob {
|
|||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
//TODO always random, or pick based on some criteria? maybe based on distance?
|
//throw closest rock to enemy
|
||||||
throwingRockFromPos = Random.element(candidateRocks);
|
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;
|
throwingRockToPos = enemy.pos;
|
||||||
|
|
||||||
Ballistica warnPath = new Ballistica(throwingRockFromPos, throwingRockToPos, Ballistica.STOP_SOLID);
|
Ballistica warnPath = new Ballistica(throwingRockFromPos, throwingRockToPos, Ballistica.STOP_SOLID);
|
||||||
@@ -233,10 +257,8 @@ public class GnollSapper extends Mob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//similar overall logic as DM-300's rock fall attack, but 5x5 and can't hit barricades
|
//similar overall logic as DM-300's rock fall attack, but 5x5 and can't hit barricades
|
||||||
// TODO externalize? we're going to use this for geomancer too
|
|
||||||
private boolean prepRockFallAttack( Char target ){
|
private boolean prepRockFallAttack( Char target ){
|
||||||
|
|
||||||
Dungeon.hero.interrupt();
|
|
||||||
final int rockCenter = target.pos;
|
final int rockCenter = target.pos;
|
||||||
|
|
||||||
int safeCell;
|
int safeCell;
|
||||||
@@ -271,7 +293,6 @@ public class GnollSapper extends Mob {
|
|||||||
}
|
}
|
||||||
//add rock cell to pos, if it is not solid, and isn't the safecell
|
//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) {
|
if (!Dungeon.level.solid[pos] && pos != safeCell && Random.Int(Dungeon.level.distance(rockCenter, pos)) == 0) {
|
||||||
//don't want to overly punish players with slow move or attack speed
|
|
||||||
rockCells.add(pos);
|
rockCells.add(pos);
|
||||||
}
|
}
|
||||||
pos++;
|
pos++;
|
||||||
@@ -280,6 +301,7 @@ public class GnollSapper extends Mob {
|
|||||||
for (int i : rockCells){
|
for (int i : rockCells){
|
||||||
sprite.parent.add(new TargetedCell(i, 0xFF0000));
|
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);
|
Buff.append(this, SapperRockFall.class, GameMath.gate(TICK, (int)Math.ceil(target.cooldown()), 3*TICK)).setRockPositions(rockCells);
|
||||||
|
|
||||||
sprite.attack(target.pos, new Callback() {
|
sprite.attack(target.pos, new Callback() {
|
||||||
@@ -292,7 +314,7 @@ public class GnollSapper extends Mob {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SapperRockFall extends DelayedRockFall {
|
public static class SapperRockFall extends DelayedRockFall {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void affectChar(Char ch) {
|
public void affectChar(Char ch) {
|
||||||
@@ -324,4 +346,31 @@ public class GnollSapper extends Mob {
|
|||||||
return spawnPos;
|
return spawnPos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String SPAWN_POS = "spawn_pos";
|
||||||
|
private static final String GUARD_ID = "guard_id";
|
||||||
|
|
||||||
|
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(GUARD_ID, guardID);
|
||||||
|
bundle.put(SPAWN_POS, spawnPos);
|
||||||
|
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);
|
||||||
|
guardID = bundle.getInt( GUARD_ID );
|
||||||
|
spawnPos = bundle.getInt(SPAWN_POS);
|
||||||
|
abilityCooldown = bundle.getInt(ABILITY_COOLDOWN);
|
||||||
|
throwingRockFromPos = bundle.getInt(ROCK_FROM_POS);
|
||||||
|
throwingRockToPos = bundle.getInt(ROCK_TO_POS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,10 @@ public class MiningLevel extends CavesLevel {
|
|||||||
losBlocking[cell] = false;
|
losBlocking[cell] = false;
|
||||||
}
|
}
|
||||||
drop( Generator.randomUsingDefaults(Generator.Category.FOOD), cell );
|
drop( Generator.randomUsingDefaults(Generator.Category.FOOD), cell );
|
||||||
|
if (Blacksmith.Quest.Type() == Blacksmith.Quest.GNOLL){
|
||||||
|
//drop a second ration for the gnoll quest type, more mining required!
|
||||||
|
drop( Generator.randomUsingDefaults(Generator.Category.FOOD), cell );
|
||||||
|
}
|
||||||
|
|
||||||
if (Dungeon.isChallenged(Challenges.DARKNESS)){
|
if (Dungeon.isChallenged(Challenges.DARKNESS)){
|
||||||
cell = randomDropCell();
|
cell = randomDropCell();
|
||||||
|
|||||||
+4
-1
@@ -139,10 +139,13 @@ public class MineLargeRoom extends CaveRoom {
|
|||||||
level.mobs.add(g);
|
level.mobs.add(g);
|
||||||
s.linkGuard(g);
|
s.linkGuard(g);
|
||||||
|
|
||||||
for (int i = 0; i < 3; i ++){
|
int barricades = Random.Int(2) == 0 ? 2 : 1;
|
||||||
|
for (int i = 0; i < barricades; i ++){
|
||||||
int barricadePos = sapperPos+PathFinder.NEIGHBOURS8[Random.Int(8)];
|
int barricadePos = sapperPos+PathFinder.NEIGHBOURS8[Random.Int(8)];
|
||||||
if (level.map[barricadePos] == Terrain.EMPTY && barricadePos != guardPos){
|
if (level.map[barricadePos] == Terrain.EMPTY && barricadePos != guardPos){
|
||||||
Painter.set(level, barricadePos, Terrain.BARRICADE);
|
Painter.set(level, barricadePos, Terrain.BARRICADE);
|
||||||
|
} else {
|
||||||
|
i--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user