v0.8.0: implemented basic 1st phase of DK fight

This commit is contained in:
Evan Debenham 2020-02-11 22:20:07 -05:00
parent e611f1a6ea
commit 82f87d9c7b
4 changed files with 372 additions and 8 deletions

View File

@ -22,25 +22,325 @@
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.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LifeLink;
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.effects.particles.ElmoParticle;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShadowParticle;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
import com.shatteredpixel.shatteredpixeldungeon.levels.NewCityBossLevel;
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.KingSprite;
import com.shatteredpixel.shatteredpixeldungeon.ui.BossHealthBar;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
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 com.watabou.utils.Reflection;
import java.util.ArrayList;
import java.util.HashSet;
//TODO currently just regular DK but with no summoning ability
public class DwarfKing extends Mob {
//TODO decide on final stats, implement later phases
{
spriteClass = KingSprite.class;
HP = HT = 1;
HP = HT = 300;
EXP = 40;
defenseSkill = 25;
properties.add(Property.BOSS);
properties.add(Property.UNDEAD);
}
protected boolean canTryToSummon() {
@Override
public int damageRoll() {
return Random.NormalIntRange( 25, 40 );
}
@Override
public int attackSkill( Char target ) {
return 32;
}
@Override
public int drRoll() {
return Random.NormalIntRange(0, 14);
}
@Override
protected boolean act() {
if (buffs(Summoning.class).size() < 1) {
if (enemy != null
&& Dungeon.level.adjacent(pos, enemy.pos) && teleportSubject()){
spend(TICK);
return true;
} else if (lifeLinkSubject()) {
spend(TICK);
return true;
} else {
summonSubject();
}
}
return super.act();
}
private boolean summonSubject(){
Summoning s = new Summoning();
s.pos = ((NewCityBossLevel)Dungeon.level).getSummoningPos();
if (s.pos == -1) return false;
switch (Random.Int(6)){
default:
s.summon = Ghoul.class;
break;
case 0:
s.summon = Monk.class;
break;
case 1:
s.summon = Warlock.class;
break;
}
s.delay = 5;
s.attachTo(this);
return true;
}
private HashSet<Mob> getSubjects(){
HashSet<Mob> subjects = new HashSet<>();
for (Mob m : Dungeon.level.mobs){
if (m.alignment == alignment && (m instanceof Ghoul || m instanceof Monk || m instanceof Warlock)){
subjects.add(m);
}
}
return subjects;
}
private boolean lifeLinkSubject(){
Mob furthest = null;
for (Mob m : getSubjects()){
boolean alreadyLinked = false;
for (LifeLink l : m.buffs(LifeLink.class)){
if (l.object == id()) alreadyLinked = true;
}
if (!alreadyLinked) {
if (furthest == null || Dungeon.level.distance(pos, furthest.pos) < Dungeon.level.distance(pos, m.pos)){
furthest = m;
}
}
}
if (furthest != null) {
Buff.append(furthest, LifeLink.class, 100f).object = id();
Buff.append(this, LifeLink.class, 100f).object = furthest.id();
yell(Messages.get(this, "lifelink_" + Random.IntRange(1, 2)));
sprite.parent.add(new Beam.HealthRay(sprite.destinationCenter(), furthest.sprite.destinationCenter()));
return true;
}
return false;
}
private boolean teleportSubject(){
Mob furthest = null;
for (Mob m : getSubjects()){
//TODO avoid warlocks?
if (furthest == null || Dungeon.level.distance(pos, furthest.pos) < Dungeon.level.distance(pos, m.pos)){
furthest = m;
}
}
if (furthest != null){
float bestDist;
int bestPos = pos;
Ballistica trajectory = new Ballistica(enemy.pos, pos, Ballistica.STOP_TARGET);
int targetCell = trajectory.path.get(trajectory.dist+1);
//if the position opposite the direction of the hero is open, go there
if (Actor.findChar(targetCell) == null && !Dungeon.level.solid[targetCell]){
bestPos = targetCell;
//Otherwise go to the neighbour cell that's open and is furthest
} else {
bestDist = Dungeon.level.trueDistance(pos, enemy.pos);
for (int i : PathFinder.NEIGHBOURS8){
if (Actor.findChar(pos+i) == null
&& !Dungeon.level.solid[pos+1]
&& Dungeon.level.trueDistance(pos+i, enemy.pos) > bestDist){
bestPos = pos+i;
bestDist = Dungeon.level.trueDistance(pos+i, enemy.pos);
}
}
}
Actor.add(new Pushing(this, pos, bestPos));
pos = bestPos;
//find closest cell that's adjacent to enemy, place subject there
bestDist = Dungeon.level.trueDistance(enemy.pos, pos);
bestPos = enemy.pos;
for (int i : PathFinder.NEIGHBOURS8){
if (Actor.findChar(enemy.pos+i) == null
&& !Dungeon.level.solid[enemy.pos+1]
&& Dungeon.level.trueDistance(enemy.pos+i, pos) < bestDist){
bestPos = enemy.pos+i;
bestDist = Dungeon.level.trueDistance(enemy.pos+i, pos);
}
}
if (bestPos != enemy.pos) ScrollOfTeleportation.appear(furthest, bestPos);
yell(Messages.get(this, "teleport_" + Random.IntRange(1, 2)));
return true;
}
return false;
}
@Override
public void notice() {
super.notice();
if (!BossHealthBar.isAssigned()) {
BossHealthBar.assignBoss(this);
yell(Messages.get(this, "notice"));
for (Char ch : Actor.chars()){
if (ch instanceof DriedRose.GhostHero){
GLog.n("\n");
((DriedRose.GhostHero) ch).sayBoss();
}
}
}
}
@Override
public void die(Object cause) {
super.die(cause);
Dungeon.level.unseal();
for (Mob m : Dungeon.level.mobs.toArray(new Mob[0])){
if (m instanceof Ghoul || m instanceof Monk || m instanceof Warlock){
m.die(null);
}
}
}
public static class Summoning extends Buff {
private int delay;
private int pos;
private Class<?extends Mob> summon;
private Emitter particles;
public int getPos() {
return pos;
}
@Override
public boolean act() {
delay--;
if (delay <= 0){
if (summon == Warlock.class){
particles.burst(ShadowParticle.CURSE, 10);
Sample.INSTANCE.play(Assets.SND_CURSED);
} else if (summon == Monk.class){
particles.burst(ElmoParticle.FACTORY, 10);
Sample.INSTANCE.play(Assets.SND_BURNING);
} else {
particles.burst(Speck.factory(Speck.BONE), 10);
Sample.INSTANCE.play(Assets.SND_BONES);
}
particles = null;
if (Actor.findChar(pos) != null){
ArrayList<Integer> candidates = new ArrayList<>();
for (int i : PathFinder.NEIGHBOURS8){
if (Dungeon.level.passable[pos+i] && Actor.findChar(pos+i) == null){
candidates.add(pos+i);
}
}
if (!candidates.isEmpty()){
pos = Random.element(candidates);
}
}
if (Actor.findChar(pos) == null) {
Mob m = Reflection.newInstance(summon);
if (m instanceof Ghoul) {
((Ghoul) m).setSolo();
}
m.pos = pos;
m.maxLvl = -2;
GameScene.add(m);
m.state = m.HUNTING;
} else {
Char ch = Actor.findChar(pos);
ch.damage(Random.NormalIntRange(20, 40), summon);
}
detach();
}
spend(TICK);
return true;
}
@Override
public void fx(boolean on) {
if (on && particles == null) {
particles = CellEmitter.get(pos);
if (summon == Warlock.class){
particles.pour(ShadowParticle.UP, 0.1f);
} else if (summon == Monk.class){
particles.pour(ElmoParticle.FACTORY, 0.1f);
} else {
particles.pour(Speck.factory(Speck.RATTLE), 0.1f);
}
} else if (!on && particles != null) {
particles.on = false;
}
}
private static final String DELAY = "delay";
private static final String POS = "pos";
private static final String SUMMON = "summon";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(DELAY, delay);
bundle.put(POS, pos);
bundle.put(SUMMON, summon);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
delay = bundle.getInt(DELAY);
pos = bundle.getInt(POS);
summon = bundle.getClass(SUMMON);
}
}
}

