Skip to content

灾难系统

概述

灾难系统是灾难玩法的核心,负责生成各种灾难事件来挑战玩家。系统采用工厂模式,通过灾难管理器创建和管理灾难实例,支持 30+ 种不同类型的灾难。

核心类

Disaster(灾难基类)

路径cn.enderrealm.disaster.game.disasters.Disaster

所有灾难类型的抽象基类,定义了灾难的基本接口和通用功能。

java
public abstract class Disaster {
    protected final disaster plugin;        // 插件实例
    protected final Room room;              // 房间实例
    protected final World world;            // 游戏世界
    protected final Set<Player> players;    // 房间内玩家
    protected final String disasterId;      // 灾难ID
    protected Location point1;              // 地图范围点1
    protected Location point2;              // 地图范围点2
    private String disasterDisplayValue;    // 灾难显示值
    private boolean disasterExpired;        // 灾难是否过期
}

抽象方法

java
// 启动灾难效果
public abstract void start();

// 停止灾难效果
public abstract void stop();

// 获取灾难描述
public String getDescription(Player player) {
    return plugin.getLanguageManager().getText("disaster-" + disasterId + "-desc", player);
}

// 获取互斥灾难ID集合(默认为空)
public Set<String> getMutuallyExclusiveDisasterIds() {
    return Collections.emptySet();
}

通用工具方法

java
// 从地图配置加载范围
protected void loadScope() {
    // 尝试从 worlds 目录加载
    File worldsFolder = new File(plugin.getDataFolder(), "worlds" + File.separator + mapName);
    // ...
    // 如果失败,使用默认范围(世界出生点 ±50 格)
}

// 获取地图范围内的随机位置
protected Location getRandomLocation() {
    double minX = Math.min(point1.getX(), point2.getX());
    double maxX = Math.max(point1.getX(), point2.getX());
    // ...
    return new Location(world, x, y, z);
}

// 获取存活玩家列表
protected List<Player> getSurvivingPlayers() {
    List<Player> survivors = new ArrayList<>();
    for (Player player : players) {
        if (!player.hasMetadata("eliminated")) {
            survivors.add(player);
        }
    }
    return survivors;
}

// 获取当前游戏剩余时间
protected int getRemainingGameTimeSeconds() {
    GameManager gameManager = plugin.getRoomManager().getGameManager(room);
    return gameManager != null ? gameManager.getRemainingGameTimeSeconds() : Integer.MAX_VALUE;
}

DisasterManager(灾难管理器)

路径cn.enderrealm.disaster.game.disasters.DisasterManager

灾难管理器负责创建、管理和销毁灾难实例。

java
public class DisasterManager {
    private final disaster plugin;
    // 房间ID -> (灾难ID -> 灾难实例)
    private final Map<String, Map<String, Disaster>> roomDisasterInstances;
    // 房间ID -> 灾难ID列表
    private final Map<String, List<String>> roomDisasterMap;
}

核心方法

java
// 创建灾难实例
public Disaster createDisaster(String disasterId, Room room) {
    String roomId = room.getWorld().getName();
    
    // 检查是否已有同类型灾难
    if (hasDisasterInRoom(roomId, disasterId)) {
        Disaster oldDisaster = getDisasterInstance(roomId, disasterId);
        if (oldDisaster != null) {
            oldDisaster.stop();
            // 移除旧灾难
        }
    }
    
    // 根据灾难ID创建实例
    Disaster disaster;
    switch (disasterId) {
        case "zombie_apocalypse":
            disaster = new ZombieApocalypseDisaster(plugin, room, disasterId);
            break;
        case "meteor_shower":
            disaster = new MeteorShowerDisaster(plugin, room, disasterId);
            break;
        // ... 其他灾难类型
        default:
            return null;
    }
    
    // 注册灾难实例
    roomDisasterInstances.computeIfAbsent(roomId, k -> new HashMap<>()).put(disasterId, disaster);
    roomDisasterMap.computeIfAbsent(roomId, k -> new ArrayList<>()).add(disasterId);
    
    // 向玩家展示灾难描述
    for (Player player : room.getPlayers()) {
        player.sendMessage(plugin.getLanguageManager().getText("disaster-" + disasterId, player) + 
                " - " + disaster.getDescription(player));
    }
    
    // 启动灾难效果
    disaster.start();
    
    return disaster;
}

