/** * Refine spice in the current structure. * * Stack: *none* * * @param script The script engine to operate on. * @return 0 if there is no spice to refine, otherwise 1. */ uint16 Script_Structure_RefineSpice(ScriptEngine *script) { const StructureInfo *si; Structure *s; Unit *u; House *h; uint16 harvesterStep, creditsStep; VARIABLE_NOT_USED(script); s = g_scriptCurrentStructure; if (s->o.linkedID == 0xFF) { Structure_SetState(s, STRUCTURE_STATE_IDLE); return 0; } u = Unit_Get_ByIndex(s->o.linkedID); si = &g_table_structureInfo[s->o.type]; harvesterStep = (s->o.hitpoints * 256 / si->o.hitpoints) * 3 / 256; if (u->amount < harvesterStep) harvesterStep = u->amount; if (u->amount != 0 && harvesterStep < 1) harvesterStep = 1; if (harvesterStep == 0) return 0; creditsStep = 7; if (u->o.houseID != g_playerHouseID) { creditsStep += (Tools_Random_256() % 4) - 1; } creditsStep *= harvesterStep; if (House_AreAllied(g_playerHouseID, s->o.houseID)) { g_scenario.harvestedAllied += creditsStep; if (g_scenario.harvestedAllied > 65000) g_scenario.harvestedAllied = 65000; } else { g_scenario.harvestedEnemy += creditsStep; if (g_scenario.harvestedEnemy > 65000) g_scenario.harvestedEnemy = 65000; } h = House_Get_ByIndex(s->o.houseID); h->credits += creditsStep; u->amount -= harvesterStep; if (u->amount == 0) u->o.flags.s.inTransport = false; s->o.script.delay = 6; return 1; }
/** * Find a Unit which is within range and not an ally. * * Stack: 1 - Range to find a target in (amount of tiles multiplied with 256). * * @param script The script engine to operate on. * @return The Unit Index of the closest unit within range and not friendly, * or 0 if none exists. */ uint16 Script_Structure_FindTargetUnit(ScriptEngine *script) { PoolFindStruct find; Structure *s; Unit *u; uint32 distanceCurrent; uint32 targetRange; s = g_scriptCurrentStructure; targetRange = STACK_PEEK(1); distanceCurrent = 32000; u = NULL; find.houseID = HOUSE_INVALID; find.index = 0xFFFF; find.type = 0xFFFF; while (true) { uint16 distance; Unit *uf; uf = Unit_Find(&find); if (uf == NULL) break; if (House_AreAllied(s->o.houseID, uf->o.houseID)) continue; if (uf->o.type != UNIT_ORNITHOPTER) { if ((uf->o.seenByHouses & (1 << s->o.houseID)) == 0) continue; } distance = Tile_GetDistance(uf->o.position, s->o.position); if (distance >= distanceCurrent) continue; if (uf->o.type == UNIT_ORNITHOPTER) { if (distance >= targetRange * 3) continue; } else { if (distance >= targetRange) continue; } /* ENHANCEMENT -- The original code swapped the assignment, making it do nothing, Now it finds the closest unit to shoot at, what seems to be the intention */ if (g_dune2_enhanced) distanceCurrent = distance; u = uf; } if (u == NULL) return IT_NONE; return Tools_Index_Encode(u->o.index, IT_UNIT); }
static void Skirmish_FindClosestStructures(HouseType houseID, uint16 packed, uint16* dist_ally, uint16* dist_enemy) { PoolFindStruct find; Structure* s; find.houseID = HOUSE_INVALID; find.type = 0xFFFF; find.index = STRUCTURE_INDEX_INVALID; *dist_ally = 0xFFFF; *dist_enemy = 0xFFFF; while ((s = Structure_Find(&find)) != NULL) { if (s->o.type == STRUCTURE_SLAB_1x1 || s->o.type == STRUCTURE_SLAB_2x2 || s->o.type == STRUCTURE_WALL) continue; const uint16 dist = Tile_GetDistancePacked(Tile_PackTile(s->o.position), packed); if (House_AreAllied(houseID, s->o.houseID)) *dist_ally = min(dist, *dist_ally); else *dist_enemy = min(dist, *dist_enemy); } }
static bool Skirmish_GenUnitsHuman(HouseType houseID, SkirmishData* sd) { const int delta[7] = { 0, -4, 4, -MAP_SIZE_MAX * 3 - 2, -MAP_SIZE_MAX * 3 + 2, MAP_SIZE_MAX * 3 - 2, MAP_SIZE_MAX * 3 + 2, }; const MapInfo* mi = &g_mapInfos[0]; /* Pick a tile that is not too close to the edge, and not too * close to the enemy. */ int r; for (int attempts = 0; attempts < 100; attempts++) { const int island = Skirmish_PickRandomIsland(sd); if (island < 0) return false; r = Tools_RandomLCG_Range(sd->island[island].start, sd->island[island].end - 1); if (!(mi->minX + 4 <= sd->buildable[r].x && sd->buildable[r].x < mi->minX + mi->sizeX - 4)) continue; if (!(mi->minY + 3 <= sd->buildable[r].y && sd->buildable[r].y < mi->minY + mi->sizeY - 3)) continue; PoolFindStruct find; find.houseID = HOUSE_INVALID; find.type = 0xFFFF; find.index = STRUCTURE_INDEX_INVALID; Structure* s = Structure_Find(&find); for (; s != NULL; s = Structure_Find(&find)) { if (s->o.type == STRUCTURE_SLAB_1x1 || s->o.type == STRUCTURE_SLAB_2x2 || s->o.type == STRUCTURE_WALL) continue; if (House_AreAllied(g_playerHouseID, s->o.houseID)) continue; const uint16 dist = Tile_GetDistancePacked(Tile_PackTile(s->o.position), sd->buildable[r].packed); if (dist < 24) break; } if (s == NULL) { break; } else { r = -1; } } if (r < 0) return false; bool mcvPlaced = false; for (int i = 0; i < 7; i++) { const uint16 packed = sd->buildable[r].packed + delta[i]; const tile32 position = Tile_UnpackTile(packed); UnitType type; if (!mcvPlaced) type = UNIT_MCV; else if (i >= 0 && i < 3) type = UNIT_SIEGE_TANK; else if (i >= 3 && i < 4) type = House_GetIXVehicle(houseID); else if (i >= 4 && i < 5) type = House_GetMediumVehicle(houseID); else if (i >= 5 && i < 6) type = UNIT_QUAD; else if (i >= 6) type = House_GetInfantrySquad(houseID); const LandscapeType lst = (const LandscapeType)Map_GetLandscapeType(packed); /* If there's a structure or a bloom here, tough luck. */ if (lst == LST_STRUCTURE || lst == LST_BLOOM_FIELD) continue; /* If there's a mountain here, build infantry instead. */ if (lst == LST_ENTIRELY_MOUNTAIN || lst == LST_PARTIAL_MOUNTAIN) type = House_GetInfantrySquad(houseID); Scenario_Create_Unit(houseID, type, 256, position, 127, (UnitActionType)g_table_unitInfo[type].o.actionsPlayer[3]); if (type == UNIT_MCV) mcvPlaced = true; } return true; }
static bool Skirmish_GenStructuresAI(HouseType houseID, SkirmishData* sd) { uint16 tech_level = 0; uint16 structure = 0; int structure_count = 0; int structure_threshold = 100; int cpu_count = 0; for (HouseType h = HOUSE_HARKONNEN; h < HOUSE_MAX; h++) { if (g_skirmish.brain[h] == BRAIN_CPU_ENEMY || g_skirmish.brain[h] == BRAIN_CPU_ALLY) cpu_count++; } assert(cpu_count != 0); const int max_structure_count = 60 / cpu_count; /* First pass finds out what to build. */ tech_level = 0; structure = 0; structure_count = 0; for (structure = 0; (structure < lengthof(buildorder)) && (tech_level <= g_techLevel); structure++) { if (buildorder[structure].type == STRUCTURE_INVALID) tech_level++; else if ((buildorder[structure].availableHouse & (1 << houseID)) != 0) structure_count++; } if (structure_count > max_structure_count) { structure_count = max_structure_count; SkirmishBuildOrder* bo = (SkirmishBuildOrder*)malloc(structure * sizeof(SkirmishBuildOrder)); assert(bo != NULL); memcpy(bo, buildorder, structure * sizeof(SkirmishBuildOrder)); qsort(bo, structure, sizeof(SkirmishBuildOrder), Skirmish_BuildOrder_Sorter); structure_threshold = bo[structure_count].priority; free(bo); } /* Second pass builds structures below the threshold priority. */ tech_level = 0; structure = 0; for (int attempts = 0; attempts < 100; attempts++) { int island = Skirmish_PickRandomIsland(sd); int range = 8; if (island < 0) return false; /* Re-flood-fill the island, using a new starting point. */ { const int r = Tools_RandomLCG_Range(sd->island[island].start, sd->island[island].end - 1); uint16 dist_ally, dist_enemy; Skirmish_FindClosestStructures(houseID, sd->buildable[r].packed, &dist_ally, &dist_enemy); if ((dist_ally > 16 && dist_ally != 0xFFFF) || (dist_enemy < 24)) continue; const int area = Skirmish_FindBuildableArea(island, sd->buildable[r].x, sd->buildable[r].y, sd, sd->buildable + sd->island[island].start); assert(area == sd->island[island].end - sd->island[island].start); UNUSED(area); for (int i = sd->island[island].start; i < sd->island[island].end; i++) sd->islandID[sd->buildable[i].packed] = island; } /* Use no-cheat-mode to verify land is buildable. */ g_validateStrictIfZero--; assert(g_validateStrictIfZero == 0); /* Place structures. */ while (structure_count > 0) { assert(structure < lengthof(buildorder)); if (buildorder[structure].type == STRUCTURE_INVALID) { tech_level++; structure++; continue; } if (buildorder[structure].priority >= structure_threshold) { structure++; continue; } const StructureType type = buildorder[structure].type; const StructureInfo* si = &g_table_structureInfo[type]; const int r = Tools_RandomLCG_Range(0, range - 1); const uint16 packed = sd->buildable[sd->island[island].start + r].packed; if (Structure_IsValidBuildLandscape(packed, type) != 0) { Structure* s = Structure_Create(STRUCTURE_INDEX_INVALID, type, houseID, packed); assert(s != NULL); s->o.hitpoints = si->o.hitpoints; s->o.flags.s.degrades = false; s->state = STRUCTURE_STATE_IDLE; if (House_AreAllied(g_playerHouseID, houseID)) s->o.seenByHouses = 0xFF; if (s->o.type == STRUCTURE_PALACE) s->countDown = g_table_houseInfo[houseID].specialCountDown; range = min(range + 4, sd->island[island].end - sd->island[island].start); structure++; structure_count--; } else { range++; if (range > sd->island[island].end - sd->island[island].start) break; } } /* Connect structures on this island with concrete slabs. */ for (range--; range > 0; range--) { const int self = sd->island[island].start + range; uint16 packed = sd->buildable[self].packed; const LandscapeType lst = (const LandscapeType)Map_GetLandscapeType(packed); if (lst != LST_STRUCTURE && lst != LST_CONCRETE_SLAB) continue; const int parent = sd->island[island].start + sd->buildable[self].parent; packed = sd->buildable[parent].packed; if (Structure_IsValidBuildLandscape(packed, STRUCTURE_SLAB_1x1) == 0) continue; g_validateStrictIfZero++; Structure_Create(STRUCTURE_INDEX_INVALID, STRUCTURE_SLAB_1x1, houseID, packed); g_validateStrictIfZero--; } /* Finished building on this island. Create sub-islands from * remaining buildable tiles. */ g_validateStrictIfZero++; Skirmish_DivideIsland(houseID, island, sd); if (structure_count <= 0) return true; } return false; }
/** * Check if a level is finished, based on the values in WinFlags. * * @return True if and only if the level has come to an end. */ static bool GameLoop_IsLevelFinished() { bool finish = false; if (s_debugForceWin) return true; /* You have to play at least 7200 ticks before you can win the game */ if (g_timerGame - g_tickScenarioStart < 7200) return false; /* Check for structure counts hitting zero */ if ((g_scenario.winFlags & 0x3) != 0) { PoolFindStruct find; uint16 countStructureEnemy = 0; uint16 countStructureFriendly = 0; find.houseID = HOUSE_INVALID; find.type = 0xFFFF; find.index = 0xFFFF; /* Calculate how many structures are left on the map */ while (true) { Structure* s; s = Structure_Find(&find); if (s == NULL) break; if (s->o.type == STRUCTURE_SLAB_1x1 || s->o.type == STRUCTURE_SLAB_2x2 || s->o.type == STRUCTURE_WALL) continue; if (s->o.type == STRUCTURE_TURRET) continue; if (s->o.type == STRUCTURE_ROCKET_TURRET) continue; if (House_AreAllied(s->o.houseID, g_playerHouseID)) countStructureFriendly++; else countStructureEnemy++; } if ((g_scenario.winFlags & 0x1) != 0 && countStructureEnemy == 0) finish = true; if ((g_scenario.winFlags & 0x2) != 0 && countStructureFriendly == 0) finish = true; } /* Check for reaching spice quota */ if ((g_scenario.winFlags & 0x4) != 0 && g_playerCredits != 0xFFFF) { if (g_playerCredits >= g_playerHouse->creditsQuota) finish = true; } /* Check for reaching timeout */ if ((g_scenario.winFlags & 0x8) != 0) { /* XXX -- This code was with '<' instead of '>=', which makes * no sense. As it is unused, who knows what the intentions * were. This at least makes it sensible. */ if (g_timerGame - g_tickScenarioStart >= g_scenario.timeOut) { finish = true; } } return finish; }
/** * Check if a level is won, based on the values in LoseFlags. * * @return True if and only if the level has been won by the human. */ static bool GameLoop_IsLevelWon() { bool win = false; if (s_debugForceWin) return true; /* Check for structure counts hitting zero */ if ((g_scenario.loseFlags & 0x3) != 0) { PoolFindStruct find; uint16 countStructureEnemy = 0; uint16 countStructureFriendly = 0; find.houseID = HOUSE_INVALID; find.type = 0xFFFF; find.index = 0xFFFF; /* Calculate how many structures are left on the map */ while (true) { Structure* s; s = Structure_Find(&find); if (s == NULL) break; if (s->o.type == STRUCTURE_SLAB_1x1 || s->o.type == STRUCTURE_SLAB_2x2 || s->o.type == STRUCTURE_WALL) continue; if (s->o.type == STRUCTURE_TURRET) continue; if (s->o.type == STRUCTURE_ROCKET_TURRET) continue; if (House_AreAllied(s->o.houseID, g_playerHouseID)) countStructureFriendly++; else countStructureEnemy++; } win = true; if ((g_scenario.loseFlags & 0x1) != 0) win = win && (countStructureEnemy == 0); if ((g_scenario.loseFlags & 0x2) != 0) win = win && (countStructureFriendly != 0); } /* Check for reaching spice quota */ if (!win && (g_scenario.loseFlags & 0x4) != 0 && g_playerCredits != 0xFFFF) win = (g_playerCredits >= g_playerHouse->creditsQuota); /* Check for reaching timeout */ if (!win && (g_scenario.loseFlags & 0x8) != 0) { /* ENHANCEMENT -- Same deal as for winFlags above. * This way we can make win-after-timeout (survival) and * lose-after-timeout (countdown) missions. * * survival: winFlags = 11, loseFlags = 9. * lose: winFlags = 11, loseFlags = 1. */ win = (g_timerGame - g_tickScenarioStart >= g_scenario.timeOut); } return win; }