Пример #1
0
/**
 * Make the current unit harvest spice.
 *
 * Stack: *none*.
 *
 * @param script The script engine to operate on.
 * @return ??.
 */
uint16 Script_Unit_Harvest(ScriptEngine *script)
{
	Unit *u;
	uint16 packed;
	uint16 type;

	VARIABLE_NOT_USED(script);

	u = g_scriptCurrentUnit;

	if (u->o.type != UNIT_HARVESTER) return 0;
	if (u->amount >= 100) return 0;

	packed = Tile_PackTile(u->o.position);

	type = Map_GetLandscapeType(packed);
	if (type != LST_SPICE && type != LST_THICK_SPICE) return 0;

	u->amount += Tools_Random_256() & 1;
	u->o.flags.s.inTransport = true;

	Unit_UpdateMap(2, u);

	if (u->amount > 100) u->amount = 100;

	if ((Tools_Random_256() & 0x1F) != 0) return 1;

	Map_ChangeSpiceAmount(packed, -1);

	return 0;
}
Пример #2
0
static bool Skirmish_IsIslandEnclosed(int start, int end, const SkirmishData* sd)
{
	const int dx[4] = {0, 1, 0, -1};
	const int dy[4] = {-1, 0, 1, 0};
	const uint16 islandID = sd->islandID[sd->buildable[start].packed];

	for (int i = start; i < end; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			const int x = sd->buildable[i].x + dx[j];
			const int y = sd->buildable[i].y + dy[j];
			if (!(Map_InRangeX(x) && Map_InRangeY(y)))
				continue;

			const uint16 packed = Tile_PackXY(x, y);
			const LandscapeType lst = (const LandscapeType)Map_GetLandscapeType(packed);
			if (sd->islandID[packed] == islandID)
				continue;

			if (!(lst == LST_ENTIRELY_MOUNTAIN || lst == LST_PARTIAL_MOUNTAIN || lst == LST_WALL || lst == LST_STRUCTURE))
				return false;
		}
	}

	return true;
}
Пример #3
0
/**
 * Start the animation on the current tile.
 *
 * Stack: *none*.
 *
 * @param script The script engine to operate on.
 * @return The value 1. Always.
 */
uint16 Script_Unit_StartAnimation(ScriptEngine *script)
{
	Unit *u;
	uint16 animationUnitID;
	uint16 position;

	VARIABLE_NOT_USED(script);

	u = g_scriptCurrentUnit;

	position = Tile_PackTile(Tile_Center(u->o.position));
	Animation_Stop_ByTile(position);

	animationUnitID = g_table_landscapeInfo[Map_GetLandscapeType(Tile_PackTile(u->o.position))].isSand ? 0 : 1;
	if (u->o.script.variables[1] == 1) animationUnitID += 2;

	g_map[position].houseID = Unit_GetHouseID(u);

	assert(animationUnitID < 4);
	if (g_table_unitInfo[u->o.type].displayMode == 3) {
		Animation_Start(g_table_animation_unitScript1[animationUnitID], u->o.position, 0, Unit_GetHouseID(u), 4);
	} else {
		Animation_Start(g_table_animation_unitScript2[animationUnitID], u->o.position, 0, Unit_GetHouseID(u), 4);
	}

	return 1;
}
Пример #4
0
/**
 * Handle damage to a tile, removing spice, removing concrete, stuff like that.
 * @param e The Explosion to handle damage on.
 * @param parameter Unused parameter.
 */