// 停止指定灾难
public void stopDisaster(String disasterId) {
    for (String roomId : new ArrayList<>(roomDisasterInstances.keySet())) {
        Map<String, Disaster> disasterMap = roomDisasterInstances.get(roomId);
        if (disasterMap.containsKey(disasterId)) {
            Disaster disaster = disasterMap.get(disasterId);
            disaster.stop();
            disasterMap.remove(disasterId);
            // ...
        }
    }
}

灾难类型

主要灾难(Main Disasters)

主要灾难是游戏中的主要威胁,每次灾难触发时 100% 会选择一个主要灾难。

灾难ID中文名称描述
zombie_apocalypse僵尸启示录僵尸群正在入侵!小心它们的围攻和破坏!
meteor_shower流星雨天空中的火球正在坠落!注意躲避!
lightning闪电雷电即将劈下!远离黄色粒子区域!
flood洪水洪水正在上涨!尽快前往高处!
dragon龙灾末影龙入侵!它们会从空中攻击你!
tnt_bomberTNT轰炸机敌机正在投弹!远离爆炸范围!
anvil_rain铁砧雨铁砧正在坠落!被砸中即死!
wither_storm凋零风暴凋零出现!它会追杀玩家,被它命中将获得凋零腐败效果!
death_cube死亡立方死亡立方正在形成!远离灰色玻璃立方体,它们会突然破碎!
blizzard暴风雪暴风雪来袭!保持温暖以避免冻伤!靠近其他玩家或光源可以提高寒冷值!
two_dimensional_foil二向箔维度坍缩进行中... 三维空间正在向二维平面压缩。坍缩完成后,所有空间将被压缩为虚无...
slime_king史莱姆之王史莱姆之王已出现!躲避他危险的行为!
tornado龙卷风龙卷风已形成!它会移动并破坏所经过的方块!远离它的路径!
nether_invasion下界入侵下界入侵开始!下界生物正在入侵这个世界!
alien_abduction外星人外星人正在绑架玩家!随机玩家将被抓走。
raid_omen袭击之兆掠夺者正在入侵!敌人会源源不断地出现!
sinkhole地陷地面正在塌陷!远离塌陷区域!
migration动物大迁徙兽群正在迁徙!避开它们的行进路线!

次要灾难(Minor Disasters)

次要灾难是辅助性灾难,每次主要灾难触发后有 30% 概率追加一个次要灾难。

灾难ID中文名称描述
acid_rain酸雨有毒的雨水正在落下!寻找掩体躲避!雨水会腐蚀木质建筑!
hot_potato烫手山芋不要成为最后持有烫手山芋的人!
werewolf狼人有人变成了狼人!它可以攻击其他玩家!
purge大清洗所有规则已解除!玩家可以互相攻击!
vampire吸血鬼玩家们变成了吸血鬼!攻击其他玩家将回复自己的生命值,并有几率使对方流血!
block_collapse方块塌陷脚下的方块变得不稳定!你脚下的方块将会随时坍塌!
nuclear_bomb核弹核弹倒计时已开始!在爆炸前找到掩体!爆炸将造成大范围的破坏和辐射!
traffic_light红绿灯红灯停,绿灯行——违反交通规则是要付出代价的。
disco舞!舞!舞!音乐响起,舞池已就绪——停下来的人会付出代价。
floor_is_lava地板是岩浆地板正在变成岩浆!玩家脚下方块会变成岩浆。
hacker黑客一名黑客正在入侵!他开着暴力外挂来虐杀玩家!
herobrine_says你说我做Herobrine正在发号施令。按令行事无赏,违令必受惩罚。
blackout停电眼前一黑,什么都看不见了!保持冷静,等待灯光恢复!
shackled脚镣无形锁链缠住双脚,一分钟内禁止跳跃。

灾难实现示例

僵尸启示录(ZombieApocalypseDisaster)

java
public class ZombieApocalypseDisaster extends Disaster implements Listener {
    private static final String ZOMBIE_APOCALYPSE_ROOM_METADATA_KEY = "zombie_apocalypse_room_id";
    private final Random random;
    private BukkitTask zombieSpawnTask;
    
    @Override
    public void start() {
        // 立即生成第一轮僵尸
        spawnZombieWave();
        
        // 每 30 秒生成一轮僵尸
        zombieSpawnTask = new BukkitRunnable() {
            @Override
            public void run() {
                spawnZombieWave();
            }
        }.runTaskTimer(plugin, 30 * 20L, 30 * 20L);
    }
    
