v3.3.6: implemented two new hazards for the vault, some WIP still

This commit is contained in:
Evan Debenham
2026-02-06 17:13:14 -05:00
parent 4c41268c41
commit cdec67e8d1
10 changed files with 416 additions and 6 deletions

View File

@@ -1792,6 +1792,12 @@ actors.mobs.tormentedspirit.thank_you=Thank you...
actors.mobs.vaultrat.desc=This rat has some behaviour changes to test out new vault enemy AI:\n_-_ Its movement can be 'heard' through walls\n_-_ It will wander along a pre-set patrol\n_-_ While wandering it has sharply reduced detection range behind the direction it moves in.\n_-_ While sleeping its detection range is also reduced\n_-_ When it detects you it will 'investigate' before swapping to hunting. Investigating enemies move toward you but don't attack and are easier to lose behind doors and corners.
actors.mobs.npcs.vaultsentry.name=vault scanning sentry
actors.mobs.npcs.vaultsentry.desc=This sentry is scanning the area in a pattern. _As this is just a tester area, the sentry won't do anything if it detects you._
actors.mobs.npcs.vaultlaser.name=vault laser sentry
actors.mobs.npcs.vaultlaser.desc=This sentry is firing lasers at regular intervals. _As this is just a tester area, the lasers won't actually harm you._
actors.mobs.warlock.name=dwarf warlock
actors.mobs.warlock.bolt_kill=The shadow bolt killed you...
actors.mobs.warlock.desc=As the dwarves' interests shifted from engineering to arcane arts, warlocks came to power in the city. They started with elemental magic, but soon switched to demonology and necromancy. The strongest of these warlocks seized the throne of the dwarven city, and his cohorts were allowed to continue practising their dark magic, so long as they surrendered their free will to him.\n\nThese warlocks possess powerful disruptive magic, and are able to temporarily hinder the upgrade magic applied to your equipment. The more upgraded an item is, the more strongly it will be affected.

View File

@@ -287,3 +287,6 @@ levels.sewerlevel.empty_deco_desc=Wet yellowish moss covers the floor.
levels.sewerlevel.bookshelf_desc=The bookshelf is packed with cheap useless books. Might it burn?
levels.sewerlevel.region_deco_name=Storage barrel
levels.sewerlevel.region_deco_desc=A stout barrel that's almost as big as you are. It must be full of something or another, it's too heavy to move.
levels.vaultlevel$vaultflametrap.name=vault flame vent
levels.vaultlevel$vaultflametrap.desc=This vent seems to be sending up green flames at set intervals. _As this is just a tester area, the flames from the vents won't actually hurt you._

View File

