Resource harvesting
Units collect a resource from the map and carry it to a structure that turns it into money.
On this page
Resource harvesting turns a resource on the map into money that pays for building. In Dune II a harvester’s load grows by a random step each time it harvests, up to a cap1, and a refinery pays a fixed rate per unit to the player as it empties the harvester2. Silos and refineries set how many credits a house can keep3.
In the remaster’s code, a Command & Conquer harvester carries at most 284 steps of Tiberium and lifts a whole or partial level from a cell each time, where Dune II’s load grows by a random step4,1. In Pikmin, a pellet or an enemy body drawn into an Onion makes it fire sprouts of its color, which bury themselves as leaf Pikmin5.
- Carried loadsDune II (1992). A harvester's load grows by a random 0 or 1 per call up to 100, and a refinery pays the player 7 credits a unit.1,2,3Command & Conquer (1995). A harvester carries 28 steps of Tiberium and a refinery pays 25 credits a step, 700 a load; the computer gets 708 a step in single player; Tiberium cells grow a level, and heavy cells and blossom trees seed clear neighbors.4,6,7Pikmin (2001). Pikmin carry pellets and enemy bodies to an Onion, which pays in sprouts of its color: one count for a pellet of its own type, another for the rest.5
Games
| Game | Year | How | Note |
|---|---|---|---|
| Factorio | 2016 | drills mine ore patches until each tile runs dry; crude oil never runs out | |
| Dune II | 1992 | Carried loads | a harvester's load grows by a random 0 or 1 per call up to 100, and a refinery pays the player 7 credits a unit |
| Command & Conquer | 1995 | Carried loads | a harvester carries 28 steps of Tiberium and a refinery pays 25 credits a step, 700 a load; the computer gets 708 a step in single player; Tiberium cells grow a level, and heavy cells and blossom trees seed clear neighbors |
| Dungeon Keeper | 1997 | Carried loads | Imps dig gold out of the rock and carry it to the Treasure Room; without one it lies on the floor and doesn't count |
| Pikmin | 2001 | Carried loads | Pikmin carry pellets and enemy bodies to an Onion, which pays in sprouts of its color: one count for a pellet of its own type, another for the rest |
| Infiniminer | 2009 | Carried loads | players carry ore to their team's bank, and gold and diamonds up to the surface, where they become the team's cash |
Every claim about this mechanic, by game (17)
References
- ^ abcEach time a harvester's script calls
Script_Unit_Harvestwhile the harvester stands on spice or thick spice with a load below 100, the load grows by 0 or 1 at random, capped at 100, and in 1 call out of 32 the tile loses a level: thick spice becomes spice, and spice becomes sand. The status line reports the load as a percentage full.CodeOpenDUNEHow we know
1651 if (u->amount >= 100) return 0; ⋮ 1656 if (type != LST_SPICE && type != LST_THICK_SPICE) return 0; ⋮ 1658 u->amount += Tools_Random_256() & 1; ⋮ 1663 if (u->amount > 100) u->amount = 100; ⋮ 1665 if ((Tools_Random_256() & 0x1F) != 0) return 1; ⋮ 1667 Map_ChangeSpiceAmount(packed, -1); ⋮ 1669 return 0;
src/script/unit.c lines 1650–1669 at ebea79a · upstream ↗ 787 type = (type == LST_THICK_SPICE) ? LST_SPICE : LST_NORMAL_SAND;src/map.c lines 784–788 at ebea79a · upstream ↗ 2064 stringID = STR_IS_D_PERCENT_FULL;src/unit.c lines 2061–2069 at ebea79a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-22.
- ^ abA refinery empties a docked harvester a step at a time: each call to
Script_Structure_RefineSpicemoves up to 3 load units at full health, 2 once damaged, and 1 below 171/256 of full health (computed fromhp × 256 / max × 3 / 256, at least 1), and pays 7 credits per unit to the player, or 6 to 9 at random per call to a computer house; the next call waits 6 script ticks. Computed: a full harvester (100) is worth 700 credits to the player.CodeOpenDUNEHow we know
125 harvesterStep = (s->o.hitpoints * 256 / si->o.hitpoints) * 3 / 256; ⋮ 128 if (u->amount != 0 && harvesterStep < 1) harvesterStep = 1; ⋮ 131 creditsStep = 7; 132 if (u->o.houseID != g_playerHouseID) { 133 creditsStep += (Tools_Random_256() % 4) - 1; ⋮ 136 creditsStep *= harvesterStep;
src/script/structure.c lines 125–136 at ebea79a · upstream ↗ 147 h->credits += creditsStep; 148 u->amount -= harvesterStep; ⋮ 151 s->o.script.delay = 6;
src/script/structure.c lines 146–151 at ebea79a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-22.
- ^ abEach refinery stores 1,005 credits and each spice silo 1,000 (the structure table's defaults). On each house tick, credits above a house's storage are lost, except that the player keeps the mission's starting credits as a floor until storage passes them. OpenDUNE moved this check: its comment says the original ran it outside the per-house loop, and enhancement.txt says 1.07 thereby updated only the house that owns the latest structure. In the original mode, a structure still being built also counts toward storage.CodeOpenDUNErevised
How we know
832 /* creditsStorage */ 1005,src/table/structureinfo.c line 832 at ebea79a · upstream ↗ 1157 /* creditsStorage */ 1000,src/table/structureinfo.c line 1157 at ebea79a · upstream ↗ 185 /* ENHANCEMENT -- Originally this code was outside the house loop, which seems very odd. 186 * This problem is considered to be so bad, that the original code has been removed. */ ⋮ 189 h->credits = h->creditsStorage; ⋮ 192 uint16 maxCredits = max(h->creditsStorage, g_playerCreditsNoSilo);
src/house.c lines 184–197 at ebea79a · upstream ↗ 78 g_playerCreditsNoSilo = h->credits;src/scenario.c line 78 at ebea79a · upstream ↗ 201 if (h->creditsStorage > g_playerCreditsNoSilo) {src/house.c lines 200–203 at ebea79a · upstream ↗ 78- Silo credit capping is done outside the main house loop, causing it to only 79 update the house which owns the latest structure. This allows you to go
enhancement.txt lines 78–79 at ebea79a · upstream ↗ 491 if (g_dune2_enhanced && s->o.flags.s.isNotOnMap) continue;src/house.c lines 490–491 at ebea79a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Revised after a check by a second AI model, 2026-10-22.
- ^ abcIn the remaster's code, a harvester carries at most 28 steps of Tiberium (
STEP_COUNT). Each time its harvest timer runs out while it stands on Tiberium short of a full load, it lifts (the cell'sOverlayDatamod 6) + 1 steps from the cell, no more than the room left, and the timer restarts atTICKS_PER_SECOND, 15 game ticks; the code's comment calls this lifting a complete "level" of 6 steps or a partial one. Dune II's harvester instead fills to 100 units by a random 0 or 1 per harvest call.CodeC&C Remastered source · OpenDUNEHow we know
862 TIBERIUM_STEP=25, // Credits per step of Tiberium. 863 STEP_COUNT=28, // Number of steps a harvester can carry. 864 FULL_LOAD_CREDITS=(TIBERIUM_STEP*STEP_COUNT),
TIBERIANDAWN/TYPE.H lines 862–864 at f1f0d42 · upstream ↗ 2470 if (Target_Legal(NavCom) || !HarvestTimer.Expired() || IsDriving || IsRotating) return(true); ⋮ 2472 if (Tiberium_Load() < 0x0100 && ptr->Land_Type() == LAND_TIBERIUM) { ⋮ 2476 ** "level" of Tiberium. A level happens to be 6 steps. If there ⋮ 2480 int reducer = (ptr->OverlayData % 6) + 1; 2481 reducer = ptr->Reduce_Tiberium(MIN(reducer, UnitTypeClass::STEP_COUNT-Tiberium)); 2482 Tiberium += reducer; ⋮ 2486 HarvestTimer = TICKS_PER_SECOND;
TIBERIANDAWN/UNIT.CPP lines 2470–2486 at f1f0d42 · upstream ↗ 154 return(Cardinal_To_Fixed(UnitTypeClass::STEP_COUNT, Tiberium));TIBERIANDAWN/DRIVE.CPP lines 151–154 at f1f0d42 · upstream ↗ 2146#define TICKS_PER_SECOND 15TIBERIANDAWN/DEFINES.H line 2146 at f1f0d42 · upstream ↗ 1651 if (u->amount >= 100) return 0; ⋮ 1658 u->amount += Tools_Random_256() & 1; ⋮ 1663 if (u->amount > 100) u->amount = 100; ⋮ 1669 return 0;
src/script/unit.c lines 1650–1669 at ebea79a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^ abWhen a pellet or an enemy body (both
Pellets with a config from parms/pelMgr.bin) finishes being drawn into an Onion,GoalItem::suckMeadds the config'smMatchingOnyonSeeds(p06) to the Onion's pending count if the config's type (p00) equals the Onion's color, andmNonMatchingOnyonSeeds(p07) otherwise; the code defaults are 1 and 1, and a negative value sets off fireworks instead. The Onion then fires one sprout of its own color per pending count, which lands and buries itself as a leaf; when no sprout object can be created, it stores a leaf Pikmin inside instead. Ship parts take the ship's path, not this one. In the decompilation's enum the type for bodies,PELTYPE_Corpse, is 0, the same value asPELTYPE_Blue, which the comparison would read as a blue match; the shipped configs' p00 values are data.CodeprojectPiki/pikminHow we know
344void GoalItem::suckMe(Pellet* item) ⋮ 346 PelletConfig* config = item->mConfig; 347 int pikiNum; 348 if (mOnionColour == config->mPelletType()) { 349 pikiNum = config->mMatchingOnyonSeeds(); 350 } else { 351 pikiNum = config->mNonMatchingOnyonSeeds(); ⋮ 354 if (pikiNum < 0) { 355 mSAICtx.mCounter += 2; ⋮ 360 } else { 361 mSAICtx.mCurrAnimId += pikiNum;
src/plugPikiKando/goalItem.cpp lines 344–365 at 9a21636 · upstream ↗ 110 , mPelletType(this, PELTYPE_Blue, 0, 0, "p00", "type(0-2:pel 3:ufo)") ⋮ 118 , mMatchingOnyonSeeds(this, 1, 0, 0, "p06", "intParm1") 119 , mNonMatchingOnyonSeeds(this, 1, 0, 0, "p07", "intParm2")
src/plugPikiKando/pelletMgr.cpp lines 110–119 at 9a21636 · upstream ↗ 1581 load("parms/", "pelMgr.bin", 1);src/plugPikiKando/pelletMgr.cpp line 1581 at 9a21636 · upstream ↗ 346 if (mSuckProgress >= 1.0f) { 347 if (pelt->mConfig->mPelletType() == PELTYPE_UfoPart) { 348 pelt->mTargetGoal->finishSuck(pelt); 349 } else { 350 pelt->mTargetGoal->suckMe(pelt);
src/plugPikiKando/pelletState.cpp lines 346–350 at 9a21636 · upstream ↗ 853 if (item->mSAICtx.mCurrAnimId > 0) { ⋮ 860 PikiHeadItem* seed = static_cast<PikiHeadItem*>(itemMgr->birth(OBJTYPE_Pikihead)); ⋮ 866 seed->setColor(obj->mOnionColour); ⋮ 872 C_SAI(seed)->start(seed, PikiHeadAI::PIKIHEAD_Flying); 873 } else { 874 pikiInfMgr.incPiki(obj->mOnionColour, Leaf); 875 obj->mHeldPikis[Leaf]++; ⋮ 886 obj->mSAICtx.mCurrAnimId--;
src/plugPikiKando/itemAI.cpp lines 853–886 at 9a21636 · upstream ↗ 170 addArrow(PIKIHEAD_Flying, saiBounceEvent, PIKIHEAD_Bounce); 171 addArrow(PIKIHEAD_Bounce, saiMotionDoneEvent, PIKIHEAD_Bury);
src/plugPikiKando/itemAI.cpp lines 170–171 at 9a21636 · upstream ↗ 147void PelletView::becomePellet(u32 id, Vector3f NRef pos, f32 direction) ⋮ 154 Pellet* pellet = pelletMgr->newPellet(id, this);
src/plugPikiKando/pelletMgr.cpp lines 147–154 at 9a21636 · upstream ↗ 37enum PelletType { 38 // Easier to label this separate, these will also have PelletColor as -1 39 PELTYPE_Corpse = 0, ⋮ 41 // Blue, Red, Yellow pellets 42 PELTYPE_Blue = 0, 43 PELTYPE_Red = 1, 44 PELTYPE_Yellow = 2,
include/Pellet.h lines 37–44 at 9a21636 · upstream ↗ 52enum PikiColor { 53 Blue = 0, 54 Red = 1, 55 Yellow = 2,
include/GlobalGameOptions.h lines 52–55 at 9a21636 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^In the remaster's code, a refinery empties a docked harvester one step at a time, and each step pays its house 25 credits if the house is human: computed, 700 for a full load of 28, which matches Dune II's 100 units at 7 credits. A computer house gets 708 credits a step in a normal (single-player) game, from a formula the remaster's comment calls "wrong" but kept "to not break missions", and 33 in multiplayer, a correction the comment dates to the remaster (14 May 2020). Credits above the house's storage capacity are lost.CodeC&C Remastered source · OpenDUNE
How we know
4511 int bail = techno->Offload_Tiberium_Bail(); ⋮ 4514 House->Harvested(bail);
TIBERIANDAWN/BUILDING.CPP lines 4511–4514 at f1f0d42 · upstream ↗ 1643 if (Tiberium) { 1644 Tiberium--; 1645 if (House->IsHuman) { 1646 return(UnitTypeClass::FULL_LOAD_CREDITS/UnitTypeClass::STEP_COUNT); // 25 in debugger ⋮ 1649 // MBL 05.14.2020: AI harvested credits fix for multiplayer, since they are miscalculated, and it's noticed ⋮ 1653 if (GameToPlay == GAME_NORMAL) // Non-multiplayer game, keep the original calculation; 708 in debugger ⋮ 1655 return(UnitTypeClass::FULL_LOAD_CREDITS+(UnitTypeClass::FULL_LOAD_CREDITS/3)/UnitTypeClass::STEP_COUNT); // Original (708), wrong calcualation but preserving to not break missions ⋮ 1659 return((UnitTypeClass::FULL_LOAD_CREDITS+(UnitTypeClass::FULL_LOAD_CREDITS/3))/UnitTypeClass::STEP_COUNT); // Corrected calculation
TIBERIANDAWN/DRIVE.CPP lines 1641–1660 at f1f0d42 · upstream ↗ 862 TIBERIUM_STEP=25, // Credits per step of Tiberium. 863 STEP_COUNT=28, // Number of steps a harvester can carry. 864 FULL_LOAD_CREDITS=(TIBERIUM_STEP*STEP_COUNT),
TIBERIANDAWN/TYPE.H lines 862–864 at f1f0d42 · upstream ↗ 1722 Tiberium += tiberium; 1723 if (Tiberium > Capacity) { 1724 Tiberium = Capacity; 1725 IsMaxedOut = true;
TIBERIANDAWN/HOUSE.CPP lines 1717–1727 at f1f0d42 · upstream ↗ 131 creditsStep = 7; 132 if (u->o.houseID != g_playerHouseID) {
src/script/structure.c lines 125–136 at ebea79a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^In the remaster's code, Tiberium grows and spreads on its own. Each map logic pass scans 30 of the 64 × 64 cells, starting at the cell where the last pass stopped (so 29 new ones), and collects up to 50 Tiberium cells below level (
OverlayData) 11 and up to 50 spread sources: Tiberium cells above level 6, and blossom trees whatever their level, each entered three times. After each full sweep, computed as 142 passes, a random collected cell grows one level, and one heavy cell seeds level-1 Tiberium on a random empty neighbor of clear land; with fast growth on, as by default, or in any multiplayer game, each happens twice, and in the remaster's own multiplayer (GAME_GLYPHX_MULTIPLAYER) a change its comment dates to 1 July 2020 adds (Tiberium setting − 1) × 2 more. A cell harvested to empty is cleared and holds Tiberium again only if a neighbor seeds it. Dune II's spice doesn't grow: during play OpenDUNE's code adds spice only where a spice bloom bursts, filling a circle of radius 5, or where a harvester carrying 32 or more units is destroyed, filling a circle of radius load ÷ 32.CodeC&C Remastered source · OpenDUNErevisedHow we know
904 if (!Special.IsTGrowth && !Special.IsTSpread) return; ⋮ 910 int subcount = 30; ⋮ 917 if (Special.IsTGrowth && ptr->Land_Type() == LAND_TIBERIUM && ptr->OverlayData < 11) { 918 if (TiberiumGrowthCount < sizeof(TiberiumGrowth)/sizeof(TiberiumGrowth[0])) { ⋮ 930 (ptr->Land_Type() == LAND_TIBERIUM && ptr->OverlayData > 6) || 931 (terrain && terrain->Class->IsTiberiumSpawn)) { ⋮ 934 if (terrain) tries = 3;
TIBERIANDAWN/MAP.CPP lines 904–943 at f1f0d42 · upstream ↗ 144 CELL TiberiumGrowth[50]; ⋮ 151 CELL TiberiumSpread[50];
TIBERIANDAWN/MAP.H lines 144–152 at f1f0d42 · upstream ↗ 948 if (TiberiumScan >= MAP_CELL_TOTAL) { 949 int tries = 1; 950 if (Special.IsTFast || GameToPlay != GAME_NORMAL) tries = 2; ⋮ 953 ** Use the Tiberium setting as a multiplier on growth rate. ST - 7/1/2020 3:05PM ⋮ 955 if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) { 956 if (MPlayerTiberium > 1) { 957 tries += (MPlayerTiberium - 1) << 1; ⋮ 972 newcell->OverlayData++; ⋮ 999 if (newcell && newcell->Cell_Object() == NULL && newcell->Land_Type() == LAND_CLEAR && newcell->Overlay == OVERLAY_NONE) { ⋮ 1012 newcell->OverlayData = 1;
TIBERIANDAWN/MAP.CPP lines 946–1012 at f1f0d42 · upstream ↗ 60 IsTGrowth = true; 61 IsTSpread = true; 62 IsTFast = true;
TIBERIANDAWN/SPECIAL.H lines 60–62 at f1f0d42 · upstream ↗ 269#define MAP_CELL_MAX_X_BITS 6 270#define MAP_CELL_MAX_Y_BITS 6 ⋮ 277#define MAP_CELL_TOTAL (MAP_CELL_W*MAP_CELL_H)
TIBERIANDAWN/DEFINES.H lines 269–277 at f1f0d42 · upstream ↗ 254 Map.Logic();TIBERIANDAWN/LOGIC.CPP line 254 at f1f0d42 · upstream ↗ 679 Map_FillCircleWithSpice(packed, 5);src/map.c lines 669–679 at ebea79a · upstream ↗ 694 if (radius == 0) return;src/map.c line 694 at ebea79a · upstream ↗ 100 Map_Bloom_ExplodeSpice(packed, g_playerHouseID);src/explosion.c line 100 at ebea79a · upstream ↗ 167 Map_Bloom_ExplodeSpice(packed, g_playerHouseID);src/explosion.c line 167 at ebea79a · upstream ↗ 1517 if (isSpiceBloom) Map_Bloom_ExplodeSpice(packed, Unit_GetHouseID(unit));src/unit.c line 1517 at ebea79a · upstream ↗ 1557 if (unit->o.type == UNIT_HARVESTER) Map_FillCircleWithSpice(Tile_PackTile(unit->o.position), unit->amount / 32);src/unit.c line 1557 at ebea79a · upstream ↗ 780 if (type == LST_THICK_SPICE && dir > 0) return; ⋮ 785 type = (type == LST_SPICE) ? LST_THICK_SPICE : LST_SPICE;
src/map.c lines 771–785 at ebea79a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
Sources
Code
- OpenDUNE contributors. 2026. “OpenDUNE: a C re-creation of Dune II from its DOS executable (OpenDUNE/OpenDUNE).” Commit ebea79a. OpenDUNE/OpenDUNE @ ebea79a · licence, all uses
- Studios, Westwood and Electronic Arts. 2025. “Command & Conquer Remastered Collection source code (electronicarts/CnC_Remastered_Collection).” Commit f1f0d42. electronicarts/CnC_Remastered_Collection @ f1f0d42 · licence, all uses
- projectPiki. 2026. “pikmin: a decompilation of Pikmin (projectPiki/pikmin).” Commit 9a21636. projectPiki/pikmin @ 9a21636 · licence, all uses