v0.7.5: added necromancers
This commit is contained in:
parent
d8dfdbda4e
commit
a871261185
BIN
android/src/main/assets/necromancer.png
Normal file
BIN
android/src/main/assets/necromancer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -101,6 +101,7 @@ public class Assets {
|
||||||
public static final String GUARDIAN = "guardian.png";
|
public static final String GUARDIAN = "guardian.png";
|
||||||
public static final String SLIME = "slime.png";
|
public static final String SLIME = "slime.png";
|
||||||
public static final String SNAKE = "snake.png";
|
public static final String SNAKE = "snake.png";
|
||||||
|
public static final String NECRO = "necromancer.png";
|
||||||
|
|
||||||
public static final String ITEMS = "items.png";
|
public static final String ITEMS = "items.png";
|
||||||
public static final String TERRAIN_FEATURES = "terrain_features.png";
|
public static final String TERRAIN_FEATURES = "terrain_features.png";
|
||||||
|
|
|
@ -90,7 +90,7 @@ public class Dungeon {
|
||||||
//Health potion sources
|
//Health potion sources
|
||||||
//enemies
|
//enemies
|
||||||
SWARM_HP,
|
SWARM_HP,
|
||||||
GUARD_HP,
|
NECRO_HP,
|
||||||
BAT_HP,
|
BAT_HP,
|
||||||
WARLOCK_HP,
|
WARLOCK_HP,
|
||||||
SCORPIO_HP,
|
SCORPIO_HP,
|
||||||
|
|
|
@ -29,6 +29,8 @@ public class Adrenaline extends FlavourBuff {
|
||||||
|
|
||||||
{
|
{
|
||||||
type = buffType.POSITIVE;
|
type = buffType.POSITIVE;
|
||||||
|
|
||||||
|
announced = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final float DURATION = 10f;
|
public static final float DURATION = 10f;
|
||||||
|
|
|
@ -78,17 +78,19 @@ public class Bestiary {
|
||||||
Shaman.class,
|
Shaman.class,
|
||||||
Guard.class));
|
Guard.class));
|
||||||
case 8:
|
case 8:
|
||||||
//3x skeleton, 1x thief, 2x shaman, 2x guard
|
//2x skeleton, 1x thief, 2x shaman, 2x guard, 1x necromancer
|
||||||
return new ArrayList<>(Arrays.asList(Skeleton.class, Skeleton.class, Skeleton.class,
|
return new ArrayList<>(Arrays.asList(Skeleton.class, Skeleton.class,
|
||||||
Thief.class,
|
Thief.class,
|
||||||
Shaman.class, Shaman.class,
|
Shaman.class, Shaman.class,
|
||||||
Guard.class, Guard.class));
|
Guard.class, Guard.class,
|
||||||
|
Necromancer.class));
|
||||||
case 9: case 10:
|
case 9: case 10:
|
||||||
//3x skeleton, 1x thief, 2x shaman, 3x guard
|
//1x skeleton, 1x thief, 2x shaman, 2x guard, 2x necromancer
|
||||||
return new ArrayList<>(Arrays.asList(Skeleton.class, Skeleton.class, Skeleton.class,
|
return new ArrayList<>(Arrays.asList(Skeleton.class,
|
||||||
Thief.class,
|
Thief.class,
|
||||||
Shaman.class, Shaman.class,
|
Shaman.class, Shaman.class,
|
||||||
Guard.class, Guard.class, Guard.class));
|
Guard.class, Guard.class,
|
||||||
|
Necromancer.class, Necromancer.class));
|
||||||
|
|
||||||
// Caves
|
// Caves
|
||||||
case 11:
|
case 11:
|
||||||
|
@ -180,9 +182,6 @@ public class Bestiary {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Prison
|
// Prison
|
||||||
case 6:
|
|
||||||
if (Random.Float() < 0.2f) rotation.add(Shaman.class);
|
|
||||||
return;
|
|
||||||
case 8:
|
case 8:
|
||||||
if (Random.Float() < 0.02f) rotation.add(Bat.class);
|
if (Random.Float() < 0.02f) rotation.add(Bat.class);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
/*
|
||||||
|
* 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.buffs.Adrenaline;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barrier;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Corruption;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.effects.Beam;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.effects.Pushing;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.items.potions.PotionOfHealing;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.sprites.NecromancerSprite;
|
||||||
|
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.Random;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class Necromancer extends Mob {
|
||||||
|
|
||||||
|
{
|
||||||
|
spriteClass = NecromancerSprite.class;
|
||||||
|
|
||||||
|
HP = HT = 35;
|
||||||
|
defenseSkill = 11;
|
||||||
|
|
||||||
|
EXP = 7;
|
||||||
|
maxLvl = 14;
|
||||||
|
|
||||||
|
loot = new PotionOfHealing();
|
||||||
|
lootChance = 0.2f; //see createloot
|
||||||
|
|
||||||
|
properties.add(Property.UNDEAD);
|
||||||
|
|
||||||
|
HUNTING = new Hunting();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean summoning = false;
|
||||||
|
private Emitter summoningEmitter = null;
|
||||||
|
private int summoningPos = -1;
|
||||||
|
|
||||||
|
private NecroSkeleton mySkeleton;
|
||||||
|
private int storedSkeletonID = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSpriteState() {
|
||||||
|
super.updateSpriteState();
|
||||||
|
|
||||||
|
if (summoning && summoningEmitter == null){
|
||||||
|
summoningEmitter = CellEmitter.get( summoningPos );
|
||||||
|
summoningEmitter.pour(Speck.factory(Speck.RATTLE), 0.2f);
|
||||||
|
((NecromancerSprite)sprite).charge( summoningPos );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int drRoll() {
|
||||||
|
return Random.NormalIntRange(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollToDropLoot() {
|
||||||
|
lootChance *= ((6f - Dungeon.LimitedDrops.NECRO_HP.count) / 6f);
|
||||||
|
super.rollToDropLoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Item createLoot(){
|
||||||
|
Dungeon.LimitedDrops.NECRO_HP.count++;
|
||||||
|
return super.createLoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String SUMMONING = "summoning";
|
||||||
|
private static final String SUMMONING_POS = "summoning_pos";
|
||||||
|
private static final String MY_SKELETON = "my_skeleton";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeInBundle(Bundle bundle) {
|
||||||
|
super.storeInBundle(bundle);
|
||||||
|
bundle.put( SUMMONING, summoning);
|
||||||
|
if (summoning){
|
||||||
|
bundle.put( SUMMONING_POS, summoningPos);
|
||||||
|
}
|
||||||
|
if (mySkeleton != null){
|
||||||
|
bundle.put( MY_SKELETON, mySkeleton.id() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restoreFromBundle(Bundle bundle) {
|
||||||
|
super.restoreFromBundle(bundle);
|
||||||
|
summoning = bundle.getBoolean( SUMMONING );
|
||||||
|
if (summoning){
|
||||||
|
summoningPos = bundle.getInt( SUMMONING_POS );
|
||||||
|
}
|
||||||
|
if (bundle.contains( MY_SKELETON )){
|
||||||
|
storedSkeletonID = bundle.getInt( MY_SKELETON );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Hunting extends Mob.Hunting{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean act(boolean enemyInFOV, boolean justAlerted) {
|
||||||
|
enemySeen = enemyInFOV;
|
||||||
|
|
||||||
|
if (storedSkeletonID != -1){
|
||||||
|
Actor ch = Actor.findById(storedSkeletonID);
|
||||||
|
storedSkeletonID = -1;
|
||||||
|
if (ch instanceof NecroSkeleton){
|
||||||
|
mySkeleton = (NecroSkeleton) ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summoning){
|
||||||
|
|
||||||
|
//push anything on summoning spot away, to the furthest valid cell
|
||||||
|
if (Actor.findChar(summoningPos) != null) {
|
||||||
|
int pushPos = pos;
|
||||||
|
for (int c : PathFinder.NEIGHBOURS8) {
|
||||||
|
if (Actor.findChar(summoningPos + c) == null
|
||||||
|
&& Dungeon.level.passable[summoningPos + c]
|
||||||
|
&& Dungeon.level.trueDistance(pos, summoningPos + c) > Dungeon.level.trueDistance(pos, pushPos)) {
|
||||||
|
pushPos = summoningPos + c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//push enemy, or wait a turn if there is no valid pushing position
|
||||||
|
if (pushPos != pos) {
|
||||||
|
Char ch = Actor.findChar(summoningPos);
|
||||||
|
Actor.addDelayed( new Pushing( ch, ch.pos, pushPos ), -1 );
|
||||||
|
|
||||||
|
ch.pos = pushPos;
|
||||||
|
Dungeon.level.occupyCell(ch );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
spend(TICK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summoning = false;
|
||||||
|
|
||||||
|
mySkeleton = new NecroSkeleton();
|
||||||
|
mySkeleton.pos = summoningPos;
|
||||||
|
GameScene.add( mySkeleton );
|
||||||
|
Sample.INSTANCE.play(Assets.SND_BONES);
|
||||||
|
summoningEmitter.burst( Speck.factory( Speck.RATTLE ), 5 );
|
||||||
|
sprite.idle();
|
||||||
|
|
||||||
|
if (buff(Corruption.class) != null){
|
||||||
|
Buff.affect(mySkeleton, Corruption.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
spend(TICK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mySkeleton != null &&
|
||||||
|
(!mySkeleton.isAlive()
|
||||||
|
|| !Dungeon.level.mobs.contains(mySkeleton)
|
||||||
|
|| mySkeleton.alignment != alignment)){
|
||||||
|
mySkeleton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if enemy is seen, and enemy is within range, and we haven no skeleton, summon a skeleton!
|
||||||
|
if (enemySeen && Dungeon.level.distance(pos, enemy.pos) <= 4 && mySkeleton == null){
|
||||||
|
|
||||||
|
summoningPos = -1;
|
||||||
|
for (int c : PathFinder.NEIGHBOURS8){
|
||||||
|
if (Actor.findChar(enemy.pos+c) == null
|
||||||
|
&& Dungeon.level.passable[enemy.pos+c]
|
||||||
|
&& fieldOfView[enemy.pos+c]
|
||||||
|
&& Dungeon.level.trueDistance(pos, enemy.pos+c) < Dungeon.level.trueDistance(pos, summoningPos)){
|
||||||
|
summoningPos = enemy.pos+c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summoningPos != -1){
|
||||||
|
|
||||||
|
summoning = true;
|
||||||
|
summoningEmitter = CellEmitter.get(summoningPos);
|
||||||
|
summoningEmitter.pour(Speck.factory(Speck.RATTLE), 0.2f);
|
||||||
|
|
||||||
|
((NecromancerSprite)sprite).charge(summoningPos);
|
||||||
|
|
||||||
|
spend(TICK);
|
||||||
|
} else {
|
||||||
|
//wait for a turn
|
||||||
|
spend(TICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
//otherwise, if enemy is seen, and we have a skeleton...
|
||||||
|
} else if (enemySeen && mySkeleton != null){
|
||||||
|
|
||||||
|
target = enemy.pos;
|
||||||
|
|
||||||
|
if (!fieldOfView[mySkeleton.pos]){
|
||||||
|
|
||||||
|
//if the skeleton is not next to the enemy
|
||||||
|
//teleport them to the closest spot next to the enemy that can be seen
|
||||||
|
if (!Dungeon.level.adjacent(mySkeleton.pos, enemy.pos)){
|
||||||
|
int telePos = -1;
|
||||||
|
for (int c : PathFinder.NEIGHBOURS8){
|
||||||
|
if (Actor.findChar(enemy.pos+c) == null
|
||||||
|
&& Dungeon.level.passable[enemy.pos+c]
|
||||||
|
&& fieldOfView[enemy.pos+c]
|
||||||
|
&& Dungeon.level.trueDistance(pos, enemy.pos+c) < Dungeon.level.trueDistance(pos, telePos)){
|
||||||
|
telePos = enemy.pos+c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telePos != -1){
|
||||||
|
sprite.zap(telePos);
|
||||||
|
ScrollOfTeleportation.appear(mySkeleton, telePos);
|
||||||
|
mySkeleton.teleportSpend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//heal skeleton first
|
||||||
|
if (mySkeleton.HP < mySkeleton.HT){
|
||||||
|
|
||||||
|
sprite.zap(mySkeleton.pos);
|
||||||
|
sprite.parent.add(new Beam.HealthRay(sprite.center(), mySkeleton.sprite.center()));
|
||||||
|
|
||||||
|
int healRoll = Random.NormalIntRange(5, 8);
|
||||||
|
mySkeleton.HP = Math.min(mySkeleton.HP + healRoll, mySkeleton.HT);
|
||||||
|
mySkeleton.sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 );
|
||||||
|
|
||||||
|
//otherwise give it adrenaline
|
||||||
|
} else if (mySkeleton.buff(Adrenaline.class) == null) {
|
||||||
|
|
||||||
|
sprite.zap(mySkeleton.pos);
|
||||||
|
sprite.parent.add(new Beam.HealthRay(sprite.center(), mySkeleton.sprite.center()));
|
||||||
|
|
||||||
|
Buff.affect(mySkeleton, Adrenaline.class, 3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spend(TICK);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
|
||||||
|
//otherwise, default to regular hunting behaviour
|
||||||
|
} else {
|
||||||
|
return super.act(enemyInFOV, justAlerted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NecroSkeleton extends Skeleton {
|
||||||
|
|
||||||
|
{
|
||||||
|
state = WANDERING;
|
||||||
|
|
||||||
|
//no loot or exp
|
||||||
|
maxLvl = -5;
|
||||||
|
|
||||||
|
//15/25 health to start
|
||||||
|
HP = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void teleportSpend(){
|
||||||
|
spend(TICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO sometimes skeleton can get blocked behind necromancer, might want to address that
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -224,7 +224,7 @@ public class Speck extends Image {
|
||||||
|
|
||||||
case RATTLE:
|
case RATTLE:
|
||||||
lifespan = 0.5f;
|
lifespan = 0.5f;
|
||||||
speed.set( 0, -200 );
|
speed.set( 0, -100 );
|
||||||
acc.set( 0, -2 * speed.y / lifespan );
|
acc.set( 0, -2 * speed.y / lifespan );
|
||||||
angle = Random.Float( 360 );
|
angle = Random.Float( 360 );
|
||||||
angularSpeed = 360;
|
angularSpeed = 360;
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* 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.watabou.noosa.TextureFilm;
|
||||||
|
|
||||||
|
//TODO placeholder graphics atm
|
||||||
|
public class NecromancerSprite extends MobSprite {
|
||||||
|
|
||||||
|
private Animation charging;
|
||||||
|
|
||||||
|
public NecromancerSprite(){
|
||||||
|
super();
|
||||||
|
|
||||||
|
texture( Assets.NECRO );
|
||||||
|
TextureFilm film = new TextureFilm( texture, 16, 16 );
|
||||||
|
|
||||||
|
idle = new Animation( 1, true );
|
||||||
|
idle.frames( film, 0, 0, 0, 1, 0, 0, 0, 0, 1 );
|
||||||
|
|
||||||
|
run = new Animation( 10, true );
|
||||||
|
run.frames( film, 0, 2, 3, 4, 0 );
|
||||||
|
|
||||||
|
zap = new Animation( 5, false );
|
||||||
|
zap.frames( film, 5, 1 );
|
||||||
|
|
||||||
|
charging = new Animation( 5, true );
|
||||||
|
charging.frames( film, 1, 5 );
|
||||||
|
|
||||||
|
die = new Animation( 10, false );
|
||||||
|
die.frames( film, 6, 7, 8, 9 );
|
||||||
|
|
||||||
|
attack = zap.clone();
|
||||||
|
|
||||||
|
idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void charge( int pos ){
|
||||||
|
turnTo(ch.pos, pos);
|
||||||
|
play(charging);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(Animation anim) {
|
||||||
|
super.onComplete(anim);
|
||||||
|
if (anim == zap){
|
||||||
|
idle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -517,6 +517,9 @@ actors.mobs.king$undead.desc=These undead dwarves, risen by the will of the King
|
||||||
actors.mobs.mimic.name=mimic
|
actors.mobs.mimic.name=mimic
|
||||||
actors.mobs.mimic.desc=Mimics are magical creatures which can take any shape they wish. In dungeons they almost always choose a shape of a treasure chest, because they know how to beckon an adventurer.
|
actors.mobs.mimic.desc=Mimics are magical creatures which can take any shape they wish. In dungeons they almost always choose a shape of a treasure chest, because they know how to beckon an adventurer.
|
||||||
|
|
||||||
|
actors.mobs.necromancer.name=necromancer
|
||||||
|
actors.mobs.necromancer.desc=These apprentice dark mages have flocked to the prison, as it is the perfect place to practise their evil craft.\n\nNecromancers will summon and empower skeletons to fight for them. Killing the necromancer will also kill the skeleton it summons.
|
||||||
|
|
||||||
actors.mobs.mob.died=You hear something die in the distance.
|
actors.mobs.mob.died=You hear something die in the distance.
|
||||||
actors.mobs.mob.rage=#$%^
|
actors.mobs.mob.rage=#$%^
|
||||||
actors.mobs.mob.exp=%+dEXP
|
actors.mobs.mob.exp=%+dEXP
|
||||||
|
|
Loading…
Reference in New Issue
Block a user