@@ -0,0 +1,133 @@
package com.shatteredpixel.shatteredpixeldungeon.actors.blobs;
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.Burning;
import com.shatteredpixel.shatteredpixeldungeon.effects.BlobEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ElmoParticle;
import com.shatteredpixel.shatteredpixeldungeon.items.Heap;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.plants.Plant;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.GameMath;
import java.util.Arrays;
//contains both blob logic and logic for seeding itself
public class VaultFlameTraps extends Blob {
public int[] initialCooldowns;
public int[] cooldowns;
@Override
public boolean act() {
super.act();
for (int i = 0; i < initialCooldowns.length; i++){
if (initialCooldowns[i] > -1){
if (cooldowns[i] <= 0){
cooldowns[i] = initialCooldowns[i];
}
cooldowns[i]--;
if (cooldowns[i] <= 0){
seed(Dungeon.level, i, 1);
}
}
}
return true;
}
@Override
protected void evolve() {
int cell;
for (int i = area.left; i < area.right; i++) {
for (int j = area.top; j < area.bottom; j++) {
cell = i + j* Dungeon.level.width();
if (cur[cell] > 0) {
//similar to fire.burn(), but Tengu is immune, and hero loses score
Char ch = Actor.findChar( cell );
if (ch == Dungeon.hero){
Sample.INSTANCE.play(Assets.Sounds.BURNING);
ch.sprite.showStatus(CharSprite.NEGATIVE, "!!!");
}
/*if (ch != null && !ch.isImmune(Fire.class)) {
Buff.affect( ch, Burning.class ).reignite( ch );
}
Heap heap = Dungeon.level.heaps.get( cell );
if (heap != null) {
heap.burn();
}
Plant plant = Dungeon.level.plants.get( cell );
if (plant != null){
plant.wither();
}
if (Dungeon.level.flamable[cell]){
Dungeon.level.destroy( cell );
GameScene.updateMap( cell );
}*/
if (Dungeon.level.heroFOV[cell]){
CellEmitter.get(cell).start(ElmoParticle.FACTORY, 0.02f, 10);
}
}
}
}
}
public void seed(Level level, int cell, int amount ) {
super.seed(level, cell, amount);
if (initialCooldowns == null) {
initialCooldowns = new int[level.length()];
Arrays.fill(initialCooldowns, -1);
}
if (cooldowns == null){
cooldowns = new int[level.length()];
}
}
private static final String ONE = "one";
private static final String TWO = "two";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(ONE, initialCooldowns);
bundle.put(TWO, cooldowns);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
initialCooldowns = bundle.getIntArray(ONE);
cooldowns = bundle.getIntArray(TWO);
}
@Override
public void use( BlobEmitter emitter ) {
super.use( emitter );
emitter.bound.set(0.4f, 0.4f, 0.6f, 0.6f);
emitter.pour( ElmoParticle.FACTORY, 0.3f );
}
@Override
public String tileDesc() {
return Messages.get(this, "desc");
}
}

View File

@@ -0,0 +1,116 @@
package com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.effects.CheckedCell;
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
import com.shatteredpixel.shatteredpixeldungeon.mechanics.ConeAOE;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.WardSprite;
import com.watabou.utils.Bundle;
public class VaultSentry extends NPC {
{
spriteClass = WardSprite.class;
properties.add(Property.IMMOVABLE);
}
public float scanWidth;
public float scanLength;
public int[][] scanDirs;
public int scanDirIdx;
@Override
protected boolean act() {
int[] scanDirsThisTurn = scanDirs[scanDirIdx];
scanDirIdx++;
if (scanDirIdx >= scanDirs.length){
scanDirIdx = 0;
}
for (int scanDir : scanDirsThisTurn) {
ConeAOE scan = new ConeAOE(
new Ballistica(pos, scanDir, Ballistica.STOP_SOLID),
scanLength,
scanWidth,
Ballistica.STOP_SOLID | Ballistica.STOP_TARGET);
for (int cell : scan.cells) {
if (Actor.findChar(cell) == Dungeon.hero){
Dungeon.hero.sprite.showStatus(CharSprite.NEGATIVE, "!!!");
}
if (Dungeon.level.heroFOV[cell]) {
GameScene.effect(new CheckedCell(cell, pos));
}
}
}
throwItems();
spend(TICK);
return true;
}
@Override
public boolean isImmune(Class effect) {
return true;
}
@Override
public boolean isInvulnerable(Class effect) {
return true;
}
@Override
public boolean reset() {
return true;
}
@Override
public boolean interact(Char c) {
return true;
}
private static final String SCAN_WIDTH = "scan_width";
private static final String SCAN_LENGTH = "scan_length";
private static final String SCAN_DIR_IDX = "scan_dir_idx";
private static final String SCAN_DIRS = "scan_dirs_";
private static final String SCAN_DIRS_LEN = "scan_dirs_len";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(SCAN_WIDTH, scanWidth);
bundle.put(SCAN_LENGTH, scanLength);
bundle.put(SCAN_DIR_IDX, scanDirIdx);
bundle.put(SCAN_DIRS_LEN, scanDirs.length);
for (int i = 0; i < scanDirs.length; i++){
bundle.put(SCAN_DIRS+i, scanDirs[i]);
}
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
scanWidth = bundle.getFloat(SCAN_WIDTH);
scanLength = bundle.getFloat(SCAN_LENGTH);
scanDirIdx = bundle.getInt(SCAN_DIR_IDX);
scanDirs = new int[bundle.getInt(SCAN_DIRS_LEN)][];
for (int i = 0; i < scanDirs.length; i++){
scanDirs[i] = bundle.getIntArray(SCAN_DIRS+i);
}
}
@Override
public CharSprite sprite() {
WardSprite sprite = (WardSprite) super.sprite();
sprite.linkVisuals(this);
return sprite;
}
}

