KNOWLEDGE MODULE 05

Controller 与房间控制

处理 Safe Mode、Controller 降级、预订与占领边界,集中管理房间控制权相关问题。

专题内进度1 / 3查看完整学习顺序 →

Screeps 基础工程

StructureController.activateSafeMode() 怎么安全开启 Safe Mode

解释可用激活次数、同一shard单房间限制、safeModeCooldown、upgradeBlocked与全部返回码,并用一次性Memory请求避免自动消耗Safe Mode。

难度
基础
适用阶段
安全与生命周期
模块位置
1 / 3
前置知识
已完成新手路线即可开始
VERIFICATION文档已核对 · 语法已检查 · Console待测试 · 主循环待验证查看验证详情
官方文档
已核对
JavaScript 语法
已检查
Screeps Console
待测试
真实主循环
待验证
最后核对
2026年7月22日
测试环境
Node.js 24 离线模拟(Controller状态与一次性请求判断,不是 Screeps 官方服务器)
测试日期
2026年7月22日
测试结果
房间不可见、非己方Controller、已开启、激活次数不足、等待状态、upgradeBlocked、降级限制和可提交场景通过。
本文目录14 个主要章节
  1. 常见误区

StructureController.activateSafeMode() 会消耗一次可用激活次数,为己方房间安排 Safe Mode。它是高影响防御操作,不应该因为“发现一个非己方 Creep”就在主循环中自动调用。

本文只解决一个问题:怎样通过一次性请求开启指定房间的 Safe Mode,并在调用前核对 Controller 状态、激活次数和返回值。

Safe Mode 能做什么,不能做什么

Safe Mode 会限制其他玩家在房间内执行会影响你 Creep 或对象的多种动作,但它不会:

  • 自动让 Tower 选择目标;
  • 自动修复 Rampart;
  • 清除房间里的敌对单位;
  • 替代防御 Creep;
  • 修复错误的房间布局;
  • 保证所有跨房间威胁已经解除。

官方防御文档把它作为重要的最后保护手段。一次激活会真实消耗 safeModeAvailable,所以触发条件必须由玩家或独立防御系统明确决定。

调用前先做只读检查

下面代码适合在 Console 中执行,不会开启 Safe Mode:

const room = Game.rooms.W1N1;
const controller = room ? room.controller : null;

if (!controller) {
  console.log('房间不可见或没有 Controller');
} else {
  console.log({
    roomName: room.name,
    my: controller.my,
    safeMode: controller.safeMode,
    safeModeAvailable: controller.safeModeAvailable,
    safeModeCooldown: controller.safeModeCooldown,
    upgradeBlocked: controller.upgradeBlocked,
    ticksToDowngrade: controller.ticksToDowngrade
  });
}

需要重点区分:

属性 含义
safeMode 当前 Safe Mode 剩余 tick,未开启时为 undefined
safeModeAvailable 可用激活次数
safeModeCooldown 新激活仍被阻止的 tick 数,未生效时为 undefined
upgradeBlocked Controller 因攻击暂时无法升级,Safe Mode 同样不可用
ticksToDowngrade Controller 距离降级的剩余 tick

只读检查不能替代真正的返回码,但可以避免明显无效的调用。

一次性请求结构

在 Console 中明确写入:

Memory.safeModeRequest = {
  enabled: true,
  roomName: 'W1N1',
  confirmation: 'ACTIVATE_SAFE_MODE'
};

Memory.safeModeRequest 是本文自定义字段,不是官方API。

确认词只证明这次请求经过显式操作,不证明目标房间真的应该开启。执行前仍应查看当前敌情和关键建筑风险。

可离线测试的状态判断

function evaluateSafeModeState(input) {
  const {
    roomVisible,
    hasController,
    owned,
    safeMode,
    safeModeAvailable,
    safeModeCooldown,
    upgradeBlocked,
    ticksToDowngrade,
    downgradeLimit
  } = input;

  if (!roomVisible || !hasController) {
    return { ready: false, reason: 'controller-missing' };
  }

  if (!owned) {
    return { ready: false, reason: 'not-owner' };
  }

  if (Number.isFinite(safeMode) && safeMode > 0) {
    return { ready: false, reason: 'already-active' };
  }

  if (!Number.isInteger(safeModeAvailable) || safeModeAvailable <= 0) {
    return { ready: false, reason: 'no-activation' };
  }

  if (Number.isFinite(safeModeCooldown) && safeModeCooldown > 0) {
    return { ready: false, reason: 'activation-waiting' };
  }

  if (Number.isFinite(upgradeBlocked) && upgradeBlocked > 0) {
    return { ready: false, reason: 'upgrade-blocked' };
  }

  if (
    Number.isFinite(ticksToDowngrade)
    && Number.isFinite(downgradeLimit)
    && ticksToDowngrade <= downgradeLimit
  ) {
    return { ready: false, reason: 'downgrade-limit' };
  }

  return { ready: true, reason: 'ready' };
}

官方 ERR_TIRED 还包含 Controller 已降级达到其限制条件的情况。不同RCL的完整阈值应以官方API返回值为最终判断,本文不在业务代码中复制一份容易失效的等级表。

完整示例

代码放入现有 main 模块,不要覆盖其他主循环逻辑。

