Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions PLAYTESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ Verifica specifica delle strutture aggiunte e aggiornate di recente.
- [ ] Ragni (`minecraft:spider` / `minecraft:cave_spider`)
- [ ] **Casse con Loot:** Le casse accanto agli spawner contengono il loot della tabella `twin_towers.json` (ferro, oro, smeraldi, diamanti, mele d'oro, libri incantati, falafel).
- [ ] Nessun blocco di arenaria compare nella struttura.
- [ ] **Aereo ogni minuto:** con un giocatore entro 160 blocchi, ogni 60 s parte un aereo da fuori della struttura (48-96 blocchi oltre il bordo), diretto al centro di una delle due torri scelta a caso; colpisce la torre ed esplode. Mai più di un aereo in volo per struttura; se non colpisce sparisce dopo 30 s. Esplosioni, fuoco e rottami restano dentro la torre colpita. Comandi: `/israelsim event plane_crash start|stop`.

### 3.2 Striscia di Gaza (`israel_simulator:gaza_strip`)
* **Comando:** `/locate structure israel_simulator:gaza_strip` (solo nel bioma `mediterranean_coast`)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ When walking through Tel Aviv, players traverse **6 functional districts**, each
- **First Amendment** *(LEGENDARY)*: High-tier artifact granting speech and immunity effects.
- **Hava Nagila Music Disc** *(LEGENDARY)*: Custom music disc with full jukebox audio integration.
- **Bibi Boss Battle**: Satirical boss entity with custom AI goals, armed security guards, speech sound effects, and unique endgame drops.
- **Other encounters**: further satirical bosses and minibosses, Mossad agents, a plane-crash world event and regional wildlife (gazelle, ibex, hyrax). See the [wiki](wiki/Home.md).
- **Other encounters**: further satirical bosses and minibosses, Mossad agents, a plane-crash world event (every minute a plane flies at one of the `twin_towers` when a player is within 160 blocks) and regional wildlife (gazelle, ibex, hyrax). See the [wiki](wiki/Home.md).

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.ExplosionDamageCalculator;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
Expand Down Expand Up @@ -192,7 +195,7 @@ public void tick() {

// Timeout: if plane doesn't hit any solid block after 30 seconds (600 ticks), despawn cleanly
if (flightTicks >= MAX_FLIGHT_TICKS) {
TwinTowersPlaneEvent.stopEvent();
TwinTowersPlaneEvent.onPlaneGone(this.getUUID());
this.discard();
}
}
Expand Down Expand Up @@ -287,22 +290,22 @@ private void detonatePlaneCrash(ServerLevel serverLevel) {
Vec3 deepCore = nosePos.add(dir.scale(10.0));

// 1. Massive 10x multi-point fuel-air airliner explosion tearing through the facade and interior
serverLevel.explode(this, wallImpact.x, wallImpact.y, wallImpact.z, 28.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, nosePos.x, nosePos.y, nosePos.z, 26.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, midPos.x, midPos.y, midPos.z, 25.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, deepCore.x, deepCore.y, deepCore.z, 25.0F, true, Level.ExplosionInteraction.BLOCK);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y, wallImpact.z, 28.0F);
explodeInTower(serverLevel, nosePos.x, nosePos.y, nosePos.z, 26.0F);
explodeInTower(serverLevel, midPos.x, midPos.y, midPos.z, 25.0F);
explodeInTower(serverLevel, deepCore.x, deepCore.y, deepCore.z, 25.0F);

// 2. Wing-span fuel tank explosions tearing across the entire facade
serverLevel.explode(this, wallImpact.x, wallImpact.y, wallImpact.z + 7.0, 22.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, wallImpact.x, wallImpact.y, wallImpact.z - 7.0, 22.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, wallImpact.x, wallImpact.y, wallImpact.z + 14.0, 20.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, wallImpact.x, wallImpact.y, wallImpact.z - 14.0, 20.0F, true, Level.ExplosionInteraction.BLOCK);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y, wallImpact.z + 7.0, 22.0F);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y, wallImpact.z - 7.0, 22.0F);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y, wallImpact.z + 14.0, 20.0F);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y, wallImpact.z - 14.0, 20.0F);

// 3. Multi-floor vertical rupture (obliterating 8-10 floors above and below)
serverLevel.explode(this, wallImpact.x, wallImpact.y + 8.0, wallImpact.z, 22.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, wallImpact.x, wallImpact.y - 7.0, wallImpact.z, 22.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, wallImpact.x, wallImpact.y + 16.0, wallImpact.z, 20.0F, true, Level.ExplosionInteraction.BLOCK);
serverLevel.explode(this, wallImpact.x, wallImpact.y - 14.0, wallImpact.z, 18.0F, true, Level.ExplosionInteraction.BLOCK);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y + 8.0, wallImpact.z, 22.0F);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y - 7.0, wallImpact.z, 22.0F);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y + 16.0, wallImpact.z, 20.0F);
explodeInTower(serverLevel, wallImpact.x, wallImpact.y - 14.0, wallImpact.z, 18.0F);