static void Explosion_Func_TileDamage(Explosion *e, uint16 parameter)
{
    static const int16 craterIconMapIndex[] = { -1, 2, 1 };

    uint16 packed;
    uint16 type;
    Tile *t;
    int16 iconMapIndex;
    uint16 overlaySpriteID;
    uint16 *iconMap;

    VARIABLE_NOT_USED(parameter);

    packed = Tile_PackTile(e->position);

    if (!Map_IsPositionUnveiled(packed)) return;

    type = Map_GetLandscapeType(packed);

    if (type == LST_STRUCTURE || type == LST_DESTROYED_WALL) return;

    t = &g_map[packed];

    if (type == LST_CONCRETE_SLAB) {
        t->groundSpriteID = g_mapSpriteID[packed];
        Map_Update(packed, 0, false);
    }

    if (g_table_landscapeInfo[type].craterType == 0) return;

    /* You cannot damage veiled tiles */
    overlaySpriteID = t->overlaySpriteID;
    if (!Sprite_IsUnveiled(overlaySpriteID)) return;

    iconMapIndex = craterIconMapIndex[g_table_landscapeInfo[type].craterType];
    iconMap = &g_iconMap[g_iconMap[iconMapIndex]];

    if (iconMap[0] <= overlaySpriteID && overlaySpriteID <= iconMap[10]) {
        /* There already is a crater; make it bigger */
        overlaySpriteID -= iconMap[0];
        if (overlaySpriteID < 4) overlaySpriteID += 2;
    } else {
        /* Randomly pick 1 of the 2 possible craters */
        overlaySpriteID = Tools_Random_256() & 1;
    }

    /* Reduce spice if there is any */
    Map_ChangeSpiceAmount(packed, -1);

    /* Boom a bloom if there is one */
    if (t->groundSpriteID == g_bloomSpriteID) {
        Map_Bloom_ExplodeSpice(packed, g_playerHouseID);
        return;
    }

    /* Update the tile with the crater */
    t->overlaySpriteID = overlaySpriteID + iconMap[0];
    Map_Update(packed, 0, false);
}
Пример #5
0
/**
 * Set the animation of a Explosion.
 * @param e The Explosion to change.
 * @param animationMapID The animation map to use.
 */
static void Explosion_Func_SetAnimation(Explosion *e, uint16 animationMapID)
{
    uint16 packed;

    packed = Tile_PackTile(e->position);

    if (Structure_Get_ByPackedTile(packed) != NULL) return;

    animationMapID += Tools_Random_256() & 0x1;
    animationMapID += g_table_landscapeInfo[Map_GetLandscapeType(packed)].isSand ? 0 : 2;

    assert(animationMapID < 16);
    Animation_Start(g_table_animation_map[animationMapID], e->position, 0, e->houseID, 3);
}
Пример #6
0
static uint16 Skirmish_PickRandomLocation(uint16 acceptableLstFlags, uint16 unacceptableLstFlags)
{
	const MapInfo* mi = &g_mapInfos[0];
	const int x = mi->minX + Tools_RandomLCG_Range(0, mi->sizeX - 1);
	const int y = mi->minY + Tools_RandomLCG_Range(0, mi->sizeY - 1);

	const uint16 packed = Tile_PackXY(x, y);
	if (g_map[packed].hasUnit)
		return 0;

	const LandscapeType lst = (const LandscapeType)Map_GetLandscapeType(packed);
	const uint16 lstFlag = (1 << lst);
	if ((acceptableLstFlags & lstFlag) && !(unacceptableLstFlags & lstFlag))
		return packed;

	return 0;
}
Пример #7
0
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--;
	}
}
Пример #8
0
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;
}
Пример #9
0
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;
}
Пример #10
0
/* Use breadth first flood fill to create a list of buildable tiles
 * around x0, y0.  Only fill tiles that are part of the same source
 * island (similar to bucket fill same colour).
 */
