Skip to content

Movement & needs

Movement speed

Tile factors

Per-tile speed factor (1 = normal; < 1 = slower; > 1 = faster). From MovementSpeed.ts:

Tile type Factor
grass 1
sand 0.5
stone 1.25
shallow_water 0.2
mountain 0.3
deep_water 0 (unwalkable)

Walkable objects

Whether a tile object can be walked through is defined on the object (sim definition), not in the movement handler:

  • AbstractObject has isWalkable(): boolean (default false).
  • Objects that can be walked through override it to return true (e.g. PineTreeObject, StoneChunkObject, WoodLogObject, StoneBrickObject).

The MovementSpeed handler is generic: it calls object.isWalkable() and, for speed, treats stack-like objects (with getQuantity/getMaxStack) as full speed and other walkable objects as 0.3× (PASS_THROUGH_SPEED_FACTOR). Pathfinding and blocking use isObjectWalkable(obj) from the same module.

Pathfinding

  • PathFinder uses A* with 8-direction movement
  • Step cost = 1 / speedFactor (higher cost = slower)
  • Unwalkable tiles get Infinity cost
  • getStepCostAt(x, y, deps) uses both tile and object factors

Step timing

  • MOVE_INTERVAL_MS = 420 ms (base step duration)
  • Step duration = MOVE_INTERVAL_MS / speedFactor
  • Faster terrain → shorter steps; slower → longer steps
  • JobMovement uses getSpeedFactor(next.x, next.y) for the next tile

Needs

Need types

Need Decay Fulfilled by
sleeping 0.35/sec SleepJob
drinking 0.35/sec DrinkJob
eating 0.35/sec (not yet)
recreation 0.35/sec (not yet)

Decay

  • Each need: 0–100, decays at 0.35 per real second
  • ColonistObject.tickNeeds(deltaTimeMs) called each frame
  • Returns { drinkingCrossed, sleepingCrossed } when need drops below threshold

Threshold

  • LOW_NEED_THRESHOLD = 10
  • When need crosses from >10% to ≤10%, emit needFulfillmentRequired
  • Threshold flag prevents repeated events until need is fulfilled

Fulfillment

  • Drinking: fulfillDrinking() – set to 90%, reset threshold
  • Sleeping: fulfillSleep() – set to 90%, reset threshold
  • Called by DrinkJob/SleepJob on completion

Need-driven job creation

Need decay and job creation are driven by colonist behaviors (see Behaviors). A needCheck behavior runs every tick: it calls colonist.tickNeeds(deltaTime) and, when drinking or sleeping crosses to ≤10%, calls context.requestNeedJob(colonistId, 'drinking'|'sleeping'). The client context implementation then calls NeedJobScheduler.createJobForColonist(id, needType). NeedJobScheduler creates a DrinkJob or SleepJob if the colonist is free and need ≤10% (or when forced from the UI).

Drink job

  • Destination: nearest tile adjacent to shallow_water or deep_water
  • findNearestWaterAdjacentTile(fromX, fromY) – scans map for water, then adjacent walkable tiles

Sleep job

  • In zone: destination = nearest tile in sleeping zone; duration 15 s
  • Outside zone: sleep at current tile; duration 30 s

Force from UI (Needs tab)

  • Needs tab in the citizen panel has "Go drink" and "Go sleep" buttons.
  • Clicking either cancels the colonist's current job (if any), then creates a DrinkJob or SleepJob for that colonist (with force: true).
  • Force-created jobs bypass the low-need threshold and DEBUG_HAUL_ONLY in NeedJobScheduler; JobAssigner still assigns them when they are in the queue.

Batch mode (legacy)

  • createDrinkJobs() / createSleepJobs() – scan all colonists, create jobs for thirsty/sleepy
  • Prefer event-driven flow; batch kept for fallback