added Bacteria monster to Backrooms game with camouflage mechanics, lure sounds, and sprinting behavior
This commit is contained in:
+12
-3
@@ -9,6 +9,7 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.L
|
|||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelRuntime;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelRuntime;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelType;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelType;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.TransitionType;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.TransitionType;
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Bacteria;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.BackroomsMonster;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.BackroomsMonster;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Hound;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Hound;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Smiler;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Smiler;
|
||||||
@@ -110,7 +111,7 @@ public class Level0Lobby extends LevelDefinition {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Smiler lauern ausschliesslich in den dunklen Blackout-Zonen. */
|
/** Smiler lauern in den Blackout-Zonen; vereinzelt steht eine Bacteria als "Wand" herum. */
|
||||||
@Override
|
@Override
|
||||||
public void populateMonsters(BackroomsGame game, LevelRuntime runtime, int density) {
|
public void populateMonsters(BackroomsGame game, LevelRuntime runtime, int density) {
|
||||||
LevelLayout layout = runtime.getLayout();
|
LevelLayout layout = runtime.getLayout();
|
||||||
@@ -119,13 +120,21 @@ public class Level0Lobby extends LevelDefinition {
|
|||||||
smiler.spawnAtCell(game, cell);
|
smiler.spawnAtCell(game, cell);
|
||||||
runtime.getMonsters().add(smiler);
|
runtime.getMonsters().add(smiler);
|
||||||
}
|
}
|
||||||
|
for(int[] cell : Cells.farWalkable(layout, runtime.getRnd(), Math.max(1, density - 1))) {
|
||||||
|
Bacteria bacteria = new Bacteria(runtime);
|
||||||
|
bacteria.spawnAtCell(game, cell);
|
||||||
|
runtime.getMonsters().add(bacteria);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Nachschub: Smiler in Dunkelzonen, selten ein Hound in den hellen Fluren. */
|
/** Nachschub: Smiler in Dunkelzonen, in den hellen Fluren selten Hound oder Bacteria. */
|
||||||
@Override
|
@Override
|
||||||
public BackroomsMonster createRoamingMonster(LevelRuntime runtime, int[] cell) {
|
public BackroomsMonster createRoamingMonster(LevelRuntime runtime, int[] cell) {
|
||||||
if(runtime.getLayout().isDarkCell(cell[0], cell[1])) return new Smiler(runtime);
|
if(runtime.getLayout().isDarkCell(cell[0], cell[1])) return new Smiler(runtime);
|
||||||
return runtime.getRnd().nextInt(100) < 30 ? new Hound(runtime) : null;
|
int roll = runtime.getRnd().nextInt(100);
|
||||||
|
if(roll < 20) return new Hound(runtime);
|
||||||
|
if(roll < 35) return new Bacteria(runtime);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+12
-2
@@ -8,6 +8,7 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.L
|
|||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelRuntime;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelRuntime;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelType;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelType;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.TransitionType;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.TransitionType;
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Bacteria;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.BackroomsMonster;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.BackroomsMonster;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Hound;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Hound;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Wretch;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster.Wretch;
|
||||||
@@ -95,12 +96,21 @@ public class LevelHabitableZone extends LevelDefinition {
|
|||||||
hound.spawnAtCell(game, cell);
|
hound.spawnAtCell(game, cell);
|
||||||
runtime.getMonsters().add(hound);
|
runtime.getMonsters().add(hound);
|
||||||
}
|
}
|
||||||
|
// Getarnte Bacteria zwischen den Betonpfeilern
|
||||||
|
for(int[] cell : Cells.farWalkable(runtime.getLayout(), runtime.getRnd(), density)) {
|
||||||
|
Bacteria bacteria = new Bacteria(runtime);
|
||||||
|
bacteria.spawnAtCell(game, cell);
|
||||||
|
runtime.getMonsters().add(bacteria);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Nachschub: meist Hounds, gelegentlich ein schneller Wretch. */
|
/** Nachschub: meist Hounds, dazu Wretches und getarnte Bacteria. */
|
||||||
@Override
|
@Override
|
||||||
public BackroomsMonster createRoamingMonster(LevelRuntime runtime, int[] cell) {
|
public BackroomsMonster createRoamingMonster(LevelRuntime runtime, int[] cell) {
|
||||||
return runtime.getRnd().nextInt(100) < 70 ? new Hound(runtime) : new Wretch(runtime);
|
int roll = runtime.getRnd().nextInt(100);
|
||||||
|
if(roll < 60) return new Hound(runtime);
|
||||||
|
if(roll < 85) return new Wretch(runtime);
|
||||||
|
return new Bacteria(runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** "The Flickering": zufaellig faellt fuer wenige Sekunden das komplette Licht aus. */
|
/** "The Flickering": zufaellig faellt fuer wenige Sekunden das komplette Licht aus. */
|
||||||
|
|||||||
+10
-1
@@ -105,13 +105,22 @@ public abstract class BackroomsMonster extends EntityCreature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CompletableFuture.allOf(loads.toArray(CompletableFuture[]::new))
|
CompletableFuture.allOf(loads.toArray(CompletableFuture[]::new))
|
||||||
.thenRun(() -> this.setInstance(instance, position))
|
.thenRun(() -> {
|
||||||
|
this.setInstance(instance, position);
|
||||||
|
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> {
|
||||||
|
if(!this.isRemoved()) this.onSpawned();
|
||||||
|
});
|
||||||
|
})
|
||||||
.exceptionally(throwable -> {
|
.exceptionally(throwable -> {
|
||||||
MinecraftServer.getExceptionManager().handleException(throwable);
|
MinecraftServer.getExceptionManager().handleException(throwable);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Wird einen Tick nach dem Spawn aufgerufen (z. B. fuer Passagier-Displays). */
|
||||||
|
protected void onSpawned() {
|
||||||
|
}
|
||||||
|
|
||||||
/** Monster, die selbst in der Dunkelheit leben, verlieren dort keine Sichtweite */
|
/** Monster, die selbst in der Dunkelheit leben, verlieren dort keine Sichtweite */
|
||||||
protected boolean ignoresDarkness = false;
|
protected boolean ignoresDarkness = false;
|
||||||
|
|
||||||
|
|||||||
+169
@@ -0,0 +1,169 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.monster;
|
||||||
|
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelRuntime;
|
||||||
|
import net.kyori.adventure.sound.Sound;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
import net.minestom.server.entity.Entity;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.entity.Player;
|
||||||
|
import net.minestom.server.entity.attribute.Attribute;
|
||||||
|
import net.minestom.server.entity.metadata.display.BlockDisplayMeta;
|
||||||
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.sound.SoundEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Bacteria (nach Kane Pixels): eine turmhohe, skelettartige Gestalt mit
|
||||||
|
* ueberlangen Gliedmassen in unnatuerlichen Winkeln. Im Stillstand traegt sie die
|
||||||
|
* Wandtextur des Levels und verschmilzt mit der Umgebung — erwacht sie, faerbt
|
||||||
|
* sich der Koerper schlagartig schwarz. Sie lockt Wanderer mit imitierten
|
||||||
|
* menschlichen Geraeuschen an und bricht dann in einen rasend schnellen Sprint aus.
|
||||||
|
* Schleichende Spieler nimmt sie kaum wahr.
|
||||||
|
*/
|
||||||
|
public class Bacteria extends BackroomsMonster {
|
||||||
|
/** Distanz, ab der ein normal gehender Spieler den Angriff ausloest */
|
||||||
|
private static final double TRIGGER_DISTANCE = 7;
|
||||||
|
/** Schleichende Spieler sind fast unsichtbar fuer ihn */
|
||||||
|
private static final double SNEAK_TRIGGER_DISTANCE = 3.5;
|
||||||
|
/** Lock-Geraeusche fuer Spieler in diesem Ring */
|
||||||
|
private static final double LURE_MIN = 8;
|
||||||
|
private static final double LURE_MAX = 24;
|
||||||
|
private static final double SPRINT_SPEED = 0.45;
|
||||||
|
private static final int CHASE_TIMEOUT_TICKS = 300;
|
||||||
|
/**
|
||||||
|
* Passagiere sitzen beim Zombie ~1.6 Bloecke ueber den Fuessen auf, Block-Displays
|
||||||
|
* rendern von ihrer Position nach oben — dieser Versatz stellt die Gestalt auf den
|
||||||
|
* Boden, sodass der Kopf auch unter 3er-Decken bleibt.
|
||||||
|
*/
|
||||||
|
private static final double BODY_Y_OFFSET = -1.6;
|
||||||
|
|
||||||
|
private final List<Entity> bodyDisplays = new ArrayList<>();
|
||||||
|
private long lastLureTick = 0;
|
||||||
|
/** Wandtextur des Levels — die Tarnfarbe im Stillstand */
|
||||||
|
private Block camouflageBlock = Block.BLACK_CONCRETE;
|
||||||
|
|
||||||
|
public Bacteria(LevelRuntime level) {
|
||||||
|
super(EntityType.ZOMBIE, level);
|
||||||
|
this.viewRange = 24;
|
||||||
|
this.attackDamage = 5;
|
||||||
|
this.attackCooldownTicks = 25;
|
||||||
|
this.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(SPRINT_SPEED);
|
||||||
|
this.setInvisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Koerper nach der Lore: eine skelettartige, humanoide Gestalt aus mehreren
|
||||||
|
* duennen, schwarzen, draht-artigen Segmenten — ueberlange Gliedmassen in
|
||||||
|
* unnatuerlichen Winkeln. Displays haben keine Kollision, der Traeger-Zombie
|
||||||
|
* bleibt normal gross und passt durch alle Raeume.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onSpawned() {
|
||||||
|
// Spawnt getarnt in der Wandtextur; die Jagd faerbt den Koerper schwarz
|
||||||
|
this.camouflageBlock = this.level.getDefinition().palette().wall().getFirst();
|
||||||
|
Block wire = this.camouflageBlock;
|
||||||
|
double height = this.level.getDefinition().palette().roomHeight() - 0.2;
|
||||||
|
|
||||||
|
// Beine: zwei duenne, leicht verkantete Staebe
|
||||||
|
this.spawnBodyPart(wire, new Vec(0.12, height * 0.5, 0.12), new Vec(-0.3, BODY_Y_OFFSET, -0.06), zTilt(8));
|
||||||
|
this.spawnBodyPart(wire, new Vec(0.12, height * 0.5, 0.12), new Vec(0.16, BODY_Y_OFFSET, 0.02), zTilt(-6));
|
||||||
|
// Torso: schmaler, schief sitzender Kern
|
||||||
|
this.spawnBodyPart(wire, new Vec(0.22, height * 0.45, 0.22), new Vec(-0.11, BODY_Y_OFFSET + height * 0.45, -0.11), zTilt(-4));
|
||||||
|
// Arme: ueberlang, in seltsamen Winkeln abgespreizt
|
||||||
|
this.spawnBodyPart(wire, new Vec(0.1, height * 0.5, 0.1), new Vec(-0.45, BODY_Y_OFFSET + height * 0.42, 0), zTilt(25));
|
||||||
|
this.spawnBodyPart(wire, new Vec(0.1, height * 0.55, 0.1), new Vec(0.32, BODY_Y_OFFSET + height * 0.38, 0.06), zTilt(-18));
|
||||||
|
// Kopf: kleiner, versetzter Klumpen ohne Gesicht
|
||||||
|
this.spawnBodyPart(wire, new Vec(0.32, 0.35, 0.32), new Vec(-0.16, BODY_Y_OFFSET + height * 0.9, -0.16), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Faerbt alle Koerperteile um (Tarnung an/aus). */
|
||||||
|
private void setBodyBlock(Block block) {
|
||||||
|
this.bodyDisplays.forEach(display ->
|
||||||
|
((BlockDisplayMeta) display.getEntityMeta()).setBlockState(block)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Quaternion fuer eine Neigung um die Z-Achse (Grad). */
|
||||||
|
private static float[] zTilt(double degrees) {
|
||||||
|
double half = Math.toRadians(degrees) / 2;
|
||||||
|
return new float[]{0, 0, (float) Math.sin(half), (float) Math.cos(half)};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnBodyPart(Block block, Vec scale, Vec translation, float[] leftRotation) {
|
||||||
|
Entity display = new Entity(EntityType.BLOCK_DISPLAY);
|
||||||
|
BlockDisplayMeta meta = (BlockDisplayMeta) display.getEntityMeta();
|
||||||
|
meta.setBlockState(block);
|
||||||
|
meta.setScale(scale);
|
||||||
|
meta.setTranslation(translation);
|
||||||
|
if(leftRotation != null) meta.setLeftRotation(leftRotation);
|
||||||
|
display.updateViewableRule(viewer ->
|
||||||
|
Math.abs(viewer.getPosition().y() - this.getPosition().y()) < LevelRuntime.LEVEL_HEIGHT
|
||||||
|
);
|
||||||
|
display.setInstance(this.getInstance(), this.getPosition());
|
||||||
|
this.addPassenger(display);
|
||||||
|
this.bodyDisplays.add(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
this.bodyDisplays.forEach(Entity::remove);
|
||||||
|
super.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void monsterTick(long tick) {
|
||||||
|
if(this.state == State.CHASE) {
|
||||||
|
this.chaseTick(tick);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tarnung: absolute Regungslosigkeit — er IST die Wand
|
||||||
|
Player closest = this.level.playersInLevel().stream()
|
||||||
|
.min((a, b) -> Double.compare(this.getDistance(a), this.getDistance(b)))
|
||||||
|
.orElse(null);
|
||||||
|
if(closest == null) return;
|
||||||
|
|
||||||
|
double distance = this.getDistance(closest);
|
||||||
|
double trigger = closest.isSneaking() ? SNEAK_TRIGGER_DISTANCE : TRIGGER_DISTANCE;
|
||||||
|
|
||||||
|
if(distance < trigger) {
|
||||||
|
// Ausbruch: die Tarnung faellt — der Koerper wird schwarz, dann der Schrei und der Sprint
|
||||||
|
this.target = closest;
|
||||||
|
this.setState(State.CHASE, tick);
|
||||||
|
this.setBodyBlock(Block.BLACK_CONCRETE);
|
||||||
|
this.emitSound(SoundEvent.ENTITY_GHAST_SCREAM, 1.6f, 0.4f);
|
||||||
|
this.emitSound(SoundEvent.ENTITY_ENDERMAN_SCREAM, 1.2f, 0.4f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Koeder: imitierte menschliche Geraeusche locken Spieler in seine Richtung
|
||||||
|
if(distance >= LURE_MIN && distance <= LURE_MAX && tick - this.lastLureTick > 240) {
|
||||||
|
this.lastLureTick = tick;
|
||||||
|
if(this.level.getRnd().nextBoolean()) {
|
||||||
|
this.emitSound(SoundEvent.BLOCK_STONE_STEP, 0.9f, 0.8f);
|
||||||
|
} else {
|
||||||
|
this.emitSound(SoundEvent.ENTITY_ZOMBIE_AMBIENT, 0.7f, 0.35f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chaseTick(long tick) {
|
||||||
|
if(this.target == null || this.target.isRemoved()
|
||||||
|
|| !this.level.playersInLevel().contains(this.target)
|
||||||
|
|| tick - this.stateSince > CHASE_TIMEOUT_TICKS
|
||||||
|
|| this.getDistance(this.target) > this.viewRange) {
|
||||||
|
// Beute verloren: schlagartig erstarren und zurueck in die Tarnfarbe
|
||||||
|
this.target = null;
|
||||||
|
this.setState(State.IDLE, tick);
|
||||||
|
this.getNavigator().setPathTo(null);
|
||||||
|
this.setBodyBlock(this.camouflageBlock);
|
||||||
|
this.emitSound(SoundEvent.BLOCK_STONE_PLACE, 0.8f, 0.5f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tick % 6 < 2) this.moveTo(this.target.getPosition(), SPRINT_SPEED);
|
||||||
|
this.tryAttack(this.target, tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -15,9 +15,9 @@ public final class MonsterAppearance {
|
|||||||
/** Hound (Backrooms) — duesterer Hundekopf. */
|
/** Hound (Backrooms) — duesterer Hundekopf. */
|
||||||
public static final String HOUND_HEAD =
|
public static final String HOUND_HEAD =
|
||||||
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDgzMzhiMGIxNDkyMDU5YzIzMGNjYzg5ZmNhZTE0ZThhODU1ZTcwYjllNWVhNDVjYWIzZjVmNWI5ODJkYzc4MiJ9fX0=";
|
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDgzMzhiMGIxNDkyMDU5YzIzMGNjYzg5ZmNhZTE0ZThhODU1ZTcwYjllNWVhNDVjYWIzZjVmNWI5ODJkYzc4MiJ9fX0=";
|
||||||
/** Smiler — weisses Grinsen auf schwarzem Grund. */
|
/** Smiler — weisses Grinsen auf schwarzem Grund (minecraft-heads.com #74583). */
|
||||||
public static final String SMILER_HEAD =
|
public static final String SMILER_HEAD =
|
||||||
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWEyMDcyYzkxNDhmNjc2N2ZlYzBlMzNiNWRiYTAyN2EyOGM0NDcyMmRlODUzYTJkNGRjMDAxN2UzMmJkMDE1OCJ9fX0=";
|
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODc2ZmViODIxZTVkYTE1NTRiODkzZmM1MzY1ZjdiN2E5ODY3M2QxZjliNzU3OTZiODdmNzRlM2M5NjFjMGU3OCJ9fX0=";
|
||||||
/** Partygoer — gelber Smiley. */
|
/** Partygoer — gelber Smiley. */
|
||||||
public static final String PARTYGOER_HEAD =
|
public static final String PARTYGOER_HEAD =
|
||||||
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDI1MzUwODhkNDQ1ZDZhNDQ5ODc2YWRhOTg4ZmExMDc5ZDU3MmQyMTQ3MTNiYjNkY2VhMWRhM2JjNGY3ZDk2YiJ9fX0=";
|
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDI1MzUwODhkNDQ1ZDZhNDQ5ODc2YWRhOTg4ZmExMDc5ZDU3MmQyMTQ3MTNiYjNkY2VhMWRhM2JjNGY3ZDk2YiJ9fX0=";
|
||||||
|
|||||||
+40
-2
@@ -4,10 +4,13 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.ambienc
|
|||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelLayout;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelLayout;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelRuntime;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.level.LevelRuntime;
|
||||||
import net.kyori.adventure.sound.Sound;
|
import net.kyori.adventure.sound.Sound;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.EntityType;
|
import net.minestom.server.entity.EntityType;
|
||||||
import net.minestom.server.entity.EquipmentSlot;
|
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.entity.attribute.Attribute;
|
import net.minestom.server.entity.attribute.Attribute;
|
||||||
|
import net.minestom.server.entity.metadata.display.AbstractDisplayMeta;
|
||||||
|
import net.minestom.server.entity.metadata.display.ItemDisplayMeta;
|
||||||
import net.minestom.server.sound.SoundEvent;
|
import net.minestom.server.sound.SoundEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,6 +20,13 @@ import net.minestom.server.sound.SoundEvent;
|
|||||||
* nur fuer den finalen Schlag.
|
* nur fuer den finalen Schlag.
|
||||||
*/
|
*/
|
||||||
public class Smiler extends BackroomsMonster {
|
public class Smiler extends BackroomsMonster {
|
||||||
|
/** Groesse des Grinsens (Item-Display, unabhaengig von der Koerpergroesse) */
|
||||||
|
private static final float GRIN_SCALE = 2.2f;
|
||||||
|
/** Versatz relativ zum Aufsitzpunkt: Grinsen auf Brust-/Augenhoehe, nicht im Boden */
|
||||||
|
private static final float GRIN_Y_OFFSET = 0.4f;
|
||||||
|
|
||||||
|
private Entity headDisplay;
|
||||||
|
|
||||||
public Smiler(LevelRuntime level) {
|
public Smiler(LevelRuntime level) {
|
||||||
super(EntityType.ZOMBIE, level);
|
super(EntityType.ZOMBIE, level);
|
||||||
this.viewRange = 16;
|
this.viewRange = 16;
|
||||||
@@ -24,7 +34,35 @@ public class Smiler extends BackroomsMonster {
|
|||||||
this.ignoresDarkness = true;
|
this.ignoresDarkness = true;
|
||||||
this.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.11);
|
this.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.11);
|
||||||
this.setInvisible(true);
|
this.setInvisible(true);
|
||||||
this.setEquipment(EquipmentSlot.HELMET, MonsterAppearance.playerHead(MonsterAppearance.SMILER_HEAD));
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das Grinsen ist ein grosses Item-Display, das als Passagier mitfaehrt:
|
||||||
|
* frei skalierbar, tief positioniert, und per Billboard dreht es sich
|
||||||
|
* clientseitig immer zu jedem Betrachter.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onSpawned() {
|
||||||
|
this.headDisplay = new Entity(EntityType.ITEM_DISPLAY);
|
||||||
|
ItemDisplayMeta meta = (ItemDisplayMeta) this.headDisplay.getEntityMeta();
|
||||||
|
meta.setItemStack(MonsterAppearance.playerHead(MonsterAppearance.SMILER_HEAD));
|
||||||
|
meta.setScale(new Vec(GRIN_SCALE, GRIN_SCALE, GRIN_SCALE));
|
||||||
|
meta.setTranslation(new Vec(0, GRIN_Y_OFFSET, 0));
|
||||||
|
meta.setBillboardRenderConstraints(AbstractDisplayMeta.BillboardConstraints.CENTER);
|
||||||
|
// Player-Heads rendern im Item-Display mit dem Hinterkopf zur Kamera —
|
||||||
|
// 180 Grad um die Y-Achse drehen, damit das Grinsen den Betrachter ansieht
|
||||||
|
meta.setLeftRotation(new float[]{0, 1, 0, 0});
|
||||||
|
this.headDisplay.updateViewableRule(viewer ->
|
||||||
|
Math.abs(viewer.getPosition().y() - this.getPosition().y()) < LevelRuntime.LEVEL_HEIGHT
|
||||||
|
);
|
||||||
|
this.headDisplay.setInstance(this.getInstance(), this.getPosition());
|
||||||
|
this.addPassenger(this.headDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
if(this.headDisplay != null) this.headDisplay.remove();
|
||||||
|
super.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user