static int Skirmish_FindBuildableArea(int island, int x0, int y0, SkirmishData* sd, BuildableTile* buildable)
{
	const int dx[4] = {0, 1, 0, -1};
	const int dy[4] = {-1, 0, 1, 0};
	int* islandID = sd->islandID;
	int n = 0;

	/* Starting case. */
	do
	{
		if (!(Map_InRangeX(x0) && Map_InRangeY(y0)))
			break;

		const uint16 packed = Tile_PackXY(x0, y0);
		if (islandID[packed] != island)
			break;

		const LandscapeType lst = (const LandscapeType)Map_GetLandscapeType(packed);
		const LandscapeInfo* li = &g_table_landscapeInfo[lst];
		if (!(li->isValidForStructure || li->isValidForStructure2))
			break;

		buildable[n].x = x0;
		buildable[n].y = y0;
		buildable[n].packed = packed;
		buildable[n].parent = 0;
		islandID[packed] = sd->nislands;
		n++;
	}
	while (false);

	for (int i = 0; i < n; i++)
	{
		const int r = Tools_Random_256() & 0x3;

		for (int j = 0; j < 4; j++)
		{
			const int x = buildable[i].x + dx[(r + j) & 0x3];
			const int y = buildable[i].y + dy[(r + j) & 0x3];
			if (!(Map_InRangeX(x) && Map_InRangeY(y)))
				continue;

			const uint16 packed = Tile_PackXY(x, y);
			if (islandID[packed] != island)
				continue;

			const LandscapeType lst = (const LandscapeType)Map_GetLandscapeType(packed);
			const LandscapeInfo* li = &g_table_landscapeInfo[lst];
			if (!(li->isValidForStructure || li->isValidForStructure2))
				continue;

			buildable[n].x = x;
			buildable[n].y = y;
			buildable[n].packed = packed;
			buildable[n].parent = i;
			islandID[packed] = sd->nislands;
			n++;
		}
	}

	return n;
}
Пример #11
0
/**
 * Draw a single tile on the screen.
 *
 * @param packed The tile to draw.
 */
void GUI_Widget_Viewport_DrawTile(uint16 packed)
{
	uint16 x;
	uint16 y;
	uint16 colour;
	uint16 spriteID;
	Tile *t;
	uint16 mapScale;

	colour = 12;
	spriteID = 0xFFFF;

	if (Tile_IsOutOfMap(packed) || !Map_IsValidPosition(packed)) return;

	x = Tile_GetPackedX(packed);
	y = Tile_GetPackedY(packed);

	mapScale = g_scenario.mapScale + 1;

	if (mapScale == 0 || BitArray_Test(g_displayedMinimap, packed)) return;

	t = &g_map[packed];

	if ((t->isUnveiled && g_playerHouse->flags.radarActivated) || g_debugScenario) {
		uint16 type = Map_GetLandscapeType(packed);
		Unit *u;

		if (mapScale > 1) {
			spriteID = g_scenario.mapScale + g_table_landscapeInfo[type].spriteID - 1;
		} else {
			colour = g_table_landscapeInfo[type].radarColour;
		}

		if (g_table_landscapeInfo[type].radarColour == 0xFFFF) {
			if (mapScale > 1) {
				spriteID = mapScale + t->houseID * 2 + 29;
			} else {
				colour = g_table_houseInfo[t->houseID].minimapColor;
			}
		}

		u = Unit_Get_ByPackedTile(packed);

		if (u != NULL) {
			if (mapScale > 1) {
				if (u->o.type == UNIT_SANDWORM) {
					spriteID = mapScale + 53;
				} else {
					spriteID = mapScale + Unit_GetHouseID(u) * 2 + 29;
				}
			} else {
				if (u->o.type == UNIT_SANDWORM) {
					colour = 255;
				} else {
					colour = g_table_houseInfo[Unit_GetHouseID(u)].minimapColor;
				}
			}
		}
	} else {
		Structure *s;

		s = Structure_Get_ByPackedTile(packed);

		if (s != NULL && s->o.houseID == g_playerHouseID) {
			if (mapScale > 1) {
				spriteID = mapScale + s->o.houseID * 2 + 29;
			} else {
				colour = g_table_houseInfo[s->o.houseID].minimapColor;
			}
		} else {
			if (mapScale > 1) {
				spriteID = g_scenario.mapScale + g_table_landscapeInfo[LST_ENTIRELY_MOUNTAIN].spriteID - 1;
			} else {
				colour = 12;
			}
		}
	}

	x -= g_mapInfos[g_scenario.mapScale].minX;
	y -= g_mapInfos[g_scenario.mapScale].minY;

	if (spriteID != 0xFFFF) {
		x *= g_scenario.mapScale + 1;
		y *= g_scenario.mapScale + 1;
		GUI_DrawSprite(g_screenActiveID, g_sprites[spriteID], x, y, 3, 0x4000);
	} else {
		GFX_PutPixel(x + 256, y + 136, colour & 0xFF);
	}
}
Пример #12
0
/**
 * Redraw parts of the viewport that require redrawing.
 *
 * @param forceRedraw If true, dirty flags are ignored, and everything is drawn.
 * @param arg08 ??
 * @param drawToMainScreen True if and only if we are drawing to the main screen and not some buffer screen.
 */
