v0.8.0: redesigned the DM-300 boss fight

This commit is contained in:
Evan Debenham 2020-01-17 22:58:01 -05:00
parent 749a81793f
commit 32a755cfee
16 changed files with 1815 additions and 15 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

View File

@ -106,6 +106,7 @@ public class Assets {
public static final String RIPPER = "ripper.png";
public static final String SPAWNER = "spawner.png";
public static final String DM100 = "dm100.png";
public static final String PYLON = "pylon.png";
public static final String ITEMS = "items.png";
public static final String TERRAIN_FEATURES = "terrain_features.png";
@ -133,6 +134,7 @@ public class Assets {
public static final String PRISON_QUEST = "custom_tiles/prison_quests.png";
public static final String PRISON_EXIT_OLD = "custom_tiles/prison_exit_old.png";
public static final String PRISON_EXIT_NEW = "custom_tiles/prison_exit_new.png";
public static final String CAVES_BOSS = "custom_tiles/caves_boss.png";
public static final String BUFFS_SMALL = "buffs.png";
public static final String BUFFS_LARGE = "large_buffs.png";

View File

@ -53,6 +53,7 @@ import com.shatteredpixel.shatteredpixeldungeon.levels.HallsLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.LastLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.LastShopLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.NewCavesBossLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.NewPrisonBossLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.PrisonLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.SewerBossLevel;
@ -263,7 +264,7 @@ public class Dungeon {
level = new CavesLevel();
break;
case 15:
level = new CavesBossLevel();
level = new NewCavesBossLevel();
break;
case 16:
case 17:

View File

@ -0,0 +1,586 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2019 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.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.blobs.Blob;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.ToxicGas;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barrier;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LockedFloor;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis;
import com.shatteredpixel.shatteredpixeldungeon.effects.BlobEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.EarthParticle;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.SparkParticle;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.LloydsBeacon;
import com.shatteredpixel.shatteredpixeldungeon.items.quest.MetalShard;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfBlastWave;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.NewCavesBossLevel;
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.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.DM300Sprite;
import com.shatteredpixel.shatteredpixeldungeon.ui.BossHealthBar;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.Camera;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
import com.watabou.utils.RectF;
public class NewDM300 extends Mob {
{
//TODO improved sprite
spriteClass = DM300Sprite.class;
HP = HT = 300;
EXP = 30;
defenseSkill = 15;
properties.add(Property.BOSS);
properties.add(Property.INORGANIC);
properties.add(Property.LARGE);
}
@Override
public int damageRoll() {
return Random.NormalIntRange( 15, 25 );
}
@Override
public int attackSkill( Char target ) {
return 20;
}
@Override
public int drRoll() {
return Random.NormalIntRange(0, 10);
}
public int pylonsActivated = 0;
public boolean supercharged = false;
public boolean chargeAnnounced = false;
private int turnsSinceLastAbility = -1;
private int abilityCooldown = Random.NormalIntRange(MIN_COOLDOWN, MAX_COOLDOWN);
private static final int MIN_COOLDOWN = 5;
private static final int MAX_COOLDOWN = 9;
private int lastAbility = 0;
private static final int NONE = 0;
private static final int GAS = 1;
private static final int ROCKS = 2;
private static final String PYLONS_ACTIVATED = "pylons_activated";
private static final String SUPERCHARGED = "supercharged";
private static final String CHARGE_ANNOUNCED = "charge_announced";
private static final String TURNS_SINCE_LAST_ABILITY = "turns_since_last_ability";
private static final String ABILITY_COOLDOWN = "ability_cooldown";
private static final String LAST_ABILITY = "last_ability";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(PYLONS_ACTIVATED, pylonsActivated);
bundle.put(SUPERCHARGED, supercharged);
bundle.put(CHARGE_ANNOUNCED, chargeAnnounced);
bundle.put(TURNS_SINCE_LAST_ABILITY, turnsSinceLastAbility);
bundle.put(ABILITY_COOLDOWN, abilityCooldown);
bundle.put(LAST_ABILITY, lastAbility);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
pylonsActivated = bundle.getInt(PYLONS_ACTIVATED);
supercharged = bundle.getBoolean(SUPERCHARGED);
chargeAnnounced = bundle.getBoolean(CHARGE_ANNOUNCED);
turnsSinceLastAbility = bundle.getInt(TURNS_SINCE_LAST_ABILITY);
abilityCooldown = bundle.getInt(ABILITY_COOLDOWN);
lastAbility = bundle.getInt(LAST_ABILITY);
if (turnsSinceLastAbility != -1){
BossHealthBar.assignBoss(this);
if (!supercharged && pylonsActivated == 2) BossHealthBar.bleed(true);
}
}
@Override
protected boolean act() {
GameScene.add(Blob.seed(pos, 0, FallingRocks.class));
GameScene.add(Blob.seed(pos, 0, ToxicGas.class));
//ability logic only triggers if DM is not supercharged
if (!supercharged){
if (turnsSinceLastAbility >= 0) turnsSinceLastAbility++;
//in case DM-300 hasn't been able to act yet
if (fieldOfView == null || fieldOfView.length != Dungeon.level.length()){
fieldOfView = new boolean[Dungeon.level.length()];
Dungeon.level.updateFieldOfView( this, fieldOfView );
}
//determine if DM can reach its enemy
boolean canReach;
if (enemy == null){
if (Dungeon.level.adjacent(pos, Dungeon.hero.pos)){
canReach = true;
} else {
canReach = (Dungeon.findStep(this, pos, Dungeon.hero.pos, Dungeon.level.openSpace, fieldOfView) != -1);
}
} else {
if (Dungeon.level.adjacent(pos, enemy.pos)){
canReach = true;
} else {
canReach = (Dungeon.findStep(this, pos, enemy.pos, Dungeon.level.openSpace, fieldOfView) != -1);
}
}
if (state != HUNTING){
if (Dungeon.hero.invisible <= 0 && canReach){
beckon(Dungeon.hero.pos);
}
} else {
if (!canReach){
if (enemy == null) enemy = Dungeon.hero;
if (fieldOfView[enemy.pos] && turnsSinceLastAbility >= MIN_COOLDOWN){
lastAbility = GAS;
turnsSinceLastAbility = 0;
spend(TICK);
GLog.w(Messages.get(this, "vent"));
if (sprite != null && (sprite.visible || enemy.sprite.visible)) {
sprite.zap(enemy.pos);
return false;
} else {
ventGas(enemy);
Sample.INSTANCE.play(Assets.SND_PUFF);
return true;
}
}
} else {
if (turnsSinceLastAbility > abilityCooldown) {
if (lastAbility == NONE) {
//50/50 either ability
lastAbility = Random.Int(2) == 0 ? GAS : ROCKS;
} else if (lastAbility == GAS) {
//more likely to use rocks
lastAbility = Random.Int(4) == 0 ? GAS : ROCKS;
} else {
//more likely to use gas
lastAbility = Random.Int(4) != 0 ? GAS : ROCKS;
}
//doesn't spend a turn if enemy is at a distance
if (Dungeon.level.adjacent(pos, enemy.pos)){
spend(TICK);
}
turnsSinceLastAbility = 0;
abilityCooldown = Random.NormalIntRange(MIN_COOLDOWN, MAX_COOLDOWN);
if (lastAbility == GAS) {
GLog.w(Messages.get(this, "vent"));
if (sprite != null && (sprite.visible || enemy.sprite.visible)) {
sprite.zap(enemy.pos);
return false;
} else {
ventGas(enemy);
Sample.INSTANCE.play(Assets.SND_PUFF);
return true;
}
} else {
GLog.w(Messages.get(this, "rocks"));
if (sprite != null && (sprite.visible || enemy.sprite.visible)) {
((DM300Sprite)sprite).slam(enemy.pos);
return false;
} else {
dropRocks(enemy);
Sample.INSTANCE.play(Assets.SND_PUFF);
return true;
}
}
}
}
}
} else {
if (!chargeAnnounced){
yell(Messages.get(this, "supercharged"));
chargeAnnounced = true;
}
if (state == WANDERING && Dungeon.hero.invisible <= 0){
beckon(Dungeon.hero.pos);
state = HUNTING;
enemy = Dungeon.hero;
}
}
return super.act();
}
@Override
public void move(int step) {
super.move(step);
Camera.main.shake( supercharged ? 3 : 1, 0.25f );
if (Dungeon.level.map[step] == Terrain.INACTIVE_TRAP && state == HUNTING) {
//don't gain energy from cells that are energized
if (NewCavesBossLevel.PylonEnergy.volumeAt(pos, NewCavesBossLevel.PylonEnergy.class) > 0){
return;
}
if (Dungeon.level.heroFOV[step]) {
if (buff(Barrier.class) == null) {
GLog.w(Messages.get(this, "shield"));
}
Sample.INSTANCE.play(Assets.SND_LIGHTNING);
sprite.emitter().start(SparkParticle.STATIC, 0.05f, 20);
}
Buff.affect(this, Barrier.class).setShield( 30 + (HT - HP)/10);
}
}
@Override
public float speed() {
return super.speed() * (supercharged ? 2 : 1);
}
@Override
public void notice() {
super.notice();
if (!BossHealthBar.isAssigned()) {
BossHealthBar.assignBoss(this);
turnsSinceLastAbility = 0;
yell(Messages.get(this, "notice"));
for (Char ch : Actor.chars()){
if (ch instanceof DriedRose.GhostHero){
GLog.n("\n");
((DriedRose.GhostHero) ch).sayBoss();
}
}
}
}
public void onZapComplete(){
ventGas(enemy);
next();
}
public void ventGas( Char target ){
int gasVented = 0;
Ballistica trajectory = new Ballistica(pos, target.pos, Ballistica.STOP_TARGET);
for (int i : trajectory.subPath(0, trajectory.dist)){
GameScene.add(Blob.seed(i, 20, ToxicGas.class));
gasVented += 20;
}
GameScene.add(Blob.seed(trajectory.collisionPos, 100, ToxicGas.class));
if (gasVented < 250){
int toVentAround = (int)Math.ceil((250 - gasVented)/8f);
for (int i : PathFinder.NEIGHBOURS8){
GameScene.add(Blob.seed(pos+i, toVentAround, ToxicGas.class));
}
}
}
public void onSlamComplete(){
dropRocks(enemy);
next();
}
public void dropRocks( Char target ) {
int rockCenter = target.pos;
if (Dungeon.level.adjacent(pos, target.pos)){
int oppositeAdjacent = target.pos + (target.pos - pos);
Ballistica trajectory = new Ballistica(target.pos, oppositeAdjacent, Ballistica.MAGIC_BOLT);
WandOfBlastWave.throwChar(target, trajectory, 2, false);
if (target == Dungeon.hero){
Dungeon.hero.interrupt();
}
rockCenter = trajectory.path.get(Math.min(trajectory.dist, 2));
}
//pick an adjacent cell to the hero as a safe cell. This cell is less likely to be in a wall or containing hazards
int safeCell;
do {
safeCell = rockCenter + PathFinder.NEIGHBOURS8[Random.Int(8)];
} while (safeCell == pos
|| (Dungeon.level.solid[safeCell] && Random.Int(2) == 0)
|| (Blob.volumeAt(safeCell, NewCavesBossLevel.PylonEnergy.class) > 0 && Random.Int(2) == 0));
int start = rockCenter - Dungeon.level.width() * 3 - 3;
int pos;
for (int y = 0; y < 7; y++) {
pos = start + Dungeon.level.width() * y;
for (int x = 0; x < 7; x++) {
if (!Dungeon.level.solid[pos] && pos != safeCell && Random.Int(Dungeon.level.distance(rockCenter, pos)) == 0) {
GameScene.add(Blob.seed(pos, 1, FallingRocks.class));
}
//add rock cell to pos, if it is not solid, and isn't the safecell
pos++;
}
}
}
@Override
public void damage(int dmg, Object src) {
if (supercharged){
dmg = 0;
}
super.damage(dmg, src);
LockedFloor lock = Dungeon.hero.buff(LockedFloor.class);
if (lock != null && !isImmune(src.getClass())) lock.addTime(dmg);
int threshold = HT/3 * (2- pylonsActivated);
if (HP < threshold){
HP = threshold;
supercharge();
}
}
public void supercharge(){
supercharged = true;
((NewCavesBossLevel)Dungeon.level).activatePylon();
pylonsActivated++;
spend(3f);
yell(Messages.get(this, "charging"));
sprite.showStatus(CharSprite.POSITIVE, Messages.get(this, "immune"));
chargeAnnounced = false;
}
public boolean isSupercharged(){
return supercharged;
}
public void loseSupercharge(){
supercharged = false;
sprite.resetColor();
if (pylonsActivated < 2){
yell(Messages.get(this, "charge_lost"));
} else {
yell(Messages.get(this, "pylons_destroyed"));
BossHealthBar.bleed(true);
}
}
@Override
public boolean isAlive() {
return HP > 0 || pylonsActivated < 2;
}
@Override
public void die( Object cause ) {
super.die( cause );
GameScene.bossSlain();
Dungeon.level.unseal();
//60% chance of 2 shards, 30% chance of 3, 10% chance for 4. Average of 2.5
int shards = Random.chances(new float[]{0, 0, 6, 3, 1});
for (int i = 0; i < shards; i++){
int ofs;
do {
ofs = PathFinder.NEIGHBOURS8[Random.Int(8)];
} while (!Dungeon.level.passable[pos + ofs]);
Dungeon.level.drop( new MetalShard(), pos + ofs ).sprite.drop( pos );
}
Badges.validateBossSlain();
LloydsBeacon beacon = Dungeon.hero.belongings.getItem(LloydsBeacon.class);
if (beacon != null) {
beacon.upgrade();
}
yell( Messages.get(this, "defeated") );
}
@Override
protected boolean getCloser(int target) {
if (super.getCloser(target)){
return true;
} else {
int bestpos = pos;
for (int i : PathFinder.NEIGHBOURS8){
if (Dungeon.level.openSpace[pos+i] && Actor.findChar(pos+i) == null &&
Dungeon.level.distance(bestpos, target) > Dungeon.level.distance(pos+i, target)){
bestpos = pos+i;
}
}
if (bestpos != pos){
move( bestpos );
return true;
}
if (!supercharged || state != HUNTING){
return false;
}
for (int i : PathFinder.NEIGHBOURS8){
if (Actor.findChar(pos+i) == null &&
Dungeon.level.trueDistance(bestpos, target) > Dungeon.level.trueDistance(pos+i, target)){
bestpos = pos+i;
}
}
if (bestpos != pos){
Sample.INSTANCE.play( Assets.SND_ROCKS );
for (int i : PathFinder.NEIGHBOURS9){
if (Dungeon.level.map[pos+i] == Terrain.WALL || Dungeon.level.map[pos+i] == Terrain.WALL_DECO){
Level.set(pos+i, Terrain.EMPTY_DECO);
GameScene.updateMap(pos+i);
}
}
Dungeon.level.cleanWalls();
Dungeon.observe();
spend(2f);
for (int i : PathFinder.NEIGHBOURS8){
if (Actor.findChar(pos+i) == null &&
Dungeon.level.trueDistance(bestpos, target) > Dungeon.level.trueDistance(pos+i, target)){
bestpos = pos+i;
}
}
if (bestpos != pos) {
move(bestpos);
}
Camera.main.shake( 5, 1f );
return true;
}
return false;
}
}
@Override
public String description() {
String desc = super.description();
if (supercharged) {
desc += "\n\n" + Messages.get(this, "desc_supercharged");
}
return desc;
}
public static class FallingRocks extends Blob {
{
alwaysVisible = true;
}
@Override
protected void evolve() {
boolean rocksFell = false;
int cell;
for (int i = area.left; i < area.right; i++){
for (int j = area.top; j < area.bottom; j++){
cell = i + j* Dungeon.level.width();
off[cell] = cur[cell] > 0 ? cur[cell] - 1 : 0;
if (off[cell] > 0) {
volume += off[cell];
}
if (cur[cell] > 0 && off[cell] == 0){
CellEmitter.get( cell ).start( Speck.factory( Speck.ROCK ), 0.07f, 10 );
Char ch = Actor.findChar(cell);
if (ch != null && !(ch instanceof NewDM300)){
Buff.prolong( ch, Paralysis.class, 3 );
}
rocksFell = true;
}
}
}
if (rocksFell){
Camera.main.shake( 3, 0.7f );
Sample.INSTANCE.play(Assets.SND_ROCKS);
}
}
@Override
public void use(BlobEmitter emitter) {
super.use(emitter);
emitter.bound = new RectF(0, -0.2f, 1, 0.4f);
emitter.pour(EarthParticle.FALLING, 0.1f);
}
@Override
public String tileDesc() {
return Messages.get(this, "desc");
}
}
}

View File

@ -0,0 +1,189 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2019 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.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.blobs.Electricity;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.ToxicGas;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Sleep;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vertigo;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.Lightning;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.SparkParticle;
import com.shatteredpixel.shatteredpixeldungeon.levels.NewCavesBossLevel;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.PylonSprite;
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
public class Pylon extends Mob {
{
spriteClass = PylonSprite.class;
HP = HT = 50;
maxLvl = -2;
properties.add(Property.BOSS);
properties.add(Property.INORGANIC);
properties.add(Property.ELECTRIC);
properties.add(Property.IMMOVABLE);
state = PASSIVE;
alignment = Alignment.NEUTRAL;
}
private int targetNeighbor = Random.Int(8);
@Override
protected boolean act() {
spend(TICK);
if (alignment == Alignment.NEUTRAL){
return true;
}
int cell1 = pos + PathFinder.CIRCLE8[targetNeighbor];
int cell2 = pos + PathFinder.CIRCLE8[(targetNeighbor+4)%8];
sprite.flash();
if (Dungeon.level.heroFOV[pos] || Dungeon.level.heroFOV[cell1] || Dungeon.level.heroFOV[cell2]) {
sprite.parent.add(new Lightning(DungeonTilemap.raisedTileCenterToWorld(cell1),
DungeonTilemap.raisedTileCenterToWorld(cell2), null));
CellEmitter.get(cell1).burst(SparkParticle.FACTORY, 3);
CellEmitter.get(cell2).burst(SparkParticle.FACTORY, 3);
Sample.INSTANCE.play( Assets.SND_LIGHTNING );
}
shockChar(Actor.findChar(cell1));
shockChar(Actor.findChar(cell2));
targetNeighbor = (targetNeighbor+1)%8;
return true;
}
private void shockChar( Char ch ){
if (ch != null && !(ch instanceof NewDM300)){
ch.sprite.flash();
ch.damage(Random.NormalIntRange(15, 25), Electricity.class);
if (ch == Dungeon.hero && !ch.isAlive()){
Dungeon.fail(NewDM300.class);
GLog.n( Messages.get(Electricity.class, "ondeath") );
}
}
}
public void activate(){
alignment = Alignment.ENEMY;
((PylonSprite) sprite).activate();
}
@Override
public CharSprite sprite() {
PylonSprite p = (PylonSprite) super.sprite();
if (alignment != Alignment.NEUTRAL) p.activate();
return p;
}
@Override
public String description() {
if (alignment == Alignment.NEUTRAL){
return Messages.get(this, "desc_inactive");
} else {
return Messages.get(this, "desc_active");
}
}
@Override
public boolean interact() {
return true;
}
@Override
public void add(Buff buff) {
//immune to all buffs/debuffs when inactive
if (alignment != Alignment.NEUTRAL) {
super.add(buff);
}
}
@Override
public void damage(int dmg, Object src) {
//immune to damage when inactive
if (alignment == Alignment.NEUTRAL){
return;
}
if (dmg >= 5){
//takes 10/11/12/13/14/15 dmg at 10/12/15/19/24/31 incoming dmg
dmg = 9 + (int)(Math.sqrt(8*(dmg - 9) + 1) - 1)/2;
}
super.damage(dmg, src);
}
@Override
public void die(Object cause) {
super.die(cause);
((NewCavesBossLevel)Dungeon.level).eliminatePylon();
}
private static final String ALIGNMENT = "alignment";
private static final String TARGET_NEIGHBOUR = "target_neighbour";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(ALIGNMENT, alignment);
bundle.put(TARGET_NEIGHBOUR, targetNeighbor);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
alignment = bundle.getEnum(ALIGNMENT, Alignment.class);
targetNeighbor = bundle.getInt(TARGET_NEIGHBOUR);
}
{
immunities.add( Paralysis.class );
immunities.add( Amok.class );
immunities.add( Sleep.class );
immunities.add( ToxicGas.class );
immunities.add( Terror.class );
immunities.add( Vertigo.class );
}
}

View File

@ -66,8 +66,9 @@ public class EarthParticle extends PixelParticle {
left = lifespan = 1f;
size = 8;
acc.y = 15;
speed.y = 0;
acc.y = 30;
speed.y = -5;
angularSpeed = Random.Float(-90, 90);
}
@Override

View File

@ -63,6 +63,7 @@ public class SparkParticle extends PixelParticle {
this.x = x;
this.y = y;
size = 5;
left = lifespan = Random.Float( 0.5f, 1.0f );
@ -78,9 +79,13 @@ public class SparkParticle extends PixelParticle {
speed.set( 0, 0 );
}
public void setMaxSize( float value ){
size = value;
}
@Override
public void update() {
super.update();
size( Random.Float( 5 * left / lifespan ) );
size( Random.Float( size * left / lifespan ) );
}
}

View File

@ -109,6 +109,10 @@ public class WandOfBlastWave extends DamageWand {
}
public static void throwChar(final Char ch, final Ballistica trajectory, int power){
throwChar(ch, trajectory, power, true);
}
public static void throwChar(final Char ch, final Ballistica trajectory, int power, boolean collideDmg){
if (ch.properties().contains(Char.Property.BOSS)) {
power /= 2;
}
@ -142,7 +146,7 @@ public class WandOfBlastWave extends DamageWand {
if (newPos == ch.pos) return;
final int finalDist = dist;
final boolean finalCollided = collided;
final boolean finalCollided = collided && collideDmg;
final int initialpos = ch.pos;
Actor.addDelayed(new Pushing(ch, ch.pos, newPos, new Callback() {

View File

@ -653,8 +653,10 @@ public abstract class Level implements Bundlable {
set( pos, Terrain.EMBERS );
}
protected void cleanWalls() {
discoverable = new boolean[length()];
public void cleanWalls() {
if (discoverable == null || discoverable.length != length) {
discoverable = new boolean[length()];
}
for (int i=0; i < length(); i++) {

View File

@ -0,0 +1,786 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2019 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.levels;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Bones;
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.blobs.Electricity;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DM300;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Pylon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.NewDM300;
import com.shatteredpixel.shatteredpixeldungeon.effects.BlobEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.BlastParticle;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.SparkParticle;
import com.shatteredpixel.shatteredpixeldungeon.items.Heap;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.levels.painters.CavesPainter;
import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.tiles.CustomTilemap;
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.Camera;
import com.watabou.noosa.Group;
import com.watabou.noosa.Image;
import com.watabou.noosa.Tilemap;
import com.watabou.noosa.audio.Sample;
import com.watabou.noosa.particles.Emitter;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Point;
import com.watabou.utils.Random;
import com.watabou.utils.Rect;
import java.util.ArrayList;
public class NewCavesBossLevel extends Level {
{
color1 = 0x534f3e;
color2 = 0xb9d661;
}
@Override
public String tilesTex() {
return Assets.TILES_CAVES;
}
@Override
public String waterTex() {
return Assets.WATER_CAVES;
}
private static int WIDTH = 33;
private static int HEIGHT = 42;
public static Rect mainArena = new Rect(5, 14, 28, 37);
public static Rect gate = new Rect(14, 13, 19, 14);
public static int[] pylonPositions = new int[]{ 4 + 13*WIDTH, 28 + 13*WIDTH, 4 + 37*WIDTH, 28 + 37*WIDTH };
private ArenaVisuals customArenaVisuals;
@Override
protected boolean build() {
setSize(WIDTH, HEIGHT);
//Painter.fill(this, 0, 0, width(), height(), Terrain.EMBERS);
//setup exit area above main boss arena
Painter.fill(this, 0, 3, width(), 4, Terrain.CHASM);
Painter.fill(this, 6, 7, 21, 1, Terrain.CHASM);
Painter.fill(this, 10, 8, 13, 1, Terrain.CHASM);
Painter.fill(this, 12, 9, 9, 1, Terrain.CHASM);
Painter.fill(this, 13, 10, 7, 1, Terrain.CHASM);
Painter.fill(this, 14, 3, 5, 10, Terrain.EMPTY);
//fill in special floor, statues, and exits
Painter.fill(this, 15, 2, 3, 3, Terrain.EMPTY_SP);
Painter.fill(this, 15, 5, 3, 1, Terrain.STATUE);
Painter.fill(this, 15, 7, 3, 1, Terrain.STATUE);
Painter.fill(this, 15, 9, 3, 1, Terrain.STATUE);
Painter.fill(this, 16, 5, 1, 6, Terrain.EMPTY_SP);
Painter.fill(this, 15, 0, 3, 3, Terrain.EXIT);
exit = 16 + 2*width();
//These signs are visually overridden with custom tile visuals
Painter.fill(this, gate, Terrain.SIGN);
//set up main boss arena
Painter.fillEllipse(this, mainArena, Terrain.EMPTY);
boolean[] patch = Patch.generate( width, height-14, 0.20f, 2, true );
for (int i= 14*width(); i < length(); i++) {
if (map[i] == Terrain.EMPTY) {
if (patch[i - 14*width()]){
map[i] = Terrain.WATER;
} else if (Random.Int(6) == 0){
map[i] = Terrain.INACTIVE_TRAP;
map[i] = Terrain.INACTIVE_TRAP;
}
}
}
buildEntrance();
buildCorners();
CustomTilemap customVisuals = new CityEntrance();
customVisuals.setRect(0, 0, width(), 11);
customTiles.add(customVisuals);
customVisuals = new EntranceOverhang();
customVisuals.setRect(0, 0, width(), 11);
customWalls.add(customVisuals);
customVisuals = customArenaVisuals = new ArenaVisuals();
customVisuals.setRect(0, 12, width(), 27);
customTiles.add(customVisuals);
new CavesPainter().paint(this, null);
return true;
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
for (CustomTilemap c : customTiles){
if (c instanceof ArenaVisuals){
customArenaVisuals = (ArenaVisuals) c;
}
}
}
@Override
protected void createMobs() { }
@Override
public Actor respawner() {
return null;
}
@Override
protected void createItems() {
Item item = Bones.get();
if (item != null) {
int pos;
do {
pos = randomRespawnCell(null);
} while (pos == entrance);
drop( item, pos ).setHauntedIfCursed().type = Heap.Type.REMAINS;
}
}
@Override
public int randomRespawnCell( Char ch ) {
int cell;
do {
cell = entrance + PathFinder.NEIGHBOURS8[Random.Int(8)];
} while (!passable[cell]
|| (Char.hasProp(ch, Char.Property.LARGE) && !openSpace[cell])
|| Actor.findChar(cell) != null);
return cell;
}
@Override
public void occupyCell(Char ch) {
super.occupyCell(ch);
//seal the level when the hero moves off the entrance, the level isn't already sealed, and the gate hasn't been destroyed
int gatePos = pointToCell(new Point(gate.left, gate.top));
if (ch == Dungeon.hero && ch.pos != entrance && !locked && solid[gatePos]){
seal();
}
}
@Override
public void seal() {
super.seal();
NewDM300 boss = new NewDM300();
boss.state = boss.WANDERING;
do {
boss.pos = pointToCell(Random.element(mainArena.getPoints()));
} while (!openSpace[boss.pos] || map[boss.pos] == Terrain.EMPTY_SP || heroFOV[boss.pos]);
GameScene.add( boss );
set( entrance, Terrain.WALL );
GameScene.updateMap( entrance );
Dungeon.observe();
CellEmitter.get( entrance ).start( Speck.factory( Speck.ROCK ), 0.07f, 10 );
Camera.main.shake( 3, 0.7f );
Sample.INSTANCE.play( Assets.SND_ROCKS );
for (int i : pylonPositions) {
Pylon pylon = new Pylon();
pylon.pos = i;
GameScene.add(pylon);
}
}
@Override
public void unseal() {
super.unseal();
blobs.get(PylonEnergy.class).fullyClear();
set( entrance, Terrain.ENTRANCE );
int i = 14 + 13*width();
for (int j = 0; j < 5; j++){
set( i+j, Terrain.EMPTY );
if (Dungeon.level.heroFOV[i+j]){
CellEmitter.get(i+j).burst(BlastParticle.FACTORY, 10);
}
}
GameScene.updateMap();
customArenaVisuals.updateState();
Dungeon.observe();
}
public void activatePylon(){
ArrayList<Pylon> pylons = new ArrayList<>();
for (Mob m : mobs){
if (m instanceof Pylon && m.alignment == Char.Alignment.NEUTRAL){
pylons.add((Pylon) m);
}
}
if (pylons.size() == 1){
pylons.get(0).activate();
} else if (!pylons.isEmpty()) {
Pylon closest = null;
for (Pylon p : pylons){
if (closest == null || trueDistance(p.pos, Dungeon.hero.pos) < trueDistance(closest.pos, Dungeon.hero.pos)){
closest = p;
}
}
pylons.remove(closest);
Random.element(pylons).activate();
}
for( int i = (mainArena.top-1)*width; i <length; i++){
if (map[i] == Terrain.INACTIVE_TRAP || map[i] == Terrain.WATER || map[i] == Terrain.SIGN){
GameScene.add(Blob.seed(i, 1, PylonEnergy.class));
}
}
}
public void eliminatePylon(){
customArenaVisuals.updateState();
int pylonsRemaining = 0;
for (Mob m : mobs){
if (m instanceof NewDM300){
((NewDM300) m).loseSupercharge();
PylonEnergy.energySourceSprite = m.sprite;
} else if (m instanceof Pylon){
pylonsRemaining++;
}
}
if (pylonsRemaining > 2) {
blobs.get(PylonEnergy.class).fullyClear();
}
}
@Override
public String tileName( int tile ) {
switch (tile) {
case Terrain.GRASS:
return Messages.get(CavesLevel.class, "grass_name");
case Terrain.HIGH_GRASS:
return Messages.get(CavesLevel.class, "high_grass_name");
case Terrain.WATER:
return Messages.get(CavesLevel.class, "water_name");
case Terrain.STATUE:
//city statues are used
return Messages.get(CityLevel.class, "statue_name");
default:
return super.tileName( tile );
}
}
@Override
public String tileDesc( int tile ) {
switch (tile) {
case Terrain.WATER:
return super.tileDesc( tile ) + "\n\n" + Messages.get(CavesBossLevel.class, "water_desc");
case Terrain.ENTRANCE:
return Messages.get(CavesLevel.class, "entrance_desc");
case Terrain.EXIT:
//city exit is used
return Messages.get(CityLevel.class, "exit_desc");
case Terrain.HIGH_GRASS:
return Messages.get(CavesLevel.class, "high_grass_desc");
case Terrain.WALL_DECO:
return Messages.get(CavesLevel.class, "wall_deco_desc");
case Terrain.BOOKSHELF:
return Messages.get(CavesLevel.class, "bookshelf_desc");
//city statues are used
case Terrain.STATUE:
return Messages.get(CityLevel.class, "statue_desc");
default:
return super.tileDesc( tile );
}
}
@Override
public Group addVisuals() {
super.addVisuals();
CavesLevel.addCavesVisuals(this, visuals);
return visuals;
}
/**
* semi-randomized setup for entrance and corners
*/
private static final short n = -1; //used when a tile shouldn't be changed
private static final short W = Terrain.WALL;
private static final short e = Terrain.EMPTY;
private static final short s = Terrain.EMPTY_SP;
private static short[] entrance1 = {
n, n, n, n, n, n, n, n,
n, n, n, n, n, n, n, n,
n, n, n, n, W, e, W, W,
n, n, n, W, W, e, W, W,
n, n, W, W, e, e, e, e,
n, n, e, e, e, W, W, e,
n, n, W, W, e, W, e, e,
n, n, W, W, e, e, e, e
};
private static short[] entrance2 = {
n, n, n, n, n, n, n, n,
n, n, n, n, n, n, W, W,
n, n, n, n, n, n, e, e,
n, n, n, n, e, W, W, W,
n, n, n, e, e, e, e, e,
n, n, n, W, e, W, W, e,
n, W, e, W, e, W, e, e,
n, W, e, W, e, e, e, e
};
private static short[] entrance3 = {
n, n, n, n, n, n, n, n,
n, n, n, n, n, n, n, n,
n, n, n, n, n, n, n, n,
n, n, n, W, W, e, W, W,
n, n, n, W, W, e, W, W,
n, n, n, e, e, e, e, e,
n, n, n, W, W, e, W, e,
n, n, n, W, W, e, e, e
};
private static short[] entrance4 = {
n, n, n, n, n, n, n, n,
n, n, n, n, n, n, n, e,
n, n, n, n, n, n, W, e,
n, n, n, n, n, W, W, e,
n, n, n, n, W, W, W, e,
n, n, n, W, W, W, W, e,
n, n, W, W, W, W, e, e,
n, e, e, e, e, e, e, e
};
private static short[][] entranceVariants = {
entrance1,
entrance2,
entrance3,
entrance4
};
private void buildEntrance(){
entrance = 16 + 25*width();
//entrance area
int NW = entrance - 7 - 7*width();
int NE = entrance + 7 - 7*width();
int SE = entrance + 7 + 7*width();
int SW = entrance - 7 + 7*width();
short[] entranceTiles = Random.oneOf(entranceVariants);
for (int i = 0; i < entranceTiles.length; i++){
if (i % 8 == 0 && i != 0){
NW += (width() - 8);
NE += (width() + 8);
SE -= (width() - 8);
SW -= (width() + 8);
}
if (entranceTiles[i] != n) map[NW] = map[NE] = map[SE] = map[SW] = entranceTiles[i];
NW++; NE--; SW++; SE--;
}
Painter.set(this, entrance, Terrain.ENTRANCE);
}
private static short[] corner1 = {
W, W, W, W, W, W, W, W, W, W,
W, s, s, s, e, e, e, W, W, W,
W, s, s, s, W, W, e, e, W, W,
W, s, s, s, W, W, W, e, e, W,
W, e, W, W, W, W, W, W, e, n,
W, e, W, W, W, W, W, n, n, n,
W, e, e, W, W, W, n, n, n, n,
W, W, e, e, W, n, n, n, n, n,
W, W, W, e, e, n, n, n, n, n,
W, W, W, W, n, n, n, n, n, n,
};
private static short[] corner2 = {
W, W, W, W, W, W, W, W, W, W,
W, s, s, s, W, W, W, W, W, W,
W, s, s, s, e, e, e, e, e, e,
W, s, s, s, W, W, W, W, W, e,
W, W, e, W, W, W, W, W, W, e,
W, W, e, W, W, W, W, n, n, n,
W, W, e, W, W, W, n, n, n, n,
W, W, e, W, W, n, n, n, n, n,
W, W, e, W, W, n, n, n, n, n,
W, W, e, e, e, n, n, n, n, n,
};
private static short[] corner3 = {
W, W, W, W, W, W, W, W, W, W,
W, s, s, s, W, e, e, e, W, W,
W, s, s, s, e, e, W, e, W, W,
W, s, s, s, W, W, W, e, W, W,
W, W, e, W, W, W, W, e, W, n,
W, e, e, W, W, W, W, e, e, n,
W, e, W, W, W, W, n, n, n, n,
W, e, e, e, e, e, n, n, n, n,
W, W, W, W, W, e, n, n, n, n,
W, W, W, W, n, n, n, n, n, n,
};
private static short[] corner4 = {
W, W, W, W, W, W, W, W, W, W,
W, s, s, s, W, W, W, W, W, W,
W, s, s, s, e, e, e, W, W, W,
W, s, s, s, W, W, e, W, W, W,
W, W, e, W, W, W, e, W, W, n,
W, W, e, W, W, W, e, e, n, n,
W, W, e, e, e, e, e, n, n, n,
W, W, W, W, W, e, n, n, n, n,
W, W, W, W, W, n, n, n, n, n,
W, W, W, W, n, n, n, n, n, n,
};
private static short[][] cornerVariants = {
corner1,
corner2,
corner3,
corner4
};
private void buildCorners(){
int NW = 2 + 11*width();
int NE = 30 + 11*width();
int SE = 30 + 39*width();
int SW = 2 + 39*width();
short[] cornerTiles = Random.oneOf(cornerVariants);
for(int i = 0; i < cornerTiles.length; i++){
if (i % 10 == 0 && i != 0){
NW += (width() - 10);
NE += (width() + 10);
SE -= (width() - 10);
SW -= (width() + 10);
}
if (cornerTiles[i] != n) map[NW] = map[NE] = map[SE] = map[SW] = cornerTiles[i];
NW++; NE--; SW++; SE--;
}
}
/**
* Visual Effects
*/
public static class CityEntrance extends CustomTilemap{
{
texture = Assets.CAVES_BOSS;
}
private static short[] entryWay = new short[]{
-1, 7, 7, 7, -1,
-1, 1, 2, 3, -1,
8, 1, 2, 3, 12,
16, 9, 10, 11, 20,
16, 16, 18, 20, 20,
16, 17, 18, 19, 20,
16, 16, 18, 20, 20,
16, 17, 18, 19, 20,
16, 16, 18, 20, 20,
16, 17, 18, 19, 20,
24, 25, 26, 27, 28
};
@Override
public Tilemap create() {
Tilemap v = super.create();
int[] data = new int[tileW*tileH];
int entryPos = 0;
for (int i = 0; i < data.length; i++){
//override the entryway
if (i % tileW == tileW/2 - 2){
data[i++] = entryWay[entryPos++];
data[i++] = entryWay[entryPos++];
data[i++] = entryWay[entryPos++];
data[i++] = entryWay[entryPos++];
data[i] = entryWay[entryPos++];
//otherwise check if we are on row 2 or 3, in which case we need to override walls
} else {
if (i / tileW == 2) data[i] = 13;
else if (i / tileW == 3) data[i] = 21;
else data[i] = -1;
}
}
v.map( data, tileW );
return v;
}
}
public static class EntranceOverhang extends CustomTilemap{
{
texture = Assets.CAVES_BOSS;
}
private static short[] entryWay = new short[]{
0, 7, 7, 7, 4,
0, 15, 15, 15, 4,
8, 23, 23, 23, 12,
-1, -1, -1, -1, -1,
-1, 6, -1, 14, -1,
-1, -1, -1, -1, -1,
-1, 6, -1, 14, -1,
-1, -1, -1, -1, -1,
-1, 6, -1, 14, -1,
-1, -1, -1, -1, -1,
-1, -1, -1, -1, -1,
};
@Override
public Tilemap create() {
Tilemap v = super.create();
int[] data = new int[tileW*tileH];
int entryPos = 0;
for (int i = 0; i < data.length; i++){
//copy over this row of the entryway
if (i % tileW == tileW/2 - 2){
data[i++] = entryWay[entryPos++];
data[i++] = entryWay[entryPos++];
data[i++] = entryWay[entryPos++];
data[i++] = entryWay[entryPos++];
data[i] = entryWay[entryPos++];
} else {
data[i] = -1;
}
}
v.map( data, tileW );
return v;
}
}
public static class ArenaVisuals extends CustomTilemap {
{
texture = Assets.CAVES_BOSS;
}
@Override
public Tilemap create() {
Tilemap v = super.create();
updateState( );
return v;
}
public void updateState( ){
if (vis != null){
int[] data = new int[tileW*tileH];
int j = Dungeon.level.width() * tileY;
for (int i = 0; i < data.length; i++){
if (Dungeon.level.map[j] == Terrain.EMPTY_SP) {
for (int k : pylonPositions) {
if (k == j) {
if (Dungeon.level.locked
&& !(Actor.findChar(k) instanceof Pylon)) {
data[i] = 38;
} else {
data[i] = -1;
}
} else if (Dungeon.level.adjacent(k, j)) {
int w = Dungeon.level.width;
data[i] = 54 + (j % w + 8 * (j / w)) - (k % w + 8 * (k / w));
}
}
} else if (Dungeon.level.map[j] == Terrain.INACTIVE_TRAP){
data[i] = 37;
} else if (gate.inside(Dungeon.level.cellToPoint(j))){
int idx = Dungeon.level.solid[j] ? 40 : 32;
data[i++] = idx++;
data[i++] = idx++;
data[i++] = idx++;
data[i++] = idx++;
data[i] = idx;
j += 4;
} else {
data[i] = -1;
}
j++;
}
vis.map(data, tileW);
}
}
@Override
public String name(int tileX, int tileY) {
int i = tileX + tileW*(tileY + this.tileY);
if (Dungeon.level.map[i] == Terrain.INACTIVE_TRAP){
return Messages.get(CavesBossLevel.class, "wires_name");
} else if (gate.inside(Dungeon.level.cellToPoint(i))){
return Messages.get(CavesBossLevel.class, "gate_name");
}
return super.name(tileX, tileY);
}
@Override
public String desc(int tileX, int tileY) {
int i = tileX + tileW*(tileY + this.tileY);
if (Dungeon.level.map[i] == Terrain.INACTIVE_TRAP){
return Messages.get(CavesBossLevel.class, "wires_desc");
} else if (gate.inside(Dungeon.level.cellToPoint(i))){
if (Dungeon.level.solid[i]){
return Messages.get(CavesBossLevel.class, "gate_desc");
} else {
return Messages.get(CavesBossLevel.class, "gate_desc_broken");
}
}
return super.desc(tileX, tileY);
}
@Override
public Image image(int tileX, int tileY) {
int i = tileX + tileW*(tileY + this.tileY);
for (int k : pylonPositions){
if (Dungeon.level.distance(i, k) <= 1){
return null;
}
}
return super.image(tileX, tileY);
}
}
public static class PylonEnergy extends Blob {
@Override
protected void evolve() {
int cell;
for (int i=area.top-1; i <= area.bottom; i++) {
for (int j = area.left-1; j <= area.right; j++) {
cell = j + i* Dungeon.level.width();
if (Dungeon.level.insideMap(cell)) {
off[cell] = cur[cell];
volume += off[cell];
if (off[cell] > 0){
Char ch = Actor.findChar(cell);
if (ch != null && !(ch instanceof NewDM300)) {
Sample.INSTANCE.play( Assets.SND_LIGHTNING );
ch.damage( Random.NormalIntRange(5, 15), Electricity.class);
ch.sprite.flash();
if (ch == Dungeon.hero && !ch.isAlive()) {
Dungeon.fail(NewDM300.class);
GLog.n( Messages.get(Electricity.class, "ondeath") );
}
}
}
}
}
}
}
@Override
public void fullyClear() {
super.fullyClear();
energySourceSprite = null;
}
private static CharSprite energySourceSprite = null;
private static Emitter.Factory DIRECTED_SPARKS = new Emitter.Factory() {
@Override
public void emit(Emitter emitter, int index, float x, float y) {
if (energySourceSprite == null){
for (Char c : Actor.chars()){
if (c instanceof Pylon && c.alignment != Char.Alignment.NEUTRAL){
energySourceSprite = c.sprite;
break;
} else if (c instanceof DM300){
energySourceSprite = c.sprite;
}
}
}
float dist = Math.abs(energySourceSprite.x - x) + Math.abs(energySourceSprite.y - y);
dist /= DungeonTilemap.SIZE;
SparkParticle s = ((SparkParticle) emitter.recycle(SparkParticle.class));
s.reset(x, y);
s.setMaxSize( 8 - dist/6 );
}
@Override
public boolean lightMode() {
return true;
}
};
@Override
public String tileDesc() {
return Messages.get(CavesBossLevel.class, "energy_desc");
}
@Override
public void use( BlobEmitter emitter ) {
super.use( emitter );
energySourceSprite = null;
emitter.bound.set( 4/16f, 4/16f, 12/16f, 12/16f);
emitter.pour(DIRECTED_SPARKS, 0.2f);
}
}
}

View File

@ -96,7 +96,7 @@ public class Terrain {
flags[EMPTY_DECO] = flags[EMPTY];
flags[LOCKED_EXIT] = SOLID;
flags[UNLOCKED_EXIT]= PASSABLE;
flags[SIGN] = PASSABLE | FLAMABLE;
flags[SIGN] = SOLID; //Currently these are unused except for visual tile overrides where we want terrain to be solid with no other properties
flags[WELL] = AVOID;
flags[STATUE] = SOLID;
flags[STATUE_SP] = flags[STATUE];

View File

@ -22,11 +22,23 @@
package com.shatteredpixel.shatteredpixeldungeon.sprites;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.NewDM300;
import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.BlastParticle;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.SparkParticle;
import com.watabou.noosa.Camera;
import com.watabou.noosa.TextureFilm;
import com.watabou.noosa.audio.Sample;
import com.watabou.noosa.particles.Emitter;
import com.watabou.utils.Callback;
public class DM300Sprite extends MobSprite {
private Animation slam;
private Emitter superchargeSparks;
public DM300Sprite() {
super();
@ -43,19 +55,109 @@ public class DM300Sprite extends MobSprite {
attack = new Animation( 15, false );
attack.frames( frames, 4, 5, 6 );
slam = attack.clone();
zap = attack.clone();
die = new Animation( 20, false );
die.frames( frames, 0, 7, 0, 7, 0, 7, 0, 7, 0, 7, 0, 7, 8 );
play( idle );
}
public void zap( int cell ) {
turnTo( ch.pos , cell );
play( zap );
MagicMissile.boltFromChar( parent,
MagicMissile.TOXIC_VENT,
this,
cell,
new Callback() {
@Override
public void call() {
((NewDM300)ch).onZapComplete();
}
} );
Sample.INSTANCE.play( Assets.SND_PUFF );
}
public void slam( int cell ){
turnTo( ch.pos , cell );
play( slam );
Sample.INSTANCE.play( Assets.SND_ROCKS );
Camera.main.shake( 3, 0.7f );
}
@Override
public void onComplete( Animation anim ) {
if (anim == zap || anim == slam){
idle();
}
if (anim == slam){
((NewDM300)ch).onSlamComplete();
}
super.onComplete( anim );
if (anim == die) {
emitter().burst( Speck.factory( Speck.WOOL ), 15 );
Sample.INSTANCE.play(Assets.SND_BLAST);
emitter().burst( BlastParticle.FACTORY, 25 );
}
}
@Override
public void link(Char ch) {
super.link(ch);
superchargeSparks = emitter();
superchargeSparks.autoKill = false;
superchargeSparks.pour(SparkParticle.STATIC, 0.05f);
superchargeSparks.on = false;
if (ch instanceof NewDM300 && ((NewDM300) ch).isSupercharged()){
tint(1, 0, 0, 0.33f);
superchargeSparks.on = true;
}
}
@Override
public void update() {
super.update();
if (ch instanceof NewDM300){
superchargeSparks.on = ((NewDM300) ch).isSupercharged();
}
if (superchargeSparks != null){
superchargeSparks.visible = visible;
}
}
@Override
public void die() {
super.die();
if (superchargeSparks != null){
superchargeSparks.on = false;
}
}
@Override
public void kill() {
super.kill();
if (superchargeSparks != null){
superchargeSparks.killAndErase();
}
}
@Override
public void resetColor() {
super.resetColor();
if (ch instanceof NewDM300 && ((NewDM300) ch).isSupercharged()){
tint(1, 0, 0, 0.33f);
}
}

View File

@ -0,0 +1,93 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2019 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.sprites;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Pylon;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.BlastParticle;
import com.watabou.noosa.TextureFilm;
import com.watabou.noosa.audio.Sample;
public class PylonSprite extends MobSprite {
private Animation activeIdle;
public PylonSprite() {
super();
perspectiveRaise = 5/16f; //1 pixel less
renderShadow = false;
texture( Assets.PYLON );
TextureFilm frames = new TextureFilm( texture, 10, 20 );
idle = new Animation( 1, false );
idle.frames( frames, 0 );
activeIdle = new Animation( 1, false );
activeIdle.frames( frames, 1 );
run = idle.clone();
attack = idle.clone();
die = new Animation( 1, false );
die.frames( frames, 2 );
play( idle );
}
@Override
public void link(Char ch) {
super.link(ch);
if (ch instanceof Pylon && ch.alignment == Char.Alignment.ENEMY){
activate();
}
if (parent != null) parent.bringToFront(this);
renderShadow = false;
}
public void activate(){
idle = activeIdle.clone();
idle();
}
@Override
public void play(Animation anim) {
if (anim == die){
turnTo(ch.pos, ch.pos+1); //always face right to merge with custom tiles
emitter().burst(BlastParticle.FACTORY, 20);
Sample.INSTANCE.play(Assets.SND_BLAST);
}
super.play(anim);
}
@Override
public void onComplete(Animation anim) {
if (anim == attack){
flash();
}
super.onComplete(anim);
}
}

View File

@ -497,6 +497,23 @@ actors.mobs.dm300.def_verb=blocked
actors.mobs.dm300.rankings_desc=Crushed by the DM-300
actors.mobs.dm300.desc=This machine was created by the Dwarves several centuries ago. Later, Dwarves started to replace machines with golems, elementals and even demons. Eventually it led their civilization to the decline. The DM-300 and similar machines were typically used for construction and mining, and in some cases, for city defense.
actors.mobs.newdm300.name=DM-300
actors.mobs.newdm300.notice=UNAUTHORIZED PERSONNEL DETECTED!
actors.mobs.newdm300.shield=DM-300 pulls power from the exposed wires and shields itself!
actors.mobs.newdm300.vent=DM-300 fires a jet of toxic exhaust at you!
actors.mobs.newdm300.rocks=DM-300 slams the ground, loosening rocks from the ceiling!
actors.mobs.newdm300.charging=SUSTAINING DAMAGE! CHARGING FROM POWER GRID...
actors.mobs.newdm300.supercharged=SUPERCHARGE COMPLETE, OPERATING AT 200% POWER!
actors.mobs.newdm300.charge_lost=POWER GRID DAMAGED, REVERTING TO LOCAL POWER!
actors.mobs.newdm300.pylons_destroyed=ALERT, INSTABILITY DETECTED IN POWER GRID!
actors.mobs.newdm300.rankings_desc=Crushed by the DM-300
actors.mobs.newdm300.immune=IMMUNE
actors.mobs.newdm300.def_verb=blocked
actors.mobs.newdm300.defeated=CRITICAL DAMAGE! ATTEMPTING SHUTDO-
actors.mobs.newdm300.desc=The DM-300 is the largest and most powerful 'defense machine' that the dwarves ever built. Such an awesome machine is difficult to manufacture, so the dwarves only ever made a few to guard the entrances to their their underground metropolis.\n\nIt is equipped with vents to jet its toxic exhaust fumes and a high power drill that it can use both to attack and disrupt the earth. DM-300 can also connect to an energy grid, further enhancing its power.
actors.mobs.newdm300.desc_supercharged=DM-300 is currently charged full of electrical energy, In this form DM-300 cannot be damaged and moves at double speed! Additionally, its dril now spins fast enough for it to _tunnel through solid rock,_ though it moves much more slowly when doing this.\n\nAttacking DM-300 directly is pointless while it is supercharged, but _something in the area must be providing it with this energy,_ destroying that may weaken it.
actors.mobs.newdm300$fallingrocks.desc=Loose rocks are tumbling down from the ceiling here, it looks like its about to collapse!
actors.mobs.elemental$fire.name=fire elemental
actors.mobs.elemental$fire.desc=Elementals are chaotic creatures that are often created when powerful occult magic isn't properly controlled. Elementals have minimal intelligence, and are usually associated with a particular type of magic.\n\nFire elementals are a common type of elemental which deals damage with fiery magic. They will set their target ablaze with melee attacks, and can occasionally shoot bolts of fire as well.
actors.mobs.elemental$newbornfire.name=newborn fire elemental
@ -589,6 +606,10 @@ actors.mobs.monk$focus.desc=This monk is perfectly honed in on their target, and
actors.mobs.piranha.name=giant piranha
actors.mobs.piranha.desc=These carnivorous fish are not natural inhabitants of underground pools. They were bred specifically to protect flooded treasure vaults.
actors.mobs.pylon.name=power pylon
actors.mobs.pylon.desc_inactive=A power pylon, meant to help regulate the electricity which powers the machinery in this area.\n\nThe pylon is currently inactive and immune to damage.
actors.mobs.pylon.desc_active=A power pylon, meant to help regulate the electricity which powers the machinery in this area. The pylon is currently surging with electrical energy, this must be what's supercharging DM-300!\n\nIn this state pylons are vulnerable, but will arc electricity to nearby cells in a clockwise pattern. Due to their heavy metal construction, pylons are resistant to large amounts of damage.
actors.mobs.rat.name=marsupial rat
actors.mobs.rat.desc=Marsupial rats are aggressive but rather weak denizens of the sewers. They have a nasty bite, but are only life threatening in large numbers.

View File

@ -128,9 +128,17 @@ levels.traps.worndarttrap.desc=A small dart-blower must be hidden nearby, activa
###levels
levels.cavesbosslevel.wires_name=Exposed wiring
levels.cavesbosslevel.wires_desc=The ground is partially dug up here, showing some large exposed wires. They must be connecting the various electrical machines together.\n\nThe wires must have some current running through them. If DM-300 steps here it may be able to draw power from there.
levels.cavesbosslevel.energy_desc=The ground here is sparking with electricity, and is harmful to step on. The sparks seem to be stronger in some places than others... perhaps they lead to the power source?
levels.cavesbosslevel.gate_name=Metal gate
levels.cavesbosslevel.gate_desc=A large metal gate that blocks the path into the dwarven metropolis. The metal box in the center is producing a loud humming noice, it must be connected to the circuitry and machines nearby. Perhaps destroying DM-300 will open the gate?
levels.cavesbosslevel.gate_desc_broken=The gate must have been connected to DM-300 in some way, as it exploded when DM-300 was defeated. Now only broken pieces remain.
levels.cavesbosslevel.water_desc=With all the electricity around here water might not always be safe...
levels.caveslevel.grass_name=Fluorescent moss
levels.caveslevel.high_grass_name=Fluorescent mushrooms
levels.caveslevel.water_name=Freezing cold water.
levels.caveslevel.water_name=Freezing cold water
levels.caveslevel.entrance_desc=The ladder leads up to the upper depth.
levels.caveslevel.exit_desc=The ladder leads down to the lower depth.
levels.caveslevel.high_grass_desc=Huge mushrooms block the view.
@ -195,7 +203,7 @@ levels.level.statue_desc=Someone wanted to adorn this place, but failed, obvious
levels.level.alchemy_desc=This pot is filled with magical water. Items can be mixed into the pot to create something new!
levels.level.empty_well_desc=The well has run dry.
levels.prisonlevel.water_name=Dark cold water.
levels.prisonlevel.water_name=Dark cold water
levels.prisonlevel.empty_deco_desc=There are old blood stains on the floor.
levels.prisonlevel.bookshelf_desc=This is probably a vestige of a prison library. Might it burn?