View File

@ -96,6 +96,10 @@ public class Ghoul extends Mob {
timesDowned = bundle.getInt( TIMES_DOWNED );
}
public void setSolo(){
partnerID = -2;
}
@Override
protected boolean act() {
//create a child

View File

@ -46,6 +46,9 @@ import com.watabou.utils.Point;
import com.watabou.utils.Random;
import com.watabou.utils.Rect;
import java.util.ArrayList;
import java.util.HashSet;
public class NewCityBossLevel extends Level {
{
@ -53,6 +56,9 @@ public class NewCityBossLevel extends Level {
color2 = 0xf2f2f2;
}
private static int WIDTH = 15;
private static int HEIGHT = 48;
private static final Rect entry = new Rect(1, 37, 14, 48);
private static final Rect arena = new Rect(1, 25, 14, 38);
private static final Rect end = new Rect(0, 0, 15, 22);
@ -60,6 +66,16 @@ public class NewCityBossLevel extends Level {
private static final int bottomDoor = 7 + (arena.bottom-1)*15;
private static final int topDoor = 7 + arena.top*15;
private static final int[] pedestals = new int[4];
static{
Point c = arena.center();
pedestals[0] = c.x-3 + (c.y-3) * WIDTH;
pedestals[1] = c.x+3 + (c.y-3) * WIDTH;
pedestals[2] = c.x+3 + (c.y+3) * WIDTH;
pedestals[3] = c.x-3 + (c.y+3) * WIDTH;
}
private ImpShopRoom impShop;
@Override
@ -90,7 +106,7 @@ public class NewCityBossLevel extends Level {
@Override
protected boolean build() {
setSize(15, 48);
setSize(WIDTH, HEIGHT);
//entrance room
Painter.fill(this, entry, Terrain.WALL);
@ -124,10 +140,10 @@ public class NewCityBossLevel extends Level {
Painter.set(this, c.x+3, c.y, Terrain.STATUE);
Painter.set(this, c.x+4, c.y, Terrain.STATUE);
Painter.set(this, c.x-3, c.y-3, Terrain.PEDESTAL);
Painter.set(this, c.x+3, c.y-3, Terrain.PEDESTAL);
Painter.set(this, c.x+3, c.y+3, Terrain.PEDESTAL);
Painter.set(this, c.x-3, c.y+3, Terrain.PEDESTAL);
Painter.set(this, pedestals[0], Terrain.PEDESTAL);
Painter.set(this, pedestals[1], Terrain.PEDESTAL);
Painter.set(this, pedestals[2], Terrain.PEDESTAL);
Painter.set(this, pedestals[3], Terrain.PEDESTAL);
Painter.set(this, c.x, arena.top, Terrain.LOCKED_DOOR);
@ -174,6 +190,37 @@ public class NewCityBossLevel extends Level {
return true;
}
//returns a random pedestal that doesn't already have a summon inbound on it
public int getSummoningPos(){
Mob king = getKing();
HashSet<DwarfKing.Summoning> summons = king.buffs(DwarfKing.Summoning.class);
ArrayList<Integer> positions = new ArrayList<>();
for (int pedestal : pedestals) {
boolean clear = true;
for (DwarfKing.Summoning s : summons) {
if (s.getPos() == pedestal) {
clear = false;
break;
}
}
if (clear) {
positions.add(pedestal);
}
}
if (positions.isEmpty()){
return -1;
} else {
return Random.element(positions);
}
}
private Mob getKing(){
for (Mob m : mobs){
if (m instanceof DwarfKing) return m;
}
return null;
}
@Override
protected void createMobs() {
}

View File

@ -517,6 +517,19 @@ actors.mobs.newdm300.desc=The DM-300 is the largest and most powerful 'defense m
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 drill 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.dwarfking.name=Dwarf King
actors.mobs.dwarfking.notice=How dare you! You have no idea what you're interfering with!
actors.mobs.dwarfking.lifelink_1=I need of your essence, slave!
actors.mobs.dwarfking.lifelink_2=Bleed for me, slave!
actors.mobs.dwarfking.teleport_1=Deal with them, slave!
actors.mobs.dwarfking.teleport_2=Keep them busy, slave!
actors.mobs.dwarfking.wave_1=Enough! Kill them my slaves!
actors.mobs.dwarfking.wave_2=More! Bleed for your king!
actors.mobs.dwarfking.wave_3=Useless! KILL THEM NOW!
actors.mobs.dwarfking.enraged=You cannot kill me %s, I AM IMMORTAL!
actors.mobs.dwarfking.dieing=No! You can't do this... you have no idea what lies below...
actors.mobs.dwarfking.dead=You've... Doomed us all...
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