v0.7.4: initial implementation for wand of warding

This commit is contained in:
Evan Debenham 2019-07-04 21:04:00 -04:00
parent b8490b5b29
commit 545cda0bba
11 changed files with 519 additions and 20 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

View File

@ -97,6 +97,7 @@ public class Assets {
public static final String ROT_LASH = "rot_lasher.png"; public static final String ROT_LASH = "rot_lasher.png";
public static final String ROT_HEART= "rot_heart.png"; public static final String ROT_HEART= "rot_heart.png";
public static final String GUARD = "guard.png"; public static final String GUARD = "guard.png";
public static final String WARDS = "wards.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";

View File

@ -132,6 +132,10 @@ public abstract class Char extends Actor {
return false; return false;
} }
public boolean canInteract( Hero h ){
return Dungeon.level.adjacent( pos, h.pos );
}
//swaps places by default //swaps places by default
public boolean interact(){ public boolean interact(){

View File

@ -630,7 +630,7 @@ public class Hero extends Char {
Char ch = action.ch; Char ch = action.ch;
if (Dungeon.level.adjacent( pos, ch.pos )) { if (ch.canInteract(this)) {
ready(); ready();
sprite.turnTo( pos, ch.pos ); sprite.turnTo( pos, ch.pos );

View File

@ -59,6 +59,7 @@ public class MagicMissile extends Emitter {
public static final int SHADOW = 7; public static final int SHADOW = 7;
public static final int RAINBOW = 8; public static final int RAINBOW = 8;
public static final int EARTH = 9; public static final int EARTH = 9;
public static final int WARD = 10;
public static final int FIRE_CONE = 100; public static final int FIRE_CONE = 100;
public static final int FOLIAGE_CONE = 101; public static final int FOLIAGE_CONE = 101;
@ -138,6 +139,10 @@ public class MagicMissile extends Emitter {
size( 4 ); size( 4 );
pour( EarthParticle.FACTORY, 0.01f ); pour( EarthParticle.FACTORY, 0.01f );
break; break;
case WARD:
size( 4 );
pour( WardParticle.FACTORY, 0.01f );
break;
case FIRE_CONE: case FIRE_CONE:
size( 10 ); size( 10 );
@ -417,25 +422,36 @@ public class MagicMissile extends Emitter {
} }
} }
public static class ColdParticle extends PixelParticle.Shrinking { public static class WardParticle extends PixelParticle.Shrinking {
public static final Emitter.Factory FACTORY = new Factory() { public static final Emitter.Factory FACTORY = new Factory() {
@Override @Override
public void emit( Emitter emitter, int index, float x, float y ) { public void emit( Emitter emitter, int index, float x, float y ) {
((ColdParticle)emitter.recycle( ColdParticle.class )).reset( x, y ); ((WardParticle)emitter.recycle( WardParticle.class )).reset( x, y );
} }
@Override @Override
public boolean lightMode() { public boolean lightMode() {
return true; return true;
}; }
}; };
public ColdParticle() { public static final Emitter.Factory UP = new Factory() {
@Override
public void emit( Emitter emitter, int index, float x, float y ) {
((WardParticle)emitter.recycle( WardParticle.class )).resetUp( x, y );
}
@Override
public boolean lightMode() {
return true;
}
};
public WardParticle() {
super(); super();
lifespan = 0.6f; lifespan = 0.6f;
color( 0x2244FF ); color( 0x8822FF );
} }
public void reset( float x, float y ) { public void reset( float x, float y ) {
@ -448,6 +464,12 @@ public class MagicMissile extends Emitter {
size = 8; size = 8;
} }
public void resetUp( float x, float y){
reset(x, y);
speed.set( Random.Float( -8, +8 ), Random.Float( -32, -48 ) );
}
@Override @Override
public void update() { public void update() {
super.update(); super.update();

View File

@ -106,10 +106,12 @@ import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfDisintegration
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfFireblast; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfFireblast;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfFrost; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfFrost;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfLightning; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfLightning;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfLivingEarth;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfMagicMissile; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfMagicMissile;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfPrismaticLight; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfPrismaticLight;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfRegrowth; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfRegrowth;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfTransfusion; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfTransfusion;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfWarding;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.AssassinsBlade; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.AssassinsBlade;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.BattleAxe; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.BattleAxe;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Crossbow; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Crossbow;
@ -297,7 +299,6 @@ public class Generator {
}; };
STONE.probs = new float[]{ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; STONE.probs = new float[]{ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
//TODO: add last ones when implemented
WAND.classes = new Class<?>[]{ WAND.classes = new Class<?>[]{
WandOfMagicMissile.class, WandOfMagicMissile.class,
WandOfLightning.class, WandOfLightning.class,
@ -305,14 +306,14 @@ public class Generator {
WandOfFireblast.class, WandOfFireblast.class,
WandOfCorrosion.class, WandOfCorrosion.class,
WandOfBlastWave.class, WandOfBlastWave.class,
//WandOfLivingEarth.class, WandOfLivingEarth.class,
WandOfFrost.class, WandOfFrost.class,
WandOfPrismaticLight.class, WandOfPrismaticLight.class,
//WandOfWarding.class, WandOfWarding.class,
WandOfTransfusion.class, WandOfTransfusion.class,
WandOfCorruption.class, WandOfCorruption.class,
WandOfRegrowth.class }; WandOfRegrowth.class };
WAND.probs = new float[]{ 5, 4, 4, 4, 4, 3, /*3,*/ 3, 3, /*3,*/ 3, 3, 3 }; WAND.probs = new float[]{ 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3 };
//see generator.randomWeapon //see generator.randomWeapon
WEAPON.classes = new Class<?>[]{}; WEAPON.classes = new Class<?>[]{};

View File

@ -111,6 +111,21 @@ public abstract class Wand extends Item {
public abstract void onHit( MagesStaff staff, Char attacker, Char defender, int damage); public abstract void onHit( MagesStaff staff, Char attacker, Char defender, int damage);
public boolean tryToZap( Hero owner ){
if (owner.buff(MagicImmune.class) != null){
GLog.w( Messages.get(this, "no_magic") );
return false;
}
if ( curCharges >= (cursed ? 1 : chargesPerCast())){
return true;
} else {
GLog.w(Messages.get(this, "fizzles"));
return false;
}
}
@Override @Override
public boolean collect( Bag container ) { public boolean collect( Bag container ) {
if (super.collect( container )) { if (super.collect( container )) {
@ -411,9 +426,6 @@ public abstract class Wand extends Item {
if (target == curUser.pos || cell == curUser.pos) { if (target == curUser.pos || cell == curUser.pos) {
GLog.i( Messages.get(Wand.class, "self_target") ); GLog.i( Messages.get(Wand.class, "self_target") );
return; return;
} else if (curUser.buff(MagicImmune.class) != null){
GLog.w( Messages.get(Wand.class, "no_magic") );
return;
} }
curUser.sprite.zap(cell); curUser.sprite.zap(cell);
@ -424,7 +436,7 @@ public abstract class Wand extends Item {
else else
QuickSlotButton.target(Actor.findChar(cell)); QuickSlotButton.target(Actor.findChar(cell));
if (curWand.curCharges >= (curWand.cursed ? 1 : curWand.chargesPerCast())) { if (curWand.tryToZap(curUser)) {
curUser.busy(); curUser.busy();
Invisibility.dispel(); Invisibility.dispel();
@ -452,10 +464,6 @@ public abstract class Wand extends Item {
} }
curWand.cursedKnown = true; curWand.cursedKnown = true;
} else {
GLog.w( Messages.get(Wand.class, "fizzles") );
} }
} }

View File

@ -0,0 +1,329 @@
package com.shatteredpixel.shatteredpixeldungeon.items.wands;
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.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.NPC;
import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MagesStaff;
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.shatteredpixel.shatteredpixeldungeon.sprites.WardSprite;
import com.shatteredpixel.shatteredpixeldungeon.ui.QuickSlotButton;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndOptions;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.Callback;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
public class WandOfWarding extends Wand {
{
collisionProperties = Ballistica.STOP_TARGET | Ballistica.STOP_TERRAIN;
image = ItemSpriteSheet.WAND_WARDING;
}
@Override
protected void onZap(Ballistica bolt) {
int currentWardLevels = 0;
for (Char ch : Actor.chars()){
if (ch instanceof Ward){
currentWardLevels += ((Ward) ch).tier;
}
}
boolean canPlaceMore = currentWardLevels < level()+2;
Char ch = Actor.findChar(bolt.collisionPos);
if (ch != null){
if (ch instanceof Ward){
if (canPlaceMore) {
((Ward) ch).upgrade(level());
} else {
if (((Ward) ch).tier <= 3){
GLog.w("Your wand can't sustain any more wards.");
} else {
((Ward) ch).wandHeal( level() );
}
}
ch.sprite.emitter().burst(MagicMissile.WardParticle.UP, ((Ward) ch).tier);
} else {
GLog.w("There isn't room to place a ward here");
}
} else if (canPlaceWard(bolt.collisionPos)){
if (canPlaceMore) {
Ward ward = new Ward();
ward.pos = bolt.collisionPos;
GameScene.add(ward, 1f);
ward.sprite.emitter().burst(MagicMissile.WardParticle.UP, ward.tier);
QuickSlotButton.target(ward);
} else {
GLog.w("Your wand can't sustain any more wards.");
}
} else {
GLog.w("There isn't room to place a ward here");
}
}
@Override
protected void fx(Ballistica bolt, Callback callback) {
MagicMissile.boltFromChar(curUser.sprite.parent,
MagicMissile.WARD,
curUser.sprite,
bolt.collisionPos,
callback);
Sample.INSTANCE.play(Assets.SND_ZAP);
}
@Override
public void onHit(MagesStaff staff, Char attacker, Char defender, int damage) {
//TODO
}
@Override
public void staffFx(MagesStaff.StaffParticle particle) {
//TODO
super.staffFx(particle);
}
public static boolean canPlaceWard(int pos){
int adjacentBlockedCells = 0;
int adjacentCellGroups = 0;
boolean prevOpen = openCell(pos + PathFinder.CIRCLE8[PathFinder.CIRCLE8.length-1]);
for (int i : PathFinder.CIRCLE8){
if (!openCell(pos + i)){
adjacentBlockedCells++;
}
if (prevOpen != openCell(pos + i)){
prevOpen = !prevOpen;
adjacentCellGroups++;
}
}
switch (adjacentBlockedCells){
case 0: case 1:
return true;
case 2:
return (openCell(pos + PathFinder.CIRCLE4[0]) || openCell( pos + PathFinder.CIRCLE4[2]))
&& (openCell(pos + PathFinder.CIRCLE4[1]) || openCell( pos + PathFinder.CIRCLE4[3]));
case 3:
return adjacentCellGroups <= 2;
default:
return false;
}
}
private static boolean openCell(int pos){
//a cell is considered blocked if it isn't passable or a ward is there
return Dungeon.level.passable[pos] && !(Actor.findChar(pos) instanceof Ward);
}
public static class Ward extends NPC {
private int tier = 1;
private int wandLevel = 1;
private int totalZaps = 0;
{
spriteClass = WardSprite.class;
alignment = Alignment.ALLY;
properties.add(Property.IMMOVABLE);
viewDistance = 3;
state = WANDERING;
}
public void upgrade( int wandLevel ){
if (this.wandLevel < wandLevel){
this.wandLevel = wandLevel;
}
wandHeal(0);
switch (tier){
case 1: case 2: default:
break; //do nothing
case 3:
HP = HT = 30;
break;
case 4:
HT = 48;
HP = Math.round(48*(HP/30f));
break;
case 5:
HT = 72;
HP = Math.round(72*(HP/48f));
break;
}
if (tier < 6){
tier++;
viewDistance++;
updateSpriteState();
}
}
private void wandHeal( int wandLevel ){
if (this.wandLevel < wandLevel){
this.wandLevel = wandLevel;
}
switch(tier){
default:
break;
case 4:
HP = Math.min(HT, HP+6);
break;
case 5:
HP = Math.min(HT, HP+8);
break;
case 6:
HP = Math.min(HT, HP+12);
break;
}
}
@Override
protected float attackDelay() {
switch (tier){
case 1: case 2: default:
return 2f;
case 3: case 4:
return 1.5f;
case 5: case 6:
return 1f;
}
}
@Override
protected boolean canAttack( Char enemy ) {
return new Ballistica( pos, enemy.pos, Ballistica.MAGIC_BOLT).collisionPos == enemy.pos;
}
@Override
protected boolean doAttack(Char enemy) {
boolean visible = fieldOfView[pos] || fieldOfView[enemy.pos];
if (visible) {
sprite.zap( enemy.pos );
} else {
zap();
}
return !visible;
}
private void zap() {
spend( 1f );
//always hits
int dmg = Random.Int( 2 + wandLevel, 8 + 4*wandLevel );
enemy.damage( dmg, WandOfWarding.class );
if (!enemy.isAlive() && enemy == Dungeon.hero) {
Dungeon.fail( getClass() );
}
totalZaps++;
switch(tier){
default:
if (totalZaps >= tier){
die(this);
}
break;
case 3:
if (totalZaps >= 4){
die(this);
}
break;
case 4:
damage(6, this);
break;
case 5:
damage(8, this);
break;
case 6:
damage(9, this);
break;
}
}
public void onZapComplete() {
zap();
next();
}
@Override
protected boolean getCloser(int target) {
return false;
}
@Override
protected boolean getFurther(int target) {
return false;
}
@Override
public CharSprite sprite() {
WardSprite sprite = (WardSprite) super.sprite();
sprite.updateTier(tier);
return sprite;
}
@Override
public void updateSpriteState() {
super.updateSpriteState();
((WardSprite)sprite).updateTier(tier);
}
@Override
public boolean canInteract(Hero h) {
return true;
}
@Override
public boolean interact() {
GameScene.show(new WndOptions("test", "dismiss this ward?", "yes", "no"){
@Override
protected void onSelect(int index) {
if (index == 0){
die(null);
}
}
});
return true;
}
private static final String TIER = "tier";
private static final String WAND_LEVEL = "wand_level";
private static final String TOTAL_ZAPS = "total_zaps";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(TIER, tier);
bundle.put(WAND_LEVEL, wandLevel);
bundle.put(TOTAL_ZAPS, totalZaps);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
tier = bundle.getInt(TIER);
wandLevel = bundle.getInt(WAND_LEVEL);
totalZaps = bundle.getInt(TOTAL_ZAPS);
}
}
}

View File

@ -57,6 +57,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.potions.PotionOfStrength;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfUpgrade; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfUpgrade;
import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfEnchantment; import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfEnchantment;
import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfIntuition; import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfIntuition;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfWarding;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.Chasm; import com.shatteredpixel.shatteredpixeldungeon.levels.features.Chasm;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.Door; import com.shatteredpixel.shatteredpixeldungeon.levels.features.Door;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.HighGrass; import com.shatteredpixel.shatteredpixeldungeon.levels.features.HighGrass;
@ -976,6 +977,22 @@ public abstract class Level implements Bundlable {
fieldOfView[p+i] = true; fieldOfView[p+i] = true;
} }
} }
for (Mob ward : mobs){
if (ward instanceof WandOfWarding.Ward){
if (ward.fieldOfView == null || ward.fieldOfView.length != length()){
ward.fieldOfView = new boolean[length()];
Dungeon.level.updateFieldOfView( ward, ward.fieldOfView );
}
for (Mob m : mobs){
if (ward.fieldOfView[m.pos] && !fieldOfView[m.pos] &&
!Dungeon.hero.mindVisionEnemies.contains(m)){
Dungeon.hero.mindVisionEnemies.add(m);
}
}
BArray.or(fieldOfView, ward.fieldOfView, fieldOfView);
}
}
} }
if (c == Dungeon.hero) { if (c == Dungeon.hero) {

View File

@ -54,6 +54,7 @@ public class SpinnerSprite extends MobSprite {
@Override @Override
public void link(Char ch) { public void link(Char ch) {
super.link(ch); super.link(ch);
if (parent != null) parent.sendToBack(this);
renderShadow = false; renderShadow = false;
} }

View File

@ -0,0 +1,116 @@
package com.shatteredpixel.shatteredpixeldungeon.sprites;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.effects.Beam;
import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfWarding;
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
import com.watabou.noosa.Game;
import com.watabou.noosa.tweeners.AlphaTweener;
public class WardSprite extends MobSprite {
private Animation tierIdles[] = new Animation[7];
public WardSprite(){
super();
texture(Assets.WARDS);
tierIdles[1] = new Animation( 1, true );
tierIdles[1].frames(texture.uvRect(0, 0, 9, 10));
tierIdles[2] = new Animation( 1, true );
tierIdles[2].frames(texture.uvRect(10, 0, 21, 12));
tierIdles[3] = new Animation( 1, true );
tierIdles[3].frames(texture.uvRect(22, 0, 37, 16));
tierIdles[4] = new Animation( 1, true );
tierIdles[4].frames(texture.uvRect(38, 0, 44, 13));
tierIdles[5] = new Animation( 1, true );
tierIdles[5].frames(texture.uvRect(45, 0, 51, 15));
tierIdles[6] = new Animation( 1, true );
tierIdles[6].frames(texture.uvRect(52, 0, 60, 15));
}
@Override
public void zap( int pos ) {
idle();
flash();
emitter().burst(MagicMissile.WardParticle.UP, 2);
if (Actor.findChar(pos) != null){
parent.add(new Beam.DeathRay(center(), Actor.findChar(pos).sprite.center()));
} else {
parent.add(new Beam.DeathRay(center(), DungeonTilemap.raisedTileCenterToWorld(pos)));
}
((WandOfWarding.Ward)ch).onZapComplete();
}
@Override
public void die() {
super.die();
//cancels die animation and fades out immediately
play(idle, true);
emitter().burst(MagicMissile.WardParticle.UP, 10);
parent.add( new AlphaTweener( this, 0, 2f ) {
@Override
protected void onComplete() {
WardSprite.this.killAndErase();
parent.erase( this );
}
} );
}
public void updateTier(int tier){
idle = tierIdles[tier];
run = idle.clone();
//zap = idle.clone();
//attack = idle.clone();
die = idle.clone();
//always render first
if (parent != null) {
parent.sendToBack(this);
}
idle();
if (tier <= 3){
shadowWidth = shadowHeight = 1f;
perspectiveRaise = (16 - height()) / 32f; //center of the cell
} else {
shadowWidth = 1.2f;
shadowHeight = 0.25f;
perspectiveRaise = 6 / 16f; //6 pixels
}
if (ch != null) {
place(ch.pos);
}
}
private float baseY;
@Override
public void place(int cell) {
super.place(cell);
baseY = y;
}
@Override
public void update() {
super.update();
//if tier is greater than 3
if (perspectiveRaise >= 6 / 16f){
y = baseY + (float) Math.sin(Game.timeTotal);
shadowOffset = 0.25f - 0.8f*(float) Math.sin(Game.timeTotal);
}
}
}