/** * Calculates the distance between the two given packed tiles. * * @param packed_from The origin. * @param packed_to The destination. * @return The longest distance between the X or Y coordinates, plus half the shortest. */ uint16 Tile_GetDistancePacked(uint16 packed_from, uint16 packed_to) { tile32 from = Tile_UnpackTile(packed_from); tile32 to = Tile_UnpackTile(packed_to); return Tile_GetDistance(from, to) >> 8; }
/** * Gets the distance from the given object to the given encoded index. * @param o The object. * @param encoded The encoded index. * @return The distance. */ uint16 Object_GetDistanceToEncoded(Object* o, uint16 encoded) { Structure* s; tile32 position; s = Tools_Index_GetStructure(encoded); if (s != NULL) { uint16 packed; position = s->o.position; packed = Tile_PackTile(position); packed += g_table_structure_layoutEdgeTiles[g_table_structureInfo[o->type].layout][(Orientation_256To8(Tile_GetDirection(o->position, position)) + 4) & 7]; position = Tile_UnpackTile(packed); } else { position = Tools_Index_GetTile(encoded); } return Tile_GetDistance(o->position, position); }
/** * Get a tile in the direction of a destination, randomized a bit. * * @param packed_from The origin. * @param packed_to The destination. * @return A packed tile. */ uint16 Tile_GetTileInDirectionOf(uint16 packed_from, uint16 packed_to) { int16 distance; uint8 direction; uint8 i; if (packed_from == 0 || packed_to == 0) return 0; distance = Tile_GetDistancePacked(packed_from, packed_to); direction = Tile_GetDirectionPacked(packed_to, packed_from); if (distance <= 10) return 0; for (i = 0; i < 4; i++) { int16 dir; tile32 position; uint16 packed; dir = 29 + (Tools_Random_256() & 0x3F); if ((Tools_Random_256() & 1) != 0) dir = -dir; position = Tile_UnpackTile(packed_to); position = Tile_MoveByDirection(position, direction + dir, min(distance, 20) << 8); packed = Tile_PackTile(position); if (Map_IsValidPosition(packed)) return packed; } return 0; }
/** * Remove fog in the radius around the given tile. * * @param tile The tile to remove fog around. * @param radius The radius to remove fog around. */ void Tile_RemoveFogInRadius(tile32 tile, uint16 radius) { uint16 packed; uint16 x, y; int16 i, j; packed = Tile_PackTile(tile); if (!Map_IsValidPosition(packed)) return; x = Tile_GetPackedX(packed); y = Tile_GetPackedY(packed); tile.tile = Tile_GetSpecialXY(tile); for (i = -radius; i <= radius; i++) { for (j = -radius; j <= radius; j++) { tile32 t; if ((x + i) < 0 || (x + i) >= 64) continue; if ((y + j) < 0 || (y + j) >= 64) continue; packed = Tile_PackXY(x + i, y + j); t.tile = Tile_GetSpecialXY(Tile_UnpackTile(packed)); if (Tile_GetDistanceRoundedUp(tile, t) > radius) continue; Map_UnveilTile(packed, g_playerHouseID); } } }
/** * Unknown function 0C5A. * * Stack: *none* * * @param script The script engine to operate on. * @return unknown. */ uint16 Script_Structure_Unknown0C5A(ScriptEngine *script) { tile32 tile; Structure *s; Unit *u; uint16 position; VARIABLE_NOT_USED(script); s = g_scriptCurrentStructure; if (s->o.linkedID == 0xFF) return 0; u = Unit_Get_ByIndex(s->o.linkedID); if (g_table_unitInfo[u->o.type].movementType == MOVEMENT_WINGER && Unit_SetPosition(u, s->o.position)) { s->o.linkedID = u->o.linkedID; u->o.linkedID = 0xFF; if (s->o.linkedID == 0xFF) Structure_SetState(s, STRUCTURE_STATE_IDLE); Object_Script_Variable4_Clear(&s->o); if (s->o.houseID == g_playerHouseID) Sound_Output_Feedback(g_playerHouseID + 49); return 1; } position = Structure_FindFreePosition(s, u->o.type == UNIT_HARVESTER); if (position == 0) return 0; u->o.seenByHouses |= s->o.seenByHouses; tile = Tile_Center(Tile_UnpackTile(position)); if (!Unit_SetPosition(u, tile)) return 0; s->o.linkedID = u->o.linkedID; u->o.linkedID = 0xFF; Unit_SetOrientation(u, Tile_GetDirection(s->o.position, u->o.position) & 0xE0, true, 0); Unit_SetOrientation(u, u->orientation[0].current, true, 1); if (u->o.houseID == g_playerHouseID && u->o.type == UNIT_HARVESTER) { GUI_DisplayHint(STR_SEARCH_FOR_SPICE_FIELDS_TO_HARVEST, 0x6A); } if (s->o.linkedID == 0xFF) Structure_SetState(s, STRUCTURE_STATE_IDLE); Object_Script_Variable4_Clear(&s->o); if (s->o.houseID != g_playerHouseID) return 1; if (s->o.type == STRUCTURE_REPAIR) return 1; Sound_Output_Feedback(g_playerHouseID + ((u->o.type == UNIT_HARVESTER) ? 68 : 30)); return 1; }
/** * Destroy a structure and spawn soldiers around the place. * * Stack: *none* * * @param script The script engine to operate on. * @return Always 0. */ uint16 Script_Structure_Destroy(ScriptEngine *script) { Structure *s; uint16 position; uint16 layout; uint16 i; VARIABLE_NOT_USED(script); s = g_scriptCurrentStructure; layout = g_table_structureInfo[s->o.type].layout; position = Tile_PackTile(s->o.position); Structure_Remove(s); for (i = 0; i < g_table_structure_layoutTileCount[layout]; i++) { tile32 tile; Unit *u; tile = Tile_UnpackTile(position + g_table_structure_layoutTiles[layout][i]); if (g_table_structureInfo[s->o.type].o.spawnChance < Tools_Random_256()) continue; u = Unit_Create(UNIT_INDEX_INVALID, UNIT_SOLDIER, s->o.houseID, tile, Tools_Random_256()); if (u == NULL) continue; u->o.hitpoints = g_table_unitInfo[UNIT_SOLDIER].o.hitpoints * (Tools_Random_256() & 3) / 256; if (s->o.houseID != g_playerHouseID) { Unit_SetAction(u, ACTION_ATTACK); continue; } Unit_SetAction(u, ACTION_MOVE); tile = Tile_MoveByRandom(u->o.position, 32, true); u->targetMove = Tools_Index_Encode(Tile_PackTile(tile), IT_TILE); } if (g_debugScenario) return 0; if (s->o.houseID != g_playerHouseID) return 0; if (g_config.language == LANGUAGE_FRENCH) { GUI_DisplayText("%s %s %s", 0, String_Get_ByIndex(g_table_structureInfo[s->o.type].o.stringID_full), g_table_houseInfo[s->o.houseID].name, String_Get_ByIndex(0x85)); } else { GUI_DisplayText("%s %s %s", 0, g_table_houseInfo[s->o.houseID].name, String_Get_ByIndex(g_table_structureInfo[s->o.type].o.stringID_full), String_Get_ByIndex(0x85)); } return 0; }
static void Skirmish_GenSandworms() { const HouseType houseID = (g_playerHouseID == HOUSE_FREMEN) ? HOUSE_ATREIDES : HOUSE_FREMEN; const UnitType type = UNIT_SANDWORM; const UnitActionType actionType = ACTION_AMBUSH; const uint16 acceptableLst = (1 << LST_NORMAL_SAND) | (1 << LST_ENTIRELY_DUNE) | (1 << LST_PARTIAL_DUNE) | (1 << LST_SPICE) | (1 << LST_THICK_SPICE) | (1 << LST_BLOOM_FIELD); /* Create two sandworms. */ for (int count = 2; count > 0;) { const uint16 packed = Skirmish_PickRandomLocation(acceptableLst, 0); if (packed == 0) continue; const tile32 position = Tile_UnpackTile(packed); Scenario_Create_Unit(houseID, type, 256, position, 127, actionType); count--; } }
/** * Make the structure explode. * * Stack: *none* * * @param script The script engine to operate on. * @return unknown. */ uint16 Script_Structure_Explode(ScriptEngine *script) { Structure *s; uint16 position; uint16 layout; uint16 i; VARIABLE_NOT_USED(script); s = g_scriptCurrentStructure; layout = g_table_structureInfo[s->o.type].layout; position = Tile_PackTile(s->o.position); for (i = 0; i < g_table_structure_layoutTileCount[layout]; i++) { tile32 tile; tile = Tile_UnpackTile(position + g_table_structure_layoutTiles[layout][i]); Map_MakeExplosion(EXPLOSION_STRUCTURE, tile, 0, 0); } return 0; }
static void Scenario_Load_Unit(const char *key, char *settings) { uint8 houseType, unitType, actionType; int8 orientation; uint16 hitpoints; tile32 position; Unit *u; char *split; VARIABLE_NOT_USED(key); /* The value should have 6 values separated by a ',' */ split = strchr(settings, ','); if (split == NULL) return; *split = '\0'; /* First value is the House type */ houseType = House_StringToType(settings); if (houseType == HOUSE_INVALID) return; /* Find the next value in the ',' separated list */ settings = split + 1; split = strchr(settings, ','); if (split == NULL) return; *split = '\0'; /* Second value is the Unit type */ unitType = Unit_StringToType(settings); if (unitType == UNIT_INVALID) return; /* Find the next value in the ',' separated list */ settings = split + 1; split = strchr(settings, ','); if (split == NULL) return; *split = '\0'; /* Third value is the Hitpoints in percent (in base 256) */ hitpoints = atoi(settings); /* Find the next value in the ',' separated list */ settings = split + 1; split = strchr(settings, ','); if (split == NULL) return; *split = '\0'; /* Fourth value is the position on the map */ position = Tile_UnpackTile(atoi(settings)); /* Find the next value in the ',' separated list */ settings = split + 1; split = strchr(settings, ','); if (split == NULL) return; *split = '\0'; /* Fifth value is orientation */ orientation = (int8)((uint8)atoi(settings)); /* Sixth value is the current state of the unit */ settings = split + 1; actionType = Unit_ActionStringToType(settings); if (actionType == ACTION_INVALID) return; u = Unit_Allocate(UNIT_INDEX_INVALID, unitType, houseType); if (u == NULL) return; u->o.flags.s.byScenario = true; u->o.hitpoints = hitpoints * g_table_unitInfo[unitType].o.hitpoints / 256; u->o.position = position; u->orientation[0].current = orientation; u->actionID = actionType; u->nextActionID = ACTION_INVALID; /* In case the above function failed and we are passed campaign 2, don't add the unit */ if (!Map_IsValidPosition(Tile_PackTile(u->o.position)) && g_campaignID > 2) { Unit_Free(u); return; } /* XXX -- There is no way this is ever possible, as the beingBuilt flag is unset by Unit_Allocate() */ if (!u->o.flags.s.isNotOnMap) Unit_SetAction(u, u->actionID); u->o.seenByHouses = 0x00; Unit_HouseUnitCount_Add(u, u->o.houseID); Unit_SetOrientation(u, u->orientation[0].current, true, 0); Unit_SetOrientation(u, u->orientation[0].current, true, 1); Unit_SetSpeed(u, 0); }
/** * Loop over all houses, preforming various of tasks. */ void GameLoop_House(void) { PoolFindStruct find; House *h = NULL; bool tickHouse = false; bool tickPowerMaintenance = false; bool tickStarport = false; bool tickReinforcement = false; bool tickMissileCountdown = false; bool tickStarportAvailability = false; if (g_debugScenario) return; if (s_tickHouseHouse <= g_timerGame) { tickHouse = true; s_tickHouseHouse = g_timerGame + 900; } if (g_tickHousePowerMaintenance <= g_timerGame) { tickPowerMaintenance = true; g_tickHousePowerMaintenance = g_timerGame + 10800; } if (s_tickHouseStarport <= g_timerGame) { tickStarport = true; s_tickHouseStarport = g_timerGame + 180; } if (s_tickHouseReinforcement <= g_timerGame) { tickReinforcement = true; s_tickHouseReinforcement = g_timerGame + (g_debugGame ? 60 : 600); } if (s_tickHouseMissileCountdown <= g_timerGame) { tickMissileCountdown = true; s_tickHouseMissileCountdown = g_timerGame + 60; } if (s_tickHouseStarportAvailability <= g_timerGame) { tickStarportAvailability = true; s_tickHouseStarportAvailability = g_timerGame + 1800; } if (tickMissileCountdown && g_houseMissileCountdown != 0) { g_houseMissileCountdown--; Sound_Output_Feedback(g_houseMissileCountdown + 41); if (g_houseMissileCountdown == 0) Unit_LaunchHouseMissile(Map_FindLocationTile(4, g_playerHouseID)); } if (tickStarportAvailability) { uint16 type; /* Pick a random unit to increase starport availability */ type = Tools_RandomLCG_Range(0, UNIT_MAX - 1); /* Increase how many of this unit is available via starport by one */ if (g_starportAvailable[type] != 0 && g_starportAvailable[type] < 10) { if (g_starportAvailable[type] == -1) { g_starportAvailable[type] = 1; } else { g_starportAvailable[type]++; } } } if (tickReinforcement) { Unit *nu = NULL; int i; for (i = 0; i < 16; i++) { uint16 locationID; bool deployed; Unit *u; if (g_scenario.reinforcement[i].unitID == UNIT_INDEX_INVALID) continue; if (g_scenario.reinforcement[i].timeLeft == 0) continue; if (--g_scenario.reinforcement[i].timeLeft != 0) continue; u = Unit_Get_ByIndex(g_scenario.reinforcement[i].unitID); locationID = g_scenario.reinforcement[i].locationID; deployed = false; if (locationID >= 4) { if (nu == NULL) { nu = Unit_Create(UNIT_INDEX_INVALID, UNIT_CARRYALL, u->o.houseID, Tile_UnpackTile(Map_FindLocationTile(Tools_Random_256() & 3, u->o.houseID)), 100); if (nu != NULL) { nu->o.flags.s.byScenario = true; Unit_SetDestination(nu, Tools_Index_Encode(Map_FindLocationTile(locationID, u->o.houseID), IT_TILE)); } } if (nu != NULL) { u->o.linkedID = nu->o.linkedID; nu->o.linkedID = (uint8)u->o.index; nu->o.flags.s.inTransport = true; g_scenario.reinforcement[i].unitID = UNIT_INDEX_INVALID; deployed = true; } else { /* Failed to create carry-all, try again in a short moment */ g_scenario.reinforcement[i].timeLeft = 1; } } else { deployed = Unit_SetPosition(u, Tile_UnpackTile(Map_FindLocationTile(locationID, u->o.houseID))); } if (deployed && g_scenario.reinforcement[i].repeat != 0) { tile32 tile; tile.x = 0xFFFF; tile.y = 0xFFFF; g_validateStrictIfZero++; u = Unit_Create(UNIT_INDEX_INVALID, u->o.type, u->o.houseID, tile, 0); g_validateStrictIfZero--; if (u != NULL) { g_scenario.reinforcement[i].unitID = u->o.index; g_scenario.reinforcement[i].timeLeft = g_scenario.reinforcement[i].timeBetween; } } } } find.houseID = HOUSE_INVALID; find.index = 0xFFFF; find.type = 0xFFFF; while (true) { h = House_Find(&find); if (h == NULL) break; if (tickHouse) { /* ENHANCEMENT -- Originally this code was outside the house loop, which seems very odd. * This problem is considered to be so bad, that the original code has been removed. */ if (h->index != g_playerHouseID) { if (h->creditsStorage < h->credits) { h->credits = h->creditsStorage; } } else { uint16 maxCredits = max(h->creditsStorage, g_playerCreditsNoSilo); if (h->credits > maxCredits) { h->credits = maxCredits; GUI_DisplayText(String_Get_ByIndex(STR_INSUFFICIENT_SPICE_STORAGE_AVAILABLE_SPICE_IS_LOST), 1); } } if (h->index == g_playerHouseID) { if (h->creditsStorage > g_playerCreditsNoSilo) { g_playerCreditsNoSilo = 0; } if (g_playerCreditsNoSilo == 0 && g_campaignID > 1 && h->credits != 0) { if (h->creditsStorage != 0 && ((h->credits * 256 / h->creditsStorage) > 200)) { GUI_DisplayText(String_Get_ByIndex(STR_SPICE_STORAGE_CAPACITY_LOW_BUILD_SILOS), 0); } } if (h->credits < 100 && g_playerCreditsNoSilo != 0) { GUI_DisplayText(String_Get_ByIndex(STR_CREDITS_ARE_LOW_HARVEST_SPICE_FOR_MORE_CREDITS), 0); } } } if (tickHouse) House_EnsureHarvesterAvailable((uint8)h->index); if (tickStarport && h->starportLinkedID != UNIT_INDEX_INVALID) { Unit *u = NULL; h->starportTimeLeft--; if ((int16)h->starportTimeLeft < 0) h->starportTimeLeft = 0; if (h->starportTimeLeft == 0) { Structure *s; s = Structure_Get_ByIndex(g_structureIndex); if (s->o.type == STRUCTURE_STARPORT && s->o.houseID == h->index) { u = Unit_CreateWrapper((uint8)h->index, UNIT_FRIGATE, Tools_Index_Encode(s->o.index, IT_STRUCTURE)); } else { PoolFindStruct find2; find2.houseID = h->index; find2.index = 0xFFFF; find2.type = STRUCTURE_STARPORT; while (true) { s = Structure_Find(&find2); if (s == NULL) break; if (s->o.linkedID != 0xFF) continue; u = Unit_CreateWrapper((uint8)h->index, UNIT_FRIGATE, Tools_Index_Encode(s->o.index, IT_STRUCTURE)); break; } } if (u != NULL) { u->o.linkedID = (uint8)h->starportLinkedID; h->starportLinkedID = UNIT_INDEX_INVALID; u->o.flags.s.inTransport = true; Sound_Output_Feedback(38); } h->starportTimeLeft = (u != NULL) ? g_table_houseInfo[h->index].starportDeliveryTime : 1; } } if (tickHouse) { House_CalculatePowerAndCredit(h); Structure_CalculateHitpointsMax(h); if (h->timerUnitAttack != 0) h->timerUnitAttack--; if (h->timerSandwormAttack != 0) h->timerSandwormAttack--; if (h->timerStructureAttack != 0) h->timerStructureAttack--; if (h->harvestersIncoming > 0 && Unit_CreateWrapper((uint8)h->index, UNIT_HARVESTER, 0) != NULL) h->harvestersIncoming--; } if (tickPowerMaintenance) { uint16 powerMaintenanceCost = (h->powerUsage / 32) + 1; h->credits -= min(h->credits, powerMaintenanceCost); } } }
static void Skirmish_GenUnitsAI(HouseType houseID) { const uint16 unacceptableLst = (1 << LST_WALL) | (1 << LST_STRUCTURE) | (1 << LST_BLOOM_FIELD); Structure* factory[8] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; unsigned int nfactories = 0; PoolFindStruct find; find.houseID = houseID; find.type = 0xFFFF; find.index = STRUCTURE_INDEX_INVALID; Structure* s; while ((nfactories < lengthof(factory)) && ((s = Structure_Find(&find)) != NULL)) { /* Do not produce from hi-tech. */ if (s->o.type == STRUCTURE_LIGHT_VEHICLE || s->o.type == STRUCTURE_HEAVY_VEHICLE || s->o.type == STRUCTURE_WOR_TROOPER || s->o.type == STRUCTURE_BARRACKS) { factory[nfactories] = s; nfactories++; } } if (nfactories == 0) return; for (int count = 8; count > 0;) { const uint16 packed = Skirmish_PickRandomLocation(0xFF, unacceptableLst); if (packed == 0) continue; uint16 dist_ally; uint16 dist_enemy; Skirmish_FindClosestStructures(houseID, packed, &dist_ally, &dist_enemy); if (dist_ally > 8) continue; /* If there's a mountain here, build infantry. */ const LandscapeType lst = (const LandscapeType)Map_GetLandscapeType(packed); UnitType type; if (lst == LST_ENTIRELY_MOUNTAIN || lst == LST_PARTIAL_MOUNTAIN) { type = House_GetInfantrySquad(houseID); } /* Otherwise, build a random vehicle. */ else { const int r = Tools_RandomLCG_Range(0, nfactories - 1); type = (UnitType)StructureAI_PickNextToBuild(factory[r]); if (type == UNIT_INVALID) continue; } const UnitActionType actionType = ((Tools_Random_256() & 0x3) == 0) ? ACTION_AMBUSH : ACTION_AREA_GUARD; const tile32 position = Tile_UnpackTile(packed); Scenario_Create_Unit(houseID, type, 256, position, 127, actionType); count--; } }
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; }