public void LoadOverworldTiles() { overworld.AssembleMap16Tiles(true); for (int i = 0; i < 160; i++) { all_maps[i] = JsonConvert.DeserializeObject <MapSave>(File.ReadAllText("ProjectDirectory//Overworld//Maps//Map" + i.ToString("D3") + ".json")); overworld.AllMapTilesFromMap(i, all_maps[i].tiles); if (i == 159) { string s = ""; int tpos = 0; for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { Tile32 map16 = new Tile32(all_maps[i].tiles[(x * 2), (y * 2)], all_maps[i].tiles[(x * 2) + 1, (y * 2)], all_maps[i].tiles[(x * 2), (y * 2) + 1], all_maps[i].tiles[(x * 2) + 1, (y * 2) + 1]); s += "[" + map16.tile0.ToString("D4") + "," + map16.tile1.ToString("D4") + "," + map16.tile2.ToString("D4") + "," + map16.tile3.ToString("D4") + "] "; tpos++; } s += "\r\n"; } File.WriteAllText("TileDebug2.txt", s); } } byte[] largemaps = getLargeMaps(); overworld.createMap32TilesFrom16(); overworld.savemapstorom(); WriteLog("Overworld tiles data loaded properly", Color.Green); }
/* * Move to the given orientation looking from the current position. * @note returns input position when going out-of-bounds. * @param position The position to move from. * @param orientation The orientation to move in. * @return The new position, or the old in case of out-of-bounds. */ internal static Tile32 Tile_MoveByOrientation(Tile32 position, byte orientation) { short[] xOffsets = { 0, 256, 256, 256, 0, -256, -256, -256 }; //[8] short[] yOffsets = { -256, -256, 0, 256, 256, 256, 0, -256 }; //[8] ushort x; ushort y; x = Tile_GetX(position); y = Tile_GetY(position); orientation = Orientation_Orientation256ToOrientation8(orientation); x += (ushort)xOffsets[orientation]; y += (ushort)yOffsets[orientation]; if (x > 16384 || y > 16384) { return(position); } position.x = x; position.y = y; return(position); }
/* * Start an Animation. * @param commands List of commands for the Animation. * @param tile The tile to do the Animation on. * @param layout The layout of tiles for the Animation. * @param houseID The house of the item being Animation. * @param iconGroup In which IconGroup the sprites of the Animation belongs. */ internal static void Animation_Start(AnimationCommandStruct[] commands, Tile32 tile, ushort tileLayout, byte houseID, byte iconGroup) { var animation = g_animations; var packed = Tile_PackTile(tile); CTile t; int i; t = g_map[packed]; Animation_Stop_ByTile(packed); for (i = 0; i < ANIMATION_MAX; i++) { //, animation++) { if (animation[i].commands != null) { continue; } animation[i].tickNext = g_timerGUI; animation[i].tileLayout = tileLayout; animation[i].houseID = houseID; animation[i].current = 0; animation[i].iconGroup = iconGroup; animation[i].commands = commands; animation[i].tile = tile; s_animationTimer = 0; t.houseID = houseID; t.hasAnimation = true; return; } }
/* * Centers the offset of the tile. * * @param tile The tile to center. */ internal static Tile32 Tile_Center(Tile32 tile) { Tile32 result; result = tile; result.x = (ushort)((result.x & 0xff00) | 0x80); result.y = (ushort)((result.y & 0xff00) | 0x80); return(result); }
/* * Unpacks a 12 bits packed tile to a 32 bits tile class. * * @param packed The uint16 containing the 12 bits packed tile information. * @return The unpacked tile. */ internal static Tile32 Tile_UnpackTile(ushort packed) { var tile = new Tile32 { x = (ushort)((((packed >> 0) & 0x3F) << 8) | 0x80), y = (ushort)((((packed >> 6) & 0x3F) << 8) | 0x80) }; return(tile); }
/* * Adds two tiles together. * * @param from The origin. * @param diff The difference. * @return The new coordinates. */ internal static Tile32 Tile_AddTileDiff(Tile32 from, Tile32 diff) { var result = new Tile32 { x = (ushort)(from.x + diff.x), y = (ushort)(from.y + diff.y) }; return(result); }
/* * Calculates the distance between the two given tiles. * * @param from The origin. * @param to The destination. * @return The longest distance between the X or Y coordinates, plus half the shortest. */ internal static ushort Tile_GetDistance(Tile32 from, Tile32 to) { var distance_x = (ushort)Math.Abs(from.x - to.x); var distance_y = (ushort)Math.Abs(from.y - to.y); if (distance_x > distance_y) { return((ushort)(distance_x + (distance_y / 2))); } return((ushort)(distance_y + (distance_x / 2))); }
/* * Remove fog in the radius around the given tile. * * @param tile The tile to remove fog around. * @param radius The radius to remove fog around. */ internal static void Tile_RemoveFogInRadius(Tile32 tile, ushort radius) { ushort packed; ushort x, y; short i, j; /* TODO this code could be simplified */ packed = Tile_PackTile(tile); if (!Map_IsValidPosition(packed)) { return; } /* setting tile from its packed position equals removing the * non integer part */ x = Tile_GetPackedX(packed); y = Tile_GetPackedY(packed); Tile_MakeXY(ref tile, x, y); for (i = (short)-radius; i <= radius; i++) { for (j = (short)-radius; j <= radius; j++) { var t = new Tile32(); if (x + i is < 0 or >= 64) { continue; } if (y + j is < 0 or >= 64) { continue; } var xi = (ushort)(x + i); var yj = (ushort)(y + j); packed = Tile_PackXY(xi, yj); Tile_MakeXY(ref t, xi, yj); if (Tile_GetDistanceRoundedUp(tile, t) > radius) { continue; } Map_UnveilTile(packed, (byte)g_playerHouseID); } } }
//UNUSED CODE public void AllMapTilesFromMap(int mapid, ushort[,] tiles, bool large = false) { string s = ""; int tpos = mapid * 256; for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { map16tiles[tpos] = new Tile32(tiles[(x * 2), (y * 2)], tiles[(x * 2) + 1, (y * 2)], tiles[(x * 2), (y * 2) + 1], tiles[(x * 2) + 1, (y * 2) + 1]); s += "[" + map16tiles[tpos].tile0.ToString("D4") + "," + map16tiles[tpos].tile1.ToString("D4") + "," + map16tiles[tpos].tile2.ToString("D4") + "," + map16tiles[tpos].tile3.ToString("D4") + "] "; tpos++; } s += "\r\n"; } File.WriteAllText("TileDebug.txt", s); }
public void createMap32TilesFrom16() { t32.Clear(); t32Unique = new Tile32[10000]; tiles32count = 0; //40960 = numbers of 32x32 tiles const int nullVal = -1; for (int i = 0; i < 40960; i++) { short foundIndex = nullVal; for (int j = 0; j < tiles32count; j++) { if (t32Unique[j].tile0 == map16tiles[i].tile0) { if (t32Unique[j].tile1 == map16tiles[i].tile1) { if (t32Unique[j].tile2 == map16tiles[i].tile2) { if (t32Unique[j].tile3 == map16tiles[i].tile3) { foundIndex = (short)j; break; } } } } } if (foundIndex == nullVal) { t32Unique[tiles32count] = new Tile32(map16tiles[i].tile0, map16tiles[i].tile1, map16tiles[i].tile2, map16tiles[i].tile3); t32.Add((ushort)tiles32count); tiles32count++; } else { t32.Add((ushort)foundIndex); } } Console.WriteLine("Nbr of tiles32 = " + tiles32count); }
/* * 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. */ internal static ushort Script_Structure_Fire(ScriptEngine script) { CStructure s; CUnit u; var position = new Tile32(); ushort target; ushort damage; ushort fireDelay; ushort type; s = g_scriptCurrentStructure; target = script.variables[2]; if (target == 0) { return(0); } if (s.o.type == (byte)StructureType.STRUCTURE_ROCKET_TURRET && Tile_GetDistance(Tools_Index_GetTile(target), s.o.position) >= 0x300) { type = (ushort)UnitType.UNIT_MISSILE_TURRET; damage = 30; fireDelay = Tools_AdjustToGameSpeed(g_table_unitInfo[(ushort)UnitType.UNIT_LAUNCHER].fireDelay, 1, 0xFFFF, true); } else { type = (ushort)UnitType.UNIT_BULLET; damage = 20; fireDelay = Tools_AdjustToGameSpeed(g_table_unitInfo[(ushort)UnitType.UNIT_TANK].fireDelay, 1, 0xFFFF, true); } position.x = (ushort)(s.o.position.x + 0x80); position.y = (ushort)(s.o.position.y + 0x80); u = Unit_CreateBullet(position, (UnitType)type, s.o.houseID, damage, target); if (u == null) { return(0); } u.originEncoded = Tools_Index_Encode(s.o.index, IndexType.IT_STRUCTURE); return(fireDelay); }
/* * Start a Explosion on a tile. * @param explosionType Type of Explosion. * @param position The position to use for init. */ internal static void Explosion_Start(ushort explosionType, Tile32 position) { ExplosionCommandStruct[] commands; ushort packed; byte i; if (explosionType > (ushort)ExplosionType.EXPLOSION_SPICE_BLOOM_TREMOR) { return; } commands = g_table_explosion[explosionType]; packed = Tile_PackTile(position); Explosion_StopAtPosition(packed); for (i = 0; i < EXPLOSION_MAX; i++) { CExplosion e; e = g_explosions[i]; if (e.commands != null) { continue; } e.commands = commands; e.current = 0; e.spriteID = 0; e.position = position; e.isDirty = false; e.timeOut = g_timerGUI; s_explosionTimer = 0; g_map[packed].hasExplosion = true; break; } }
public void DecompressAllMapTiles() { //locat functions int genPointer(int address, int i) => PointerRead.LongRead_LoHiBank(address + i * 3); byte[] Decomp(int pointer, ref int compressedSize) => Decompress.ALTTPDecompressOverworld(ROM.DATA, pointer, 1000, ref compressedSize); int npos = 0; for (int i = 0; i < 160; i++) { int p1 = genPointer(ConstantsReader.GetAddress("compressedAllMap32PointersHigh"), i), p2 = genPointer(ConstantsReader.GetAddress("compressedAllMap32PointersLow"), i); int ttpos = 0, compressedSize1 = 0, compressedSize2 = 0; byte[] bytes = Decomp(p2, ref compressedSize1), bytes2 = Decomp(p1, ref compressedSize2); for (int y = 0; y < 16; y++) { for (int x = 0, tpos; x < 16; x++, npos++, ttpos++) { tpos = (ushort)((bytes2[ttpos] << 8) + bytes[ttpos]); if (tpos < tiles32.Count) { map16tiles[npos] = new Tile32(tiles32[tpos].tile0, tiles32[tpos].tile1, tiles32[tpos].tile2, tiles32[tpos].tile3); } else { Console.WriteLine("Found 0,0,0,0"); map16tiles[npos] = new Tile32(0, 0, 0, 0); } } } } }
/* * Get the tile from given tile at given maximum distance in random direction. * * @param tile The origin. * @param distance The distance maximum. * @param center Wether to center the offset of the tile. * @return The tile. */ internal static Tile32 Tile_MoveByRandom(Tile32 tile, ushort distance, bool center) { ushort x; ushort y; var ret = new Tile32(); byte orientation; ushort newDistance; if (distance == 0) { return(tile); } x = Tile_GetX(tile); y = Tile_GetY(tile); newDistance = Tools_Random_256(); while (newDistance > distance) { newDistance /= 2; } distance = newDistance; orientation = Tools_Random_256(); x += (ushort)(((_stepX[orientation] * distance) / 128) * 16); y -= (ushort)(((_stepY[orientation] * distance) / 128) * 16); if (x > 16384 || y > 16384) { return(tile); } ret.x = x; ret.y = y; return(center ? Tile_Center(ret) : ret); }
/* * Get the tile from given tile at given distance in given direction. * * @param tile The origin. * @param orientation The direction to follow. * @param distance The distance. * @return The tile. */ internal static Tile32 Tile_MoveByDirection(Tile32 tile, short orientation, ushort distance) { int diffX, diffY; int roundingOffsetX, roundingOffsetY; distance = Math.Min(distance, (ushort)0xFF); if (distance == 0) { return(tile); } diffX = _stepX[orientation & 0xFF]; diffY = _stepY[orientation & 0xFF]; /* Always round away from zero */ roundingOffsetX = diffX < 0 ? -64 : 64; roundingOffsetY = diffY < 0 ? -64 : 64; tile.x += (ushort)((diffX * distance + roundingOffsetX) / 128); tile.y -= (ushort)((diffY * distance + roundingOffsetY) / 128); return(tile); }
/* * Check whether a tile is valid. * * @param tile The tile32 to check for validity. * @return True if valid, false if not. */ /*extern bool Tile_IsValid(tile32 tile);*/ /*#define Tile_IsValid(tile) (((tile).x & 0xc000) == 0 && ((tile).y & 0xc000) == 0)*/ internal static bool Tile_IsValid(Tile32 tile) => ((tile.x | tile.y) & 0xc000) == 0;
internal ScriptEngine script; /*!< The script engine instance of this Structure. */ internal CObject() { flags = new ObjectFlags(); script = new ScriptEngine(); position = new Tile32(); }
/* * Make a tile32 from an X- and Y-position. * * @param x The X-position. * @param y The Y-position. * @return A tile32 at the top-left corner of the X- and Y-position. */ /*extern tile32 Tile_MakeXY(uint16 x, uint16 y);*/ internal static void /*tile32*/ Tile_MakeXY(ref Tile32 tile, ushort X, ushort Y) { tile.x = (ushort)(X << 8); tile.y = (ushort)(Y << 8); //return tile; }
/* * Calculates the rounded up distance between the two given packed tiles. * * @param from The origin. * @param to The destination. * @return The longest distance between the X or Y coordinates, plus half the shortest. */ internal static ushort Tile_GetDistanceRoundedUp(Tile32 from, Tile32 to) => (ushort)((Tile_GetDistance(from, to) + 0x80) >> 8);
/* * Get to direction to follow to go from \a from to \a to. * * @param from The origin. * @param to The destination. * @return The direction. */ internal static sbyte Tile_GetDirection(Tile32 from, Tile32 to) { int dx; int dy; ushort i; int gradient; ushort baseOrientation; bool invert; ushort quadrant = 0; dx = to.x - from.x; dy = to.y - from.y; if (Math.Abs(dx) + Math.Abs(dy) > 8000) { dx /= 2; dy /= 2; } if (dy <= 0) { quadrant |= 0x2; dy = -dy; } if (dx < 0) { quadrant |= 0x1; dx = -dx; } baseOrientation = orientationOffsets[quadrant]; invert = false; gradient = 0x7FFF; if (dx >= dy) { if (dy != 0) { gradient = (dx << 8) / dy; } } else { invert = true; if (dx != 0) { gradient = (dy << 8) / dx; } } for (i = 0; i < directions.Length /*lengthof(directions)*/; i++) { if (directions[i] <= gradient) { break; } } if (!invert) { i = (ushort)(64 - i); } if (quadrant is 0 or 3) { return((sbyte)((baseOrientation + 64 - i) & 0xFF)); } return((sbyte)((baseOrientation + i) & 0xFF)); }
/* * Returns the X-position of the tile. * * @param tile The tile32 to get the X-position from. * @return The X-position of the tile. */ /*extern uint16 Tile_GetX(tile32 tile);*/ internal static ushort Tile_GetX(Tile32 tile) => tile.x;
/* * Loop over all houses, preforming various of tasks. */ internal static void GameLoop_House() { var find = new PoolFindStruct(); CHouse h; // = NULL; var tickHouse = false; var tickPowerMaintenance = false; var tickStarport = false; var tickReinforcement = false; var tickMissileCountdown = false; var 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 = (uint)(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((ushort)(g_houseMissileCountdown + 41)); if (g_houseMissileCountdown == 0) { Unit_LaunchHouseMissile(Map_FindLocationTile(4, (byte)g_playerHouseID)); } } if (tickStarportAvailability) { ushort type; /* Pick a random unit to increase starport availability */ type = Tools_RandomLCG_Range(0, (ushort)(UnitType.UNIT_MAX - 1)); /* Increase how many of this unit is available via starport by one */ if (g_starportAvailable[type] is not 0 and < 10) { if (g_starportAvailable[type] == -1) { g_starportAvailable[type] = 1; } else { g_starportAvailable[type]++; } } } if (tickReinforcement) { CUnit nu = null; int i; for (i = 0; i < 16; i++) { ushort locationID; bool deployed; CUnit u; if (g_scenario.reinforcement[i].unitID == (ushort)UnitIndex.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((ushort)UnitIndex.UNIT_INDEX_INVALID, (byte)UnitType.UNIT_CARRYALL, u.o.houseID, Tile_UnpackTile(Map_FindLocationTile((ushort)(Tools_Random_256() & 3), u.o.houseID)), 100); if (nu != null) { nu.o.flags.byScenario = true; Unit_SetDestination(nu, Tools_Index_Encode(Map_FindLocationTile(locationID, u.o.houseID), IndexType.IT_TILE)); } } if (nu != null) { u.o.linkedID = nu.o.linkedID; nu.o.linkedID = (byte)u.o.index; nu.o.flags.inTransport = true; g_scenario.reinforcement[i].unitID = (ushort)UnitIndex.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) { var tile = new Tile32 { x = 0xFFFF, y = 0xFFFF }; g_validateStrictIfZero++; u = Unit_Create((ushort)UnitIndex.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 = (byte)HouseType.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 != (byte)g_playerHouseID) { if (h.creditsStorage < h.credits) { h.credits = h.creditsStorage; } } else { var maxCredits = Math.Max(h.creditsStorage, g_playerCreditsNoSilo); if (h.credits > maxCredits) { h.credits = maxCredits; GUI_DisplayText(String_Get_ByIndex(Text.STR_INSUFFICIENT_SPICE_STORAGE_AVAILABLE_SPICE_IS_LOST), 1); } } if (h.index == (byte)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(Text.STR_SPICE_STORAGE_CAPACITY_LOW_BUILD_SILOS), 0); } } if (h.credits < 100 && g_playerCreditsNoSilo != 0) { GUI_DisplayText(String_Get_ByIndex(Text.STR_CREDITS_ARE_LOW_HARVEST_SPICE_FOR_MORE_CREDITS), 0); } } } if (tickHouse) { House_EnsureHarvesterAvailable(h.index); } if (tickStarport && h.starportLinkedID != (ushort)UnitIndex.UNIT_INDEX_INVALID) { CUnit u = null; h.starportTimeLeft--; if ((short)h.starportTimeLeft < 0) { h.starportTimeLeft = 0; } if (h.starportTimeLeft == 0) { CStructure s; s = Structure_Get_ByIndex(g_structureIndex); if (s.o.type == (byte)StructureType.STRUCTURE_STARPORT && s.o.houseID == h.index) { u = Unit_CreateWrapper(h.index, UnitType.UNIT_FRIGATE, Tools_Index_Encode(s.o.index, IndexType.IT_STRUCTURE)); } else { var find2 = new PoolFindStruct { houseID = h.index, index = 0xFFFF, type = (ushort)StructureType.STRUCTURE_STARPORT }; while (true) { s = Structure_Find(find2); if (s == null) { break; } if (s.o.linkedID != 0xFF) { continue; } u = Unit_CreateWrapper(h.index, UnitType.UNIT_FRIGATE, Tools_Index_Encode(s.o.index, IndexType.IT_STRUCTURE)); break; } } if (u != null) { u.o.linkedID = (byte)h.starportLinkedID; h.starportLinkedID = (ushort)UnitIndex.UNIT_INDEX_INVALID; u.o.flags.inTransport = true; Sound_Output_Feedback(38); } h.starportTimeLeft = (ushort)((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(h.index, UnitType.UNIT_HARVESTER, 0) != null) { h.harvestersIncoming--; } } if (tickPowerMaintenance) { var powerMaintenanceCost = (ushort)((h.powerUsage / 32) + 1); h.credits -= Math.Min(h.credits, powerMaintenanceCost); } } }
internal ushort[][] ai_structureRebuild; //[5][2] /*!< An array for the AI which stores the type and position of a destroyed structure, for rebuilding. */ internal CHouse() { flags = new HouseFlags(); palacePosition = new Tile32(); ai_structureRebuild = new ushort[][] { new ushort[2], new ushort[2], new ushort[2], new ushort[2], new ushort[2] }; }
/* * Returns the Y-position of the tile. * * @param tile The tile32 to get the Y-position from. * @return The Y-position of the tile. */ /*extern uint16 Tile_GetY(tile32 tile);*/ internal static ushort Tile_GetY(Tile32 tile) => tile.y;
/* * Packs a 32 bits tile class into a 12 bits packed tile. * * @param tile The tile32 to get it's Y-position from. * @return The tile packed into 12 bits. */ /*extern uint16 Tile_PackTile(tile32 tile);*/ internal static ushort Tile_PackTile(Tile32 tile) => (ushort)((Tile_GetPosY(tile) << 6) | Tile_GetPosX(tile));
/* * Returns the Y-position of the tile. * * @param tile The tile32 to get the Y-position from. * @return The Y-position of the tile. */ /*extern uint8 Tile_GetPosY(tile32 tile);*/ internal static byte Tile_GetPosY(Tile32 tile) => (byte)((tile.y >> 8) & 0x3f);