diff --git a/android/src/main/assets/custom_tiles/prison_exit_new.png b/android/src/main/assets/custom_tiles/prison_exit_new.png new file mode 100644 index 000000000..f7251f44b Binary files /dev/null and b/android/src/main/assets/custom_tiles/prison_exit_new.png differ diff --git a/android/src/main/assets/custom_tiles/prison_exit.png b/android/src/main/assets/custom_tiles/prison_exit_old.png similarity index 100% rename from android/src/main/assets/custom_tiles/prison_exit.png rename to android/src/main/assets/custom_tiles/prison_exit_old.png diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Assets.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Assets.java index 26d11f65c..0cd629b61 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Assets.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Assets.java @@ -124,10 +124,11 @@ public class Assets { public static final String LOADING_CITY = "loading_city.png"; public static final String LOADING_HALLS = "loading_halls.png"; - public static final String WEAK_FLOOR = "custom_tiles/weak_floor.png"; - public static final String SEWER_BOSS = "custom_tiles/sewer_boss.png"; - public static final String PRISON_QUEST = "custom_tiles/prison_quests.png"; - public static final String PRISON_EXIT = "custom_tiles/prison_exit.png"; + public static final String WEAK_FLOOR = "custom_tiles/weak_floor.png"; + public static final String SEWER_BOSS = "custom_tiles/sewer_boss.png"; + 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 BUFFS_SMALL = "buffs.png"; public static final String BUFFS_LARGE = "large_buffs.png"; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java index a2553eb7d..a8a9f63de 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java @@ -53,7 +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.PrisonBossLevel; +import com.shatteredpixel.shatteredpixeldungeon.levels.NewPrisonBossLevel; import com.shatteredpixel.shatteredpixeldungeon.levels.PrisonLevel; import com.shatteredpixel.shatteredpixeldungeon.levels.SewerBossLevel; import com.shatteredpixel.shatteredpixeldungeon.levels.SewerLevel; @@ -253,7 +253,7 @@ public class Dungeon { level = new PrisonLevel(); break; case 10: - level = new PrisonBossLevel(); + level = new NewPrisonBossLevel(); break; case 11: case 12: diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ShatteredPixelDungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ShatteredPixelDungeon.java index abbcf305e..35d23d645 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ShatteredPixelDungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ShatteredPixelDungeon.java @@ -102,6 +102,12 @@ public class ShatteredPixelDungeon extends Game { com.watabou.utils.Bundle.addAlias( com.shatteredpixel.shatteredpixeldungeon.levels.rooms.sewerboss.SewerBossEntranceRoom.class, "com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.SewerBossEntranceRoom" ); + com.watabou.utils.Bundle.addAlias( + com.shatteredpixel.shatteredpixeldungeon.levels.OldPrisonBossLevel.class, + "com.shatteredpixel.shatteredpixeldungeon.levels.OldPrisonBossLevel" ); + com.watabou.utils.Bundle.addAlias( + com.shatteredpixel.shatteredpixeldungeon.actors.mobs.OldTengu.class, + "com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Tengu" ); } @Override diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/NewTengu.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/NewTengu.java new file mode 100644 index 000000000..d5b4da00c --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/NewTengu.java @@ -0,0 +1,315 @@ +/* + * 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 + */ + +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.LockedFloor; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass; +import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; +import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; +import com.shatteredpixel.shatteredpixeldungeon.items.TomeOfMastery; +import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose; +import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.LloydsBeacon; +import com.shatteredpixel.shatteredpixeldungeon.levels.Level; +import com.shatteredpixel.shatteredpixeldungeon.levels.NewPrisonBossLevel; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; +import com.shatteredpixel.shatteredpixeldungeon.sprites.TenguSprite; +import com.shatteredpixel.shatteredpixeldungeon.ui.BossHealthBar; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.noosa.audio.Sample; +import com.watabou.utils.Bundle; +import com.watabou.utils.Random; + +//TODO currently has attack/defence stats for testing, need to add those +public class NewTengu extends Mob { + + { + spriteClass = TenguSprite.class; + + HP = HT = 160; + EXP = 20; + defenseSkill = 0; + + HUNTING = new Hunting(); + + flying = true; //doesn't literally fly, but he is fleet-of-foot enough to avoid hazards + + properties.add(Property.BOSS); + } + + @Override + protected void onAdd() { + //when he's removed and re-added to the fight, his time is always set to now. + spend(-cooldown()); + super.onAdd(); + } + + @Override + public int damageRoll() { + return Random.NormalIntRange( 0, 0 ); + } + + @Override + public int attackSkill( Char target ) { + return 20; + } + + @Override + public int drRoll() { + return Random.NormalIntRange(0, 0); + } + + @Override + public void damage(int dmg, Object src) { + NewPrisonBossLevel.State state = ((NewPrisonBossLevel)Dungeon.level).state(); + + int hpBracket; + if (state == NewPrisonBossLevel.State.FIGHT_START){ + hpBracket = 20; + } else { + hpBracket = 20; + } + + int beforeHitHP = HP; + super.damage(dmg, src); + dmg = beforeHitHP - HP; + + //tengu cannot be hit through multiple brackets at a time + if ((beforeHitHP/hpBracket - HP/hpBracket) >= 2){ + HP = hpBracket * ((beforeHitHP/hpBracket)-1); + } + + LockedFloor lock = Dungeon.hero.buff(LockedFloor.class); + if (lock != null) { + int multiple = state == NewPrisonBossLevel.State.FIGHT_START ? 1 : 4; + lock.addTime(dmg*multiple); + } + + //phase 2 of the fight is over + if (HP == 0 && state == NewPrisonBossLevel.State.FIGHT_ARENA) { + //let full attack action complete first + Actor.add(new Actor() { + + { + actPriority = VFX_PRIO; + } + + @Override + protected boolean act() { + Actor.remove(this); + ((NewPrisonBossLevel)Dungeon.level).progress(); + return true; + } + }); + return; + } + + //phase 1 of the fight is over + if (state == NewPrisonBossLevel.State.FIGHT_START && HP <= HT/2){ + HP = (HT/2)-1; + yell(Messages.get(this, "interesting")); + ((NewPrisonBossLevel)Dungeon.level).progress(); + BossHealthBar.bleed(true); + + //if tengu has lost a certain amount of hp, jump + } else if (beforeHitHP / hpBracket != HP / hpBracket) { + jump(); + } + } + + @Override + public boolean isAlive() { + return HP > 0 || Dungeon.level.mobs.contains(this); //Tengu has special death rules, see prisonbosslevel.progress() + } + + @Override + public void die( Object cause ) { + + if (Dungeon.hero.subClass == HeroSubClass.NONE) { + Dungeon.level.drop( new TomeOfMastery(), pos ).sprite.drop(); + } + + GameScene.bossSlain(); + super.die( cause ); + + Badges.validateBossSlain(); + + LloydsBeacon beacon = Dungeon.hero.belongings.getItem(LloydsBeacon.class); + if (beacon != null) { + beacon.upgrade(); + } + + yell( Messages.get(this, "defeated") ); + } + + @Override + protected boolean canAttack( Char enemy ) { + return new Ballistica( pos, enemy.pos, Ballistica.PROJECTILE).collisionPos == enemy.pos; + } + + //tengu's attack is always visible + @Override + protected boolean doAttack(Char enemy) { + sprite.attack( enemy.pos ); + spend( attackDelay() ); + return false; + } + + private void jump() { + + //in case tengu hasn't had a chance to act yet + if (fieldOfView == null || fieldOfView.length != Dungeon.level.length()){ + fieldOfView = new boolean[Dungeon.level.length()]; + Dungeon.level.updateFieldOfView( this, fieldOfView ); + } + + if (enemy == null) enemy = chooseEnemy(); + if (enemy == null) return; + + int newPos; + if (Dungeon.level instanceof NewPrisonBossLevel){ + NewPrisonBossLevel level = (NewPrisonBossLevel) Dungeon.level; + + //if we're in phase 1, want to warp around within the room + if (level.state() == NewPrisonBossLevel.State.FIGHT_START) { + + level.cleanTenguCell(); + + do { + newPos = ((NewPrisonBossLevel)Dungeon.level).randomTenguCellPos(); + } while ( (level.distance(newPos, enemy.pos) <= 2 || Actor.findChar(newPos) != null)); + + if (level.heroFOV[pos]) CellEmitter.get( pos ).burst( Speck.factory( Speck.WOOL ), 6 ); + + sprite.move( pos, newPos ); + move( newPos ); + + if (level.heroFOV[newPos]) CellEmitter.get( newPos ).burst( Speck.factory( Speck.WOOL ), 6 ); + Sample.INSTANCE.play( Assets.SND_PUFF ); + + spend( 1 / speed() ); + + float fill = 0.9f - 0.5f*((HP-80)/80f); + level.placeTrapsInTenguCell(fill); + + return; + + //otherwise.. TODO! + } else { + do { + newPos = Random.Int(level.length()); + } while ( + level.solid[newPos] || + level.distance(newPos, enemy.pos) < 6 || + Actor.findChar(newPos) != null); + + if (level.heroFOV[pos]) CellEmitter.get( pos ).burst( Speck.factory( Speck.WOOL ), 6 ); + + sprite.move( pos, newPos ); + move( newPos ); + + if (level.heroFOV[newPos]) CellEmitter.get( newPos ).burst( Speck.factory( Speck.WOOL ), 6 ); + Sample.INSTANCE.play( Assets.SND_PUFF ); + + spend( 1 / speed() ); + } + + //if we're on another type of level + } else { + Level level = Dungeon.level; + + newPos = level.randomRespawnCell(); + + if (level.heroFOV[pos]) CellEmitter.get( pos ).burst( Speck.factory( Speck.WOOL ), 6 ); + + sprite.move( pos, newPos ); + move( newPos ); + + if (level.heroFOV[newPos]) CellEmitter.get( newPos ).burst( Speck.factory( Speck.WOOL ), 6 ); + Sample.INSTANCE.play( Assets.SND_PUFF ); + + spend( 1 / speed() ); + } + + } + + @Override + public void notice() { + super.notice(); + if (!BossHealthBar.isAssigned()) { + BossHealthBar.assignBoss(this); + if (HP <= HT/2) BossHealthBar.bleed(true); + if (HP == HT) { + yell(Messages.get(this, "notice_mine", Dungeon.hero.givenName())); + for (Char ch : Actor.chars()){ + if (ch instanceof DriedRose.GhostHero){ + GLog.n("\n"); + ((DriedRose.GhostHero) ch).sayBoss(); + } + } + } else { + yell(Messages.get(this, "notice_face", Dungeon.hero.givenName())); + } + } + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + BossHealthBar.assignBoss(this); + if (HP <= HT/2) BossHealthBar.bleed(true); + } + + //tengu is always hunting + private class Hunting extends Mob.Hunting{ + + @Override + public boolean act(boolean enemyInFOV, boolean justAlerted) { + enemySeen = enemyInFOV; + if (enemyInFOV && !isCharmedBy( enemy ) && canAttack( enemy )) { + + return doAttack( enemy ); + + } else { + + if (enemyInFOV) { + target = enemy.pos; + } else { + chooseEnemy(); + if (enemy != null) { + target = enemy.pos; + } + } + + spend( TICK ); + return true; + + } + } + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Tengu.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/OldTengu.java similarity index 91% rename from core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Tengu.java rename to core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/OldTengu.java index 6430b5cd2..3769e0c24 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/Tengu.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/OldTengu.java @@ -35,7 +35,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose; import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.LloydsBeacon; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfMagicMapping; import com.shatteredpixel.shatteredpixeldungeon.levels.Level; -import com.shatteredpixel.shatteredpixeldungeon.levels.PrisonBossLevel; +import com.shatteredpixel.shatteredpixeldungeon.levels.OldPrisonBossLevel; import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; import com.shatteredpixel.shatteredpixeldungeon.levels.traps.GrippingTrap; import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; @@ -48,7 +48,8 @@ import com.watabou.noosa.audio.Sample; import com.watabou.utils.Bundle; import com.watabou.utils.Random; -public class Tengu extends Mob { +//Exists to support pre-0.7.5 saves +public class OldTengu extends Mob { { spriteClass = TenguSprite.class; @@ -89,10 +90,10 @@ public class Tengu extends Mob { @Override public void damage(int dmg, Object src) { - PrisonBossLevel.State state = ((PrisonBossLevel)Dungeon.level).state(); + OldPrisonBossLevel.State state = ((OldPrisonBossLevel)Dungeon.level).state(); int hpBracket; - if (state == PrisonBossLevel.State.FIGHT_START){ + if (state == OldPrisonBossLevel.State.FIGHT_START){ hpBracket = 12; } else { hpBracket = 20; @@ -104,12 +105,12 @@ public class Tengu extends Mob { LockedFloor lock = Dungeon.hero.buff(LockedFloor.class); if (lock != null) { - int multiple = state == PrisonBossLevel.State.FIGHT_START ? 1 : 4; + int multiple = state == OldPrisonBossLevel.State.FIGHT_START ? 1 : 4; lock.addTime(dmg*multiple); } //phase 2 of the fight is over - if (HP == 0 && state == PrisonBossLevel.State.FIGHT_ARENA) { + if (HP == 0 && state == OldPrisonBossLevel.State.FIGHT_ARENA) { //let full attack action complete first Actor.add(new Actor() { @@ -120,7 +121,7 @@ public class Tengu extends Mob { @Override protected boolean act() { Actor.remove(this); - ((PrisonBossLevel)Dungeon.level).progress(); + ((OldPrisonBossLevel)Dungeon.level).progress(); return true; } }); @@ -128,10 +129,10 @@ public class Tengu extends Mob { } //phase 1 of the fight is over - if (state == PrisonBossLevel.State.FIGHT_START && HP <= HT/2){ + if (state == OldPrisonBossLevel.State.FIGHT_START && HP <= HT/2){ HP = (HT/2)-1; yell(Messages.get(this, "interesting")); - ((PrisonBossLevel)Dungeon.level).progress(); + ((OldPrisonBossLevel)Dungeon.level).progress(); BossHealthBar.bleed(true); //if tengu has lost a certain amount of hp, jump @@ -193,7 +194,7 @@ public class Tengu extends Mob { int newPos; //if we're in phase 1, want to warp around within the room - if (((PrisonBossLevel)Dungeon.level).state() == PrisonBossLevel.State.FIGHT_START) { + if (((OldPrisonBossLevel)Dungeon.level).state() == OldPrisonBossLevel.State.FIGHT_START) { //place new traps int tries; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/NewPrisonBossLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/NewPrisonBossLevel.java new file mode 100644 index 000000000..cf77bbd62 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/NewPrisonBossLevel.java @@ -0,0 +1,768 @@ +/* + * 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 + */ + +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.mobs.Mob; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.NewTengu; +import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; +import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; +import com.shatteredpixel.shatteredpixeldungeon.items.Heap; +import com.shatteredpixel.shatteredpixeldungeon.items.Item; +import com.shatteredpixel.shatteredpixeldungeon.items.keys.IronKey; +import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.HeavyBoomerang; +import com.shatteredpixel.shatteredpixeldungeon.levels.features.Maze; +import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter; +import com.shatteredpixel.shatteredpixeldungeon.levels.traps.TenguDartTrap; +import com.shatteredpixel.shatteredpixeldungeon.levels.traps.Trap; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.plants.Plant; +import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; +import com.shatteredpixel.shatteredpixeldungeon.tiles.CustomTilemap; +import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap; +import com.shatteredpixel.shatteredpixeldungeon.ui.TargetHealthIndicator; +import com.shatteredpixel.shatteredpixeldungeon.utils.BArray; +import com.watabou.noosa.Camera; +import com.watabou.noosa.Group; +import com.watabou.noosa.Tilemap; +import com.watabou.noosa.audio.Sample; +import com.watabou.noosa.tweeners.AlphaTweener; +import com.watabou.utils.Bundlable; +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 NewPrisonBossLevel extends Level { + + { + color1 = 0x6a723d; + color2 = 0x88924c; + + //the player should be able to see all of Tengu's arena + viewDistance = 12; + } + + public enum State { + START, + FIGHT_START, + TRAP_MAZES, + FIGHT_ARENA, + WON + } + + private State state; + private NewTengu tengu; + + public State state(){ + return state; + } + + @Override + public String tilesTex() { + return Assets.TILES_PRISON; + } + + @Override + public String waterTex() { + return Assets.WATER_PRISON; + } + + private static final String STATE = "state"; + private static final String TENGU = "tengu"; + private static final String STORED_ITEMS = "storeditems"; + private static final String TRIGGERED = "triggered"; + + @Override + public void storeInBundle( Bundle bundle ) { + super.storeInBundle(bundle); + bundle.put( STATE, state ); + bundle.put( TENGU, tengu ); + bundle.put( STORED_ITEMS, storedItems); + bundle.put(TRIGGERED, triggered ); + } + + @Override + public void restoreFromBundle( Bundle bundle ) { + super.restoreFromBundle(bundle); + state = bundle.getEnum( STATE, State.class ); + + //in some states tengu won't be in the world, in others he will be. + if (state == State.START || state == State.TRAP_MAZES) { + tengu = (NewTengu)bundle.get( TENGU ); + } else { + for (Mob mob : mobs){ + if (mob instanceof NewTengu) { + tengu = (NewTengu) mob; + break; + } + } + } + + for (Bundlable item : bundle.getCollection(STORED_ITEMS)){ + storedItems.add( (Item)item ); + } + + triggered = bundle.getBooleanArray(TRIGGERED); + } + + @Override + protected boolean build() { + setSize(32, 32); + + state = State.START; + setMapStart(); + + return true; + } + + private static final int ENTRANCE_POS = 10 + 4*32; + private static final Rect entranceRoom = new Rect(8, 2, 13, 8); + private static final Rect startHallway = new Rect(9, 7, 12, 24); + private static final Rect[] startCells = new Rect[]{ new Rect(5, 9, 10, 16), new Rect(11, 9, 16, 16), + new Rect(5, 15, 10, 22), new Rect(11, 15, 16, 22)}; + private static final Rect tenguCell = new Rect(6, 23, 15, 32); + private static final Point tenguCellCenter = new Point(10, 27); + private static final Point tenguCellDoor = new Point(10, 23); + private static final Point[] startTorches = new Point[]{ new Point(10, 2), + new Point(7, 9), new Point(13, 9), + new Point(7, 15), new Point(13, 15), + new Point(8, 23), new Point(12, 23)}; + + private void setMapStart(){ + entrance = ENTRANCE_POS; + exit = 0; + + Painter.fill(this, 0, 0, 32, 32, Terrain.WALL); + + //Start + Painter.fill(this, entranceRoom, Terrain.WALL); + Painter.fill(this, entranceRoom, 1, Terrain.EMPTY); + Painter.set(this, entrance, Terrain.ENTRANCE); + + Painter.fill(this, startHallway, Terrain.WALL); + Painter.fill(this, startHallway, 1, Terrain.EMPTY); + + Painter.set(this, startHallway.left+1, startHallway.top, Terrain.DOOR); + + for (Rect r : startCells){ + Painter.fill(this, r, Terrain.WALL); + Painter.fill(this, r, 1, Terrain.EMPTY); + } + + Painter.set(this, startHallway.left, startHallway.top+5, Terrain.DOOR); + Painter.set(this, startHallway.right-1, startHallway.top+5, Terrain.DOOR); + Painter.set(this, startHallway.left, startHallway.top+11, Terrain.DOOR); + Painter.set(this, startHallway.right-1, startHallway.top+11, Terrain.DOOR); + + Painter.fill(this, tenguCell, Terrain.WALL); + Painter.fill(this, tenguCell, 1, Terrain.EMPTY); + + Painter.set(this, tenguCell.left+4, tenguCell.top, Terrain.LOCKED_DOOR); + + drop(new IronKey(10), randomPrisonCellPos()); + + for (Point p : startTorches){ + Painter.set(this, p, Terrain.WALL_DECO); + } + } + + private static final Rect mazeHallway = new Rect(9, 6, 12, 24); + private static final Rect[] mazeCells = new Rect[]{ new Rect(1, 9, 10, 16), new Rect(11, 9, 20, 16), + new Rect(3, 15, 10, 22), new Rect(11, 15, 18, 22)}; + private static final Point[] mazeKeySpawns = new Point[]{new Point(mazeCells[0].left+1, mazeCells[0].top+3), + new Point(mazeCells[1].right-2, mazeCells[1].top+3), + new Point(mazeCells[2].left+1, mazeCells[2].top+3), + new Point(mazeCells[3].right-2, mazeCells[3].top+3)}; + private static final Point[] mazeCellDoors = new Point[]{new Point(mazeCells[0].right-1, mazeCells[0].top+3), + new Point(mazeCells[1].left, mazeCells[1].top+3), + new Point(mazeCells[2].right-1, mazeCells[2].top+3), + new Point(mazeCells[3].left, mazeCells[3].top+3)}; + private static final Point[] mazeTorches = new Point[]{ new Point(5, 9), new Point(15, 9), + new Point(6, 15), new Point(14, 15), + new Point(8, 23), new Point(12, 23)}; + + private void setMapMazes(){ + exit = entrance = 0; + + Painter.fill(this, 0, 0, 32, 32, Terrain.WALL); + + Painter.fill(this, mazeHallway, Terrain.WALL); + Painter.fill(this, mazeHallway, 1, Terrain.EMPTY); + + for (Rect r : mazeCells){ + Painter.fill(this, r, Terrain.WALL); + Painter.fill(this, r, 1, Terrain.EMPTY); + } + + for (Point p : mazeCellDoors){ + Painter.set(this, p, Terrain.DOOR); + } + + Painter.fill(this, tenguCell, Terrain.WALL); + Painter.fill(this, tenguCell, 1, Terrain.EMPTY); + + Painter.set(this, tenguCell.left+4, tenguCell.top, Terrain.DOOR); + + Painter.set(this, mazeHallway.left+1, mazeHallway.top+2, Terrain.LOCKED_DOOR); + Painter.set(this, mazeHallway.left+1, mazeHallway.top+4, Terrain.LOCKED_DOOR); + Painter.set(this, mazeHallway.left+1, mazeHallway.top+8, Terrain.LOCKED_DOOR); + Painter.set(this, mazeHallway.left+1, mazeHallway.top+10, Terrain.LOCKED_DOOR); + + for (Point p : mazeKeySpawns){ + drop(new IronKey(10), pointToCell(p)); + } + + for (Point p : mazeTorches){ + Painter.set(this, p, Terrain.WALL_DECO); + } + } + + private static final Rect arena = new Rect(3, 1, 18, 16); + + private void setMapArena(){ + exit = entrance = 0; + + Painter.fill(this, 0, 0, 32, 32, Terrain.WALL); + + Painter.fill(this, arena, Terrain.WALL); + Painter.fillEllipse(this, arena, 1, Terrain.EMPTY); + + } + + private static int W = Terrain.WALL; + private static int D = Terrain.WALL_DECO; + private static int e = Terrain.EMPTY; + private static int C = Terrain.CHASM; + + private static final Point endStart = new Point( startHallway.left+2, startHallway.top+2); + private static final Point levelExit = new Point( endStart.x+12, endStart.y+8); + private static final int[] endMap = new int[]{ + W, W, W, W, W, W, W, W, W, W, W, W, W, W, + W, e, e, e, W, W, W, W, W, W, W, W, W, W, + W, e, e, e, e, e, e, e, e, W, W, W, W, W, + e, e, e, e, e, e, e, e, e, e, e, e, W, W, + e, e, e, e, e, e, e, e, e, e, e, e, e, W, + e, e, e, e, C, C, C, C, C, C, e, e, e, W, + e, W, C, C, C, C, C, C, C, C, C, e, e, W, + e, e, e, e, C, C, C, C, C, C, C, C, e, W, + e, e, e, e, e, e, e, C, C, C, C, C, e, W, + e, e, e, e, e, e, e, W, W, W, C, C, C, W, + e, e, e, e, e, e, W, W, W, W, C, C, C, W, + W, e, e, e, e, W, W, W, W, W, W, C, C, W, + W, W, W, W, W, W, W, W, W, W, W, C, C, W, + W, W, W, W, W, W, W, W, W, W, W, C, C, W, + W, D, W, W, W, W, W, W, W, W, W, C, C, W, + e, e, e, W, W, W, W, W, W, W, W, C, C, W, + e, e, e, W, W, W, W, W, W, W, W, C, C, W, + e, e, e, W, W, W, W, W, W, W, W, C, C, W, + e, e, e, W, W, W, W, W, W, W, W, C, C, W, + e, e, e, W, W, W, W, W, W, W, W, C, C, W, + e, e, e, W, W, W, W, W, W, W, W, C, C, W, + e, e, e, W, W, W, W, W, W, W, W, C, C, W, + W, W, W, W, W, W, W, W, W, W, W, C, C, W + }; + + private void setMapEnd(){ + + Painter.fill(this, 0, 0, 32, 32, Terrain.WALL); + + setMapStart(); + + for (Heap h : heaps.valueList()){ + if (h.peek() instanceof IronKey){ + h.destroy(); + } + } + + Painter.set(this, tenguCell.left+4, tenguCell.top, Terrain.DOOR); + + int cell = pointToCell(endStart); + int i = 0; + while (cell < length()){ + System.arraycopy(endMap, i, map, cell, 14); + i += 14; + cell += width(); + } + + exit = pointToCell(levelExit); + Painter.set(this, exit, Terrain.EXIT); + } + + //keep track of removed items as the level is changed. Dump them back into the level at the end. + private ArrayList storedItems = new ArrayList<>(); + + private void clearEntities(Rect safeArea){ + for (Heap heap : heaps.valueList()){ + if (safeArea == null || !safeArea.inside(cellToPoint(heap.pos))){ + storedItems.addAll(heap.items); + heap.destroy(); + } + } + + for (HeavyBoomerang.CircleBack b : Dungeon.hero.buffs(HeavyBoomerang.CircleBack.class)){ + if (safeArea == null || !safeArea.inside(cellToPoint(b.returnPos()))){ + storedItems.add(b.cancel()); + } + } + + for (Mob mob : Dungeon.level.mobs.toArray(new Mob[0])){ + if (mob != tengu && (safeArea == null || !safeArea.inside(cellToPoint(mob.pos)))){ + mob.destroy(); + if (mob.sprite != null) + mob.sprite.killAndErase(); + } + } + for (Plant plant : plants.valueList()){ + if (safeArea == null || !safeArea.inside(cellToPoint(plant.pos))){ + plants.remove(plant.pos); + } + } + } + + private void cleanMapState(){ + buildFlagMaps(); + cleanWalls(); + + BArray.setFalse(visited); + BArray.setFalse(mapped); + + for (Blob blob: blobs.values()){ + blob.fullyClear(); + } + addVisuals(); //this also resets existing visuals + traps.clear(); + + GameScene.resetMap(); + Dungeon.observe(); + } + + @Override + public Group addVisuals() { + super.addVisuals(); + PrisonLevel.addPrisonVisuals(this, visuals); + return visuals; + } + + public void progress(){ + switch (state){ + case START: + + //if something is occupying Tengu's space, wait and do nothing. + if (Actor.findChar(pointToCell(tenguCellCenter)) != null){ + return; + } + + seal(); + set(pointToCell(tenguCellDoor), Terrain.LOCKED_DOOR); + GameScene.updateMap(pointToCell(tenguCellDoor)); + + for (Mob m : mobs){ + //bring the first ally with you + if (m.alignment == Char.Alignment.ALLY && !m.properties().contains(Char.Property.IMMOVABLE)){ + m.pos = pointToCell(tenguCellDoor); //they should immediately walk out of the door + m.sprite.place(m.pos); + break; + } + } + + tengu.state = tengu.HUNTING; + tengu.pos = pointToCell(tenguCellCenter); //in the middle of the fight room + GameScene.add( tengu ); + tengu.notice(); + + state = State.FIGHT_START; + break; + + case FIGHT_START: + + clearEntities( tenguCell ); //clear anything not in tengu's cell + + Actor.remove(tengu); + mobs.remove(tengu); + TargetHealthIndicator.instance.target(null); + tengu.sprite.kill(); + + setMapMazes(); + cleanMapState(); + + GameScene.flash(0xFFFFFF); + Sample.INSTANCE.play(Assets.SND_BLAST); + + state = State.TRAP_MAZES; + break; + + case TRAP_MAZES: + + Dungeon.hero.interrupt(); + + clearEntities( arena ); //clear anything not in the arena + + setMapArena(); + cleanMapState(); + + tengu.state = tengu.HUNTING; + do { + tengu.pos = Random.Int(length()); + } while (solid[tengu.pos] || distance(tengu.pos, Dungeon.hero.pos) < 6); + GameScene.add(tengu); + tengu.notice(); + + GameScene.flash(0xFFFFFF); + Sample.INSTANCE.play(Assets.SND_BLAST); + + state = State.FIGHT_ARENA; + break; + + case FIGHT_ARENA: + + unseal(); + + CustomTilemap vis = new exitVisual(); + vis.pos(11, 9); + customTiles.add(vis); + GameScene.add(vis, false); + + vis = new exitVisualWalls(); + vis.pos(12, 9); + customWalls.add(vis); + GameScene.add(vis, true); + + Dungeon.hero.interrupt(); + Dungeon.hero.pos = tenguCell.left+4 + (tenguCell.top+2)*width(); + Dungeon.hero.sprite.interruptMotion(); + Dungeon.hero.sprite.place(Dungeon.hero.pos); + Camera.main.snapTo(Dungeon.hero.sprite.center()); + + tengu.pos = pointToCell(tenguCellCenter); + tengu.sprite.place(tengu.pos); + + //remove all mobs, but preserve allies + ArrayList allies = new ArrayList<>(); + for(Mob m : mobs.toArray(new Mob[0])){ + if (m.alignment == Char.Alignment.ALLY && !m.properties().contains(Char.Property.IMMOVABLE)){ + allies.add(m); + mobs.remove(m); + } + } + + setMapEnd(); + cleanMapState(); + + for (Mob m : allies){ + do{ + m.pos = randomTenguCellPos(); + } while (findMob(m.pos) != null); + if (m.sprite != null) m.sprite.place(m.pos); + mobs.add(m); + } + + tengu.die(Dungeon.hero); + + clearEntities(tenguCell); + + for (Item item : storedItems) { + drop(item, randomTenguCellPos()); + } + + GameScene.flash(0xFFFFFF); + Sample.INSTANCE.play(Assets.SND_BLAST); + + state = State.WON; + break; + } + } + + private boolean[] triggered = new boolean[]{false, false, false, false}; + + @Override + public void occupyCell(Char ch) { + super.occupyCell(ch); + + if (ch == Dungeon.hero){ + switch (state){ + case START: + if (cellToPoint(ch.pos).y > tenguCell.top){ + progress(); + } + break; + case TRAP_MAZES: + + for (int i = 0; i < mazeCellDoors.length; i++){ + if (ch.pos == pointToCell(mazeCellDoors[i]) && !triggered[i]){ + triggered[i] = true; + Maze.allowDiagonals = true; + boolean[][] maze = Maze.generate(mazeCells[i], map, width(), Terrain.WALL); + + for (int x = 1; x < maze.length-1; x++) { + for (int y = 1; y < maze[0].length-1; y++) { + if (maze[x][y] && !(x == 1 && y == 3)){ + int cell = mazeCells[i].left+x + width()*(mazeCells[i].top+y); + if (heaps.get(cell) == null){ + setTrap(new TenguDartTrap().hide(), cell); + Painter.set(this, cell, Terrain.SECRET_TRAP); + CellEmitter.get(cell).burst(Speck.factory(Speck.LIGHT), 2); + } + } + } + } + + FadingTraps f = new FadingTraps(); + f.setCoveringArea(mazeCells[i]); + GameScene.add(f, false); + f.startFade(2.5f); + + Sample.INSTANCE.play(Assets.SND_TELEPORT); + int roomCenter = (mazeCells[i].left + mazeCells[i].right)/2 + + (mazeCells[i].top + mazeCells[i].bottom)/2 * width(); + Camera.main.panTo(DungeonTilemap.tileCenterToWorld(roomCenter), 5f); + + Dungeon.hero.interrupt(); + } + } + + if (cellToPoint(ch.pos).y <= mazeHallway.top+2){ + progress(); + } + break; + } + } + } + + @Override + protected void createMobs() { + tengu = new NewTengu(); //We want to keep track of tengu independently of other mobs, he's not always in the level. + } + + public Actor respawner() { + return null; + } + + @Override + protected void createItems() { + Item item = Bones.get(); + if (item != null) { + drop( item, randomRespawnCell() ).setHauntedIfCursed(1f).type = Heap.Type.REMAINS; + } + } + + private int randomPrisonCellPos(){ + Rect room = startCells[Random.Int(startCells.length)]; + + return Random.IntRange(room.left+1, room.right-2) + + width()*Random.IntRange(room.top+1, room.bottom-2); + } + + public int randomTenguCellPos(){ + return Random.IntRange(tenguCell.left+1, tenguCell.right-2) + + width()*Random.IntRange(tenguCell.top+1, tenguCell.bottom-2); + } + + public void cleanTenguCell(){ + + traps.clear(); + Painter.fill(this, tenguCell, 1, Terrain.EMPTY); + buildFlagMaps(); + + } + + public void placeTrapsInTenguCell(float fill){ + + Point tenguPoint = cellToPoint(tengu.pos); + Point heroPoint = cellToPoint(Dungeon.hero.pos); + + PathFinder.setMapSize(7, 7); + + int tenguPos = tenguPoint.x-(tenguCell.left+1) + (tenguPoint.y-(tenguCell.top+1))*7; + int heroPos = heroPoint.x-(tenguCell.left+1) + (heroPoint.y-(tenguCell.top+1))*7; + + boolean[] trapsPatch; + + do { + trapsPatch = Patch.generate(7, 7, fill, 0, false); + + PathFinder.buildDistanceMap(tenguPos, BArray.not(trapsPatch, null)); + } while (PathFinder.distance[heroPos] > 6); + + PathFinder.setMapSize(width(), height()); + + for (int i = 0; i < trapsPatch.length; i++){ + if (trapsPatch[i]) { + int x = i % 7; + int y = i / 7; + int cell = x+tenguCell.left+1 + (y+tenguCell.top+1)*width(); + Level.set(cell, Terrain.SECRET_TRAP); + setTrap(new TenguDartTrap().hide(), cell); + CellEmitter.get(cell).burst(Speck.factory(Speck.LIGHT), 2); + } else { + + } + } + + GameScene.updateMap(); + + FadingTraps t = new FadingTraps(); + t.setCoveringArea(tenguCell); + GameScene.add(t, false); + t.startFade( 2.5f ); + } + + @Override + public int randomRespawnCell() { + int pos = ENTRANCE_POS; //random cell adjacent to the entrance. + int cell; + do { + cell = pos + PathFinder.NEIGHBOURS8[Random.Int(8)]; + } while (!passable[cell] || Actor.findChar(cell) != null); + return cell; + } + + @Override + public String tileName( int tile ) { + switch (tile) { + case Terrain.WATER: + return Messages.get(PrisonLevel.class, "water_name"); + default: + return super.tileName( tile ); + } + } + + @Override + public String tileDesc(int tile) { + switch (tile) { + case Terrain.EMPTY_DECO: + return Messages.get(PrisonLevel.class, "empty_deco_desc"); + case Terrain.BOOKSHELF: + return Messages.get(PrisonLevel.class, "bookshelf_desc"); + default: + return super.tileDesc( tile ); + } + } + + private class FadingTraps extends CustomTilemap { + + { + texture = Assets.TERRAIN_FEATURES; + } + + Rect area; + + public void setCoveringArea(Rect area){ + tileX = area.left; + tileY = area.top; + tileH = area.bottom - area.top; + tileW = area.right - area.left; + + this.area = area; + } + + @Override + public Tilemap create() { + Tilemap v = super.create(); + int[] data = new int[tileW*tileH]; + int cell; + Trap t; + int i = 0; + for (int y = tileY; y < tileY + tileH; y++){ + cell = tileX + y*width(); + for (int x = tileX; x < tileX + tileW; x++){ + t = traps.get(cell); + if (t != null){ + data[i] = t.color + t.shape*16; + } else { + data[i] = -1; + } + cell++; + i++; + } + } + + v.map( data, tileW ); + return v; + } + + public void startFade( float duration ){ + if (vis == null || vis.parent == null){ + return; + } + + vis.parent.add(new AlphaTweener(vis, 0f, duration){ + @Override + protected void onComplete() { + super.onComplete(); + vis.killAndErase(); + killAndErase(); + vis = null; + } + }); + } + } + + public static class exitVisual extends CustomTilemap { + + { + texture = Assets.PRISON_EXIT_NEW; + + tileW = 13; + tileH = 24; + } + + final int TEX_WIDTH = 512; + + @Override + public Tilemap create() { + Tilemap v = super.create(); + v.map(mapSimpleImage(0, 0, TEX_WIDTH), tileW); + return v; + } + + } + + public static class exitVisualWalls extends CustomTilemap { + + { + texture = Assets.PRISON_EXIT_NEW; + + tileW = 13; + tileH = 24; + } + + final int TEX_WIDTH = 512; + + @Override + public Tilemap create() { + Tilemap v = super.create(); + v.map(mapSimpleImage(13, 0, TEX_WIDTH), tileW); + return v; + } + + } + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/PrisonBossLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/OldPrisonBossLevel.java similarity index 98% rename from core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/PrisonBossLevel.java rename to core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/OldPrisonBossLevel.java index a725e4233..9936067d3 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/PrisonBossLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/OldPrisonBossLevel.java @@ -29,7 +29,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; -import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Tengu; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.OldTengu; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.keys.IronKey; @@ -55,7 +55,8 @@ import com.watabou.utils.Random; import java.util.ArrayList; -public class PrisonBossLevel extends Level { +//Exists to support pre-0.7.5 saves +public class OldPrisonBossLevel extends Level { { color1 = 0x6a723d; @@ -74,7 +75,7 @@ public class PrisonBossLevel extends Level { private static final int ARENA_DOOR = 5+25*32; private State state; - private Tengu tengu; + private OldTengu tengu; public State state(){ return state; @@ -112,11 +113,11 @@ public class PrisonBossLevel extends Level { //in some states tengu won't be in the world, in others he will be. if (state == State.START || state == State.MAZE) { - tengu = (Tengu)bundle.get( TENGU ); + tengu = (OldTengu)bundle.get( TENGU ); } else { for (Mob mob : mobs){ - if (mob instanceof Tengu) { - tengu = (Tengu) mob; + if (mob instanceof OldTengu) { + tengu = (OldTengu) mob; break; } } @@ -148,7 +149,7 @@ public class PrisonBossLevel extends Level { @Override protected void createMobs() { - tengu = new Tengu(); //We want to keep track of tengu independently of other mobs, he's not always in the level. + tengu = new OldTengu(); //We want to keep track of tengu independently of other mobs, he's not always in the level. } public Actor respawner() { @@ -280,7 +281,7 @@ public class PrisonBossLevel extends Level { } private void clearEntities(Room safeArea){ - for (Heap heap : heaps.values()){ + for (Heap heap : heaps.valueList()){ if (safeArea == null || !safeArea.inside(cellToPoint(heap.pos))){ storedItems.addAll(heap.items); heap.destroy(); @@ -300,7 +301,7 @@ public class PrisonBossLevel extends Level { mob.sprite.killAndErase(); } } - for (Plant plant : plants.values()){ + for (Plant plant : plants.valueList()){ if (safeArea == null || !safeArea.inside(cellToPoint(plant.pos))){ plants.remove(plant.pos); } @@ -613,7 +614,7 @@ public class PrisonBossLevel extends Level { public static class exitVisual extends CustomTilemap { { - texture = Assets.PRISON_EXIT; + texture = Assets.PRISON_EXIT_OLD; tileW = 12; tileH = 14; @@ -656,7 +657,7 @@ public class PrisonBossLevel extends Level { public static class exitVisualWalls extends CustomTilemap { { - texture = Assets.PRISON_EXIT; + texture = Assets.PRISON_EXIT_OLD; tileW = 12; tileH = 14; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/PoisonDartTrap.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/PoisonDartTrap.java index e9cec27eb..dcab6cfc5 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/PoisonDartTrap.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/PoisonDartTrap.java @@ -48,15 +48,23 @@ public class PoisonDartTrap extends Trap { return 8 + Math.round(2*Dungeon.depth / 3f); } + protected boolean canTarget( Char ch ){ + return true; + } + @Override public void activate() { Char target = Actor.findChar(pos); + if (target != null && !canTarget(target)){ + target = null; + } + //find the closest char that can be aimed at if (target == null){ for (Char ch : Actor.chars()){ Ballistica bolt = new Ballistica(pos, ch.pos, Ballistica.PROJECTILE); - if (bolt.collisionPos == ch.pos && + if (canTarget(ch) && bolt.collisionPos == ch.pos && (target == null || Dungeon.level.trueDistance(pos, ch.pos) < Dungeon.level.trueDistance(pos, target.pos))){ target = ch; } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/TenguDartTrap.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/TenguDartTrap.java new file mode 100644 index 000000000..120a6906a --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/traps/TenguDartTrap.java @@ -0,0 +1,43 @@ +/* + * 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 + */ + +package com.shatteredpixel.shatteredpixeldungeon.levels.traps; + +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.NewTengu; + +public class TenguDartTrap extends PoisonDartTrap { + + { + canBeHidden = true; + canBeSearched = false; + } + + @Override + protected int poisonAmount() { + return 9; //21 damage total + } + + @Override + protected boolean canTarget(Char ch) { + return !(ch instanceof NewTengu); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/ShadowCaster.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/ShadowCaster.java index c6abe5ef8..c83e509a6 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/ShadowCaster.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/ShadowCaster.java @@ -47,6 +47,10 @@ public final class ShadowCaster { } public static void castShadow( int x, int y, boolean[] fieldOfView, boolean[] blocking, int distance ) { + + if (distance >= MAX_DISTANCE){ + distance = MAX_DISTANCE; + } BArray.setFalse(fieldOfView); diff --git a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties index e98bee446..fb7b7d546 100644 --- a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties +++ b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/actors/actors.properties @@ -582,13 +582,21 @@ actors.mobs.swarm.name=swarm of flies actors.mobs.swarm.def_verb=evaded actors.mobs.swarm.desc=The deadly swarm of flies buzzes angrily. Every non-magical attack will split it into two smaller but equally dangerous swarms. -actors.mobs.tengu.name=Tengu -actors.mobs.tengu.notice_mine=You're mine, %s! -actors.mobs.tengu.notice_face=Face me, %s! -actors.mobs.tengu.interesting=Let's make this interesting... -actors.mobs.tengu.defeated=Free at last... -actors.mobs.tengu.rankings_desc=Assassinated by the Tengu -actors.mobs.tengu.desc=A famous and enigmatic assassin, named for the mask grafted to his face.\n\nTengu is held down with large clasps on his wrists and knees, though he seems to have gotten rid of his chains long ago.\n\nHe will try to use traps, deceptive magic, and precise attacks to eliminate the only thing stopping his escape: you. +actors.mobs.newtengu.name=Tengu +actors.mobs.newtengu.notice_mine=You're mine, %s! +actors.mobs.newtengu.notice_face=Face me, %s! +actors.mobs.newtengu.interesting=Let's make this interesting... +actors.mobs.newtengu.defeated=Free at last... +actors.mobs.newtengu.rankings_desc=Assassinated by the Tengu +actors.mobs.newtengu.desc=A famous and enigmatic assassin, named for the mask grafted to his face.\n\nTengu is held down with large clasps on his wrists and knees, though he seems to have gotten rid of his chains long ago.\n\nHe will try to use traps, deceptive magic, and precise attacks to eliminate the only thing stopping his escape: you. + +actors.mobs.oldtengu.name=Tengu +actors.mobs.oldtengu.notice_mine=You're mine, %s! +actors.mobs.oldtengu.notice_face=Face me, %s! +actors.mobs.oldtengu.interesting=Let's make this interesting... +actors.mobs.oldtengu.defeated=Free at last... +actors.mobs.oldtengu.rankings_desc=Assassinated by the Tengu +actors.mobs.oldtengu.desc=A famous and enigmatic assassin, named for the mask grafted to his face.\n\nTengu is held down with large clasps on his wrists and knees, though he seems to have gotten rid of his chains long ago.\n\nHe will try to use traps, deceptive magic, and precise attacks to eliminate the only thing stopping his escape: you. actors.mobs.thief.name=crazy thief actors.mobs.thief.stole=The thief stole %s from you! diff --git a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/levels/levels.properties b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/levels/levels.properties index cf650c023..668b828c1 100644 --- a/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/levels/levels.properties +++ b/core/src/main/resources/com/shatteredpixel/shatteredpixeldungeon/messages/levels/levels.properties @@ -104,6 +104,8 @@ levels.traps.summoningtrap.desc=Triggering this trap will summon a number of thi levels.traps.teleportationtrap.name=teleportation trap levels.traps.teleportationtrap.desc=Whatever triggers this trap will be teleported to some other location on this floor. +levels.traps.tengudarttrap.desc=Tengu has clearly been preparing for a fight. This trap will activate a hidden dart blower which will shoot a poison dart at the nearest thing that isn't Tengu.\n\nThe trap is so well made that the trigger mechanism is impossible to detect without magical aid. However the trap is visible for a moment when it is being set. + levels.traps.toxictrap.name=toxic gas trap levels.traps.toxictrap.desc=Triggering this trap will set a cloud of toxic gas loose within the surrounding area.