View File

@@ -23,6 +23,8 @@ package com.shatteredpixel.shatteredpixeldungeon.levels;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.VaultFlameTraps;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.items.Generator;
@@ -34,7 +36,9 @@ import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
import com.shatteredpixel.shatteredpixeldungeon.levels.builders.Builder;
import com.shatteredpixel.shatteredpixeldungeon.levels.builders.GridBuilder;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.LevelTransition;
import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault.AlternatingTrapsRoom;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault.VaultCircleRoom;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault.VaultCrossRoom;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault.VaultEnemyCenterRoom;
@@ -45,6 +49,7 @@ import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault.VaultQu
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault.VaultRingRoom;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault.VaultRingsRoom;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault.VaultSimpleEnemyTreasureRoom;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.Trap;
import com.watabou.utils.Random;
import java.util.ArrayList;
@@ -65,6 +70,7 @@ public class VaultLevel extends CityLevel {
initRooms.add(new VaultEnemyCenterRoom());
initRooms.add(new VaultRingsRoom());
initRooms.add(new VaultSimpleEnemyTreasureRoom());
initRooms.add(new AlternatingTrapsRoom());
}
initRooms.add(new VaultLongRoom());
@@ -143,4 +149,30 @@ public class VaultLevel extends CityLevel {
public int randomRespawnCell( Char ch ) {
return entrance()-width();
}
public static class VaultFlameTrap extends Trap {
{
color = BLACK;
shape = DOTS;
canBeHidden = false;
active = false;
}
@Override
public void activate() {
//does nothing, this trap is just decoration and is always deactivated
}
public static void setupTrap(Level level, int cell, int initialDelay, int cooldown){
VaultFlameTraps traps = Blob.seed(0, 0, VaultFlameTraps.class, level);
traps.initialCooldowns[cell] = cooldown;
traps.cooldowns[cell] = initialDelay;
level.setTrap(new VaultLevel.VaultFlameTrap().reveal(), cell);
Painter.set(level, cell, Terrain.INACTIVE_TRAP);
}
}
}

View File

@@ -0,0 +1,46 @@
package com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.levels.VaultLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.StandardRoom;
import com.watabou.utils.Point;
public class AlternatingTrapsRoom extends StandardRoom {
@Override
public float[] sizeCatProbs() {
return new float[]{0, 1, 0};
}
@Override
public void paint(Level level) {
Painter.fill(level, this, Terrain.WALL);
Painter.fill(level, this, 1, Terrain.EMPTY);
for (Room.Door door : connected.values()) {
door.set(Room.Door.Type.REGULAR);
}
int cell;
boolean alternate = false;
for (int x = left+1; x <= right-1; x++){
for (int y = top+1; y <= bottom-1; y++){
cell = x + y*level.width();
VaultLevel.VaultFlameTrap.setupTrap(level, cell, alternate ? 2 : 1, 2);
alternate = !alternate;
}
}
}
@Override
public boolean canMerge(Level l, Room other, Point p, int mergeTerrain) {
return false;
}
}

View File