// 4. Catastrophic visual effects (massive mushroom cloud, firestorm and lava embers)
serverLevel.sendParticles(ParticleTypes.EXPLOSION_EMITTER, wallImpact.x, wallImpact.y, wallImpact.z, 15, 6.0, 6.0, 6.0, 0.0);
Expand All @@ -321,7 +324,7 @@ private void detonatePlaneCrash(ServerLevel serverLevel) {
for (int oz = -10; oz <= 10; oz++) {
if (serverLevel.getRandom().nextFloat() < 0.35F) {
BlockPos firePos = centerImpact.offset(ox, oy, oz);
if (serverLevel.getBlockState(firePos).isAir() && !serverLevel.getBlockState(firePos.below()).isAir()) {
if (isInsideTower(firePos) && serverLevel.getBlockState(firePos).isAir() && !serverLevel.getBlockState(firePos.below()).isAir()) {
serverLevel.setBlock(firePos, Blocks.FIRE.defaultBlockState(), 3);
}
}
Expand All @@ -339,7 +342,33 @@ private void detonatePlaneCrash(ServerLevel serverLevel) {
serverLevel.playSound(null, wallImpact.x, wallImpact.y, wallImpact.z,
SoundEvents.GENERIC_EXPLODE.value(), SoundSource.BLOCKS, 20.0F, 1.1F);

TwinTowersPlaneEvent.onPlaneCrashImpact(serverLevel, nosePos);
TwinTowersPlaneEvent.onPlaneCrashImpact(serverLevel, nosePos, this.getUUID());
}

/** True when {@code pos} lies inside the targeted tower; nothing outside it may be broken or set on fire. */
public boolean isInsideTower(BlockPos pos) {
if (towerMin.equals(towerMax)) {
return false; // no tower known (e.g. plane reloaded from disk): never touch blocks
}
return pos.getX() >= Math.min(towerMin.getX(), towerMax.getX()) && pos.getX() <= Math.max(towerMin.getX(), towerMax.getX())
&& pos.getY() >= Math.min(towerMin.getY(), towerMax.getY()) && pos.getY() <= Math.max(towerMin.getY(), towerMax.getY())
&& pos.getZ() >= Math.min(towerMin.getZ(), towerMax.getZ()) && pos.getZ() <= Math.max(towerMin.getZ(), towerMax.getZ());
}

/** Explosion that damages entities normally but only breaks (and ignites) blocks inside the targeted tower. */
private void explodeInTower(ServerLevel serverLevel, double x, double y, double z, float radius) {
ExplosionDamageCalculator towerOnly = new ExplosionDamageCalculator() {
@Override
public boolean shouldBlockExplode(Explosion explosion, BlockGetter level, BlockPos pos, BlockState state, float power) {
return isInsideTower(pos);
}
};
serverLevel.explode(this, null, towerOnly, x, y, z, radius, true, Level.ExplosionInteraction.BLOCK);
}

@Override
public boolean shouldBeSaved() {
return false; // a plane reloaded from disk would have lost its flight path
}

@Override
Expand All @@ -354,6 +383,9 @@ private void placeWreckageBlocks(ServerLevel level) {
for (PlaneModelData.BlockEntry entry : blocks) {
if (count++ < 500) {
BlockPos target = center.offset(entry.dx(), entry.dy(), entry.dz());
if (!isInsideTower(target)) {
continue;
}
BlockState current = level.getBlockState(target);
if (current.isAir() || current.is(Blocks.GLASS) || current.is(Blocks.STAINED_GLASS.gray())
|| current.is(Blocks.CONCRETE.lightGray())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
ctx.getSource().sendFailure(Component.literal("§cNessun evento aereo attivo al momento."));
return 0;
}
TwinTowersPlaneEvent.stopEvent();
TwinTowersPlaneEvent.stopEvent(ctx.getSource().getLevel());
ctx.getSource().sendSuccess(() -> Component.literal("§aEvento aereo arrestato."), true);
return 1;
}))))
Expand All @@ -46,7 +46,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
private static int start(CommandContext<CommandSourceStack> ctx, BlockPos pos) {
ServerLevel level = ctx.getSource().getLevel();
if (!TwinTowersPlaneEvent.startEvent(level, pos)) {
ctx.getSource().sendFailure(Component.literal("§cImpossibile avviare l'evento: un evento è già in corso."));
ctx.getSource().sendFailure(Component.literal("§cImpossibile avviare l'evento: nessuna twin_towers caricata vicino, aereo già in volo o nessun punto di partenza caricato."));
return 0;
}
ctx.getSource().sendSuccess(() -> Component.literal("§6Allarme antiaereo attivato! Aereo in arrivo verso le coordinate " + pos.toShortString()), true);
Expand Down
Loading
Loading