diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71916135c..7cb4e8a8e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -240,6 +240,7 @@ set(GAMEENGINE_SRC game/common/rts/specialpower.cpp game/common/rts/team.cpp game/common/rts/teamsinfo.cpp + game/common/skirmishbattlehonors.cpp game/common/statemachine.cpp game/common/staticnamekey.cpp game/common/statscollector.cpp @@ -266,6 +267,7 @@ set(GAMEENGINE_SRC game/common/system/memdynalloc.cpp game/common/system/mempool.cpp game/common/system/mempoolfact.cpp + game/common/system/quotedprintable.cpp game/common/system/radar.cpp game/common/system/ramfile.cpp game/common/system/registryget.cpp diff --git a/src/game/client/gui/gadget/gadgetlistbox.cpp b/src/game/client/gui/gadget/gadgetlistbox.cpp index 9c87a2cab..86f72e167 100644 --- a/src/game/client/gui/gadget/gadgetlistbox.cpp +++ b/src/game/client/gui/gadget/gadgetlistbox.cpp @@ -17,6 +17,7 @@ #include "gadgetpushbutton.h" #include "gadgetslider.h" #include "gamewindow.h" +#include "gamewindowmanager.h" void Gadget_List_Box_Set_Font(GameWindow *list_box, GameFont *font) { @@ -182,3 +183,279 @@ void Gadget_List_Box_Set_Audio_Feedback(GameWindow *list_box, bool audio_feedbac } } } + +int Get_List_Box_Top_Entry(_ListboxData *list) +{ + for (int i = 0;; i++) { + if (list->m_listData[i].m_listHeight > list->m_displayPos) { + return i; + } + + if (i >= list->m_endPos) { + break; + } + } + + return 0; +} + +void Adjust_Display(GameWindow *list_box, int adjustment, bool update_slider) +{ + _ListboxData *data = static_cast<_ListboxData *>(list_box->Win_Get_User_Data()); + int index = adjustment + Get_List_Box_Top_Entry(data); + + if (index >= 0) { + if (index >= data->m_endPos) { + index = data->m_endPos - 1; + } + } else { + index = 0; + } + + if (update_slider) { + if (index <= 0) { + data->m_displayPos = 0; + } else { + data->m_displayPos = data->m_listData[index - 1].m_listHeight + 1; + } + } + + if (data->m_slider != nullptr) { + _SliderData *silder_data = static_cast<_SliderData *>(data->m_slider->Win_Get_User_Data()); + int slider_width; + int slider_height; + data->m_slider->Win_Get_Size(&slider_width, &slider_height); + silder_data->m_maxVal = data->m_totalHeight - (data->m_displayHeight - 2) + 1; + + if (silder_data->m_maxVal < 0) { + silder_data->m_maxVal = 0; + } + + GameWindow *child = data->m_slider->Win_Get_Child(); + int child_width; + int child_height; + child->Win_Get_Size(&child_width, &child_height); + silder_data->m_numTicks = (float)(slider_height - child_height) / (float)silder_data->m_maxVal; + + if (update_slider) { + g_theWindowManager->Win_Send_System_Msg( + data->m_slider, GSM_SET_SLIDER, silder_data->m_maxVal - data->m_displayPos, 0); + } + } +} + +void Gadget_List_Box_Set_Top_Visible_Entry(GameWindow *list_box, int row) +{ + if (list_box != nullptr) { + if (list_box->Win_Get_User_Data() != nullptr) { + Adjust_Display(list_box, row - Gadget_List_Box_Get_Top_Visible_Entry(list_box), true); + } + } +} + +void Gadget_List_Box_Set_Selected(GameWindow *list_box, int select_count, int *select_list) +{ +#ifdef GAME_DLL // temporary since we can't change the definition of Win_Send_System_Msg at this point and we can't cast a + // pointer to an unsigned int on 64 bit + if (list_box != nullptr) { + g_theWindowManager->Win_Send_System_Msg( + list_box, GLM_SET_SELECTION, select_count, reinterpret_cast(select_list)); + } +#endif +} + +void Gadget_List_Box_Set_Item_Data(GameWindow *list_box, void *data, int row, int column) +{ +#ifdef GAME_DLL // temporary since we can't change the definition of Win_Send_System_Msg at this point and we can't cast a + // pointer to an unsigned int on 64 bit + ICoord2D pos; + pos.x = column; + pos.y = row; + + if (list_box != nullptr) { + g_theWindowManager->Win_Send_System_Msg( + list_box, GLM_SET_ITEM_DATA, reinterpret_cast(&pos), reinterpret_cast(data)); + } +#endif +} + +void Gadget_List_Box_Reset(GameWindow *list_box) +{ + if (list_box != nullptr) { + g_theWindowManager->Win_Send_System_Msg(list_box, GLM_DEL_ALL, 0, 0); + } +} + +int Gadget_List_Box_Get_Top_Visible_Entry(GameWindow *list_box) +{ + if (list_box == nullptr) { + return false; + } + + _ListboxData *data = static_cast<_ListboxData *>(list_box->Win_Get_User_Data()); + + if (data != nullptr) { + return Get_List_Box_Top_Entry(data); + } else { + return 0; + } +} + +int Gadget_List_Box_Get_Num_Columns(GameWindow *list_box) +{ + if (list_box == nullptr) { + return false; + } + + _ListboxData *data = static_cast<_ListboxData *>(list_box->Win_Get_User_Data()); + + if (data != nullptr) { + return data->m_columns; + } else { + return 0; + } +} + +int Gadget_List_Box_Get_Column_Width(GameWindow *list_box, int column) +{ + if (list_box == nullptr) { + return false; + } + + _ListboxData *data = static_cast<_ListboxData *>(list_box->Win_Get_User_Data()); + + if (data == nullptr) { + return 0; + } + + if (data->m_columns > column && column >= 0) { + return data->m_columnWidth[column]; + } + + return 0; +} + +int Get_List_Box_Bottom_Entry(_ListboxData *list) +{ + for (int i = list->m_endPos - 1;; i--) { + if (list->m_listData[i].m_listHeight == list->m_displayHeight + list->m_displayPos) { + return i; + } + + if (list->m_listData[i].m_listHeight < list->m_displayHeight + list->m_displayPos && i != list->m_endPos - 1) { + return i + 1; + } + + if (list->m_listData[i].m_listHeight < list->m_displayHeight + list->m_displayPos) { + return i; + } + + if (i < 0) { + break; + } + } + + return 0; +} + +int Gadget_List_Box_Get_Bottom_Visible_Entry(GameWindow *list_box) +{ + if (list_box == nullptr) { + return 0; + } + + _ListboxData *data = static_cast<_ListboxData *>(list_box->Win_Get_User_Data()); + + if (data != nullptr) { + return Get_List_Box_Bottom_Entry(data); + } else { + return 0; + } +} + +int Gadget_List_Box_Add_Entry_Text(GameWindow *list_box, Utf16String text, int color, int row, int column, bool overwrite) +{ + if (list_box != nullptr) { + if (text.Is_Empty()) { + text = U_CHAR(" "); + } + + _AddMessageStruct entry; + entry.row = row; + entry.column = column; + entry.type = 1; + entry.data = &text; + entry.overwrite = overwrite; + entry.width = -1; + entry.height = -1; + _ListboxData *data = static_cast<_ListboxData *>(list_box->Win_Get_User_Data()); + + bool list_exceeded = data->m_listLength <= data->m_endPos; + int i = list_exceeded ? 0 : 1; + int bottom = Gadget_List_Box_Get_Bottom_Visible_Entry(list_box); + int index = 0; +#ifdef GAME_DLL // temporary since we can't change the definition of Win_Send_System_Msg at this point and we can't cast a + // pointer to an unsigned int on 64 bit + index = static_cast( + g_theWindowManager->Win_Send_System_Msg(list_box, GLM_ADD_ENTRY, reinterpret_cast(&entry), color)); +#endif + if (data->m_scrollIfAtEnd && (index - bottom) == i && Gadget_List_Box_Is_Full(list_box)) { + Gadget_List_Box_Set_Bottom_Visible_Entry(list_box, index); + } + + return index; + } else { + return -1; + } +} + +int Gadget_List_Box_Add_Entry_Image( + GameWindow *list_box, const Image *image, int row, int column, int width, int height, bool overwrite, int color) +{ + _AddMessageStruct entry; + entry.row = row; + entry.column = column; + entry.type = 2; + entry.data = const_cast(image); + entry.overwrite = overwrite; + entry.width = width; + entry.height = height; + int index = 0; +#ifdef GAME_DLL // temporary since we can't change the definition of Win_Send_System_Msg at this point and we can't cast a + // pointer to an unsigned int on 64 bit + index = static_cast( + g_theWindowManager->Win_Send_System_Msg(list_box, GLM_ADD_ENTRY, reinterpret_cast(&entry), color)); +#endif + return index; +} + +void Gadget_List_Box_Set_Selected(GameWindow *list_box, int select_index) +{ +#ifdef GAME_DLL // temporary since we can't change the definition of Win_Send_System_Msg at this point and we can't cast a + // pointer to an unsigned int on 64 bit + if (list_box != nullptr) { + g_theWindowManager->Win_Send_System_Msg( + list_box, GLM_SET_SELECTION, reinterpret_cast(&select_index), 1); + } +#endif +} + +void Gadget_List_Box_Set_Bottom_Visible_Entry(GameWindow *list_box, int row) +{ + if (list_box != nullptr) { + if (list_box->Win_Get_User_Data() != nullptr) { + Adjust_Display(list_box, row - Gadget_List_Box_Get_Bottom_Visible_Entry(list_box) + 1, true); + } + } +} + +bool Gadget_List_Box_Is_Full(GameWindow *list_box) +{ + if (list_box == nullptr) { + return false; + } + + _ListboxData *data = static_cast<_ListboxData *>(list_box->Win_Get_User_Data()); + return data != nullptr + && data->m_listData[Get_List_Box_Bottom_Entry(data)].m_listHeight >= data->m_displayPos + data->m_displayHeight - 5; +} diff --git a/src/game/client/maputil.cpp b/src/game/client/maputil.cpp index 564b560f5..59cd5ee38 100644 --- a/src/game/client/maputil.cpp +++ b/src/game/client/maputil.cpp @@ -13,8 +13,24 @@ * LICENSE */ #include "maputil.h" +#include "cachedfileinputstream.h" +#include "colorspace.h" +#include "crc.h" +#include "datachunk.h" +#include "dict.h" +#include "filesystem.h" +#include "fpusetting.h" +#include "gadgetlistbox.h" +#include "gamestate.h" +#include "gametext.h" #include "globaldata.h" +#include "image.h" +#include "localfilesystem.h" +#include "mapobject.h" +#include "quotedprintable.h" +#include "skirmishbattlehonors.h" #include "staticnamekey.h" +#include "thingfactory.h" #include #ifndef GAME_DLL @@ -22,6 +38,17 @@ WaypointMap *g_waypoints; MapCache *g_theMapCache; #endif +static std::vector s_boundaries; +static Dict s_worldDict; +static std::list s_supplyPositions; +static std::list s_techPositions; +static unsigned int s_width = 0; +static unsigned int s_height = 0; +static int s_borderSize = 0; +static WaypointMap *s_waypoints = nullptr; +static int s_mapDX = 0; +static int s_mapDY = 0; + const char *const MapCache::s_mapDirName = "Maps"; const char *const MapCache::s_mapExtension = "map"; const char *const MapCache::s_mapCacheName = "MapCache.ini"; @@ -67,11 +94,846 @@ Utf8String MapCache::Get_User_Map_Dir() return dir; } +void MapCache::Write_Cache_INI(bool custom_cache) +{ + Utf8String user_dir; + + if (custom_cache && !g_theWriteableGlobalData->m_buildMapCache) { + user_dir = Get_User_Map_Dir(); + } else { + user_dir = Get_Map_Dir(); + } + + Utf8String name(user_dir); + name.Concat('\\'); + + g_theFileSystem->Create_Directory(user_dir); + name.Concat("MapCache.ini"); + FILE *file = fopen(name.Str(), "w"); + captainslog_dbgassert(file != nullptr, "Failed to create %s", name.Str()); + + if (file != nullptr) { + fprintf(file, "; FILE: %s /////////////////////////////////////////////////////////////\n", name.Str()); + fprintf(file, "; This INI file is auto-generated - do not modify\n"); + fprintf(file, "; /////////////////////////////////////////////////////////////////////////////\n"); + user_dir.To_Lower(); + + MapMetaData data; + for (auto it = begin(); it != end(); it++) { + if (it->first.Starts_With_No_Case(user_dir.Str())) { + data = it->second; + fprintf(file, "\nMapCache %s\n", Ascii_String_To_Quoted_Printable(it->first.Str()).Str()); + fprintf(file, " fileSize = %u\n", data.m_fileSize); + fprintf(file, " fileCRC = %u\n", data.m_CRC); + fprintf(file, " timestampLo = %d\n", data.m_timestamp.m_lowTimeStamp); + fprintf(file, " timestampHi = %d\n", data.m_timestamp.m_highTimeStamp); + fprintf(file, " isOfficial = %s\n", data.m_isOfficial ? "yes" : "no"); + fprintf(file, " isMultiplayer = %s\n", data.m_isMultiplayer ? "yes" : "no"); + fprintf(file, " numPlayers = %d\n", data.m_numPlayers); + fprintf(file, + " extentMin = X:%2.2f Y:%2.2f Z:%2.2f\n", + data.m_extent.lo.x, + data.m_extent.lo.y, + data.m_extent.lo.z); + fprintf(file, + " extentMax = X:%2.2f Y:%2.2f Z:%2.2f\n", + data.m_extent.hi.x, + data.m_extent.hi.y, + data.m_extent.hi.z); + fprintf(file, " nameLookupTag = %s\n", data.m_lookupTag.Str()); + + for (auto waypoint = data.m_waypoints.begin(); waypoint != data.m_waypoints.end(); waypoint++) { + fprintf(file, + " %s = X:%2.2f Y:%2.2f Z:%2.2f\n", + waypoint->first.Str(), + waypoint->second.x, + waypoint->second.y, + waypoint->second.z); + } + + for (auto tech_position = data.m_techPositions.begin(); tech_position != data.m_techPositions.end(); + tech_position++) { + fprintf(file, + " techPosition = X:%2.2f Y:%2.2f Z:%2.2f\n", + (*tech_position).x, + (*tech_position).y, + (*tech_position).z); + } + + for (auto supply_position = data.m_supplyPositions.begin(); supply_position != data.m_supplyPositions.end(); + supply_position++) { + fprintf(file, + " supplyPosition = X:%2.2f Y:%2.2f Z:%2.2f\n", + (*supply_position).x, + (*supply_position).y, + (*supply_position).z); + } + + fprintf(file, "END\n\n"); + } + } + + fclose(file); + } +} + void MapCache::Update_Cache() { -#ifdef GAME_DLL - Call_Method(PICK_ADDRESS(0x0047F3B0, 0x009EE29A), this); -#endif + Set_FP_Mode(); + g_theFileSystem->Create_Directory(g_theMapCache->Get_User_Map_Dir()); + + if (Load_User_Maps()) { + Write_Cache_INI(true); + } + + Load_Standard_Maps(); + + if (g_theLocalFileSystem->Does_File_Exist(Get_Map_Dir().Str())) { + bool build = g_theWriteableGlobalData->m_buildMapCache; + g_theWriteableGlobalData->m_buildMapCache = true; + Load_User_Maps(); + g_theWriteableGlobalData->m_buildMapCache = build; + Write_Cache_INI(false); + } +} + +bool MapCache::Clear_Unseen_Maps(Utf8String prefix) +{ + prefix.To_Lower(); + bool found = false; + + for (auto it = m_seen.begin(); it != m_seen.end(); it++) { + Utf8String name = it->first; + + if (!it->second) { + if (name.Starts_With_No_Case(prefix.Str())) { + erase(name); + found = true; + } + } + } + + return found; +} + +void MapCache::Load_Standard_Maps() +{ + INI ini; + Utf8String name; + name.Format("%s\\%s", Get_Map_Dir().Str(), "MapCache.ini"); + File *file = g_theFileSystem->Open_File(name.Str(), File::READ); + + if (file != nullptr) { + file->Close(); + file = nullptr; + ini.Load(name, INI_LOAD_OVERWRITE, nullptr); + } +} + +bool MapCache::Load_User_Maps() +{ + Utf8String map_root; + + if (g_theWriteableGlobalData->m_buildMapCache) { + map_root = Get_Map_Dir(); + } else { + map_root = Get_User_Map_Dir(); + INI ini; + Utf8String name; + name.Format("%s\\%s", map_root.Str(), "MapCache.ini"); + File *file = g_theFileSystem->Open_File(name.Str(), File::READ); + + if (file != nullptr) { + file->Close(); + file = nullptr; + ini.Load(name, INI_LOAD_OVERWRITE, nullptr); + } + } + + m_seen.clear(); + + for (auto it = begin(); it != end(); it++) { + m_seen[it->first] = false; + } + + std::set> map_paths; + Utf8String dir; + dir.Format("%s\\", map_root.Str()); + bool map_added = false; + + Utf8String filter; + filter.Format("*.%s", Get_Map_Extension().Str()); + g_theFileSystem->Get_File_List_In_Directory(dir, filter, map_paths, true); + + for (auto it = map_paths.begin(); it != map_paths.end(); it++) { + Utf8String map_path; + map_path = *it; + map_path.To_Lower(); + const char *map_file_name = map_path.Reverse_Find('\\'); + + if (map_file_name != nullptr) { + Utf8String expected_map_file; + Utf8String map_name(map_file_name + 1); + + for (unsigned int i = 0; i < strlen(".map"); i++) { + map_name.Remove_Last_Char(); + } + + expected_map_file.Format("%s\\%s%s", map_name.Str(), map_name.Str(), ".map"); + bool not_in_unk = false; + + if (g_theWriteableGlobalData->m_buildMapCache) { + auto it2 = m_unk.find(map_name); + + if (m_unk.size() != 0) { + if (it2 == m_unk.end()) { + not_in_unk = true; + } + } + } + + if (!not_in_unk) { + if (map_path.Ends_With_No_Case(expected_map_file.Str())) { + FileInfo info; + + if (g_theFileSystem->Get_File_Info(map_path, &info)) { + char dest[PATH_MAX]; + strcpy(dest, map_path.Str()); + + for (const char *c = dest; *c != '\0'; c++) { + if (*c == '\\' || *c == '/') { + c++; + } + } + + m_seen[map_path] = true; + map_added |= Add_Map(map_root, *it, &info, g_theWriteableGlobalData->m_buildMapCache); + } else { + captainslog_dbgassert(false, "Could not get file info for map %s", (*it).Str()); + } + } else { + captainslog_dbgassert(false, "Found map '%s' in wrong spot (%s)", map_name.Str(), map_path.Str()); + } + } + } else { + captainslog_dbgassert(false, "Couldn't find \\ in map name!"); + } + } + + if (Clear_Unseen_Maps(map_root)) { + return true; + } else { + return map_added; + } +} + +bool Parse_Size_Only(DataChunkInput &input, DataChunkInfo *info, void *data) +{ + s_width = input.Read_Int32(); + s_height = input.Read_Int32(); + + if (info->version < 3) { + s_borderSize = 0; + } else { + s_borderSize = input.Read_Int32(); + } + + if (info->version >= 4) { + int count = input.Read_Int32(); + s_boundaries.resize(count); + + for (int i = 0; i < count; i++) { + s_boundaries[i].x = input.Read_Int32(); + s_boundaries[i].y = input.Read_Int32(); + } + } + + return true; +} + +bool Parse_Size_Only_In_Chunk(DataChunkInput &input, DataChunkInfo *info, void *data) +{ + return Parse_Size_Only(input, info, data); +} + +bool Parse_World_Dict_Data_Chunk(DataChunkInput &input, DataChunkInfo *info, void *data) +{ + s_worldDict = input.Read_Dict(); + return true; +} + +bool Parse_Object_Data_Chunk(DataChunkInput &input, DataChunkInfo *info, void *data) +{ + bool use_props = info->version >= 2; + Coord3D loc; + loc.x = input.Read_Real32(); + loc.y = input.Read_Real32(); + loc.z = input.Read_Real32(); + + if (info->version <= 2) { + loc.z = 0.0f; + } + + float angle = input.Read_Real32(); + int flags = input.Read_Int32(); + Utf8String name = input.Read_AsciiString(); + Dict props; + + if (use_props) { + props = input.Read_Dict(); + } + + ThingTemplate *tmplate = nullptr; + + MapObject *object = new MapObject(loc, name, angle, flags, &props, g_theThingFactory->Find_Template(name, false)); + + if (object->Get_Properties()->Get_Type(g_waypointIDKey) == Dict::DICT_INT) { + object->Set_Is_Waypoint(); + (*s_waypoints)[object->Get_Waypoint_Name()] = loc; + } else if (object->Get_Thing_Template() != nullptr && object->Get_Thing_Template()->Is_KindOf(KINDOF_TECH_BUILDING)) { + s_techPositions.push_back(loc); + } else if (object->Get_Thing_Template() != nullptr) { + if (object->Get_Thing_Template()->Is_KindOf(KINDOF_SUPPLY_SOURCE_ON_PREVIEW)) { + s_supplyPositions.push_back(loc); + } + } + + object->Delete_Instance(); + return true; +} + +bool Parse_Objects_Data_Chunk(DataChunkInput &input, DataChunkInfo *info, void *data) +{ + input.m_currentObject = nullptr; + input.Register_Parser("Object", info->label, Parse_Object_Data_Chunk, nullptr); + return input.Parse(data); +} + +bool Load_Map(Utf8String name) +{ + Utf8String name_copy2; + int len = 0; + char name_copy[PATH_MAX]; + strcpy(name_copy, name.Str()); + len = strlen(name_copy); + char name_without_ext[PATH_MAX]; + + if (len >= 4) { + memset(name_without_ext, 0, PATH_MAX); + strncpy(name_without_ext, name_copy, len - 4); + } + + CachedFileInputStream stream; + name_copy2 = name; + + if (stream.Open(name_copy2)) { + DataChunkInput input(&stream); + s_waypoints = new WaypointMap(); + input.Register_Parser("HeightMapData", Utf8String::s_emptyString, Parse_Size_Only_In_Chunk, nullptr); + input.Register_Parser("WorldInfo", Utf8String::s_emptyString, Parse_World_Dict_Data_Chunk, nullptr); + input.Register_Parser("ObjectsList", Utf8String::s_emptyString, Parse_Objects_Data_Chunk, nullptr); + + if (!input.Parse(nullptr)) { + throw CODE_05; + } + + s_mapDX = s_width - 2 * s_borderSize; + s_mapDY = s_height - 2 * s_borderSize; + return true; + } else { + return false; + } +} + +unsigned int Calc_CRC(Utf8String dir, Utf8String name) +{ + CRC crc; + crc.Clear(); + + Utf8String str; + char name_copy[PATH_MAX]; + strcpy(name_copy, name.Str()); + int len = strlen(name_copy); + char name_without_ext[PATH_MAX]; + + if (len >= 4) { + memset(name_without_ext, 0, PATH_MAX); + strncpy(name_without_ext, name_copy, len - 4); + } + + str = name; + File *file = g_theFileSystem->Open_File(str.Str(), File::READ); + + if (file != nullptr) { + for (;;) { + unsigned char buf3[4096]; + int val = file->Read(buf3, 4096); + + if (val <= 0) { + break; + } + + crc.Compute_CRC(buf3, val); + } + + file->Close(); + return crc.Get_CRC(); + } else { + captainslog_dbgassert(false, "Couldn't open '%s'", name.Str()); + return 0; + } +} + +void Reset_Map() +{ + if (s_waypoints != nullptr) { + delete s_waypoints; + s_waypoints = nullptr; + } + + s_techPositions.clear(); + s_supplyPositions.clear(); +} + +void Get_Extent(Region3D *region) +{ + region->lo.x = 0.0f; + region->lo.y = 0.0f; + region->hi.x = s_mapDX * 10.0f; + region->hi.y = s_mapDY * 10.0f; + region->lo.z = 0.0f; + region->hi.z = 0.0f; +} + +bool Would_Map_Transfer(const Utf8String &path) +{ + Utf8String str = g_theMapCache->Get_User_Map_Dir(); + return path.Starts_With_No_Case(str.Str()); +} + +int Populate_Map_List_Box_No_Reset(GameWindow *listbox, bool is_official, bool is_multiplayer, Utf8String map_name) +{ + if (g_theMapCache != nullptr && listbox != nullptr) { + int columns = Gadget_List_Box_Get_Num_Columns(listbox); + const Image *star_bronze = nullptr; + const Image *star_silver = nullptr; + const Image *star_gold = nullptr; + const Image *red_yel_star = nullptr; + SkirmishBattleHonors *honors = nullptr; + int width = 10; + int height = 10; + + if (columns > 1) { + star_bronze = g_theMappedImageCollection->Find_Image_By_Name("Star-Bronze"); + star_silver = g_theMappedImageCollection->Find_Image_By_Name("Star-Silver"); + star_gold = g_theMappedImageCollection->Find_Image_By_Name("Star-Gold"); + red_yel_star = g_theMappedImageCollection->Find_Image_By_Name("RedYell_Star"); + honors = new SkirmishBattleHonors(); + int image_width; + + if (star_gold != nullptr) { + image_width = star_gold->Get_Image_Width(); + } else { + image_width = 10; + } + + width = image_width; + int column_width = Gadget_List_Box_Get_Column_Width(listbox, 0); + width = GameMath::Min(column_width, image_width); + height = width; + } + + int color = Make_Color(0xFF, 0xFF, 0xFF, 0xFF); + Utf16String map_display_name; + int selected = 0; + Utf8String map_root; + + if (is_official) { + map_root = g_theMapCache->Get_Map_Dir(); + } else { + map_root = g_theWriteableGlobalData->Get_Path_User_Data(); + map_root += g_theMapCache->Get_Map_Dir(); + } + + map_root.To_Lower(); + std::set set; + std::map map; + unsigned int count = 0; + + for (int i = 0; count < g_theMapCache->size(); i++) { + captainslog_debug("Adding maps with %d players", i); + + for (auto it = g_theMapCache->begin(); it != g_theMapCache->end(); it++) { + if (it->second.m_numPlayers == i) { + set.insert(it->second.m_displayName); + map[it->second.m_displayName] = it->first; + captainslog_debug("Adding map %s to temp cache.", it->first.Str()); + count++; + } + } + + for (auto it = set.begin(); it != set.end(); it++) { + Utf8String name; + name = map[*it]; + auto it2 = g_theMapCache->find(name); + captainslog_dbgassert(it2 != g_theMapCache->end(), "Map %ls not found in map cache.", (*it).Str()); + + if (it2->first.Starts_With_No_Case(map_root.Str()) && is_multiplayer == it2->second.m_isMultiplayer) { + if (!it2->second.m_displayName.Is_Empty()) { + map_display_name = it2->second.m_displayName; + int i1 = -1; + int i2 = -1; + + if (columns > 1 && it2->second.m_isMultiplayer) { + int medal1 = honors->Get_Endurance_Medal(it2->first, 2); + int medal2 = honors->Get_Endurance_Medal(it2->first, 3); + int medal3 = honors->Get_Endurance_Medal(it2->first, 4); + + if (medal3 != 0) { + if (medal3 == it2->second.m_numPlayers - 1) { + i1 = Gadget_List_Box_Add_Entry_Image( + listbox, red_yel_star, i1, 0, width, height, true, -1); + i2 = 4; + } else { + i1 = Gadget_List_Box_Add_Entry_Image(listbox, star_gold, i1, 0, width, height, true, -1); + i2 = 3; + } + } else if (medal2 != 0) { + i2 = 2; + i1 = Gadget_List_Box_Add_Entry_Image(listbox, star_silver, i1, 0, width, height, true, -1); + } else if (medal1 != 0) { + i2 = 1; + i1 = Gadget_List_Box_Add_Entry_Image(listbox, star_bronze, i1, 0, width, height, true, -1); + } else { + i2 = 0; + i1 = Gadget_List_Box_Add_Entry_Image(listbox, nullptr, i1, 0, width, height, true, -1); + } + } + + i1 = Gadget_List_Box_Add_Entry_Text(listbox, map_display_name, color, i1, columns - 1, 1); + + if (it2->first == map_name) { + selected = i1; + } + + Gadget_List_Box_Set_Item_Data(listbox, const_cast(it2->first.Str()), i1, 0); + + if (columns > 1) { + Gadget_List_Box_Set_Item_Data(listbox, reinterpret_cast(i2), i1, 1); + } + } + } + } + + set.clear(); + map.clear(); + } + + if (honors != nullptr) { + delete honors; + honors = nullptr; + } + + Gadget_List_Box_Set_Selected(listbox, selected); + + if (selected >= 0) { + int top_entry = Gadget_List_Box_Get_Top_Visible_Entry(listbox); + int bottom_entry = Gadget_List_Box_Get_Bottom_Visible_Entry(listbox); + int i3 = bottom_entry - top_entry; + + if (selected >= bottom_entry) { + Gadget_List_Box_Set_Top_Visible_Entry(listbox, GameMath::Max(0, selected - GameMath::Max(1, i3 / 2))); + } + } + + return selected; + } else { + return -1; + } +} + +int Populate_Map_List_Box(GameWindow *listbox, bool is_official, bool is_multiplayer, Utf8String map_name) +{ + if (g_theMapCache != nullptr && listbox != nullptr) { + Gadget_List_Box_Reset(listbox); + return Populate_Map_List_Box_No_Reset(listbox, is_official, is_multiplayer, map_name); + } else { + return -1; + } +} + +bool Is_Valid_Map(Utf8String map_name, bool is_multiplayer) +{ + if (g_theMapCache != nullptr && !map_name.Is_Empty()) { + g_theMapCache->Update_Cache(); + map_name.To_Lower(); + auto it = g_theMapCache->find(map_name); + if (it != g_theMapCache->end() && is_multiplayer == it->second.m_isMultiplayer) { + return true; + } else { + return false; + } + } else { + return false; + } +} + +Utf8String Get_Default_Map(bool is_multiplayer) +{ + if (g_theMapCache != nullptr) { + g_theMapCache->Update_Cache(); + + for (auto it = g_theMapCache->begin(); it != g_theMapCache->end(); it++) { + if (is_multiplayer == it->second.m_isMultiplayer) { + return it->first; + } + } + + return Utf8String::s_emptyString; + } + + return Utf8String::s_emptyString; +} + +Utf8String Get_Official_Multiplayer_Map() +{ + if (g_theMapCache != nullptr) { + g_theMapCache->Update_Cache(); + + for (auto it = g_theMapCache->begin(); it != g_theMapCache->end(); it++) { + if (it->second.m_isMultiplayer && it->second.m_isOfficial) { + return it->first; + } + } + + return Utf8String::s_emptyString; + } + + return Utf8String::s_emptyString; +} + +bool Is_Map_Official(Utf8String map_name) +{ + if (g_theMapCache != nullptr && !map_name.Is_Empty()) { + g_theMapCache->Update_Cache(); + map_name.To_Lower(); + auto it = g_theMapCache->find(map_name); + if (it != g_theMapCache->end()) { + return it->second.m_isOfficial; + } else { + return false; + } + } else { + return false; + } +} + +void Copy_From_Big_To_Dir(Utf8String &source, Utf8String &dest) +{ + File *file = g_theFileSystem->Open_File(source.Str(), File::READ | File::BINARY); + captainslog_relassert(file != nullptr, 6, "Copy_From_Big_To_Dir - Error opening source file '%s'", source.Str()); + int size = file->Seek(0, File::END); + file->Seek(0, File::START); + char *buf = new char[size]; + captainslog_relassert(buf != nullptr, 6, "Copy_From_Big_To_Dir - Unable to allocate buffer for file '%s'", source.Str()); + int read_size = file->Read(buf, size); + captainslog_relassert(read_size >= size, 6, "Copy_From_Big_To_Dir - Error reading from file '%s'", source.Str()); + file->Close(); + + File *out_file = g_theFileSystem->Open_File(dest.Str(), File::WRITE | File::CREATE | File::BINARY); + captainslog_relassert(out_file != nullptr, 6, "Copy_From_Big_To_Dir - Error writing to file '%s'", source.Str()); + int write_size = out_file->Write(buf, size); + captainslog_relassert(write_size >= size, 6, "Copy_From_Big_To_Dir - Error writing to file '%s'", source.Str()); + out_file->Close(); + delete[] buf; +} + +Image *Get_Map_Preview_Image(Utf8String map_name) +{ + if (g_theWriteableGlobalData == nullptr) { + return nullptr; + } + + captainslog_debug("%s Map Name ", map_name.Str()); + Utf8String map_image(map_name); + Utf8String image_file; + Utf8String image_name; + Utf8String str4; + map_image.Remove_Last_Char(); + map_image.Remove_Last_Char(); + map_image.Remove_Last_Char(); + map_image.Remove_Last_Char(); + image_file = map_image; + str4 = map_image.Reverse_Find('\\') + 1; + str4.Concat(".tga"); + map_image.Concat(".tga"); + Utf8String portable_map_path = g_theGameState->Real_To_Portable_Map_Path(image_file); + image_name.Set(Utf8String::s_emptyString); + + for (int i = 0; i < portable_map_path.Get_Length(); i++) { + char c = portable_map_path.Get_Char(i); + + if (c == '\\' || c == ':') { + image_name.Concat('_'); + } else { + image_name.Concat(c); + } + } + + image_file = image_name; + image_file.Concat(".tga"); + Image *image = g_theMappedImageCollection->Find_Image_By_Name(image_name); + + if (image == nullptr) { + if (!g_theFileSystem->Does_File_Exist(map_image.Str())) { + return nullptr; + } + + Utf8String preview_image; + preview_image.Format("%sMapPreviews/", g_theWriteableGlobalData->Get_Path_User_Data().Str()); + g_theFileSystem->Create_Directory(preview_image); + preview_image += image_file; + Copy_From_Big_To_Dir(map_image, preview_image); + + image = new Image(); + image->Set_Name(image_name); + image->Set_Filename(image_file); + image->Set_Status(0); + + Region2D region; + region.hi.x = 1.0f; + region.hi.y = 1.0f; + region.lo.x = 0.0f; + region.lo.y = 0.0f; + image->Set_UV_Region(®ion); + + image->Set_Texture_Height(128); + image->Set_Texture_Width(128); + g_theMappedImageCollection->Add_Image(image); + } + + return image; +} + +bool MapCache::Add_Map(Utf8String dir, Utf8String name, FileInfo *info, bool build_cache) +{ + if (info == nullptr) { + return false; + } + + Utf8String name_lower; + name_lower = name; + name_lower.To_Lower(); + auto map_it = find(name_lower); + MapMetaData data; + int size = info->file_size_low; + + if (map_it != end()) { + data = map_it->second; + + if (data.m_fileSize == size && data.m_CRC != 0) { + if (data.m_lookupTag.Is_Empty()) { + Utf8String str2; + str2 = name.Reverse_Find('\\') + 1; + (*this)[name_lower].m_displayName.Translate(str2); + + if (data.m_numPlayers >= 2) { + Utf16String str3; + str3.Format(U_CHAR(" (%d)"), data.m_numPlayers); + (*this)[name_lower].m_displayName.Concat(str3); + } + } else { + (*this)[name_lower].m_displayName = g_theGameText->Fetch(data.m_lookupTag); + + if (data.m_numPlayers >= 2) { + Utf16String str3; + str3.Format(U_CHAR(" (%d)"), data.m_numPlayers); + (*this)[name_lower].m_displayName.Concat(str3); + } + } + + return false; + } + + captainslog_debug("%s didn't match file in MapCache", name.Str()); + captainslog_debug("size: %d / %d", size, data.m_fileSize); + captainslog_debug("time1: %d / %d", info->write_time_high, data.m_timestamp.m_highTimeStamp); + captainslog_debug("time2: %d / %d", info->write_time_low, data.m_timestamp.m_lowTimeStamp); + } + + captainslog_debug("MapCache::Add_Map(): caching '%s' because '%s' was not found", name.Str(), name_lower.Str()); + Load_Map(name); + data.m_mapFilename = name_lower; + data.m_fileSize = size; + data.m_isOfficial = build_cache; + data.m_waypoints.Update(); + data.m_numPlayers = data.m_waypoints.m_numStartSpots; + data.m_isMultiplayer = data.m_waypoints.m_numStartSpots >= 2; + data.m_timestamp.m_highTimeStamp = info->write_time_high; + data.m_timestamp.m_lowTimeStamp = info->write_time_low; + data.m_supplyPositions = s_supplyPositions; + data.m_techPositions = s_techPositions; + data.m_CRC = Calc_CRC(dir, name); + bool exists = false; + + Utf8String map = s_worldDict.Get_AsciiString(g_mapName, &exists); + data.m_lookupTag = map; + + if (exists && !map.Is_Empty()) { + Utf8String map_str; + map_str.Format("%s\\%s", dir.Str(), name.Str()); + + for (int i = 0; i < 4; i++) { + map_str.Remove_Last_Char(); + } + + map_str.Concat("\\map.str"); + g_theGameText->Init_Map_String_File(map_str); + data.m_displayName = g_theGameText->Fetch(map); + + if (data.m_numPlayers >= 2) { + Utf16String str6; + str6.Format(U_CHAR(" (%d)"), data.m_numPlayers); + data.m_displayName.Concat(str6); + } + + captainslog_debug("Map name is now '%ls'", data.m_displayName.Str()); + g_theGameText->Reset(); + } else { + captainslog_debug("Missing TheKey_mapName!"); + Utf8String str7; + str7 = name.Reverse_Find('\\') + 1; + data.m_displayName.Translate(str7); + + if (data.m_numPlayers >= 2) { + Utf16String str8; + str8.Format(U_CHAR(" (%d)"), data.m_numPlayers); + data.m_displayName.Concat(str8); + } + + g_theGameText->Reset(); + } + + Get_Extent(&data.m_extent); + (*this)[name_lower] = data; + captainslog_debug(" filesize = %d bytes", data.m_fileSize); + captainslog_debug(" displayName = %ls", data.m_displayName.Str()); + captainslog_debug(" CRC = %X", data.m_CRC); + captainslog_debug(" isOfficial = %s", data.m_isOfficial ? "yes" : "no"); + captainslog_debug(" isMultiplayer = %s", data.m_isMultiplayer ? "yes" : "no"); + captainslog_debug(" numPlayers = %d", data.m_numPlayers); + captainslog_debug(" extent = (%2.2f,%2.2f) -> (%2.2f,%2.2f)", + data.m_extent.lo.x, + data.m_extent.lo.y, + data.m_extent.hi.x, + data.m_extent.hi.y); + + for (auto waypoint = data.m_waypoints.begin(); waypoint != data.m_waypoints.end(); waypoint++) { + captainslog_debug(" waypoint %s: (%2.2f,%2.2f)", waypoint->first.Str(), waypoint->second.x, waypoint->second.y); + } + + Reset_Map(); + return true; } MapMetaData *MapCache::Find_Map(Utf8String map_name) diff --git a/src/game/client/maputil.h b/src/game/client/maputil.h index 61174dc0f..b2342db0d 100644 --- a/src/game/client/maputil.h +++ b/src/game/client/maputil.h @@ -21,6 +21,10 @@ #include #include +struct FileInfo; +class GameWindow; +class Image; + class WaypointMap : public std::map { public: @@ -44,7 +48,7 @@ struct MapMetaData int m_numPlayers; bool m_isMultiplayer; bool m_isOfficial; - unsigned int m_filesize; + unsigned int m_fileSize; unsigned int m_CRC; WinTimeStamp m_timestamp; WaypointMap m_waypoints; @@ -70,6 +74,7 @@ class MapCache : public std::map void Load_Standard_Maps(); bool Load_User_Maps(); MapMetaData *Find_Map(Utf8String map_name); + bool Add_Map(Utf8String dir, Utf8String name, FileInfo *info, bool build_cache); private: static const char *const s_mapDirName; @@ -80,6 +85,14 @@ class MapCache : public std::map std::set m_unk; }; +bool Would_Map_Transfer(const Utf8String &path); +int Populate_Map_List_Box_No_Reset(GameWindow *listbox, bool is_official, bool is_multiplayer, Utf8String map_name); +int Populate_Map_List_Box(GameWindow *listbox, bool is_official, bool is_multiplayer, Utf8String map_name); +bool Is_Valid_Map(Utf8String map_name, bool is_multiplayer); +Utf8String Get_Default_Map(bool is_multiplayer); +Utf8String Get_Official_Multiplayer_Map(); +bool Is_Map_Official(Utf8String map_name); +Image *Get_Map_Preview_Image(Utf8String map_name); void Find_Draw_Positions(int start_x, int start_y, int width, int height, Region3D extent, ICoord2D *ul, ICoord2D *lr); #ifdef GAME_DLL diff --git a/src/game/client/system/image.h b/src/game/client/system/image.h index 42fc70a02..14f631265 100644 --- a/src/game/client/system/image.h +++ b/src/game/client/system/image.h @@ -42,7 +42,16 @@ class Image : public MemoryPoolObject Utf8String Get_File_Name() const { return m_filename; } Utf8String Get_Name() { return m_name; } void Set_Name(Utf8String str) { m_name = str; } + void Set_Filename(Utf8String str) { m_filename = str; } Region2D Get_UV_Region() const { return m_UVCoords; } + void Set_Texture_Width(int width) { m_textureSize.x = width; } + void Set_Texture_Height(int height) { m_textureSize.y = height; } + void Set_UV_Region(Region2D *region) + { + if (region != nullptr) { + m_UVCoords = *region; + } + } int Get_Image_Width() const { return m_imageSize.x; } int Get_Image_Height() const { return m_imageSize.y; } const ICoord2D *Get_Image_Size() const { return &m_imageSize; } diff --git a/src/game/common/crc.h b/src/game/common/crc.h index cd463b392..0eb7e0507 100644 --- a/src/game/common/crc.h +++ b/src/game/common/crc.h @@ -24,6 +24,7 @@ class CRC CRC() : m_crc(0) {} void Compute_CRC(void const *data, int bytes); uint32_t Get_CRC() { return m_crc; } + void Clear() { m_crc = 0; } private: void Add_CRC(uint8_t byte); diff --git a/src/game/common/skirmishbattlehonors.cpp b/src/game/common/skirmishbattlehonors.cpp new file mode 100644 index 000000000..08b97936b --- /dev/null +++ b/src/game/common/skirmishbattlehonors.cpp @@ -0,0 +1,29 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Skirmish Battle Honors + * + * @copyright Thyme is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "skirmishbattlehonors.h" + +SkirmishBattleHonors::SkirmishBattleHonors() +{ + Load("SkirmishStats.ini"); +} + +SkirmishBattleHonors::~SkirmishBattleHonors() {} + +int SkirmishBattleHonors::Get_Endurance_Medal(Utf8String name, int level) const +{ + Utf8String str; + str.Format("%s_%d", name.Str(), level); + return Get_Int(str, 0); +} diff --git a/src/game/common/skirmishbattlehonors.h b/src/game/common/skirmishbattlehonors.h new file mode 100644 index 000000000..5430ed45f --- /dev/null +++ b/src/game/common/skirmishbattlehonors.h @@ -0,0 +1,25 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Skirmish Battle Honors + * + * @copyright Thyme is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once +#include "always.h" +#include "userpreferences.h" + +class SkirmishBattleHonors : public UserPreferences +{ +public: + SkirmishBattleHonors(); + virtual ~SkirmishBattleHonors() override; + int Get_Endurance_Medal(Utf8String name, int level) const; +}; diff --git a/src/game/common/staticnamekey.cpp b/src/game/common/staticnamekey.cpp index cccf2b3a2..0bfdb122b 100644 --- a/src/game/common/staticnamekey.cpp +++ b/src/game/common/staticnamekey.cpp @@ -131,6 +131,7 @@ StaticNameKey g_waypointPathLabel1("waypointPathLabel1"); StaticNameKey g_waypointPathLabel2("waypointPathLabel2"); StaticNameKey g_waypointPathLabel3("waypointPathLabel3"); StaticNameKey g_waypointPathBiDirectional("waypointPathBiDirectional"); +StaticNameKey g_mapName("mapName"); NameKeyType StaticNameKey::Key() { diff --git a/src/game/common/staticnamekey.h b/src/game/common/staticnamekey.h index 9bcebca02..fc525c87d 100644 --- a/src/game/common/staticnamekey.h +++ b/src/game/common/staticnamekey.h @@ -174,3 +174,4 @@ extern StaticNameKey g_waypointPathLabel1; extern StaticNameKey g_waypointPathLabel2; extern StaticNameKey g_waypointPathLabel3; extern StaticNameKey g_waypointPathBiDirectional; +extern StaticNameKey g_mapName; diff --git a/src/game/common/system/datachunk.h b/src/game/common/system/datachunk.h index 89391a959..5a57df4c9 100644 --- a/src/game/common/system/datachunk.h +++ b/src/game/common/system/datachunk.h @@ -111,4 +111,5 @@ class DataChunkInput void *m_currentObject; void *m_userData; friend class WorldHeightMap; + friend bool Parse_Objects_Data_Chunk(DataChunkInput &input, DataChunkInfo *info, void *data); }; diff --git a/src/game/common/system/quotedprintable.cpp b/src/game/common/system/quotedprintable.cpp new file mode 100644 index 000000000..2227b5eeb --- /dev/null +++ b/src/game/common/system/quotedprintable.cpp @@ -0,0 +1,51 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Quoted Printable + * + * @copyright Thyme is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "quotedprintable.h" +#include + +char Int_To_Hex_Digit(int num) +{ + if (num >= 16) { + return '\0'; + } + + if (num >= 10) { + return num + '7'; + } + + return num + '0'; +} + +Utf8String Ascii_String_To_Quoted_Printable(Utf8String string) +{ + static char dest[1024]; + const char *str = string.Str(); + int count = 0; + + while (*str != '\0' && count < 1021) { + if (isalnum(*str)) { + dest[count++] = *str; + } else { + dest[count++] = '_'; + dest[count++] = Int_To_Hex_Digit(*str >> 4); + dest[count++] = Int_To_Hex_Digit(*str & 15); + } + + str++; + } + + dest[count] = '\0'; + return dest; +} diff --git a/src/game/common/system/quotedprintable.h b/src/game/common/system/quotedprintable.h new file mode 100644 index 000000000..502397cf7 --- /dev/null +++ b/src/game/common/system/quotedprintable.h @@ -0,0 +1,19 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Quoted Printable + * + * @copyright Thyme is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once +#include "always.h" +#include "asciistring.h" + +Utf8String Ascii_String_To_Quoted_Printable(Utf8String string); diff --git a/src/game/common/userpreferences.cpp b/src/game/common/userpreferences.cpp index a696c7220..48cac2bb3 100644 --- a/src/game/common/userpreferences.cpp +++ b/src/game/common/userpreferences.cpp @@ -73,7 +73,7 @@ bool UserPreferences::Write() return false; } -Utf8String UserPreferences::Get_AsciiString(Utf8String key, Utf8String def_arg) +Utf8String UserPreferences::Get_AsciiString(Utf8String key, Utf8String def_arg) const { auto it = find(key); @@ -84,7 +84,7 @@ Utf8String UserPreferences::Get_AsciiString(Utf8String key, Utf8String def_arg) return it->second; } -int UserPreferences::Get_Int(Utf8String key, int def_arg) +int UserPreferences::Get_Int(Utf8String key, int def_arg) const { Utf8String value = Get_AsciiString(key); @@ -95,7 +95,7 @@ int UserPreferences::Get_Int(Utf8String key, int def_arg) return atoi(value.Str()); } -float UserPreferences::Get_Real(Utf8String key, float def_arg) +float UserPreferences::Get_Real(Utf8String key, float def_arg) const { Utf8String value = Get_AsciiString(key); @@ -106,7 +106,7 @@ float UserPreferences::Get_Real(Utf8String key, float def_arg) return (float)atof(value.Str()); } -bool UserPreferences::Get_Bool(Utf8String key, bool def_arg) +bool UserPreferences::Get_Bool(Utf8String key, bool def_arg) const { Utf8String value = Get_AsciiString(key); diff --git a/src/game/common/userpreferences.h b/src/game/common/userpreferences.h index d4423df66..81a2fa78e 100644 --- a/src/game/common/userpreferences.h +++ b/src/game/common/userpreferences.h @@ -26,10 +26,10 @@ class UserPreferences : public std::map virtual bool Load(Utf8String filename); virtual bool Write(); - Utf8String Get_AsciiString(Utf8String key, Utf8String def_arg = Utf8String::s_emptyString); - int Get_Int(Utf8String key, int def_arg); - float Get_Real(Utf8String key, float def_arg); - bool Get_Bool(Utf8String key, bool def_arg); + Utf8String Get_AsciiString(Utf8String key, Utf8String def_arg = Utf8String::s_emptyString) const; + int Get_Int(Utf8String key, int def_arg) const; + float Get_Real(Utf8String key, float def_arg) const; + bool Get_Bool(Utf8String key, bool def_arg) const; void Set_AsciiString(Utf8String key, Utf8String value); void Set_Int(Utf8String key, int value); void Set_Real(Utf8String key, float value); diff --git a/src/game/logic/map/sideslist.cpp b/src/game/logic/map/sideslist.cpp index 0c48bde5b..c0ea28e53 100644 --- a/src/game/logic/map/sideslist.cpp +++ b/src/game/logic/map/sideslist.cpp @@ -13,6 +13,8 @@ * LICENSE */ #include "sideslist.h" +#include "cachedfileinputstream.h" +#include "scriptlist.h" #include "staticnamekey.h" #include "unicodestring.h" #include "xfer.h" @@ -405,9 +407,160 @@ bool SidesList::Parse_Sides_Data_Chunk(DataChunkInput &input, DataChunkInfo *inf return true; } +static Utf8String s_readPlayerNames[MAX_PLAYER_COUNT]; + +bool Parse_Players_Data_Chunk(DataChunkInput &input, DataChunkInfo *info, void *data) +{ + int read_dict = 0; + + if (info->version >= 2) { + read_dict = input.Read_Int32(); + } + + int player_count = input.Read_Int32(); + + for (int i = 0; i < player_count && i < MAX_PLAYER_COUNT; i++) { + s_readPlayerNames[i] = input.Read_AsciiString(); + + if (read_dict != 0) { + input.Read_Dict(); + } + } + + captainslog_dbgassert(input.At_End_Of_Chunk(), "Unexpected data left over."); + return true; +} + +bool Parse_Teams_Data_Chunk(DataChunkInput &input, DataChunkInfo *info, void *data) +{ + SidesList *list = static_cast(data); + + while (!input.At_End_Of_Chunk()) { + Dict dict = input.Read_Dict(); + Utf8String name = dict.Get_AsciiString(g_teamNameKey); + Utf8String owner = dict.Get_AsciiString(g_teamOwnerKey); + + if (list->Find_Skirmish_Side_Info(owner) != nullptr) { + list->Add_Skirmish_Team(&dict); + } + } + + captainslog_dbgassert(input.At_End_Of_Chunk(), "Unexpected data left over."); + return true; +} + void SidesList::Prepare_For_MP_Or_Skirmish() { -#ifdef GAME_DLL - Call_Method(PICK_ADDRESS(0x004D6DA0, 0x006B9F32), this); -#endif + m_skirmishTeamsRec.Clear(); + + for (int i = 0; i < Get_Num_Teams(); i++) { + m_skirmishTeamsRec.Add_Team(Get_Team_Info(i)->Get_Dict()); + } + + m_teamRec.Clear(); + + for (int i = 0; i < MAX_PLAYER_COUNT; i++) { + m_skirmishSides[i].Reset(); + } + + m_numSkirmishSides = 0; + + for (int i = 0; i < m_numSides; i++) { + m_skirmishSides[m_numSkirmishSides] = m_sides[i]; + m_numSkirmishSides++; + Utf8String faction = m_sides[i].Get_Dict().Get_AsciiString(g_playerFactionKey); + + if (!(faction == "FactionCivilian")) { + if (m_numSides == 1) { + break; + } + + Remove_Side(i); + i--; + } + } + + bool has_script = false; + + for (int i = 0; i < m_numSkirmishSides; i++) { + Utf8String faction = m_skirmishSides[i].Get_Dict().Get_AsciiString(g_playerFactionKey); + + if (!(faction == "FactionCivilian")) { + if (m_skirmishSides[i].Get_Script_List() != nullptr) { + if (m_skirmishSides[i].Get_Script_List()->Get_Script() != nullptr + || m_skirmishSides[i].Get_Script_List()->Get_Script_Group() != nullptr) { + has_script = true; + } + } + } + } + + if (!has_script) { + Utf8String name("data\\Scripts\\SkirmishScripts.scb"); + captainslog_debug("Skirmish map using standard scripts"); + m_skirmishTeamsRec.Clear(); + CachedFileInputStream stream; + + if (stream.Open(name)) { + DataChunkInput input(&stream); + input.Register_Parser( + "PlayerScriptsList", Utf8String::s_emptyString, ScriptList::Parse_Scripts_Data_Chunk, nullptr); + input.Register_Parser("ScriptsPlayers", Utf8String::s_emptyString, Parse_Players_Data_Chunk, nullptr); + input.Register_Parser("ScriptTeams", Utf8String::s_emptyString, Parse_Teams_Data_Chunk, nullptr); + + if (!input.Parse(this)) { + captainslog_debug("ERROR - Unable to read in skirmish scripts."); + return; + } + + ScriptList *scripts[MAX_LIST_COUNT]; + int count = ScriptList::Get_Read_Scripts(scripts); + + for (int j = 0; j < count; j++) { + int player_index = -1; + + for (int k = 0; k < m_numSkirmishSides; k++) { + Utf8String playername = Get_Skirmish_Side_Info(k)->Get_Dict().Get_AsciiString(g_playerNameKey); + + if (playername == s_readPlayerNames[j]) { + player_index = k; + break; + } + } + + if (player_index != -1) { + ScriptList *list = Get_Skirmish_Side_Info(player_index)->Get_Script_List(); + Get_Skirmish_Side_Info(player_index)->Set_Script_List(scripts[j]); + scripts[j] = nullptr; + + if (list != nullptr) { + list->Delete_Instance(); + } + + scripts[j] = nullptr; + } + } + + for (int j = 0; j < MAX_PLAYER_COUNT; j++) { + s_readPlayerNames[j].Clear(); + } + } + } +} + +void SidesList::Remove_Side(int index) +{ + if (index >= 0 && index < m_numSides && m_numSides > 1) { + while (index < m_numSides - 1) { + m_sides[index] = m_sides[index + 1]; + index++; + } + + while (index < MAX_PLAYER_COUNT) { + m_sides[index].Reset(); + index++; + } + + m_numSides--; + } } diff --git a/src/game/logic/scriptengine/scriptaction.cpp b/src/game/logic/scriptengine/scriptaction.cpp index c62294ff1..c351f68ca 100644 --- a/src/game/logic/scriptengine/scriptaction.cpp +++ b/src/game/logic/scriptengine/scriptaction.cpp @@ -199,10 +199,21 @@ bool ScriptAction::Parse_Action_False_Data_Chunk(DataChunkInput &input, DataChun */ void ScriptAction::Set_Action_Type(ScriptActionType type) { - // TODO Requires ScriptEngine vtable -#ifdef GAME_DLL - Call_Method(PICK_ADDRESS(0x0051FE50, 0x006FA4A5), this, type); -#endif + for (int i = 0; i < m_numParams; i++) { + if (m_params[i] != nullptr) { + m_params[i]->Delete_Instance(); + } + + m_params[i] = nullptr; + } + + m_actionType = type; + ActionTemplate *action = g_theScriptEngine->Get_Action_Template(m_actionType); + m_numParams = action->Get_Num_Parameters(); + + for (int i = 0; i < m_numParams; i++) { + m_params[i] = new Parameter(action->Get_Parameter_Type(i)); + } } /** @@ -212,13 +223,169 @@ void ScriptAction::Set_Action_Type(ScriptActionType type) */ ScriptAction *ScriptAction::Parse_Action(DataChunkInput &input, DataChunkInfo *info, void *data) { - // TODO Requires ScriptEngine vtable -#ifdef GAME_DLL - return Call_Function( - PICK_ADDRESS(0x005208A0, 0), input, info, data); -#else - return nullptr; -#endif + ScriptAction *action = new ScriptAction(); + action->m_actionType = static_cast(input.Read_Int32()); + ActionTemplate *action_template = g_theScriptEngine->Get_Action_Template(action->m_actionType); + + if (info->version >= 2) { + NameKeyType key = input.Read_Name_Key(); + bool match = false; + + if (action_template != nullptr && action_template->m_nameKey == key) { + match = true; + } + + if (!match) { + for (int i = 0; i < ScriptAction::ACTION_COUNT; i++) { + action_template = g_theScriptEngine->Get_Action_Template(i); + + if (key == action_template->m_nameKey) { + match = true; + captainslog_debug("Rematching script action %s", g_theNameKeyGenerator->Key_To_Name(key).Str()); + action->m_actionType = static_cast(i); + break; + } + } + + if (!match) { + captainslog_dbgassert(false, "Invalid script action. Making it noop. jba."); + action->m_actionType = ScriptAction::NO_OP; + action->m_numParams = 0; + } + } + } + + Script *script = static_cast