/** * 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; }
/** * Transform an MCV into Construction Yard. * * Stack: *none*. * * @param script The script engine to operate on. * @return 1 if and only if the transformation succeeded. */ uint16 Script_Unit_MCVDeploy(ScriptEngine *script) { Unit *u; Structure *s = NULL; uint16 i; VARIABLE_NOT_USED(script); u = g_scriptCurrentUnit; Unit_UpdateMap(0, u); for (i = 0; i < 4; i++) { static int8 offsets[4] = { 0, -1, -64, -65 }; s = Structure_Create(STRUCTURE_INDEX_INVALID, STRUCTURE_CONSTRUCTION_YARD, Unit_GetHouseID(u), Tile_PackTile(u->o.position) + offsets[i]); if (s != NULL) { Unit_Remove(u); return 1; } } if (Unit_GetHouseID(u) == g_playerHouseID) { GUI_DisplayText(String_Get_ByIndex(STR_UNIT_IS_UNABLE_TO_DEPLOY_HERE), 0); } Unit_UpdateMap(1, u); return 0; }
/** * Get information about the unit, like hitpoints, current target, etc. * * Stack: 1 - Which information you would like. * * @param script The script engine to operate on. * @return The information you requested. */ uint16 Script_Unit_GetInfo(ScriptEngine *script) { const UnitInfo *ui; Unit *u; u = g_scriptCurrentUnit; ui = &g_table_unitInfo[u->o.type]; switch (STACK_PEEK(1)) { case 0x00: return u->o.hitpoints * 256 / ui->o.hitpoints; case 0x01: return Tools_Index_IsValid(u->targetMove) ? u->targetMove : 0; case 0x02: return ui->fireDistance << 8; case 0x03: return u->o.index; case 0x04: return u->orientation[0].current; case 0x05: return u->targetAttack; case 0x06: if (u->originEncoded == 0 || u->o.type == UNIT_HARVESTER) Unit_FindClosestRefinery(u); return u->originEncoded; case 0x07: return u->o.type; case 0x08: return Tools_Index_Encode(u->o.index, IT_UNIT); case 0x09: return u->movingSpeed; case 0x0A: return abs(u->orientation[0].target - u->orientation[0].current); case 0x0B: return u->currentDestination.tile == 0 ? 0 : 1; case 0x0C: return u->fireDelay == 0 ? 1 : 0; case 0x0D: return ui->flags.explodeOnDeath; case 0x0E: return Unit_GetHouseID(u); case 0x0F: return u->o.flags.s.byScenario ? 1 : 0; case 0x10: return u->orientation[ui->o.flags.hasTurret ? 1 : 0].current; case 0x11: return abs(u->orientation[ui->o.flags.hasTurret ? 1 : 0].target - u->orientation[ui->o.flags.hasTurret ? 1 : 0].current); case 0x12: return (ui->movementType & 0x40) == 0 ? 0 : 1; case 0x13: return (u->o.seenByHouses & (1 << g_playerHouseID)) == 0 ? 0 : 1; default: return 0; } }
/** * Check if the given tile is a valid destination. In case of for example * a carry-all it checks if the unit carrying can be placed on destination. * In case of structures, it checks if you can walk into it. * * Stack: 1 - An encoded tile, indicating the destination. * * @param script The script engine to operate on. * @return ??. */ uint16 Script_Unit_IsValidDestination(ScriptEngine *script) { Unit *u; Unit *u2; uint16 encoded; uint16 index; u = g_scriptCurrentUnit; encoded = STACK_PEEK(1); index = Tools_Index_Decode(encoded); switch (Tools_Index_GetType(encoded)) { case IT_TILE: if (!Map_IsValidPosition(index)) return 1; if (u->o.linkedID == 0xFF) return 1; u2 = Unit_Get_ByIndex(u->o.linkedID); u2->o.position = Tools_Index_GetTile(encoded); if (!Unit_IsTileOccupied(u2)) return 0; u2->o.position.tile = 0xFFFFFFFF; return 1; case IT_STRUCTURE: { Structure *s; s = Structure_Get_ByIndex(index); if (s->o.houseID == Unit_GetHouseID(u)) return 0; if (u->o.linkedID == 0xFF) return 1; u2 = Unit_Get_ByIndex(u->o.linkedID); return Unit_IsValidMovementIntoStructure(u2, s) != 0 ? 1 : 0; } default: return 1; } }
/** * Finds a structure. * * Stack: 1 - A structure type. * * @param script The script engine to operate on. * @return An encoded structure index, or 0 if none found. */ uint16 Script_Unit_FindStructure(ScriptEngine *script) { Unit *u; PoolFindStruct find; u = g_scriptCurrentUnit; find.houseID = Unit_GetHouseID(u); find.index = 0xFFFF; find.type = STACK_PEEK(1); while (true) { Structure *s; s = Structure_Find(&find); if (s == NULL) break; if (s->state != STRUCTURE_STATE_IDLE) continue; if (s->o.linkedID != 0xFF) continue; if (s->o.script.variables[4] != 0) continue; return Tools_Index_Encode(s->o.index, IT_STRUCTURE); } return 0; }
/** * Move the unit to the first available structure it can find of the required * type. * * Stack: 1 - Type of structure. * * @param script The script engine to operate on. * @return An encoded structure index. */ uint16 Script_Unit_MoveToStructure(ScriptEngine *script) { Unit *u; PoolFindStruct find; u = g_scriptCurrentUnit; if (u->o.linkedID != 0xFF) { Structure *s; s = Tools_Index_GetStructure(Unit_Get_ByIndex(u->o.linkedID)->originEncoded); if (s != NULL && s->state == STRUCTURE_STATE_IDLE && s->o.script.variables[4] == 0) { uint16 encoded; encoded = Tools_Index_Encode(s->o.index, IT_STRUCTURE); Object_Script_Variable4_Link(Tools_Index_Encode(u->o.index, IT_UNIT), encoded); u->targetMove = u->o.script.variables[4]; return encoded; } } find.houseID = Unit_GetHouseID(u); find.index = 0xFFFF; find.type = STACK_PEEK(1); while (true) { Structure *s; uint16 encoded; s = Structure_Find(&find); if (s == NULL) break; if (s->state != STRUCTURE_STATE_IDLE) continue; if (s->o.script.variables[4] != 0) continue; encoded = Tools_Index_Encode(s->o.index, IT_STRUCTURE); Object_Script_Variable4_Link(Tools_Index_Encode(u->o.index, IT_UNIT), encoded); u->targetMove = encoded; return encoded; } return 0; }
/** * 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, Unit_GetHouseID(uf))) 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); }
/** * Displays the "XXX XXX destroyed." message for the current unit. * * Stack: *none*. * * @param script The script engine to operate on. * @return The value 0. Always. */ uint16 Script_Unit_DisplayDestroyedText(ScriptEngine *script) { const UnitInfo *ui; Unit *u; VARIABLE_NOT_USED(script); u = g_scriptCurrentUnit; ui = &g_table_unitInfo[u->o.type]; if (g_config.language == LANGUAGE_FRENCH) { GUI_DisplayText(String_Get_ByIndex(STR_S_S_DESTROYED), 0, String_Get_ByIndex(ui->o.stringID_abbrev), g_table_houseInfo[Unit_GetHouseID(u)].name); } else { GUI_DisplayText(String_Get_ByIndex(STR_S_S_DESTROYED), 0, g_table_houseInfo[Unit_GetHouseID(u)].name, String_Get_ByIndex(ui->o.stringID_abbrev)); } return 0; }
/** * Find the first matching Unit based on the PoolFindStruct filter data. * * @param find A pointer to a PoolFindStruct which contains filter data and * last known tried index. Calling this functions multiple times with the * same 'find' parameter walks over all possible values matching the filter. * @return The Unit, or NULL if nothing matches (anymore). */ Unit *Unit_Find(PoolFindStruct *find) { if (find->index >= g_unitFindCount && find->index != 0xFFFF) return NULL; find->index++; /* First, we always go to the next index */ for (; find->index < g_unitFindCount; find->index++) { Unit *u = g_unitFindArray[find->index]; if (u == NULL) continue; if (u->o.flags.s.isNotOnMap && g_var_38BC == 0) continue; if (find->houseID != HOUSE_INVALID && find->houseID != Unit_GetHouseID(u)) continue; if (find->type != UNIT_INDEX_INVALID && find->type != u->o.type) continue; return u; } return NULL; }
/** * Makes the current unit to go to the closest structure of the given type. * * Stack: 1 - The type of the structure. * * @param script The script engine to operate on. * @return The value 1 if and only if a structure has been found. */ uint16 Script_Unit_GoToClosestStructure(ScriptEngine *script) { Unit *u; Structure *s = NULL; PoolFindStruct find; uint16 distanceMin =0; u = g_scriptCurrentUnit; find.houseID = Unit_GetHouseID(u); find.index = 0xFFFF; find.type = STACK_PEEK(1); while (true) { Structure *s2; uint16 distance; s2 = Structure_Find(&find); if (s2 == NULL) break; if (s2->state != STRUCTURE_STATE_IDLE) continue; if (s2->o.linkedID != 0xFF) continue; if (s2->o.script.variables[4] != 0) continue; distance = Tile_GetDistanceRoundedUp(s2->o.position, u->o.position); if (distance >= distanceMin && distanceMin != 0) continue; distanceMin = distance; s = s2; } if (s == NULL) return 0; Unit_SetAction(u, ACTION_MOVE); Unit_SetDestination(u, Tools_Index_Encode(s->o.index, IT_STRUCTURE)); return 1; }
/** * Call a UnitType and make it go to the current unit. In general, type should * be a Carry-All for this to make any sense. * * Stack: 1 - An unit type. * * @param script The script engine to operate on. * @return An encoded unit index. */ uint16 Script_Unit_CallUnitByType(ScriptEngine *script) { Unit *u; Unit *u2; uint16 encoded; uint16 encoded2; u = g_scriptCurrentUnit; if (u->o.script.variables[4] != 0) return u->o.script.variables[4]; if (!g_table_unitInfo[u->o.type].o.flags.canBePickedUp || u->deviated != 0) return 0; encoded = Tools_Index_Encode(u->o.index, IT_UNIT); u2 = Unit_CallUnitByType(STACK_PEEK(1), Unit_GetHouseID(u), encoded, false); if (u2 == NULL) return 0; encoded2 = Tools_Index_Encode(u2->o.index, IT_UNIT); Object_Script_Variable4_Link(encoded, encoded2); u2->targetMove = encoded; return encoded2; }
/** * Handles Click event for unit commands button. * * @param w The widget. * @return True, always. */ bool GUI_Widget_TextButton_Click(Widget *w) { const UnitInfo *ui; const ActionInfo *ai; const uint16 *actions; ActionType action; Unit *u; uint16 *found; ActionType unitAction; u = g_unitSelected; ui = &g_table_unitInfo[u->o.type]; actions = ui->o.actionsPlayer; if (Unit_GetHouseID(u) != g_playerHouseID && u->o.type != UNIT_HARVESTER) { actions = g_table_actionsAI; } action = actions[w->index - 8]; unitAction = u->nextActionID; if (unitAction == ACTION_INVALID) { unitAction = u->actionID; } if (u->deviated != 0) { Unit_Deviation_Decrease(u, 5); if (u->deviated == 0) { GUI_Widget_MakeNormal(w, false); return true; } } GUI_Widget_MakeSelected(w, false); ai = &g_table_actionInfo[action]; if (ai->selectionType != g_selectionType) { g_unitActive = g_unitSelected; g_activeAction = action; GUI_ChangeSelectionType(ai->selectionType); return true; } Object_Script_Variable4_Clear(&u->o); u->targetAttack = 0; u->targetMove = 0; u->route[0] = 0xFF; Unit_SetAction(u, action); if (ui->movementType == MOVEMENT_FOOT) Sound_StartSound(ai->soundID); if (unitAction == action) return true; found = memchr(actions, unitAction, 4); if (found == NULL) return true; GUI_Widget_MakeNormal(GUI_Widget_Get_ByIndex(g_widgetLinkedListHead, found - actions + 8), false); return true; }
/** * Makes the current unit fire a bullet (or eat its target). * * Stack: *none*. * * @param script The script engine to operate on. * @return The value 1 if the current unit fired/eat, 0 otherwise. */ uint16 Script_Unit_Fire(ScriptEngine *script) { const UnitInfo *ui; Unit *u; uint16 target; UnitType typeID; uint16 distance; bool fireTwice; uint16 damage; u = g_scriptCurrentUnit; target = u->targetAttack; if (target == 0 || !Tools_Index_IsValid(target)) return 0; if (u->o.type != UNIT_SANDWORM && target == Tools_Index_Encode(Tile_PackTile(u->o.position), IT_TILE)) u->targetAttack = 0; if (u->targetAttack != target) { Unit_SetTarget(u, target); return 0; } ui = &g_table_unitInfo[u->o.type]; if (u->o.type != UNIT_SANDWORM && u->orientation[ui->o.flags.hasTurret ? 1 : 0].speed != 0) return 0; if (Tools_Index_GetType(target) == IT_TILE && Object_GetByPackedTile(Tools_Index_GetPackedTile(target)) != NULL) Unit_SetTarget(u, target); if (u->fireDelay != 0) return 0; distance = Object_GetDistanceToEncoded(&u->o, target); if ((int16)(ui->fireDistance << 8) < (int16)distance) return 0; if (u->o.type != UNIT_SANDWORM && (Tools_Index_GetType(target) != IT_UNIT || g_table_unitInfo[Tools_Index_GetUnit(target)->o.type].movementType != MOVEMENT_WINGER)) { int16 diff = 0; int8 orientation; orientation = Tile_GetDirection(u->o.position, Tools_Index_GetTile(target)); diff = abs(u->orientation[ui->o.flags.hasTurret ? 1 : 0].current - orientation); if (ui->movementType == MOVEMENT_WINGER) diff /= 8; if (diff >= 8) return 0; } damage = ui->damage; typeID = ui->bulletType; fireTwice = ui->flags.firesTwice && u->o.hitpoints > ui->o.hitpoints / 2; if ((u->o.type == UNIT_TROOPERS || u->o.type == UNIT_TROOPER) && (int16)distance > 512) typeID = UNIT_MISSILE_TROOPER; switch (typeID) { case UNIT_SANDWORM: { Unit *u2; Unit_UpdateMap(0, u); u2 = Tools_Index_GetUnit(target); if (u2 != NULL) { u2->o.script.variables[1] = 0xFFFF; Unit_RemovePlayer(u2); Unit_HouseUnitCount_Remove(u2); Unit_Remove(u2); } Map_MakeExplosion(ui->explosionType, u->o.position, 0, 0); Voice_PlayAtTile(63, u->o.position); Unit_UpdateMap(1, u); u->amount--; script->delay = 12; if ((int8)u->amount < 1) Unit_SetAction(u, ACTION_DIE); } break; case UNIT_MISSILE_TROOPER: damage -= damage / 4; /* FALL-THROUGH */ case UNIT_MISSILE_ROCKET: case UNIT_MISSILE_TURRET: case UNIT_MISSILE_DEVIATOR: case UNIT_BULLET: case UNIT_SONIC_BLAST: { Unit *bullet; bullet = Unit_CreateBullet(u->o.position, typeID, Unit_GetHouseID(u), damage, target); if (bullet == NULL) return 0; bullet->originEncoded = Tools_Index_Encode(u->o.index, IT_UNIT); Voice_PlayAtTile(ui->bulletSound, u->o.position); Unit_Deviation_Decrease(u, 20); } break; default: break; } u->fireDelay = Tools_AdjustToGameSpeed(ui->fireDelay * 2, 1, 0xFFFF, true); if (fireTwice) { u->o.flags.s.fireTwiceFlip = !u->o.flags.s.fireTwiceFlip; if (u->o.flags.s.fireTwiceFlip) u->fireDelay = Tools_AdjustToGameSpeed(5, 1, 10, true) & 0xFF; } else { u->o.flags.s.fireTwiceFlip = false; } u->fireDelay += Tools_Random_256() & 1; Unit_UpdateMap(2, u); return 1; }
/** * Pickup a unit (either from structure or on the map). The unit that does the * picking up returns the unit to his last position. * * Stack: *none*. * * @param script The script engine to operate on. * @return The value 0. Always. */ uint16 Script_Unit_Pickup(ScriptEngine *script) { Unit *u; VARIABLE_NOT_USED(script); u = g_scriptCurrentUnit; if (u->o.linkedID != 0xFF) return 0; switch (Tools_Index_GetType(u->targetMove)) { case IT_STRUCTURE: { Structure *s; Unit *u2; s = Tools_Index_GetStructure(u->targetMove); /* There was nothing to pickup here */ if (s->state != STRUCTURE_STATE_READY) { Object_Script_Variable4_Clear(&u->o); u->targetMove = 0; return 0; } u->o.flags.s.inTransport = true; Object_Script_Variable4_Clear(&u->o); u->targetMove = 0; u2 = Unit_Get_ByIndex(s->o.linkedID); /* Pickup the unit */ u->o.linkedID = u2->o.index & 0xFF; s->o.linkedID = u2->o.linkedID; u2->o.linkedID = 0xFF; if (s->o.linkedID == 0xFF) Structure_SetState(s, STRUCTURE_STATE_IDLE); /* Check if the unit has a return-to position or try to find spice in case of a harvester */ if (u2->targetLast.tile != 0) { u->targetMove = Tools_Index_Encode(Tile_PackTile(u2->targetLast), IT_TILE); } else if (u2->o.type == UNIT_HARVESTER && Unit_GetHouseID(u2) != g_playerHouseID) { u->targetMove = Tools_Index_Encode(Map_SearchSpice(Tile_PackTile(u->o.position), 20), IT_TILE); } Unit_UpdateMap(2, u); return 1; } case IT_UNIT: { Unit *u2; Structure *s = NULL; PoolFindStruct find; int16 minDistance = 0; u2 = Tools_Index_GetUnit(u->targetMove); if (!u2->o.flags.s.allocated) return 0; find.houseID = Unit_GetHouseID(u); find.index = 0xFFFF; find.type = 0xFFFF; /* Find closest refinery / repair station */ while (true) { Structure *s2; int16 distance; s2 = Structure_Find(&find); if (s2 == NULL) break; distance = Tile_GetDistanceRoundedUp(s2->o.position, u->o.position); if (u2->o.type == UNIT_HARVESTER) { if (s2->o.type != STRUCTURE_REFINERY || s2->state != STRUCTURE_STATE_IDLE || s2->o.script.variables[4] != 0) continue; if (minDistance != 0 && distance >= minDistance) break; minDistance = distance; s = s2; break; } if (s2->o.type != STRUCTURE_REPAIR || s2->state != STRUCTURE_STATE_IDLE || s2->o.script.variables[4] != 0) continue; if (minDistance != 0 && distance >= minDistance) continue; minDistance = distance; s = s2; } if (s == NULL) return 0; /* Deselect the unit as it is about to be picked up */ if (u2 == g_unitSelected) Unit_Select(NULL); /* Pickup the unit */ u->o.linkedID = u2->o.index & 0xFF; u->o.flags.s.inTransport = true; Unit_UpdateMap(0, u2); Unit_Hide(u2); /* Set where we are going to */ Object_Script_Variable4_Link(Tools_Index_Encode(u->o.index, IT_UNIT), Tools_Index_Encode(s->o.index, IT_STRUCTURE)); u->targetMove = u->o.script.variables[4]; Unit_UpdateMap(2, u); if (u2->o.type != UNIT_HARVESTER) return 0; /* Check if we want to return to this spice field later */ if (Map_SearchSpice(Tile_PackTile(u2->o.position), 2) == 0) { u2->targetPreLast.tile = 0; u2->targetLast.tile = 0; } return 0; } default: return 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); } }
/** * 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); }