void GUI_Widget_Viewport_Draw(bool forceRedraw, bool arg08, bool drawToMainScreen)
{
	static const uint16 values_32A4[8][2] = {
		{0, 0}, {1, 0}, {2, 0}, {3, 0},
		{4, 0}, {3, 1}, {2, 1}, {1, 1}
	};

	uint16 x;
	uint16 y;
	uint16 i;
	uint16 curPos;
	bool updateDisplay;
	Screen oldScreenID;
	uint16 oldValue_07AE_0000;
	int16 minX[10];
	int16 maxX[10];

	PoolFindStruct find;

	updateDisplay = forceRedraw;

	memset(minX, 0xF, sizeof(minX));
	memset(maxX, 0,   sizeof(minX));

	oldScreenID = GFX_Screen_SetActive(SCREEN_1);

	oldValue_07AE_0000 = Widget_SetCurrentWidget(2);

	if (g_dirtyViewportCount != 0 || forceRedraw) {
		for (y = 0; y < 10; y++) {
			uint16 top = (y << 4) + 0x28;
			for (x = 0; x < (drawToMainScreen ? 15 : 16); x++) {
				Tile *t;
				uint16 left;

				curPos = g_viewportPosition + Tile_PackXY(x, y);

				if (x < 15 && !forceRedraw && BitArray_Test(g_dirtyViewport, curPos)) {
					if (maxX[y] < x) maxX[y] = x;
					if (minX[y] > x) minX[y] = x;
					updateDisplay = true;
				}

				if (!BitArray_Test(g_dirtyMinimap, curPos) && !forceRedraw) continue;

				BitArray_Set(g_dirtyViewport, curPos);

				if (x < 15) {
					updateDisplay = true;
					if (maxX[y] < x) maxX[y] = x;
					if (minX[y] > x) minX[y] = x;
				}

				t = &g_map[curPos];
				left = x << 4;

				if (!g_debugScenario && g_veiledSpriteID == t->overlaySpriteID) {
					GUI_DrawFilledRectangle(left, top, left + 15, top + 15, 12);
					continue;
				}

				GFX_DrawSprite(t->groundSpriteID, left, top, t->houseID);

				if (t->overlaySpriteID == 0 || g_debugScenario) continue;

				GFX_DrawSprite(t->overlaySpriteID, left, top, t->houseID);
			}
		}
		g_dirtyViewportCount = 0;
	}

	find.type    = UNIT_SANDWORM;
	find.index   = 0xFFFF;
	find.houseID = HOUSE_INVALID;

	while (true) {
		Unit *u;
		uint8 *sprite;

		u = Unit_Find(&find);

		if (u == NULL) break;

		if (!u->o.flags.s.isDirty && !forceRedraw) continue;
		u->o.flags.s.isDirty = false;

		if (!g_map[Tile_PackTile(u->o.position)].isUnveiled && !g_debugScenario) continue;

		sprite = GUI_Widget_Viewport_Draw_GetSprite(g_table_unitInfo[u->o.type].groundSpriteID, Unit_GetHouseID(u));

		s_spriteFlags = 0x200;

		if (Map_IsPositionInViewport(u->o.position, &x, &y)) GUI_DrawSprite(g_screenActiveID, sprite, x, y, 2, s_spriteFlags | 0xC000);

		if (Map_IsPositionInViewport(u->targetLast, &x, &y)) GUI_DrawSprite(g_screenActiveID, sprite, x, y, 2, s_spriteFlags | 0xC000);

		if (Map_IsPositionInViewport(u->targetPreLast, &x, &y)) GUI_DrawSprite(g_screenActiveID, sprite, x, y, 2, s_spriteFlags | 0xC000);

		if (u != g_unitSelected) continue;

		if (!Map_IsPositionInViewport(u->o.position, &x, &y)) continue;

		GUI_DrawSprite(g_screenActiveID, g_sprites[6], x, y, 2, 0xC000);
	}

	if (g_unitSelected == NULL && (g_var_3A08 != 0 || arg08) && (Structure_Get_ByPackedTile(g_selectionRectanglePosition) != NULL || g_selectionType == SELECTIONTYPE_PLACE || g_debugScenario)) {
		uint16 x1 = (Tile_GetPackedX(g_selectionRectanglePosition) - Tile_GetPackedX(g_minimapPosition)) << 4;
		uint16 y1 = ((Tile_GetPackedY(g_selectionRectanglePosition) - Tile_GetPackedY(g_minimapPosition)) << 4) + 0x28;
		uint16 x2 = x1 + (g_selectionWidth << 4) - 1;
		uint16 y2 = y1 + (g_selectionHeight << 4) - 1;

		GUI_SetClippingArea(0, 40, 239, SCREEN_HEIGHT - 1);
		GUI_DrawWiredRectangle(x1, y1, x2, y2, 0xFF);

		if (g_selectionState == 0 && g_selectionType == SELECTIONTYPE_PLACE) {
			GUI_DrawLine(x1, y1, x2, y2, 0xFF);
			GUI_DrawLine(x2, y1, x1, y2, 0xFF);
		}

		GUI_SetClippingArea(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);

		g_var_3A08 = 0;
	}

	if (g_dirtyUnitCount != 0 || forceRedraw || updateDisplay) {
		find.type    = 0xFFFF;
		find.index   = 0xFFFF;
		find.houseID = HOUSE_INVALID;

		while (true) {
			Unit *u;
			UnitInfo *ui;
			uint16 packed;
			uint8 orientation;
			uint16 index;

			u = Unit_Find(&find);

			if (u == NULL) break;

			if (u->o.index < 20 || u->o.index > 101) continue;

			packed = Tile_PackTile(u->o.position);

			if ((!u->o.flags.s.isDirty || u->o.flags.s.isNotOnMap) && !forceRedraw && !BitArray_Test(g_dirtyViewport, packed)) continue;
			u->o.flags.s.isDirty = false;

			if (!g_map[packed].isUnveiled && !g_debugScenario) continue;

			ui = &g_table_unitInfo[u->o.type];

			if (!Map_IsPositionInViewport(u->o.position, &x, &y)) continue;

			x += g_table_tilediff[0][u->wobbleIndex].x;
			y += g_table_tilediff[0][u->wobbleIndex].y;

			orientation = Orientation_Orientation256ToOrientation8(u->orientation[0].current);

			if (u->spriteOffset >= 0 || ui->destroyedSpriteID == 0) {
				static const uint16 values_32C4[8][2] = {
					{0, 0}, {1, 0}, {1, 0}, {1, 0},
					{2, 0}, {1, 1}, {1, 1}, {1, 1}
				};

				index = ui->groundSpriteID;

				switch (ui->displayMode) {
					case DISPLAYMODE_UNIT:
					case DISPLAYMODE_ROCKET:
						if (ui->movementType == MOVEMENT_SLITHER) break;
						index += values_32A4[orientation][0];
						s_spriteFlags = values_32A4[orientation][1];
						break;

					case DISPLAYMODE_INFANTRY_3_FRAMES: {
						static const uint16 values_334A[4] = {0, 1, 0, 2};

						index += values_32C4[orientation][0] * 3;
						index += values_334A[u->spriteOffset & 3];
						s_spriteFlags = values_32C4[orientation][1];
					} break;

					case DISPLAYMODE_INFANTRY_4_FRAMES:
						index += values_32C4[orientation][0] * 4;
						index += u->spriteOffset & 3;
						s_spriteFlags = values_32C4[orientation][1];
						break;

					default:
						s_spriteFlags = 0;
						break;
				}
			} else {
				index = ui->destroyedSpriteID - u->spriteOffset - 1;
				s_spriteFlags = 0;
			}

			if (u->o.type != UNIT_SANDWORM && u->o.flags.s.isHighlighted) s_spriteFlags |= 0x100;
			if (ui->o.flags.blurTile) s_spriteFlags |= 0x200;

			GUI_DrawSprite(g_screenActiveID, GUI_Widget_Viewport_Draw_GetSprite(index, (u->deviated != 0) ? u->deviatedHouse : Unit_GetHouseID(u)), x, y, 2, s_spriteFlags | 0xE000, s_paletteHouse, g_paletteMapping2, 1);

			if (u->o.type == UNIT_HARVESTER && u->actionID == ACTION_HARVEST && u->spriteOffset >= 0 && (u->actionID == ACTION_HARVEST || u->actionID == ACTION_MOVE)) {
				uint16 type = Map_GetLandscapeType(packed);
				if (type == LST_SPICE || type == LST_THICK_SPICE) {
					static const int16 values_334E[8][2] = {
						{0, 7},  {-7,  6}, {-14, 1}, {-9, -6},
						{0, -9}, { 9, -6}, { 14, 1}, { 7,  6}
					};

					GUI_DrawSprite(g_screenActiveID, GUI_Widget_Viewport_Draw_GetSprite((u->spriteOffset % 3) + 0xDF + (values_32A4[orientation][0] * 3), Unit_GetHouseID(u)), x + values_334E[orientation][0], y + values_334E[orientation][1], 2, values_32A4[orientation][1] | 0xC000);
				}
			}

			if (u->spriteOffset >= 0 && ui->turretSpriteID != 0xFFFF) {
				int16 offsetX = 0;
				int16 offsetY = 0;
				uint16 spriteID = ui->turretSpriteID;

				orientation = Orientation_Orientation256ToOrientation8(u->orientation[ui->o.flags.hasTurret ? 1 : 0].current);

				switch (ui->turretSpriteID) {
					case 0x8D: /* sonic tank */
						offsetY = -2;
						break;

					case 0x92: /* rocket launcher */
						offsetY = -3;
						break;

					case 0x7E: { /* siege tank */
						static const int16 values_336E[8][2] = {
							{ 0, -5}, { 0, -5}, { 2, -3}, { 2, -1},
							{-1, -3}, {-2, -1}, {-2, -3}, {-1, -5}
						};

						offsetX = values_336E[orientation][0];
						offsetY = values_336E[orientation][1];
					} break;

					case 0x88: { /* devastator */
						static const int16 values_338E[8][2] = {
							{ 0, -4}, {-1, -3}, { 2, -4}, {0, -3},
							{-1, -3}, { 0, -3}, {-2, -4}, {1, -3}
						};

						offsetX = values_338E[orientation][0];
						offsetY = values_338E[orientation][1];
					} break;

					default:
						break;
				}

				s_spriteFlags = values_32A4[orientation][1];
				spriteID += values_32A4[orientation][0];

				GUI_DrawSprite(g_screenActiveID, GUI_Widget_Viewport_Draw_GetSprite(spriteID, Unit_GetHouseID(u)), x + offsetX, y + offsetY, 2, s_spriteFlags | 0xE000, s_paletteHouse);
			}

			if (u->o.flags.s.isSmoking) {
				uint16 spriteID = 180 + (u->spriteOffset & 3);
				if (spriteID == 183) spriteID = 181;

				GUI_DrawSprite(g_screenActiveID, g_sprites[spriteID], x, y - 14, 2, 0xC000);
			}

			if (u != g_unitSelected) continue;

			GUI_DrawSprite(g_screenActiveID, g_sprites[6], x, y, 2, 0xC000);
		}

		g_dirtyUnitCount = 0;
	}

	for (i = 0; i < EXPLOSION_MAX; i++) {
		Explosion *e = Explosion_Get_ByIndex(i);

		curPos = Tile_PackTile(e->position);

		if (BitArray_Test(g_dirtyViewport, curPos)) e->isDirty = true;

		if (e->commands == NULL) continue;
		if (!e->isDirty && !forceRedraw) continue;
		if (e->spriteID == 0) continue;

		e->isDirty = false;

		if (!g_map[curPos].isUnveiled && !g_debugScenario) continue;
		if (!Map_IsPositionInViewport(e->position, &x, &y)) continue;

		s_spriteFlags = 0xC000;

		GUI_DrawSprite(g_screenActiveID, GUI_Widget_Viewport_Draw_GetSprite(e->spriteID, e->houseID), x, y, 2, s_spriteFlags, s_paletteHouse);
	}

	if (g_dirtyAirUnitCount != 0 || forceRedraw || updateDisplay) {
		find.type    = 0xFFFF;
		find.index   = 0xFFFF;
		find.houseID = HOUSE_INVALID;

		while (true) {
			static const uint16 values_32E4[8][2] = {
				{0, 0}, {1, 0}, {2, 0}, {1, 2},
				{0, 2}, {1, 3}, {2, 1}, {1, 1}
			};

			Unit *u;
			UnitInfo *ui;
			uint8 orientation;
			uint8 *sprite;
			uint16 index;

			u = Unit_Find(&find);

			if (u == NULL) break;

			if (u->o.index > 15) continue;

			curPos = Tile_PackTile(u->o.position);

			if ((!u->o.flags.s.isDirty || u->o.flags.s.isNotOnMap) && !forceRedraw && !BitArray_Test(g_dirtyViewport, curPos)) continue;
			u->o.flags.s.isDirty = false;

			if (!g_map[curPos].isUnveiled && !g_debugScenario) continue;

			ui = &g_table_unitInfo[u->o.type];

			if (!Map_IsPositionInViewport(u->o.position, &x, &y)) continue;

			index = ui->groundSpriteID;
			orientation = u->orientation[0].current;
			s_spriteFlags = 0xC000;

			switch (ui->displayMode) {
				case DISPLAYMODE_SINGLE_FRAME:
					if (u->o.flags.s.bulletIsBig) index++;
					break;

				case DISPLAYMODE_UNIT:
					orientation = Orientation_Orientation256ToOrientation8(orientation);

					index += values_32E4[orientation][0];
					s_spriteFlags |= values_32E4[orientation][1];
					break;

				case DISPLAYMODE_ROCKET: {
					static const uint16 values_3304[16][2] = {
						{0, 0}, {1, 0}, {2, 0}, {3, 0},
						{4, 0}, {3, 2}, {2, 2}, {1, 2},
						{0, 2}, {3, 3}, {2, 3}, {3, 3},
						{4, 1}, {3, 1}, {2, 1}, {1, 1}
					};

					orientation = Orientation_Orientation256ToOrientation16(orientation);

					index += values_3304[orientation][0];
					s_spriteFlags |= values_3304[orientation][1];
				} break;

				case DISPLAYMODE_ORNITHOPTER: {
					static const uint16 values_33AE[4] = {2, 1, 0, 1};

					orientation = Orientation_Orientation256ToOrientation8(orientation);

					index += (values_32E4[orientation][0] * 3) + values_33AE[u->spriteOffset & 3];
					s_spriteFlags |= values_32E4[orientation][1];
				} break;

				default:
					s_spriteFlags = 0x0;
					break;
			}

			if (ui->flags.hasAnimationSet && u->o.flags.s.animationFlip) index += 5;
			if (u->o.type == UNIT_CARRYALL && u->o.flags.s.inTransport) index += 3;

			sprite = GUI_Widget_Viewport_Draw_GetSprite(index, Unit_GetHouseID(u));

			if (ui->o.flags.hasShadow) GUI_DrawSprite(g_screenActiveID, sprite, x + 1, y + 3, 2, (s_spriteFlags & 0xDFFF) | 0x300, g_paletteMapping1, 1);

			if (ui->o.flags.blurTile) s_spriteFlags |= 0x200;

			GUI_DrawSprite(g_screenActiveID, sprite, x, y, 2, s_spriteFlags | 0x2000, s_paletteHouse);
		}

		g_dirtyAirUnitCount = 0;
	}

	if (updateDisplay) {
		memset(g_dirtyMinimap,  0, sizeof(g_dirtyMinimap));
		memset(g_dirtyViewport, 0, sizeof(g_dirtyViewport));
	}

	if (g_changedTilesCount != 0) {
		bool init = false;
		bool update = false;
		Screen oldScreenID2 = SCREEN_1;

		for (i = 0; i < g_changedTilesCount; i++) {
			curPos = g_changedTiles[i];
			BitArray_Clear(g_changedTilesMap, curPos);

			if (!init) {
				init = true;

				oldScreenID2 = GFX_Screen_SetActive(SCREEN_1);

				GUI_Mouse_Hide_InWidget(3);
			}

			GUI_Widget_Viewport_DrawTile(curPos);

			if (!update && BitArray_Test(g_displayedMinimap, curPos)) update = true;
		}

		if (update) Map_UpdateMinimapPosition(g_minimapPosition, true);

		if (init) {
			GUI_Screen_Copy(32, 136, 32, 136, 8, 64, g_screenActiveID, SCREEN_0);

			GFX_Screen_SetActive(oldScreenID2);

			GUI_Mouse_Show_InWidget();
		}

		if (g_changedTilesCount == lengthof(g_changedTiles)) {
			g_changedTilesCount = 0;

			for (i = 0; i < 4096; i++) {
				if (!BitArray_Test(g_changedTilesMap, i)) continue;
				g_changedTiles[g_changedTilesCount++] = i;
				if (g_changedTilesCount == lengthof(g_changedTiles)) break;
			}
		} else {
			g_changedTilesCount = 0;
		}
	}

	if ((g_viewportMessageCounter & 1) != 0 && g_viewportMessageText != NULL && (minX[6] <= 14 || maxX[6] >= 0 || arg08 || forceRedraw)) {
		GUI_DrawText_Wrapper(g_viewportMessageText, 112, 139, 15, 0, 0x132);
		minX[6] = -1;
		maxX[6] = 14;
	}

	if (updateDisplay && !drawToMainScreen) {
		if (g_viewport_fadein) {
			GUI_Mouse_Hide_InWidget(g_curWidgetIndex);

			/* ENHANCEMENT -- When fading in the game on start, you don't see the fade as it is against the already drawn screen. */
			if (g_dune2_enhanced) {
				Screen oldScreenID2 = g_screenActiveID;

				GFX_Screen_SetActive(SCREEN_0);
				GUI_DrawFilledRectangle(g_curWidgetXBase << 3, g_curWidgetYBase, (g_curWidgetXBase + g_curWidgetWidth) << 3, g_curWidgetYBase + g_curWidgetHeight, 0);
				GFX_Screen_SetActive(oldScreenID2);
			}

			GUI_Screen_FadeIn(g_curWidgetXBase, g_curWidgetYBase, g_curWidgetXBase, g_curWidgetYBase, g_curWidgetWidth, g_curWidgetHeight, g_screenActiveID, SCREEN_0);
			GUI_Mouse_Show_InWidget();

			g_viewport_fadein = false;
		} else {
			bool init = false;

			for (i = 0; i < 10; i++) {
				uint16 width;
				uint16 height;

				if (arg08) {
					minX[i] = 0;
					maxX[i] = 14;
				}

				if (maxX[i] < minX[i]) continue;

				x = minX[i] * 2;
				y = (i << 4) + 0x28;
				width  = (maxX[i] - minX[i] + 1) * 2;
				height = 16;

				if (!init) {
					GUI_Mouse_Hide_InWidget(g_curWidgetIndex);

					init = true;
				}

				GUI_Screen_Copy(x, y, x, y, width, height, g_screenActiveID, SCREEN_0);
			}

			if (init) GUI_Mouse_Show_InWidget();
		}
	}

	GFX_Screen_SetActive(oldScreenID);

	Widget_SetCurrentWidget(oldValue_07AE_0000);
}