@@ -1,11 +1,13 @@
package com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.VaultSentry;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.StandardRoom;
import com.watabou.utils.Point;
import com.watabou.utils.Random;
public class VaultCircleRoom extends StandardRoom {
@@ -22,7 +24,58 @@ public class VaultCircleRoom extends StandardRoom {
Painter.fill( level, this, 4, 1, 4, 1, Terrain.EMPTY );
Painter.fill( level, this, 1, 4, 1, 4, Terrain.EMPTY );
Painter.set( level, center(), Terrain.STATUE);
Painter.set( level, center(), Terrain.PEDESTAL);
VaultSentry sentry = new VaultSentry();
sentry.pos = level.pointToCell(center());
sentry.scanLength = 4.49f;
int w = level.width();
switch (Random.Int(3)){
case 0:
sentry.scanWidth = 90f;
sentry.scanDirs = new int[][]{
new int[]{sentry.pos-1},
new int[]{sentry.pos-1-w},
new int[]{sentry.pos-w},
new int[]{sentry.pos+1-w},
new int[]{sentry.pos+1},
new int[]{sentry.pos+1+w},
new int[]{sentry.pos+w},
new int[]{sentry.pos+w-1},
};
case 1:
sentry.scanWidth = 45f;
sentry.scanDirs = new int[][]{
new int[]{sentry.pos-2, sentry.pos+2},
new int[]{sentry.pos-2-level.width(), sentry.pos+2+level.width()},
new int[]{sentry.pos-2-2*level.width(), sentry.pos+2+2*level.width()},
new int[]{sentry.pos-1-2*level.width(), sentry.pos+1+2*level.width()},
new int[]{sentry.pos-2*level.width(), sentry.pos+2*level.width()},
new int[]{sentry.pos+1-2*level.width(), sentry.pos-1+2*level.width()},
new int[]{sentry.pos+2-2*level.width(), sentry.pos-2+2*level.width()},
new int[]{sentry.pos+2-level.width(), sentry.pos-2+level.width()},
};
break;
case 2:
sentry.scanWidth = 22.5f;
sentry.scanDirs = new int[][]{
new int[]{sentry.pos-3, sentry.pos-3*w, sentry.pos+3, sentry.pos+3*w},
new int[]{sentry.pos-3-1*w, sentry.pos+1-3*w, sentry.pos+3+1*w, sentry.pos-1+3*w},
new int[]{sentry.pos-3-2*w, sentry.pos+2-3*w, sentry.pos+3+2*w, sentry.pos-2+3*w},
new int[]{sentry.pos-3-3*w, sentry.pos+3-3*w, sentry.pos+3+3*w, sentry.pos-3+3*w},
new int[]{sentry.pos-2-3*w, sentry.pos+3-2*w, sentry.pos+2+3*w, sentry.pos-3+2*w},
new int[]{sentry.pos-1-3*w, sentry.pos+3-1*w, sentry.pos+1+3*w, sentry.pos-3+1*w},
};
break;
}
level.mobs.add(sentry);
for (Door door : connected.values()) {
door.set( Door.Type.REGULAR );

View File

@@ -1,5 +1,6 @@
package com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.VaultSentry;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter;
@@ -21,10 +22,25 @@ public class VaultCrossRoom extends StandardRoom {
Painter.fill( level, this, 4, 1, 4, 1, Terrain.EMPTY );
Painter.fill( level, this, 1, 4, 1, 4, Terrain.EMPTY );
Painter.set( level, center(), Terrain.STATUE);
Painter.set( level, center(), Terrain.PEDESTAL);
//TODO only shapes for sides with doors?
VaultSentry sentry = new VaultSentry();
sentry.pos = level.pointToCell(center());
sentry.scanLength = 4;
sentry.scanWidth = 90;
sentry.scanDirs = new int[][]{
new int[]{sentry.pos-1},
new int[]{sentry.pos-level.width()},
new int[]{sentry.pos+1},
new int[]{sentry.pos+level.width()},
};
level.mobs.add(sentry);
for (Door door : connected.values()) {
door.set( Door.Type.REGULAR );
}

View File

@@ -1,8 +1,11 @@
package com.shatteredpixel.shatteredpixeldungeon.levels.rooms.quest.vault;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.VaultFlameTraps;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.levels.VaultLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.LevelTransition;
import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;

View File

@@ -105,10 +105,12 @@ public class WardSprite extends MobSprite {
}
public void linkVisuals(Char ch ){
if (ch == null) return;
updateTier( ((WandOfWarding.Ward)ch).tier );
if (ch instanceof WandOfWarding.Ward) {
updateTier(((WandOfWarding.Ward) ch).tier);
} else {
updateTier(5); //defaults to 5
}
}