    @Override
    public void stop() {
        if (zombieSpawnTask != null) {
            zombieSpawnTask.cancel();
            zombieSpawnTask = null;
        }
        HandlerList.unregisterAll(this);
    }
    
    private void spawnZombieWave() {
        List<Player> survivors = getSurvivingPlayers();
        if (survivors.isEmpty()) return;
        
        // 为每个存活玩家生成 1-2 只僵尸
        for (Player player : survivors) {
            int zombieCount = 1 + random.nextInt(2);
            for (int i = 0; i < zombieCount; i++) {
                spawnZombieNearPlayer(player);
            }
        }
    }
    
    private void spawnZombieNearPlayer(Player player) {
        Location playerLoc = player.getLocation();
        
        // 在玩家周围 10 格范围内随机位置
        double offsetX = (random.nextDouble() * 20 - 10);
        double offsetZ = (random.nextDouble() * 20 - 10);
        
        Location spawnLoc = playerLoc.clone().add(offsetX, 0, offsetZ);
        int y = world.getHighestBlockYAt(spawnLoc.getBlockX(), spawnLoc.getBlockZ());
        
        Location spawnLocation = new Location(world, spawnLoc.getX(), y + 1, spawnLoc.getZ());
        
        // 生成僵尸
        Zombie zombie = (Zombie) world.spawnEntity(spawnLocation, EntityType.ZOMBIE);
        zombie.setMetadata(ZOMBIE_APOCALYPSE_ROOM_METADATA_KEY,
                new FixedMetadataValue(plugin, room.getWorld().getName()));
        
        // 设置僵尸属性
        zombie.setFireTicks(0);  // 防火
        
        // 10% 概率生成小僵尸
        if (random.nextDouble() < 0.1) {
            zombie.setBaby(true);
        }
        
        // 40% 概率穿戴装备
        if (random.nextDouble() < 0.4) {
            equipZombie(zombie);
        }
    }
    
    // 僵尸死亡事件处理
    @EventHandler
    public void onZombieDeath(EntityDeathEvent event) {
        if (!(event.getEntity() instanceof Zombie)) return;
        
        Zombie zombie = (Zombie) event.getEntity();
        if (!isZombieFromThisDisaster(zombie)) return;
        
        Player killer = zombie.getKiller();
        if (killer == null || !room.getPlayers().contains(killer) || killer.hasMetadata("eliminated")) {
            return;
        }
        
        // 40% 概率掉落神秘药水
        if (random.nextDouble() < 0.4) {
            event.getDrops().add(MysteriousPotion.create());
        }
    }
}

流星雨(MeteorShowerDisaster)

java
public class MeteorShowerDisaster extends Disaster {
    private BukkitTask meteorTask;
    private final Random random;
    
    @Override
    public void start() {
        meteorTask = new BukkitRunnable() {
            @Override
            public void run() {
                spawnMeteors();
            }
        }.runTaskTimer(plugin, 0L, 10 * 20L);  // 每 10 秒一次
    }
    
    private void spawnMeteors() {
        List<Player> survivors = getSurvivingPlayers();
        if (survivors.isEmpty()) return;
        
        // 随机选择 2-3 名玩家
        int playerCount = 2 + random.nextInt(2);
        playerCount = Math.min(playerCount, survivors.size());
        
        List<Player> selectedPlayers = new ArrayList<>(survivors);
        Collections.shuffle(selectedPlayers);
        
        for (int i = 0; i < playerCount; i++) {
            spawnMeteorAtPlayer(selectedPlayers.get(i));
        }
    }
    
