v0.7.5: added necromancers

This commit is contained in:
Evan Debenham 2019-09-17 18:48:28 -04:00
parent d8dfdbda4e
commit a871261185
9 changed files with 389 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -101,6 +101,7 @@ public class Assets {
public static final String GUARDIAN = "guardian.png";
public static final String SLIME = "slime.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 TERRAIN_FEATURES = "terrain_features.png";

View File

@ -90,7 +90,7 @@ public class Dungeon {
//Health potion sources
//enemies
SWARM_HP,
GUARD_HP,
NECRO_HP,
BAT_HP,
WARLOCK_HP,
SCORPIO_HP,

View File

@ -29,6 +29,8 @@ public class Adrenaline extends FlavourBuff {
{
type = buffType.POSITIVE;
announced = true;
}
public static final float DURATION = 10f;

View File

@ -78,17 +78,19 @@ public class Bestiary {
Shaman.class,
Guard.class));
case 8:
//3x skeleton, 1x thief, 2x shaman, 2x guard
return new ArrayList<>(Arrays.asList(Skeleton.class, Skeleton.class, Skeleton.class,
//2x skeleton, 1x thief, 2x shaman, 2x guard, 1x necromancer
return new ArrayList<>(Arrays.asList(Skeleton.class, Skeleton.class,
Thief.class,
Shaman.class, Shaman.class,
Guard.class, Guard.class));
Guard.class, Guard.class,
Necromancer.class));
case 9: case 10:
//3x skeleton, 1x thief, 2x shaman, 3x guard
return new ArrayList<>(Arrays.asList(Skeleton.class, Skeleton.class, Skeleton.class,
//1x skeleton, 1x thief, 2x shaman, 2x guard, 2x necromancer
return new ArrayList<>(Arrays.asList(Skeleton.class,
Thief.class,
Shaman.class, Shaman.class,
Guard.class, Guard.class, Guard.class));
Guard.class, Guard.class,
Necromancer.class, Necromancer.class));
// Caves
case 11:
@ -180,9 +182,6 @@ public class Bestiary {
return;
// Prison
case 6:
if (Random.Float() < 0.2f) rotation.add(Shaman.class);
return;
case 8:
if (Random.Float() < 0.02f) rotation.add(Bat.class);
return;

View File

@ -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
}
}

View File

@ -224,7 +224,7 @@ public class Speck extends Image {
case RATTLE:
lifespan = 0.5f;
speed.set( 0, -200 );
speed.set( 0, -100 );
acc.set( 0, -2 * speed.y / lifespan );
angle = Random.Float( 360 );
angularSpeed = 360;

View File

@ -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();
}
}
}

View File

@ -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.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.rage=#$%^
actors.mobs.mob.exp=%+dEXP