#include "../audio/audio.h" #include "../game.h" #include "../localisation/localisation.h" #include "../localisation/string_ids.h" #include "../interface/viewport.h" #include "../util/sawyercoding.h" #include "../windows/error.h" #include "../world/scenery.h" #include "ride_data.h" #include "track.h" #include "track_data.h" #include "track_design.h" #define TRACK_MAX_SAVED_MAP_ELEMENTS 1500 static bool track_design_save_should_select_scenery_around(int rideIndex, rct_map_element *mapElement); static void track_design_save_select_nearby_scenery_for_tile(int rideIndex, int cx, int cy); static bool track_design_save_contains_map_element(rct_map_element *mapElement); static bool track_design_save_add_map_element(int interactionType, int x, int y, rct_map_element *mapElement); static void track_design_save_remove_map_element(int interactionType, int x, int y, rct_map_element *mapElement); static bool track_design_save_copy_scenery_to_track(uint8 **track_pointer); static bool track_design_save_to_td6(rct_track_td6 *td6, uint8 rideIndex); static bool track_design_save_to_td6_for_maze(uint8 rideIndex, rct_track_td6 *td6, uint8 *trackElements); static bool track_design_save_to_td6_for_tracked_ride(uint8 rideIndex, rct_track_td6 *td6, uint8 *trackElements); static bool track_design_save_to_file(rct_track_td6 *track_design, utf8 *path); /** * * rct2: 0x006D2B07 */ void track_design_save_toggle_map_element(int interactionType, int x, int y, rct_map_element *mapElement) { if (track_design_save_contains_map_element(mapElement)) { track_design_save_remove_map_element(interactionType, x, y, mapElement); } else { if (!track_design_save_add_map_element(interactionType, x, y, mapElement)) { window_error_open( STR_SAVE_TRACK_SCENERY_UNABLE_TO_SELECT_ADDITIONAL_ITEM_OF_SCENERY, STR_SAVE_TRACK_SCENERY_TOO_MANY_ITEMS_SELECTED ); } } } /** * * rct2: 0x006D303D */ void track_design_save_select_nearby_scenery(int rideIndex) { rct_map_element *mapElement; for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { mapElement = map_get_first_element_at(x, y); do { if (track_design_save_should_select_scenery_around(rideIndex, mapElement)) { track_design_save_select_nearby_scenery_for_tile(rideIndex, x, y); break; } } while (!map_element_is_last_for_tile(mapElement++)); } } gfx_invalidate_screen(); } /** * * rct2: 0x006D3026 */ void track_design_save_reset_scenery() { RCT2_GLOBAL(0x009DA193, uint8) = 255; gTrackSavedMapElements[0] = (rct_map_element*)0xFFFFFFFF; gfx_invalidate_screen(); } /** * * rct2: 0x006D2804, 0x006D264D */ bool track_design_save(uint8 rideIndex) { rct_ride* ride = get_ride(rideIndex); if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)){ window_error_open(STR_CANT_SAVE_TRACK_DESIGN, gGameCommandErrorText); return false; } if (ride->ratings.excitement == (ride_rating)0xFFFF){ window_error_open(STR_CANT_SAVE_TRACK_DESIGN, gGameCommandErrorText); return false; } if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_TRACK)) { window_error_open(STR_CANT_SAVE_TRACK_DESIGN, gGameCommandErrorText); return false; } rct_track_td6 *td6 = RCT2_ADDRESS(0x009D8178, rct_track_td6); if (!track_design_save_to_td6(td6, rideIndex)) { window_error_open(STR_CANT_SAVE_TRACK_DESIGN, gGameCommandErrorText); return false; } uint8 *trackPointer = RCT2_GLOBAL(0x00F44058, uint8*); if (RCT2_GLOBAL(0x009DEA6F, uint8) & 1) { if (!track_design_save_copy_scenery_to_track(&trackPointer)) { return false; } } while (trackPointer < RCT2_ADDRESS(0x009DE217, uint8))*trackPointer++ = 0; char track_name[MAX_PATH]; // Get track name format_string(track_name, ride->name, &ride->name_arguments); char path[MAX_PATH]; substitute_path(path, RCT2_ADDRESS(RCT2_ADDRESS_TRACKS_PATH, char), track_name); // Save track design format_string(RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, char), 2306, NULL); // Track design files format_string(RCT2_ADDRESS(0x141EE68, char), 2305, NULL); // Show save dialog utf8 initialDirectory[MAX_PATH]; { strcpy(initialDirectory, path); utf8 *a = strrchr(initialDirectory, '/'); utf8 *b = strrchr(initialDirectory, '\\'); utf8 *c = max(a, b); if (c != NULL) { *c = '\0'; } } file_dialog_desc desc; memset(&desc, 0, sizeof(desc)); desc.type = FD_SAVE; desc.title = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, utf8); desc.initial_directory = initialDirectory; desc.default_filename = path; desc.filters[0].name = language_get_string(STR_OPENRCT2_TRACK_DESIGN_FILE); desc.filters[0].pattern = "*.td6"; audio_pause_sounds(); bool result = platform_open_common_file_dialog(path, &desc); audio_unpause_sounds(); if (!result) { ride_list_item item = { .type = 0xFD, .entry_index = 0 }; // track_load_list(item); return true; } track_design_save_to_file(RCT2_ADDRESS(0x009D8178, rct_track_td6), path); ride_list_item item = { .type = 0xFC, .entry_index = 0 }; // track_load_list(item); gfx_invalidate_screen(); return true; } static bool track_design_save_contains_map_element(rct_map_element *mapElement) { rct_map_element **savedMapElement; savedMapElement = gTrackSavedMapElements; do { if (*savedMapElement == mapElement) { return true; } } while (*savedMapElement++ != (rct_map_element*)-1); return false; } static int map_element_get_total_element_count(rct_map_element *mapElement) { int elementCount; rct_scenery_entry *sceneryEntry; rct_large_scenery_tile *tile; switch (map_element_get_type(mapElement)) { case MAP_ELEMENT_TYPE_PATH: case MAP_ELEMENT_TYPE_SCENERY: case MAP_ELEMENT_TYPE_FENCE: return 1; case MAP_ELEMENT_TYPE_SCENERY_MULTIPLE: sceneryEntry = g_largeSceneryEntries[mapElement->properties.scenerymultiple.type & 0x3FF]; tile = sceneryEntry->large_scenery.tiles; elementCount = 0; do { tile++; elementCount++; } while (tile->x_offset != (sint16)0xFFFF); return elementCount; default: return 0; } } static bool track_design_save_scenery_is_null(rct_track_scenery *trackScenery) { return *((uint8*)trackScenery) == 0xFF; } static void track_design_save_scenery_set_to_null(rct_track_scenery *trackScenery) { *((uint8*)trackScenery) = 0xFF; } static rct_map_element **track_design_save_get_next_spare_map_element() { rct_map_element **savedMapElement = gTrackSavedMapElements; while (*savedMapElement != (rct_map_element*)0xFFFFFFFF) { savedMapElement++; } return savedMapElement; } /** * * rct2: 0x006D2ED2 */ static bool track_design_save_can_add_map_element(rct_map_element *mapElement) { int newElementCount = map_element_get_total_element_count(mapElement); if (newElementCount == 0) { return false; } // Get number of saved elements so far rct_map_element **savedMapElement = track_design_save_get_next_spare_map_element(); // Get number of spare elements left int numSavedElements = savedMapElement - gTrackSavedMapElements; int spareSavedElements = TRACK_MAX_SAVED_MAP_ELEMENTS - numSavedElements; if (newElementCount > spareSavedElements) { // No more spare saved elements left return false; } // Probably checking for spare elements in the TD6 struct rct_track_scenery *trackScenery = (rct_track_scenery*)0x009DA193; while (!track_design_save_scenery_is_null(trackScenery)) { trackScenery++; } if (trackScenery >= (rct_track_scenery*)0x9DE207) { return false; } return true; } /** * * rct2: 0x006D2F4C */ static void track_design_save_push_map_element(int x, int y, rct_map_element *mapElement) { rct_map_element **savedMapElement; map_invalidate_tile_full(x, y); savedMapElement = track_design_save_get_next_spare_map_element(); *savedMapElement = mapElement; *(savedMapElement + 1) = (rct_map_element*)0xFFFFFFFF; } /** * * rct2: 0x006D2FA7 */ static void track_design_save_push_map_element_desc(rct_object_entry *entry, int x, int y, int z, uint8 flags, uint8 primaryColour, uint8 secondaryColour) { rct_track_scenery *item = (rct_track_scenery*)0x009DA193; while (!track_design_save_scenery_is_null(item)) { item++; } item->scenery_object = *entry; item->x = x / 32; item->y = y / 32; item->z = z; item->flags = flags; item->primary_colour = primaryColour; item->secondary_colour = secondaryColour; track_design_save_scenery_set_to_null(item + 1); } static void track_design_save_add_scenery(int x, int y, rct_map_element *mapElement) { int entryType = mapElement->properties.scenery.type; rct_object_entry *entry = (rct_object_entry*)&object_entry_groups[OBJECT_TYPE_SMALL_SCENERY].entries[entryType]; uint8 flags = 0; flags |= mapElement->type & 3; flags |= (mapElement->type & 0xC0) >> 4; uint8 primaryColour = mapElement->properties.scenery.colour_1 & 0x1F; uint8 secondaryColour = mapElement->properties.scenery.colour_2 & 0x1F; track_design_save_push_map_element(x, y, mapElement); track_design_save_push_map_element_desc(entry, x, y, mapElement->base_height, flags, primaryColour, secondaryColour); } static void track_design_save_add_large_scenery(int x, int y, rct_map_element *mapElement) { rct_large_scenery_tile *sceneryTiles, *tile; int x0, y0, z0, z; int direction, sequence; int entryType = mapElement->properties.scenerymultiple.type & 0x3FF; rct_object_entry *entry = (rct_object_entry*)&object_entry_groups[OBJECT_TYPE_LARGE_SCENERY].entries[entryType]; sceneryTiles = g_largeSceneryEntries[entryType]->large_scenery.tiles; z = mapElement->base_height; direction = mapElement->type & 3; sequence = mapElement->properties.scenerymultiple.type >> 10; if (!map_large_scenery_get_origin(x, y, z, direction, sequence, &x0, &y0, &z0, NULL)) { return; } // Iterate through each tile of the large scenery element sequence = 0; for (tile = sceneryTiles; tile->x_offset != -1; tile++, sequence++) { sint16 offsetX = tile->x_offset; sint16 offsetY = tile->y_offset; rotate_map_coordinates(&offsetX, &offsetY, direction); x = x0 + offsetX; y = y0 + offsetY; z = (z0 + tile->z_offset) / 8; mapElement = map_get_large_scenery_segment(x, y, z, direction, sequence); if (mapElement != NULL) { if (sequence == 0) { uint8 flags = mapElement->type & 3; uint8 primaryColour = mapElement->properties.scenerymultiple.colour[0] & 0x1F; uint8 secondaryColour = mapElement->properties.scenerymultiple.colour[1] & 0x1F; track_design_save_push_map_element_desc(entry, x, y, z, flags, primaryColour, secondaryColour); } track_design_save_push_map_element(x, y, mapElement); } } } static void track_design_save_add_wall(int x, int y, rct_map_element *mapElement) { int entryType = mapElement->properties.fence.type; rct_object_entry *entry = (rct_object_entry*)&object_entry_groups[OBJECT_TYPE_WALLS].entries[entryType]; uint8 flags = 0; flags |= mapElement->type & 3; flags |= mapElement->properties.fence.item[0] << 2; uint8 secondaryColour = ((mapElement->flags & 0x60) >> 2) | (mapElement->properties.fence.item[1] >> 5); uint8 primaryColour = mapElement->properties.fence.item[1] & 0x1F; track_design_save_push_map_element(x, y, mapElement); track_design_save_push_map_element_desc(entry, x, y, mapElement->base_height, flags, primaryColour, secondaryColour); } static void track_design_save_add_footpath(int x, int y, rct_map_element *mapElement) { int entryType = mapElement->properties.path.type >> 4; rct_object_entry *entry = (rct_object_entry*)&object_entry_groups[OBJECT_TYPE_PATHS].entries[entryType]; uint8 flags = 0; flags |= mapElement->properties.path.edges & 0x0F; flags |= (mapElement->properties.path.type & 4) << 2; flags |= (mapElement->properties.path.type & 3) << 5; flags |= (mapElement->type & 1) << 7; track_design_save_push_map_element(x, y, mapElement); track_design_save_push_map_element_desc(entry, x, y, mapElement->base_height, flags, 0, 0); } /** * * rct2: 0x006D2B3C */ static bool track_design_save_add_map_element(int interactionType, int x, int y, rct_map_element *mapElement) { if (!track_design_save_can_add_map_element(mapElement)) { return false; } switch (interactionType) { case VIEWPORT_INTERACTION_ITEM_SCENERY: track_design_save_add_scenery(x, y, mapElement); return true; case VIEWPORT_INTERACTION_ITEM_LARGE_SCENERY: track_design_save_add_large_scenery(x, y, mapElement); return true; case VIEWPORT_INTERACTION_ITEM_WALL: track_design_save_add_wall(x, y, mapElement); return true; case VIEWPORT_INTERACTION_ITEM_FOOTPATH: track_design_save_add_footpath(x, y, mapElement); return true; default: return false; } } /** * * rct2: 0x006D2F78 */ static void track_design_save_pop_map_element(int x, int y, rct_map_element *mapElement) { map_invalidate_tile_full(x, y); // Find map element and total of saved elements int removeIndex = -1; int numSavedElements = 0; rct_map_element **savedMapElement = gTrackSavedMapElements; while (*savedMapElement != (rct_map_element*)0xFFFFFFFF) { if (*savedMapElement == mapElement) { removeIndex = numSavedElements; } savedMapElement++; numSavedElements++; } if (removeIndex == -1) { return; } // Remove item and shift rest up one item if (removeIndex < numSavedElements - 1) { memmove(&gTrackSavedMapElements[removeIndex], &gTrackSavedMapElements[removeIndex + 1], (numSavedElements - removeIndex - 1) * sizeof(rct_map_element*)); } gTrackSavedMapElements[numSavedElements - 1] = (rct_map_element*)0xFFFFFFFF; } /** * * rct2: 0x006D2FDD */ static void track_design_save_pop_map_element_desc(rct_object_entry *entry, int x, int y, int z, uint8 flags, uint8 primaryColour, uint8 secondaryColour) { int removeIndex = -1; int totalItems = 0; rct_track_scenery *items = (rct_track_scenery*)0x009DA193; rct_track_scenery *item = items; for (; !track_design_save_scenery_is_null(item); item++, totalItems++) { if (item->x != x / 32) continue; if (item->y != y / 32) continue; if (item->z != z) continue; if (item->flags != flags) continue; if (!object_entry_compare(&item->scenery_object, entry)) continue; removeIndex = totalItems; } if (removeIndex == -1) { return; } // Remove item and shift rest up one item if (removeIndex < totalItems - 1) { memmove(&items[removeIndex], &items[removeIndex + 1], (totalItems - removeIndex - 1) * sizeof(rct_track_scenery)); } track_design_save_scenery_set_to_null(&items[totalItems - 1]); } static void track_design_save_remove_scenery(int x, int y, rct_map_element *mapElement) { int entryType = mapElement->properties.scenery.type; rct_object_entry *entry = (rct_object_entry*)&object_entry_groups[OBJECT_TYPE_SMALL_SCENERY].entries[entryType]; uint8 flags = 0; flags |= mapElement->type & 3; flags |= (mapElement->type & 0xC0) >> 4; uint8 primaryColour = mapElement->properties.scenery.colour_1 & 0x1F; uint8 secondaryColour = mapElement->properties.scenery.colour_2 & 0x1F; track_design_save_pop_map_element(x, y, mapElement); track_design_save_pop_map_element_desc(entry, x, y, mapElement->base_height, flags, primaryColour, secondaryColour); } static void track_design_save_remove_large_scenery(int x, int y, rct_map_element *mapElement) { rct_large_scenery_tile *sceneryTiles, *tile; int x0, y0, z0, z; int direction, sequence; int entryType = mapElement->properties.scenerymultiple.type & 0x3FF; rct_object_entry *entry = (rct_object_entry*)&object_entry_groups[OBJECT_TYPE_LARGE_SCENERY].entries[entryType]; sceneryTiles = g_largeSceneryEntries[entryType]->large_scenery.tiles; z = mapElement->base_height; direction = mapElement->type & 3; sequence = mapElement->properties.scenerymultiple.type >> 10; if (!map_large_scenery_get_origin(x, y, z, direction, sequence, &x0, &y0, &z0, NULL)) { return; } // Iterate through each tile of the large scenery element sequence = 0; for (tile = sceneryTiles; tile->x_offset != -1; tile++, sequence++) { sint16 offsetX = tile->x_offset; sint16 offsetY = tile->y_offset; rotate_map_coordinates(&offsetX, &offsetY, direction); x = x0 + offsetX; y = y0 + offsetY; z = (z0 + tile->z_offset) / 8; mapElement = map_get_large_scenery_segment(x, y, z, direction, sequence); if (mapElement != NULL) { if (sequence == 0) { uint8 flags = mapElement->type & 3; uint8 primaryColour = mapElement->properties.scenerymultiple.colour[0] & 0x1F; uint8 secondaryColour = mapElement->properties.scenerymultiple.colour[1] & 0x1F; track_design_save_pop_map_element_desc(entry, x, y, z, flags, primaryColour, secondaryColour); } track_design_save_pop_map_element(x, y, mapElement); } } } static void track_design_save_remove_wall(int x, int y, rct_map_element *mapElement) { int entryType = mapElement->properties.fence.type; rct_object_entry *entry = (rct_object_entry*)&object_entry_groups[OBJECT_TYPE_WALLS].entries[entryType]; uint8 flags = 0; flags |= mapElement->type & 3; flags |= mapElement->properties.fence.item[0] << 2; uint8 secondaryColour = ((mapElement->flags & 0x60) >> 2) | (mapElement->properties.fence.item[1] >> 5); uint8 primaryColour = mapElement->properties.fence.item[1] & 0x1F; track_design_save_pop_map_element(x, y, mapElement); track_design_save_pop_map_element_desc(entry, x, y, mapElement->base_height, flags, primaryColour, secondaryColour); } static void track_design_save_remove_footpath(int x, int y, rct_map_element *mapElement) { int entryType = mapElement->properties.path.type >> 4; rct_object_entry *entry = (rct_object_entry*)&object_entry_groups[OBJECT_TYPE_PATHS].entries[entryType]; uint8 flags = 0; flags |= mapElement->properties.path.edges & 0x0F; flags |= (mapElement->properties.path.type & 4) << 2; flags |= (mapElement->properties.path.type & 3) << 5; flags |= (mapElement->type & 1) << 7; track_design_save_pop_map_element(x, y, mapElement); track_design_save_pop_map_element_desc(entry, x, y, mapElement->base_height, flags, 0, 0); } /** * * rct2: 0x006D2B3C */ static void track_design_save_remove_map_element(int interactionType, int x, int y, rct_map_element *mapElement) { switch (interactionType) { case VIEWPORT_INTERACTION_ITEM_SCENERY: track_design_save_remove_scenery(x, y, mapElement); break; case VIEWPORT_INTERACTION_ITEM_LARGE_SCENERY: track_design_save_remove_large_scenery(x, y, mapElement); break; case VIEWPORT_INTERACTION_ITEM_WALL: track_design_save_remove_wall(x, y, mapElement); break; case VIEWPORT_INTERACTION_ITEM_FOOTPATH: track_design_save_remove_footpath(x, y, mapElement); break; } } static bool track_design_save_should_select_scenery_around(int rideIndex, rct_map_element *mapElement) { switch (map_element_get_type(mapElement)) { case MAP_ELEMENT_TYPE_PATH: if ((mapElement->type & 1) && mapElement->properties.path.addition_status == rideIndex) return true; break; case MAP_ELEMENT_TYPE_TRACK: if (mapElement->properties.track.ride_index == rideIndex) return true; break; case MAP_ELEMENT_TYPE_ENTRANCE: if (mapElement->properties.entrance.type != ENTRANCE_TYPE_RIDE_ENTRANCE) break; if (mapElement->properties.entrance.type != ENTRANCE_TYPE_RIDE_EXIT) break; if (mapElement->properties.entrance.ride_index == rideIndex) return true; break; } return false; } static void track_design_save_select_nearby_scenery_for_tile(int rideIndex, int cx, int cy) { rct_map_element *mapElement; for (int y = cy - 1; y <= cy + 1; y++) { for (int x = cx - 1; x <= cx + 1; x++) { mapElement = map_get_first_element_at(x, y); do { int interactionType = VIEWPORT_INTERACTION_ITEM_NONE; switch (map_element_get_type(mapElement)) { case MAP_ELEMENT_TYPE_PATH: if (!(mapElement->type & 1)) interactionType = VIEWPORT_INTERACTION_ITEM_FOOTPATH; else if (mapElement->properties.path.addition_status == rideIndex) interactionType = VIEWPORT_INTERACTION_ITEM_FOOTPATH; break; case MAP_ELEMENT_TYPE_SCENERY: interactionType = VIEWPORT_INTERACTION_ITEM_SCENERY; break; case MAP_ELEMENT_TYPE_FENCE: interactionType = VIEWPORT_INTERACTION_ITEM_WALL; break; case MAP_ELEMENT_TYPE_SCENERY_MULTIPLE: interactionType = VIEWPORT_INTERACTION_ITEM_LARGE_SCENERY; break; } if (interactionType != VIEWPORT_INTERACTION_ITEM_NONE) { if (!track_design_save_contains_map_element(mapElement)) { track_design_save_add_map_element(interactionType, x * 32, y * 32, mapElement); } } } while (!map_element_is_last_for_tile(mapElement++)); } } } /* Based on rct2: 0x006D2897 */ static bool track_design_save_copy_scenery_to_track(uint8 **track_pointer) { rct_track_scenery* track_scenery = (rct_track_scenery*)(*track_pointer - 1); rct_track_scenery* scenery_source = RCT2_ADDRESS(0x009DA193, rct_track_scenery); while (true) { memcpy(track_scenery, scenery_source, sizeof(rct_track_scenery)); if ((track_scenery->scenery_object.flags & 0xFF) == 0xFF) break; //0x00F4414D is direction of track? if ((track_scenery->scenery_object.flags & 0xF) == OBJECT_TYPE_PATHS){ uint8 slope = (track_scenery->flags & 0x60) >> 5; slope -= RCT2_GLOBAL(0x00F4414D, uint8); track_scenery->flags &= 0x9F; track_scenery->flags |= ((slope & 3) << 5); // Direction of connection on path uint8 direction = track_scenery->flags & 0xF; // Rotate the direction by the track direction direction = ((direction << 4) >> RCT2_GLOBAL(0x00F4414D, uint8)); track_scenery->flags &= 0xF0; track_scenery->flags |= (direction & 0xF) | (direction >> 4); } else if ((track_scenery->scenery_object.flags & 0xF) == OBJECT_TYPE_WALLS){ uint8 direction = track_scenery->flags & 3; direction -= RCT2_GLOBAL(0x00F4414D, uint8); track_scenery->flags &= 0xFC; track_scenery->flags |= (direction & 3); } else { uint8 direction = track_scenery->flags & 3; uint8 quadrant = (track_scenery->flags & 0xC) >> 2; direction -= RCT2_GLOBAL(0x00F4414D, uint8); quadrant -= RCT2_GLOBAL(0x00F4414D, uint8); track_scenery->flags &= 0xF0; track_scenery->flags |= (direction & 3) | ((quadrant & 3) << 2); } sint16 x = ((uint8)track_scenery->x) * 32 - RCT2_GLOBAL(0x00F44142, sint16); sint16 y = ((uint8)track_scenery->y) * 32 - RCT2_GLOBAL(0x00F44144, sint16); rotate_map_coordinates(&x, &y, RCT2_GLOBAL(0x00F4414D, uint8)); x /= 32; y /= 32; if (x > 127 || y > 127 || x < -126 || y < -126){ window_error_open(3346, STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY); return false; } track_scenery->x = (sint8)x; track_scenery->y = (sint8)y; int z = track_scenery->z * 8 - RCT2_GLOBAL(0xF44146, sint16); z /= 8; if (z > 127 || z < -126){ window_error_open(3346, STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY); return false; } track_scenery->z = z; track_scenery++; scenery_source++; } *track_pointer = (uint8*)track_scenery; //Skip end of scenery elements byte (*track_pointer)++; return true; } /** * * rct2: 0x006CE44F */ static bool track_design_save_to_td6(rct_track_td6 *td6, uint8 rideIndex) { rct_ride *ride = get_ride(rideIndex); td6->type = ride->type; rct_object_entry_extended *object = &object_entry_groups[OBJECT_TYPE_RIDE].entries[ride->subtype]; // Note we are only copying rct_object_entry in size and // not the extended as we don't need the chunk size. memcpy(&td6->vehicle_object, object, sizeof(rct_object_entry)); td6->ride_mode = ride->mode; td6->version_and_colour_scheme = (ride->colour_scheme_type & 3) | (1 << 3); // Version .TD6 for (int i = 0; i < 32; i++) { td6->vehicle_colours[i] = ride->vehicle_colours[i]; td6->vehicle_additional_colour[i] = ride->vehicle_colours_extended[i]; } for (int i = 0; i < 4; i++) { td6->track_spine_colour[i] = ride->track_colour_main[i]; td6->track_rail_colour[i] = ride->track_colour_additional[i]; td6->track_support_colour[i] = ride->track_colour_supports[i]; } td6->depart_flags = ride->depart_flags; td6->number_of_trains = ride->num_vehicles; td6->number_of_cars_per_train = ride->num_cars_per_train; td6->min_waiting_time = ride->min_waiting_time; td6->max_waiting_time = ride->max_waiting_time; td6->var_50 = ride->operation_option; td6->lift_hill_speed_num_circuits = ride->lift_hill_speed | (ride->num_circuits << 5); td6->entrance_style = ride->entrance_style; td6->max_speed = (sint8)(ride->max_speed / 65536); td6->average_speed = (sint8)(ride->average_speed / 65536); td6->ride_length = ride_get_total_length(ride) / 65536; td6->max_positive_vertical_g = ride->max_positive_vertical_g / 32; td6->max_negative_vertical_g = ride->max_negative_vertical_g / 32; td6->max_lateral_g = ride->max_lateral_g / 32; td6->inversions = ride->inversions; td6->drops = ride->drops; td6->highest_drop_height = ride->highest_drop_height; uint16 total_air_time = (ride->total_air_time * 123) / 1024; if (total_air_time > 255) { total_air_time = 0; } td6->total_air_time = (uint8)total_air_time; td6->excitement = ride->ratings.excitement / 10; td6->intensity = ride->ratings.intensity / 10; td6->nausea = ride->ratings.nausea / 10; td6->upkeep_cost = ride->upkeep_cost; td6->flags = 0; td6->flags2 = 0; uint8 *trackElements = td6->elements; memset(trackElements, 0, 8000); if (td6->type == RIDE_TYPE_MAZE) { return track_design_save_to_td6_for_maze(rideIndex, td6, trackElements); } else { return track_design_save_to_td6_for_tracked_ride(rideIndex, td6, trackElements); } } /** * * rct2: 0x006CEAAE */ static bool track_design_save_to_td6_for_maze(uint8 rideIndex, rct_track_td6 *td6, uint8 *trackElements) { rct_map_element *mapElement = NULL; bool mapFound = false; sint16 startX, startY; for (startY = 0; startY < 8192; startY += 32) { for (startX = 0; startX < 8192; startX += 32) { mapElement = map_get_first_element_at(startX >> 5, startY >> 5); do { if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_TRACK) continue; if (mapElement->properties.track.ride_index == rideIndex){ mapFound = true; break; } } while (!map_element_is_last_for_tile(mapElement++)); if (mapFound) { break; } } if (mapFound) { break; } } if (mapFound == 0) { gGameCommandErrorText = STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY; return false; } RCT2_GLOBAL(0x00F44142, sint16) = startX; RCT2_GLOBAL(0x00F44144, sint16) = startY; RCT2_GLOBAL(0x00F44146, sint16) = mapElement->base_height * 8; rct_maze_element *maze = (rct_maze_element*)trackElements; // x is defined here as we can start the search // on tile start_x, start_y but then the next row // must restart on 0 for (sint16 y = startY, x = startX; y < 8192; y += 32) { for (; x < 8192; x += 32) { mapElement = map_get_first_element_at(x / 32, y / 32); do { if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_TRACK) continue; if (mapElement->properties.track.ride_index != rideIndex) continue; maze->maze_entry = mapElement->properties.track.maze_entry; maze->x = (x - startX) / 32; maze->y = (y - startY) / 32; maze++; if (maze >= RCT2_ADDRESS(0x009DA151, rct_maze_element)) { gGameCommandErrorText = STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY; return false; } } while (!map_element_is_last_for_tile(mapElement++)); } x = 0; } rct_ride *ride = get_ride(rideIndex); uint16 location = ride->entrances[0]; if (location == 0xFFFF) { gGameCommandErrorText = STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY; return false; } sint16 x = (location & 0xFF) * 32; sint16 y = ((location & 0xFF00) >> 8) * 32; mapElement = map_get_first_element_at(x >> 5, y >> 5); do { if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_ENTRANCE) continue; if (mapElement->properties.entrance.type != ENTRANCE_TYPE_RIDE_ENTRANCE) continue; if (mapElement->properties.entrance.ride_index == rideIndex) break; } while (!map_element_is_last_for_tile(mapElement++)); // Add something that stops this from walking off the end uint8 entrance_direction = mapElement->type & MAP_ELEMENT_DIRECTION_MASK; maze->unk_2 = entrance_direction; maze->type = 8; maze->x = (sint8)((x - startX) / 32); maze->y = (sint8)((y - startY) / 32); maze++; location = ride->exits[0]; if (location == 0xFFFF) { gGameCommandErrorText = STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY; return 0; } x = (location & 0xFF) * 32; y = ((location & 0xFF00) >> 8) * 32; mapElement = map_get_first_element_at(x >> 5, y >> 5); do { if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_ENTRANCE) continue; if (mapElement->properties.entrance.type != ENTRANCE_TYPE_RIDE_EXIT) continue; if (mapElement->properties.entrance.ride_index == rideIndex) break; } while (!map_element_is_last_for_tile(mapElement++)); // Add something that stops this from walking off the end uint8 exit_direction = mapElement->type & MAP_ELEMENT_DIRECTION_MASK; maze->unk_2 = exit_direction; maze->type = 0x80; maze->x = (sint8)((x - startX) / 32); maze->y = (sint8)((y - startY) / 32); maze++; maze->all = 0; maze++; trackElements = (uint8*)maze; *trackElements++ = 0xFF; RCT2_GLOBAL(0x00F44058, uint8*) = trackElements; // Save global vars as they are still used by scenery sint16 start_z = RCT2_GLOBAL(0x00F44146, sint16); sub_6D01B3(td6, PTD_OPERATION_DRAW_OUTLINES, 0, 4096, 4096, 0); RCT2_GLOBAL(0x00F44142, sint16) = startX; RCT2_GLOBAL(0x00F44144, sint16) = startY; RCT2_GLOBAL(0x00F44146, sint16) = start_z; RCT2_GLOBAL(RCT2_ADDRESS_MAP_SELECTION_FLAGS, sint16) &= 0xFFF9; RCT2_GLOBAL(RCT2_ADDRESS_MAP_SELECTION_FLAGS, sint16) &= 0xFFF7; x = RCT2_GLOBAL(RCT2_ADDRESS_TRACK_PREVIEW_X_MAX, sint16) - RCT2_GLOBAL(RCT2_ADDRESS_TRACK_PREVIEW_X_MIN, sint16); y = RCT2_GLOBAL(RCT2_ADDRESS_TRACK_PREVIEW_Y_MAX, sint16) - RCT2_GLOBAL(RCT2_ADDRESS_TRACK_PREVIEW_Y_MIN, sint16); x /= 32; y /= 32; x++; y++; td6->space_required_x = (uint8)x; td6->space_required_y = (uint8)y; return true; } /** * * rct2: 0x006CE68D */ static bool track_design_save_to_td6_for_tracked_ride(uint8 rideIndex, rct_track_td6 *td6, uint8 *trackElements) { rct_ride *ride = get_ride(rideIndex); rct_xy_element trackElement; track_begin_end trackBeginEnd; if (!ride_try_get_origin_element(rideIndex, &trackElement)) { gGameCommandErrorText = STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY; return false; } int z = 0; // Find the start of the track. // It has to do this as a backwards loop incase this is an incomplete track. if (track_block_get_previous(trackElement.x, trackElement.y, trackElement.element, &trackBeginEnd)) { rct_map_element* initial_map = trackElement.element; do { rct_xy_element lastGood = { .element = trackBeginEnd.begin_element, .x = trackBeginEnd.begin_x, .y = trackBeginEnd.begin_y }; if (!track_block_get_previous(trackBeginEnd.end_x, trackBeginEnd.end_y, trackBeginEnd.begin_element, &trackBeginEnd)) { trackElement = lastGood; break; } } while (initial_map != trackBeginEnd.begin_element); } z = trackElement.element->base_height * 8; uint8 track_type = trackElement.element->properties.track.type; uint8 direction = trackElement.element->type & MAP_ELEMENT_DIRECTION_MASK; RCT2_GLOBAL(0x00F4414D, uint8) = direction; if (sub_6C683D(&trackElement.x, &trackElement.y, &z, direction, track_type, 0, &trackElement.element, 0)) { gGameCommandErrorText = STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY; return 0; } const rct_track_coordinates *trackCoordinates = &TrackCoordinates[trackElement.element->properties.track.type]; // Used in the following loop to know when we have // completed all of the elements and are back at the // start. rct_map_element *initialMap = trackElement.element; sint16 start_x = trackElement.x; sint16 start_y = trackElement.y; sint16 start_z = z + trackCoordinates->z_begin; RCT2_GLOBAL(0x00F44142, sint16) = start_x; RCT2_GLOBAL(0x00F44144, sint16) = start_y; RCT2_GLOBAL(0x00F44146, sint16) = start_z; rct_track_element *track = (rct_track_element*)trackElements; do{ track->type = trackElement.element->properties.track.type; if (track->type == 0xFF) { track->type = 101; } if (track->type == TRACK_ELEM_LEFT_VERTICAL_LOOP || track->type == TRACK_ELEM_RIGHT_VERTICAL_LOOP ) { td6->flags |= (1 << 7); } if (track->type == TRACK_ELEM_LEFT_TWIST_DOWN_TO_UP || track->type == TRACK_ELEM_RIGHT_TWIST_DOWN_TO_UP || track->type == TRACK_ELEM_LEFT_TWIST_UP_TO_DOWN || track->type == TRACK_ELEM_RIGHT_TWIST_UP_TO_DOWN ) { td6->flags |= (1 << 17); } if (track->type == TRACK_ELEM_LEFT_BARREL_ROLL_UP_TO_DOWN || track->type == TRACK_ELEM_RIGHT_BARREL_ROLL_UP_TO_DOWN || track->type == TRACK_ELEM_LEFT_BARREL_ROLL_DOWN_TO_UP || track->type == TRACK_ELEM_RIGHT_BARREL_ROLL_DOWN_TO_UP ) { td6->flags |= (1 << 29); } if (track->type == TRACK_ELEM_HALF_LOOP_UP || track->type == TRACK_ELEM_HALF_LOOP_DOWN ) { td6->flags |= (1 << 18); } if (track->type == TRACK_ELEM_LEFT_CORKSCREW_UP || track->type == TRACK_ELEM_RIGHT_CORKSCREW_UP || track->type == TRACK_ELEM_LEFT_CORKSCREW_DOWN || track->type == TRACK_ELEM_RIGHT_CORKSCREW_DOWN ) { td6->flags |= (1 << 19); } if (track->type == TRACK_ELEM_WATER_SPLASH) { td6->flags |= (1 << 27); } if (track->type == TRACK_ELEM_POWERED_LIFT) { td6->flags |= (1 << 30); } if (track->type == TRACK_ELEM_LEFT_LARGE_HALF_LOOP_UP || track->type == TRACK_ELEM_RIGHT_LARGE_HALF_LOOP_UP || track->type == TRACK_ELEM_RIGHT_LARGE_HALF_LOOP_DOWN || track->type == TRACK_ELEM_LEFT_LARGE_HALF_LOOP_DOWN ) { td6->flags |= (1 << 31); } if (track->type == TRACK_ELEM_LOG_FLUME_REVERSER) { td6->flags2 |= TRACK_FLAGS2_CONTAINS_LOG_FLUME_REVERSER; } uint8 bh; if (track->type == TRACK_ELEM_BRAKES) { bh = trackElement.element->properties.track.sequence >> 4; } else { bh = trackElement.element->properties.track.colour >> 4; } uint8 flags = (trackElement.element->type & (1 << 7)) | bh; flags |= (trackElement.element->properties.track.colour & 3) << 4; if ( RideData4[ride->type].flags & RIDE_TYPE_FLAG4_3 && trackElement.element->properties.track.colour & (1 << 2) ) { flags |= (1 << 6); } track->flags = flags; track++; if (!track_block_get_next(&trackElement, &trackElement, NULL, NULL)) { break; } z = trackElement.element->base_height * 8; direction = trackElement.element->type & MAP_ELEMENT_DIRECTION_MASK; track_type = trackElement.element->properties.track.type; if (sub_6C683D(&trackElement.x, &trackElement.y, &z, direction, track_type, 0, &trackElement.element, 0)) { break; } } while (trackElement.element != initialMap); trackElements = (uint8*)track; // Mark the elements as finished. *trackElements++ = 0xFF; rct_track_entrance *entrance = (rct_track_entrance*)trackElements; // First entrances, second exits for (int i = 0; i < 2; i++) { for (int station_index = 0; station_index < 4; station_index++) { z = ride->station_heights[station_index]; uint16 location; if (i == 0) { location = ride->entrances[station_index]; } else { location = ride->exits[station_index]; } if (location == 0xFFFF) { continue; } sint16 x = (location & 0xFF) * 32; sint16 y = ((location & 0xFF00) >> 8) * 32; rct_map_element *map_element = map_get_first_element_at(x >> 5, y >> 5); do { if (map_element_get_type(map_element) != MAP_ELEMENT_TYPE_ENTRANCE) continue; if (map_element->base_height == z) break; } while (!map_element_is_last_for_tile(map_element++)); // Add something that stops this from walking off the end uint8 entrance_direction = map_element->type & MAP_ELEMENT_DIRECTION_MASK; entrance_direction -= RCT2_GLOBAL(0x00F4414D, uint8); entrance_direction &= MAP_ELEMENT_DIRECTION_MASK; entrance->direction = entrance_direction; x -= RCT2_GLOBAL(0x00F44142, sint16); y -= RCT2_GLOBAL(0x00F44144, sint16); rotate_map_coordinates(&x, &y, RCT2_GLOBAL(0x00F4414D, uint8)); entrance->x = x; entrance->y = y; z *= 8; z -= RCT2_GLOBAL(0x00F44146, sint16); z /= 8; if (z > 127 || z < -126) { gGameCommandErrorText = STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY; return 0; } if (z == 0xFF) { z = 0x80; } entrance->z = z; // If this is the exit version if (i == 1) { entrance->direction |= (1 << 7); } entrance++; } } trackElements = (uint8*)entrance; *trackElements++ = 0xFF; *trackElements++ = 0xFF; RCT2_GLOBAL(0x00F44058, uint8*) = trackElements; sub_6D01B3(td6, PTD_OPERATION_DRAW_OUTLINES, 0, 4096, 4096, 0); // Resave global vars for scenery reasons. RCT2_GLOBAL(0x00F44142, sint16) = start_x; RCT2_GLOBAL(0x00F44144, sint16) = start_y; RCT2_GLOBAL(0x00F44146, sint16) = start_z; RCT2_GLOBAL(RCT2_ADDRESS_MAP_SELECTION_FLAGS, sint16) &= 0xFFF9; RCT2_GLOBAL(RCT2_ADDRESS_MAP_SELECTION_FLAGS, sint16) &= 0xFFF7; int x = RCT2_GLOBAL(RCT2_ADDRESS_TRACK_PREVIEW_X_MAX, sint16) - RCT2_GLOBAL(RCT2_ADDRESS_TRACK_PREVIEW_X_MIN, sint16); int y = RCT2_GLOBAL(RCT2_ADDRESS_TRACK_PREVIEW_Y_MAX, sint16) - RCT2_GLOBAL(RCT2_ADDRESS_TRACK_PREVIEW_Y_MIN, sint16); x /= 32; y /= 32; x++; y++; td6->space_required_x = x; td6->space_required_y = y; return true; } /** * * rct2: 0x006771DC but not really its branched from that * quite far. */ static bool track_design_save_to_file(rct_track_td6 *track_design, utf8 *path) { window_close_construction_windows(); // Encode TD6 data uint8 *encodedData = malloc(0x8000); int encodedDataLength = sawyercoding_encode_td6((uint8 *)track_design, encodedData, 0x609F); // Save encoded TD6 data to file bool result; log_verbose("saving track %s", path); SDL_RWops *file = SDL_RWFromFile(path, "wb"); if (file != NULL) { SDL_RWwrite(file, encodedData, encodedDataLength, 1); SDL_RWclose(file); result = true; } else { log_error("Failed to save %s", path); result = false; } free(encodedData); return result; }