KNOWLEDGE MODULE 05

Controller 与房间控制

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

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

Screeps 进阶开发

reserveController() 和 claimController() 怎么选:预订、占领与 GCL 边界

比较 reserveController() 与 claimController() 的目标、持续方式、GCL限制和返回值,并提供带确认条件的远程Controller任务示例。

难度
进阶
适用阶段
预订与占领
模块位置
3 / 3
前置知识
Controller 快降级了怎么办:监控、应急升级与恢复条件
VERIFICATION文档已核对 · 语法已检查 · Console待测试 · 主循环待验证查看验证详情
官方文档
已核对
JavaScript 语法
已检查
Screeps Console
待测试
真实主循环
待验证
最后核对
2026年7月22日
测试环境
Node.js 24 离线模拟(任务配置、所有权、预订归属、GCL余量与CLAIM部件,不是Screeps官方服务器)
测试日期
2026年7月22日
测试结果
无效任务、无CLAIM部件、已有所有者、敌对预订、GCL不足、自有预订、预订与占领可执行场景通过。
本文目录14 个主要章节
  1. 返回值差异
  2. 常见错误

reserveController()claimController() 都作用于远程房间的 Controller,也都需要有效的 CLAIM 身体部件,但它们解决的是两类完全不同的问题:

  • reserveController():临时预订中立房间,持续增加预订剩余时间;
  • claimController():把中立房间正式纳入自己的已控制房间。

本文只解决一个问题:怎样根据任务目的、Controller状态和GCL余量,安全决定应该预订还是占领,而不是看到中立Controller就直接调用其中一个方法。

先看结论

对比项 reserveController() claimController()
主要目标 临时使用远程房间 永久扩张一个已控制房间
Controller所有权 仍然是中立状态 成为己方Controller
是否占用GCL房间名额 不占用 占用
是否需要持续执行 通常需要定期续期 成功后不应继续调用
每个有效CLAIM部件的作用 每tick增加1tick预订时间 参与一次占领动作
最大持续时间 最多维持5000tick预订 不适用
常见用途 远程采集、未来扩张准备 建立新房间、建造Spawn

选择规则可以简化为:

只想远程采集或暂时保留房间
→ reserveController()

准备正式建设新房间,并且GCL允许
→ claimController()

两种动作共同的前置条件

两种方法都要求:

  1. Creep属于自己;
  2. Creep已经完成生成;
  3. 至少有一个有效的 CLAIM 部件;
  4. 目标是有效的中立Controller;
  5. Creep与Controller相邻;
  6. 当前Shard允许执行该动作。

CLAIM部件存在不等于有效。受伤到0 hits的身体部件不能提供能力,因此应使用:

const activeClaimParts = creep.getActiveBodyparts(CLAIM);

而不是只检查身体数组中是否曾经配置过 CLAIM

reserveController() 的关键规则

官方API说明:每次成功执行时,每个有效 CLAIM 部件会为预订增加1tick,最大预订时间为5000tick。

例如一只拥有2个有效 CLAIM 部件的Creep,在一个tick内成功执行一次,会增加2tick预订时间。

预订还有两个重要效果:

  • 暂时阻止其他玩家占领该Controller;
  • 让该中立房间的Source恢复完整容量。

预订不是永久状态。Creep停止续期后,controller.reservation.ticksToEnd 会逐步减少,最终回到没有预订的中立状态。

可以读取:

const reservation = controller.reservation;

if (reservation) {
  console.log({
    username: reservation.username,
    ticksToEnd: reservation.ticksToEnd
  });
}

controller.reservation不存在时,不要直接读取 usernameticksToEnd

claimController() 的关键规则

claimController() 会尝试把中立Controller变成自己的Controller。成功后,该房间计入GCL允许控制的房间数量。

GCL等级决定可以同时控制多少个房间。例如GCL为3时,通常最多控制3个房间。

可以计算当前是否还有名额:

function getClaimCapacity() {
  const ownedRooms = Object.values(Game.rooms)
    .filter(room => room.controller?.my === true)
    .length;

  return {
    ownedRooms,
    gclLevel: Game.gcl.level,
    available: Math.max(0, Game.gcl.level - ownedRooms)
  };
}

己方房间始终可见,因此用 Game.rooms 统计己方Controller是可行的。这里计算的是当前脚本观察到的基础名额,不处理特殊区域额外限制。

占领成功后不要在后续tick继续调用 claimController()。Controller已经属于自己时,它不再是有效的中立目标。