    private void spawnMeteorAtPlayer(Player player) {
        Location playerLoc = player.getLocation();
        
        // 10% 概率直接命中,否则偏移
        Location targetLoc;
        if (random.nextDouble() < 0.1) {
            targetLoc = playerLoc.clone();
        } else {
            double offsetX = (random.nextDouble() * 20 - 10);
            double offsetZ = (random.nextDouble() * 20 - 10);
            targetLoc = playerLoc.clone().add(offsetX, 0, offsetZ);
        }
        
        // 在玩家上方 30-40 格创建火球
        Location meteorStart = targetLoc.clone();
        meteorStart.setY(playerLoc.getY() + 30 + random.nextInt(10));
        
        Fireball fireball = (Fireball) world.spawnEntity(meteorStart, EntityType.FIREBALL);
        fireball.setYield(2.0f);  // 爆炸威力
        fireball.setIsIncendiary(true);  // 点燃方块
        
        // 设置飞行方向
        Vector direction = new Vector(
                (random.nextDouble() - 0.5) * 0.3,
                -1,
                (random.nextDouble() - 0.5) * 0.3
        ).normalize();
        fireball.setDirection(direction);
        fireball.setVelocity(direction.multiply(0.5));
        
        // 添加火焰粒子效果
        // ...
        
        // 监听爆炸,创建重力方块效果
        // ...
    }
}

灾难选择机制

选择流程

java
// GameManager 中的灾难选择方法
private void selectDisaster() {
    Room currentRoom = findCurrentRoom();
    if (currentRoom == null) return;
    
    // 1. 从主灾难池中选择
    Disaster selectedMainDisaster = trySelectAndStartDisaster(availableMainDisasters, currentRoom);
    
    // 2. 30% 概率从次灾难池中选择
    if (selectedMainDisaster != null && random.nextDouble() < 0.3) {
        trySelectAndStartDisaster(availableMinorDisasters, currentRoom);
    }
}

private Disaster trySelectAndStartDisaster(List<String> disasterPool, Room room) {
    if (disasterPool.isEmpty()) return null;
    
    // 随机选择一个灾难ID
    String selectedDisasterId = disasterPool.remove(random.nextInt(disasterPool.size()));
    
    // 创建灾难实例
    Disaster disaster = disasterManager.createDisaster(selectedDisasterId, room);
    if (disaster == null) return null;
    
    // 记录激活的灾难
    activeDisastersIds.add(selectedDisasterId);
    activeDisasters.add(disaster);
    
    // 应用互斥灾难池移除
    applyMutuallyExclusiveDisasterPoolRemoval(disaster, selectedDisasterId);
    
    return disaster;
}

互斥机制

某些灾难之间存在互斥关系,不能同时存在:

java
private void applyMutuallyExclusiveDisasterPoolRemoval(Disaster disaster, String selectedDisasterId) {
    // 移除自身
    removeDisasterIdFromAvailablePools(selectedDisasterId);
    
    // 移除互斥灾难
    for (String mutuallyExclusiveDisasterId : disaster.getMutuallyExclusiveDisasterIds()) {
        removeDisasterIdFromAvailablePools(mutuallyExclusiveDisasterId);
    }
}

基岩玩家限制

当房间内存在基岩版玩家时,会自动移除某些灾难:

java
private void applyBedrockPlayerDisasterRestriction() {
    for (Player player : players) {
        if (Easy4FormAPI.isBedrockPlayer(player)) {
            removeDisasterIdFromAvailablePools("tornado");  // 移除龙卷风
            return;
        }
    }
}

配置示例

yaml
# 灾难配置
disasterCountdownTime: 12  # 灾难爆发倒计时(秒)
disasterInterval: 60       # 灾难间隔时间(秒)

# 主要灾难列表
mainDisasters:
  - zombie_apocalypse # 僵尸启示录
  - meteor_shower # 流星雨
  - lightning # 闪电
  - flood # 洪水
  - dragon # 龙灾
  - tnt_bomber # TNT轰炸机
  - anvil_rain # 铁砧雨
  - wither_storm # 凋零风暴
  - death_cube # 死亡立方
  - blizzard # 暴风雪
  - two_dimensional_foil # 二向箔
  - slime_king # 史莱姆之王
  - tornado # 龙卷风
  - nether_invasion # 下界入侵
  - alien_abduction # 外星人
  - raid_omen # 袭击之兆
  - sinkhole # 地陷
  - migration # 动物大迁徙

# 次要灾难列表
minorDisasters:
  - acid_rain # 酸雨
  - hot_potato # 烫手山芋
  - werewolf # 狼人
  - purge # 大清洗
  - vampire # 吸血鬼
  - block_collapse # 方块塌陷
  - nuclear_bomb # 核弹
  - traffic_light # 红绿灯
  - disco # 舞!舞!舞!
  - floor_is_lava # 地板是岩浆
  - hacker # 黑客
  - herobrine_says # 你说我做
  - blackout # 停电
  - shackled # 脚镣

相关文档