function handleSafeModeRequest() {
  const request = Memory.safeModeRequest;

  if (!request || request.enabled !== true) {
    return;
  }

  if (
    typeof request.roomName !== 'string'
    || request.confirmation !== 'ACTIVATE_SAFE_MODE'
  ) {
    delete Memory.safeModeRequest;
    return;
  }

  const room = Game.rooms[request.roomName];
  const controller = room ? room.controller : null;

  if (!controller || controller.my !== true) {
    delete Memory.safeModeRequest;
    return;
  }

  const state = {
    safeMode: controller.safeMode,
    safeModeAvailable: controller.safeModeAvailable,
    safeModeCooldown: controller.safeModeCooldown,
    upgradeBlocked: controller.upgradeBlocked,
    ticksToDowngrade: controller.ticksToDowngrade
  };

  if (
    (Number.isFinite(controller.safeMode)
      && controller.safeMode > 0)
    || !Number.isInteger(controller.safeModeAvailable)
    || controller.safeModeAvailable <= 0
    || (Number.isFinite(controller.safeModeCooldown)
      && controller.safeModeCooldown > 0)
    || (Number.isFinite(controller.upgradeBlocked)
      && controller.upgradeBlocked > 0)
  ) {
    request.enabled = false;
    request.status = 'precheck-failed';
    request.checkedAt = Game.time;
    request.controllerState = state;
    return;
  }

  request.enabled = false;
  request.status = 'submitted';
  request.submittedAt = Game.time;
  request.controllerState = state;

  const result = controller.activateSafeMode();

  request.result = result;
  request.resultAt = Game.time;
  request.status = result === OK
    ? 'accepted'
    : 'failed-review-required';

  console.log({
    type: 'safe-mode-result',
    roomName: room.name,
    safeModeAvailableBefore: state.safeModeAvailable,
    result
  });
}

module.exports.loop = function () {
  handleSafeModeRequest();
};

为什么调用前关闭请求

Safe Mode 会真实消耗激活次数。示例在调用前执行:

request.enabled = false;

即使API返回错误,下一 tick 也不会自动重试。失败后需要检查 Controller 状态和返回值,再由玩家明确重新开启请求。

这比在调用后直接 delete Memory.safeModeRequest 更便于保存失败证据。请求对象会保留:

  • 提交房间;
  • 提交 tick;
  • 调用前状态;
  • 返回值;
  • 最终状态。

确认不再需要后再由维护逻辑清理。

全部返回值

返回值 官方含义 优先检查
OK 操作已安排 下一 tick 查看 controller.safeMode
ERR_NOT_OWNER Controller 不属于自己 房间名和 controller.my
ERR_BUSY 同一 shard 已有其他房间处于 Safe Mode 检查全部己方房间
ERR_NOT_ENOUGH_RESOURCES 没有可用激活次数 safeModeAvailable
ERR_TIRED 等待状态、upgradeBlocked 或降级限制阻止激活 三类状态逐项排查

OK 不代表当前代码后面的读取已经反映新状态。应在下一 tick 重新取得 Controller。

怎样检查是否有其他房间已经开启

function getActiveSafeModeRooms() {
  return Object.values(Game.rooms)
    .filter(room =>
      room.controller
      && room.controller.my
      && Number.isFinite(room.controller.safeMode)
      && room.controller.safeMode > 0
    )
    .map(room => room.name);
}

这只能检查当前可见的己方房间。正常情况下己方结构会提供视野,但仍应以 ERR_BUSY 为最终依据。

下一 tick 怎样核对

const room = Game.rooms.W1N1;
const controller = room ? room.controller : null;

console.log({
  safeMode: controller?.safeMode,
  safeModeAvailable: controller?.safeModeAvailable
});

预期可以观察到 Safe Mode 剩余 tick,并看到可用激活次数变化。没有真实账号结果时,不能把一次离线判断写成官方服务器实测。

离线模拟结果

构建检查覆盖:

  1. 房间不可见或没有 Controller;
  2. Controller 不属于自己;
  3. 已经处于 Safe Mode;
  4. 激活次数为0;
  5. safeModeCooldown 仍有剩余;
  6. upgradeBlocked 仍有剩余;
  7. 模拟降级限制;
  8. 基础状态满足时允许提交。

离线模拟没有调用真实 activateSafeMode(),也没有验证同一 shard 的其他房间状态。

常见误区

看见敌对单位就自动开启

侦察者、路过单位和真正攻击关键建筑的威胁等级不同,应由独立防御系统判断。

ERR_BUSY 理解为 Controller 正在工作

它表示另一个房间已经处于 Safe Mode。

失败后每 tick 重试

可能在等待状态结束后意外消耗一次激活次数。

只看 safeModeAvailable

仍可能被同 shard 单房间限制、safeModeCooldownupgradeBlocked 或降级状态阻止。

返回 OK 后同 tick 宣布已生效

应在下一 tick 重新读取 Controller。

适用边界

本文没有实现:

  • 自动威胁评分;
  • Tower 与防御 Creep 调度;
  • Rampart 风险计算;
  • 关键建筑价值模型;
  • 多房间 Safe Mode 选择;
  • 自动生成激活次数;
  • Safe Mode 到期后的恢复计划。

JavaScript语法和状态判断离线模拟已经通过。真实激活、跨房间限制和连续防御行为仍待 Screeps 环境验证。

相关站内内容

官方资料

资料核对日期:2026-07-22。离线状态判断已通过;真实 Safe Mode 激活仍待环境验证。

返回目录