敌对预订为什么要单独检查

Controller没有 owner,不代表它一定可以直接预订或占领。它可能已经被其他玩家预订:

controller.reservation

当预订用户名不是当前Creep的所有者时,本文把它视为敌对预订,不自动继续任务。

function hasHostileReservation(controller, username) {
  const reservation = controller.reservation;
  return Boolean(
    reservation
    && reservation.username !== username
  );
}

处理敌对预订通常需要另外设计 attackController()、战斗、等待或放弃任务。本文不会把这些高影响动作自动混入预订与占领流程。

用纯函数先判断任务能否执行

下面的函数不调用游戏API,只根据当前状态给出决定,因此适合离线测试。

function evaluateControllerMission(input) {
  const {
    action,
    activeClaimParts,
    controllerOwned,
    reservationUsername,
    creepUsername,
    ownedRoomCount,
    gclLevel,
    claimConfirmed
  } = input;

  if (!['reserve', 'claim'].includes(action)) {
    return { ready: false, reason: 'invalid-action' };
  }

  if (!Number.isInteger(activeClaimParts) || activeClaimParts <= 0) {
    return { ready: false, reason: 'no-active-claim-part' };
  }

  if (controllerOwned) {
    return { ready: false, reason: 'controller-owned' };
  }

  if (
    reservationUsername
    && reservationUsername !== creepUsername
  ) {
    return { ready: false, reason: 'hostile-reservation' };
  }

  if (action === 'claim') {
    if (claimConfirmed !== true) {
      return { ready: false, reason: 'claim-not-confirmed' };
    }

    if (
      !Number.isInteger(ownedRoomCount)
      || !Number.isInteger(gclLevel)
      || ownedRoomCount >= gclLevel
    ) {
      return { ready: false, reason: 'gcl-not-enough' };
    }
  }

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

claimConfirmed是本站加入的人工确认条件,不是官方API字段。它用于避免把原本只想预订的任务误改成正式占领。

完整示例:一个Creep执行明确的Controller任务

先写入任务配置:

Memory.controllerMissions ??= {};
Memory.controllerMissions.Claimer1 = {
  enabled: true,
  action: 'reserve',
  claimConfirmed: false
};

正式占领时必须显式改为:

Memory.controllerMissions.Claimer1 = {
  enabled: true,
  action: 'claim',
  claimConfirmed: true
};

完整主循环:

function evaluateControllerMission(input) {
  const {
    action,
    activeClaimParts,
    controllerOwned,
    reservationUsername,
    creepUsername,
    ownedRoomCount,
    gclLevel,
    claimConfirmed
  } = input;

  if (!['reserve', 'claim'].includes(action)) {
    return { ready: false, reason: 'invalid-action' };
  }

  if (!Number.isInteger(activeClaimParts) || activeClaimParts <= 0) {
    return { ready: false, reason: 'no-active-claim-part' };
  }

  if (controllerOwned) {
    return { ready: false, reason: 'controller-owned' };
  }

  if (
    reservationUsername
    && reservationUsername !== creepUsername
  ) {
    return { ready: false, reason: 'hostile-reservation' };
  }

  if (action === 'claim') {
    if (claimConfirmed !== true) {
      return { ready: false, reason: 'claim-not-confirmed' };
    }

    if (
      !Number.isInteger(ownedRoomCount)
      || !Number.isInteger(gclLevel)
      || ownedRoomCount >= gclLevel
    ) {
      return { ready: false, reason: 'gcl-not-enough' };
    }
  }

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

function getOwnedRoomCount() {
  return Object.values(Game.rooms)
    .filter(room => room.controller?.my === true)
    .length;
}

function runControllerMission(creep) {
  const mission = Memory.controllerMissions?.[creep.name];

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

  const controller = creep.room.controller;

  if (!controller) {
    mission.lastStatus = 'controller-missing';
    mission.lastCheckedAt = Game.time;
    return;
  }

  const decision = evaluateControllerMission({
    action: mission.action,
    activeClaimParts: creep.getActiveBodyparts(CLAIM),
    controllerOwned: Boolean(controller.owner),
    reservationUsername: controller.reservation?.username ?? null,
    creepUsername: creep.owner.username,
    ownedRoomCount: getOwnedRoomCount(),
    gclLevel: Game.gcl.level,
    claimConfirmed: mission.claimConfirmed
  });

  mission.lastStatus = decision.reason;
  mission.lastCheckedAt = Game.time;

  if (!decision.ready) {
    return;
  }

  let result;

  if (mission.action === 'reserve') {
    result = creep.reserveController(controller);
  } else {
    result = creep.claimController(controller);
  }

  mission.lastResult = result;
  mission.lastResultAt = Game.time;

  if (result === ERR_NOT_IN_RANGE) {
    const moveResult = creep.moveTo(controller, {
      range: 1,
      reusePath: 10
    });

    mission.lastMoveResult = moveResult;
    return;
  }

  if (result === OK && mission.action === 'claim') {
    mission.enabled = false;
    mission.completedAt = Game.time;
  }

  if (result !== OK) {
    console.log({
      type: 'controller-mission-failed',
      creepName: creep.name,
      roomName: creep.room.name,
      action: mission.action,
      result
    });
  }
}

module.exports.loop = function () {
  const creep = Game.creeps.Claimer1;

  if (!creep || creep.spawning === true) {
    return;
  }

  runControllerMission(creep);
};

Claimer1 换成真实Creep名称。

为什么预订任务不能像占领一样自动关闭

占领成功是一次性结果,Controller成为己方对象后任务应结束。

预订不同。一次成功只增加有限时间,通常需要:

  • Creep持续执行;
  • 下一只预订Creep接替;
  • 根据 ticksToEnd 提前生成替补;
  • 远程房间失去视野时保留任务状态。

因此示例只在 claim 成功后关闭任务,不在 reserve 返回 OK 后关闭。

返回值差异

reserveController()

返回值 常见原因 处理方向
OK 预订命令已提交 下一tick读取预订状态
ERR_NOT_OWNER Creep不是自己的 检查对象来源
ERR_BUSY Creep仍在生成 等生成结束
ERR_INVALID_TARGET 目标不是有效中立Controller 检查所有权与预订状态
ERR_NOT_IN_RANGE 不相邻 移动到范围1
ERR_NO_BODYPART 没有有效CLAIM部件 检查身体与受伤状态
ERR_ACCESS_DENIED 当前Shard无访问权限 检查Shard权限

claimController()

除了共同返回值,还可能出现:

返回值 常见原因 处理方向
ERR_GCL_NOT_ENOUGH GCL无法再控制新房间 改为预订或提升GCL
ERR_FULL 特殊新手区域的占领数量达到限制 检查区域规则

返回 OK 只表示命令已安排,不应在同一tick手动修改Controller所有权状态。下一tick重新读取 controller.mycontroller.owner 和房间状态。

常见错误

把预订当成永久占领

预订会过期,也不能让房间变成己方已控制房间。

为远程采集直接使用 claimController()

这会占用GCL名额,并改变房间建设与管理责任。只需要Source完整容量时,预订通常更符合目标。

每tick重复调用 claimController()

占领是一次性动作。成功后继续调用只会面对无效目标,并制造无意义日志。

只检查 controller.my

中立Controller的 my 为假,但它仍可能有其他玩家的预订。还要检查 controller.ownercontroller.reservation

忽略CLAIM部件价格和寿命

CLAIM部件昂贵,并且预订任务通常涉及远距离移动。任务规划还需要考虑生成时间、路线长度和接替时机。

OK 写成已经占领成功

命令在脚本执行后统一处理。当前tick的 OK 不能替代下一tick状态确认。

离线模拟结果

构建检查覆盖:

  1. 无效任务类型;
  2. 没有有效CLAIM部件;
  3. Controller已有所有者;
  4. Controller被其他玩家预订;
  5. 占领没有人工确认;
  6. GCL名额不足;
  7. 自己已经预订的房间继续预订;
  8. GCL允许时执行占领。

这些测试只验证决策分支,不模拟官方Controller状态变化、房间视野、跨房间移动或实际GCL结算。

适用边界

本文不覆盖:

  • attackController()
  • 战斗清理敌对预订;
  • 自动选择扩张房间;
  • 跨房间寻路与路线避险;
  • Claimer自动生成与接替;
  • 多Shard扩张;
  • 新手区域全部特殊规则;
  • 占领后的Spawn建设流程。

JavaScript语法和离线任务决策已检查,真实Controller预订、占领和跨tick状态仍待Screeps环境验证。

相关站内内容

官方资料

资料核对日期:2026-07-22。离线任务决策模拟已通过;真实Controller动作仍待环境验证。

返回目录