/** * Unknown function 2BD5. * * Stack: *none*. * * @param script The script engine to operate on. * @return ??. */ uint16 Script_Unit_Unknown2BD5(ScriptEngine *script) { Unit *u; VARIABLE_NOT_USED(script); u = g_scriptCurrentUnit; switch (Tools_Index_GetType(u->o.script.variables[4])) { case IT_UNIT: { Unit *u2; u2 = Tools_Index_GetUnit(u->o.script.variables[4]); if (Tools_Index_Encode(u->o.index, IT_UNIT) == u2->o.script.variables[4] && u2->o.houseID == u->o.houseID) return 1; u2->targetMove = 0; } break; case IT_STRUCTURE: { Structure *s; s = Tools_Index_GetStructure(u->o.script.variables[4]); if (Tools_Index_Encode(u->o.index, IT_UNIT) == s->o.script.variables[4] && s->o.houseID == u->o.houseID) return 1; } break; default: break; } Object_Script_Variable4_Clear(&u->o); return 0; }
/** * Find a UnitType and make it go to the current structure. 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 unknown. */ uint16 Script_Structure_FindUnitByType(ScriptEngine *script) { Structure *s; Unit *u; Unit *carryall; uint16 type; uint16 position; uint16 carryallIndex; s = g_scriptCurrentStructure; if (s->state != STRUCTURE_STATE_READY) return IT_NONE; if (s->o.linkedID == 0xFF) return IT_NONE; type = STACK_PEEK(1); position = Structure_FindFreePosition(s, false); u = Unit_Get_ByIndex(s->o.linkedID); if (g_playerHouseID == s->o.houseID && u->o.type == UNIT_HARVESTER && u->targetLast.tile == 0 && position != 0) { return IT_NONE; } carryall = Unit_CallUnitByType(type, s->o.houseID, Tools_Index_Encode(s->o.index, IT_STRUCTURE), position == 0); if (carryall == NULL) return IT_NONE; carryallIndex = Tools_Index_Encode(carryall->o.index, IT_UNIT); Object_Script_Variable4_Set(&s->o, carryallIndex); return carryallIndex; }
/** * Unknown function 0788. * * Stack: *none*. * * @param script The script engine to operate on. * @return The value 0. Always. */ uint16 Script_Team_Unknown0788(ScriptEngine *script) { Team *t; tile32 tile; PoolFindStruct find; VARIABLE_NOT_USED(script); t = g_scriptCurrentTeam; if (t->target == 0) return 0; tile = Tools_Index_GetTile(t->target); find.houseID = t->houseID; find.index = 0xFFFF; find.type = 0xFFFF; while (true) { Unit *u; uint16 distance; uint16 packed; int16 orientation; u = Unit_Find(&find); if (u == NULL) break; if (u->team - 1 != t->index) continue; if (t->target == 0) { Unit_SetAction(u, ACTION_GUARD); continue; } distance = g_table_unitInfo[u->o.type].fireDistance << 8; if (u->actionID == ACTION_ATTACK && u->targetAttack == t->target) { if (u->targetMove != 0) continue; if (Tile_GetDistance(u->o.position, tile) >= distance) continue; } if (u->actionID != ACTION_ATTACK) Unit_SetAction(u, ACTION_ATTACK); orientation = (Tile_GetDirection(tile, u->o.position) & 0xC0) + Tools_RandomLCG_Range(0, 127); if (orientation < 0) orientation += 256; packed = Tile_PackTile(Tile_MoveByDirection(tile, orientation, distance)); if (Object_GetByPackedTile(packed) == NULL) { Unit_SetDestination(u, Tools_Index_Encode(packed, IT_TILE)); } else { Unit_SetDestination(u, Tools_Index_Encode(Tile_PackTile(tile), IT_TILE)); } Unit_SetTarget(u, t->target); } 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; }
/** * 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; } }
/** * 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; }
/** * Get a random tile around the Unit. * * Stack: 1 - An encoded index of a tile, completely ignored, as long as it is a tile. * * @param script The script engine to operate on. * @return An encoded tile, or 0. */ uint16 Script_Unit_GetRandomTile(ScriptEngine *script) { Unit *u; tile32 tile; u = g_scriptCurrentUnit; if (Tools_Index_GetType(STACK_PEEK(1)) != IT_TILE) return 0; tile = Tile_MoveByRandom(u->o.position, 80, true); return Tools_Index_Encode(Tile_PackTile(tile), IT_TILE); }
/** * Unknown function 0543. * * Stack: 1 - A distance. * * @param script The script engine to operate on. * @return The number of moving units. */ uint16 Script_Team_Unknown0543(ScriptEngine *script) { Team *t; uint16 count = 0; uint16 distance; PoolFindStruct find; t = g_scriptCurrentTeam; distance = STACK_PEEK(1); find.houseID = t->houseID; find.index = 0xFFFF; find.type = 0xFFFF; while (true) { Unit *u; tile32 tile; uint16 distanceUnitDest; uint16 distanceUnitTeam; uint16 distanceTeamDest; u = Unit_Find(&find); if (u == NULL) break; if (t->index != u->team - 1) continue; tile = Tools_Index_GetTile(u->targetMove); distanceUnitTeam = Tile_GetDistanceRoundedUp(u->o.position, t->position); if (u->targetMove != 0) { distanceUnitDest = Tile_GetDistanceRoundedUp(u->o.position, tile); distanceTeamDest = Tile_GetDistanceRoundedUp(t->position, tile); } else { distanceUnitDest = 64; distanceTeamDest = 64; } if ((distanceUnitDest < distanceTeamDest && (distance + 2) < distanceUnitTeam) || (distanceUnitDest >= distanceTeamDest && distanceUnitTeam > distance)) { Unit_SetAction(u, ACTION_MOVE); tile = Tile_MoveByRandom(t->position, distance << 4, true); Unit_SetDestination(u, Tools_Index_Encode(Tile_PackTile(tile), IT_TILE)); count++; continue; } Unit_SetAction(u, ACTION_GUARD); } return count; }
/** * 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; }
/** * Get the best target around you. Only considers units on sand. * * Stack: *none*. * * @param script The script engine to operate on. * @return An encoded unit index, or 0. */ uint16 Script_Unit_Sandworm_GetBestTarget(ScriptEngine *script) { Unit *u; Unit *u2; VARIABLE_NOT_USED(script); u = g_scriptCurrentUnit; u2 = Unit_Sandworm_FindBestTarget(u); if (u2 == NULL) return 0; return Tools_Index_Encode(u2->o.index, IT_UNIT); }
/** * 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; }
/** * Gives a harvester to the given house if it has a refinery and no harvesters. * * @param houseID The index of the house to give a harvester to. */ void House_EnsureHarvesterAvailable(uint8 houseID) { PoolFindStruct find; Structure *s; find.houseID = houseID; find.type = 0xFFFF; find.index = 0xFFFF; while (true) { s = Structure_Find(&find); if (s == NULL) break; /* ENHANCEMENT -- Dune2 checked the wrong type to skip. LinkedID is a structure for a Construction Yard */ if (!g_dune2_enhanced && s->o.type == STRUCTURE_HEAVY_VEHICLE) continue; if (g_dune2_enhanced && s->o.type == STRUCTURE_CONSTRUCTION_YARD) continue; if (s->o.linkedID == UNIT_INVALID) continue; if (Unit_Get_ByIndex(s->o.linkedID)->o.type == UNIT_HARVESTER) return; } find.houseID = houseID; find.type = UNIT_CARRYALL; find.index = 0xFFFF; while (true) { Unit *u; u = Unit_Find(&find); if (u == NULL) break; if (u->o.linkedID == UNIT_INVALID) continue; if (Unit_Get_ByIndex(u->o.linkedID)->o.type == UNIT_HARVESTER) return; } if (Unit_IsTypeOnMap(houseID, UNIT_HARVESTER)) return; find.houseID = houseID; find.type = STRUCTURE_REFINERY; find.index = 0xFFFF; s = Structure_Find(&find); if (s == NULL) return; if (Unit_CreateWrapper(houseID, UNIT_HARVESTER, Tools_Index_Encode(s->o.index, IT_STRUCTURE)) == NULL) return; if (houseID != g_playerHouseID) return; GUI_DisplayText(String_Get_ByIndex(STR_HARVESTER_IS_HEADING_TO_REFINERY), 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, 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); }
/** * Unknown function 0A81. * * Stack: *none* * * @param script The script engine to operate on. * @return unknown. */ uint16 Script_Structure_Unknown0A81(ScriptEngine *script) { uint16 structureIndex; Structure *s; Unit *u; VARIABLE_NOT_USED(script); s = g_scriptCurrentStructure; structureIndex = Tools_Index_Encode(s->o.index, IT_STRUCTURE); u = Tools_Index_GetUnit(s->o.script.variables[4]); if (u != NULL) { if (structureIndex == u->o.script.variables[4]) return s->o.script.variables[4]; Object_Script_Variable4_Clear(&u->o); } Object_Script_Variable4_Clear(&s->o); return 0; }
/** * 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; }
/** * Fire a bullet or missile from a (rocket) turret. * * Stack: *none* * Variables: 2 - Target to shoot at. * * @param script The script engine to operate on. * @return The time between this and the next time firing. */ uint16 Script_Structure_Fire(ScriptEngine *script) { Structure *s; Unit *u; tile32 position; uint16 target; uint16 damage; uint16 fireDelay; uint16 type; s = g_scriptCurrentStructure; target = script->variables[2]; if (target == 0) return 0; if (s->o.type == STRUCTURE_ROCKET_TURRET && Tile_GetDistance(Tools_Index_GetTile(target), s->o.position) >= 0x300) { type = UNIT_MISSILE_TURRET; damage = 30; fireDelay = Tools_AdjustToGameSpeed(g_table_unitInfo[UNIT_LAUNCHER].fireDelay, 1, 255, true); } else { type = UNIT_BULLET; damage = 20; fireDelay = Tools_AdjustToGameSpeed(g_table_unitInfo[UNIT_TANK].fireDelay, 1, 255, true); } position.tile = s->o.position.tile; position.s.x += 0x80; position.s.y += 0x80; u = Unit_CreateBullet(position, type, s->o.houseID, damage, target); if (u == NULL) return 0; u->originEncoded = Tools_Index_Encode(s->o.index, IT_STRUCTURE); return fireDelay; }
/** * 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; } }
/** * Make an explosion at the coordinates of the unit. * It does damage to the surrounding units based on the unit. * * Stack: 1 - Explosion type * * @param script The script engine to operate on. * @return The value 0. Always. */ uint16 Script_Unit_ExplosionSingle(ScriptEngine *script) { Unit *u; u = g_scriptCurrentUnit; Map_MakeExplosion(STACK_PEEK(1), u->o.position, g_table_unitInfo[u->o.type].o.hitpoints, Tools_Index_Encode(u->o.index, IT_UNIT)); return 0; }
/** * 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; }
/** * 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); } } }
/** * Handles the Click events for the Viewport widget. * * @param w The widget. */ bool GUI_Widget_Viewport_Click(Widget *w) { uint16 direction; uint16 x, y; uint16 spriteID; uint16 packed; bool click, drag; spriteID = g_cursorSpriteID; switch (w->index) { default: break; case 39: spriteID = 1; break; case 40: spriteID = 2; break; case 41: spriteID = 4; break; case 42: spriteID = 3; break; case 43: spriteID = g_cursorDefaultSpriteID; break; case 44: spriteID = g_cursorDefaultSpriteID; break; case 45: spriteID = 0; break; } if (spriteID != g_cursorSpriteID) { /* HotSpots for different cursor types. */ static const XYPosition cursorHotSpots[6] = {{0, 0}, {5, 0}, {8, 5}, {5, 8}, {0, 5}, {8, 8}}; s_tickCursor = g_timerGame; Sprites_SetMouseSprite(cursorHotSpots[spriteID].x, cursorHotSpots[spriteID].y, g_sprites[spriteID]); g_cursorSpriteID = spriteID; } if (w->index == 45) return true; click = false; drag = false; if ((w->state.buttonState & 0x11) != 0) { click = true; g_var_37B8 = false; } else if ((w->state.buttonState & 0x22) != 0 && !g_var_37B8) { drag = true; } /* ENHANCEMENT -- Dune2 depends on slow CPUs to limit the rate mouse clicks are handled. */ if (g_dune2_enhanced && (click || drag)) { if (s_tickClick + 2 >= g_timerGame) return true; s_tickClick = g_timerGame; } direction = 0xFFFF; switch (w->index) { default: break; case 39: direction = 0; break; case 40: direction = 2; break; case 41: direction = 6; break; case 42: direction = 4; break; } if (direction != 0xFFFF) { /* Always scroll if we have a click or a drag */ if (!click && !drag) { /* Wait for either one of the timers */ if (s_tickMapScroll + 10 >= g_timerGame || s_tickCursor + 20 >= g_timerGame) return true; /* Don't scroll if we have a structure/unit selected and don't want to autoscroll */ if (g_gameConfig.autoScroll == 0 && (g_selectionType == SELECTIONTYPE_STRUCTURE || g_selectionType == SELECTIONTYPE_UNIT)) return true; } s_tickMapScroll = g_timerGame; Map_MoveDirection(direction); return true; } if (click) { x = g_mouseClickX; y = g_mouseClickY; } else { x = g_mouseX; y = g_mouseY; } if (w->index == 43) { x = x / 16 + Tile_GetPackedX(g_minimapPosition); y = (y - 40) / 16 + Tile_GetPackedY(g_minimapPosition); } else if (w->index == 44) { uint16 mapScale; const MapInfo *mapInfo; mapScale = g_scenario.mapScale; mapInfo = &g_mapInfos[mapScale]; x = min((max(x, 256) - 256) / (mapScale + 1), mapInfo->sizeX - 1) + mapInfo->minX; y = min((max(y, 136) - 136) / (mapScale + 1), mapInfo->sizeY - 1) + mapInfo->minY; } packed = Tile_PackXY(x, y); if (click && g_selectionType == SELECTIONTYPE_TARGET) { Unit *u; ActionType action; uint16 encoded; GUI_DisplayText(NULL, -1); if (g_unitHouseMissile != NULL) { Unit_LaunchHouseMissile(packed); return true; } u = g_unitActive; action = g_activeAction; Object_Script_Variable4_Clear(&u->o); u->targetAttack = 0; u->targetMove = 0; u->route[0] = 0xFF; if (action != ACTION_MOVE && action != ACTION_HARVEST) { encoded = Tools_Index_Encode(Unit_FindTargetAround(packed), IT_TILE); } else { encoded = Tools_Index_Encode(packed, IT_TILE); } Unit_SetAction(u, action); if (action == ACTION_MOVE) { Unit_SetDestination(u, encoded); } else if (action == ACTION_HARVEST) { u->targetMove = encoded; } else { Unit *target; Unit_SetTarget(u, encoded); target = Tools_Index_GetUnit(u->targetAttack); if (target != NULL) target->blinkCounter = 8; } if (g_enableVoices == 0) { Driver_Sound_Play(36, 0xFF); } else if (g_table_unitInfo[u->o.type].movementType == MOVEMENT_FOOT) { Sound_StartSound(g_table_actionInfo[action].soundID); } else { Sound_StartSound(((Tools_Random_256() & 0x1) == 0) ? 20 : 17); } g_unitActive = NULL; g_activeAction = 0xFFFF; GUI_ChangeSelectionType(SELECTIONTYPE_UNIT); return true; } if (click && g_selectionType == SELECTIONTYPE_PLACE) { const StructureInfo *si; Structure *s; House *h; s = g_structureActive; si = &g_table_structureInfo[g_structureActiveType]; h = g_playerHouse; if (Structure_Place(s, g_selectionPosition)) { Voice_Play(20); if (s->o.type == STRUCTURE_PALACE) House_Get_ByIndex(s->o.houseID)->palacePosition = s->o.position; if (g_structureActiveType == STRUCTURE_REFINERY && g_validateStrictIfZero == 0) { Unit *u; g_validateStrictIfZero++; u = Unit_CreateWrapper(g_playerHouseID, UNIT_HARVESTER, Tools_Index_Encode(s->o.index, IT_STRUCTURE)); g_validateStrictIfZero--; if (u == NULL) { h->harvestersIncoming++; } else { u->originEncoded = Tools_Index_Encode(s->o.index, IT_STRUCTURE); } } GUI_ChangeSelectionType(SELECTIONTYPE_STRUCTURE); s = Structure_Get_ByPackedTile(g_structureActivePosition); if (s != NULL) { if ((Structure_GetBuildable(s) & (1 << s->objectType)) == 0) Structure_BuildObject(s, 0xFFFE); } g_structureActiveType = 0xFFFF; g_structureActive = NULL; g_selectionState = 0; /* Invalid. */ GUI_DisplayHint(si->o.hintStringID, si->o.spriteID); House_UpdateRadarState(h); if (h->powerProduction < h->powerUsage) { if ((h->structuresBuilt & (1 << STRUCTURE_OUTPOST)) != 0) { GUI_DisplayText(String_Get_ByIndex(STR_NOT_ENOUGH_POWER_FOR_RADAR_BUILD_WINDTRAPS), 3); } } return true; } Voice_Play(47); if (g_structureActiveType == STRUCTURE_SLAB_1x1 || g_structureActiveType == STRUCTURE_SLAB_2x2) { GUI_DisplayText(String_Get_ByIndex(STR_CAN_NOT_PLACE_FOUNDATION_HERE), 2); } else { GUI_DisplayHint(STR_STRUCTURES_MUST_BE_PLACED_ON_CLEAR_ROCK_OR_CONCRETE_AND_ADJACENT_TO_ANOTHER_FRIENDLY_STRUCTURE, 0xFFFF); GUI_DisplayText(String_Get_ByIndex(STR_CAN_NOT_PLACE_S_HERE), 2, String_Get_ByIndex(si->o.stringID_abbrev)); } return true; } if (click && w->index == 43) { uint16 position; if (g_debugScenario) { position = packed; } else { position = Unit_FindTargetAround(packed); } if (g_map[position].overlaySpriteID != g_veiledSpriteID || g_debugScenario) { if (Object_GetByPackedTile(position) != NULL || g_debugScenario) { Map_SetSelection(position); Unit_DisplayStatusText(g_unitSelected); } } if ((w->state.buttonState & 0x10) != 0) Map_SetViewportPosition(packed); return true; } if ((click || drag) && w->index == 44) { Map_SetViewportPosition(packed); return true; } if (g_selectionType == SELECTIONTYPE_TARGET) { Map_SetSelection(Unit_FindTargetAround(packed)); } else if (g_selectionType == SELECTIONTYPE_PLACE) { Map_SetSelection(packed); } return true; }